Skip to content

feat: Add livequery support #411

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: master
Choose a base branch
from

Conversation

theSlyest
Copy link

@theSlyest theSlyest commented Jun 25, 2025

Added:

  • ParseLiveQuery class
  • ParseLiveQueryController class
  • ParseLiveQuerySubscription class
  • API ParseQuery.GetLive() for creating a ParseLiveQuery
  • LiveQueryServerConnectionData as a ServerConnectionData field to ParseClient to save live query server info.
  • APIs ConnectLiveQueryServerAsync(...) and DisconnectLiveQueryServerAsync(...) to ObjectServiceExtensions for connecting to and disconnecting respectively from the given parse live query server.
  • Some other smaller classes and interfaces...

Summary by CodeRabbit

  • New Features
    • Introduced real-time Live Query support, allowing applications to subscribe to and receive live updates for Parse queries via WebSocket.
    • Added new interfaces and classes for managing live query connections, subscriptions, and event handling.
    • Enabled configuration of Live Query server connection settings, including timeouts and buffer sizes.
    • Provided extension methods to easily connect and disconnect from the Live Query server.
    • Added error handling and event notifications for live query operations.

Copy link

parse-github-assistant bot commented Jun 25, 2025

🚀 Thanks for opening this pull request!

Copy link

coderabbitai bot commented Jun 25, 2025

📝 Walkthrough

Walkthrough

This change introduces comprehensive support for Parse Live Query functionality. It adds interfaces, classes, and infrastructure for WebSocket-based real-time queries, including live query controllers, subscriptions, event argument types, and connection data models. Service hubs and the main Parse client are extended to support configuration and orchestration of live query services, with new extension methods for connecting and disconnecting from the live query server.

Changes

File(s) Change Summary
Parse/Abstractions/Infrastructure/ILiveQueryServerConnectionData.cs
Parse/Infrastructure/LiveQueryServerConnectionData.cs
Added interface and struct for live query server connection configuration, including timeout and buffer size properties.
Parse/Abstractions/Infrastructure/IWebSocketClient.cs
Parse/Infrastructure/Execution/TextWebSocketClient.cs
Introduced IWebSocketClient interface and its implementation for WebSocket communication, supporting async open/close/send and message events.
Parse/Abstractions/Platform/LiveQueries/IParseLiveQueryController.cs
Parse/Platform/LiveQueries/ParseLiveQueryController.cs
Added interface and implementation for managing live query connections, subscriptions, updates, and errors.
Parse/Abstractions/Platform/LiveQueries/IParseLiveQuerySubscription.cs
Parse/Platform/LiveQueries/ParseLiveQuerySubscription.cs
Introduced interface and class for live query subscriptions, supporting event handling for object lifecycle changes and subscription management.
Parse/Platform/LiveQueries/ParseLiveQuery.cs Added generic class for defining and subscribing to live queries with support for filters, key selections, and watched keys.
Parse/Platform/LiveQueries/ParseLiveQueryEventArgs.cs
Parse/Platform/LiveQueries/ParseLiveQueryDualEventArgs.cs
Parse/Platform/LiveQueries/ParseLiveQueryErrorEventArgs.cs
Added event argument types for live query events, including object state and error details.
Parse/Abstractions/Infrastructure/IServiceHub.cs
Parse/Abstractions/Infrastructure/IMutableServiceHub.cs
Extended service hub interfaces to include live query controller, WebSocket client, and live query connection data properties.
Parse/Infrastructure/ServiceHub.cs
Parse/Infrastructure/MutableServiceHub.cs
Parse/Infrastructure/LateInitializedMutableServiceHub.cs
Parse/Infrastructure/OrchestrationServiceHub.cs
Parse/Abstractions/Infrastructure/CustomServiceHub.cs
Extended service hub implementations to support live query services, with lazy initialization and property delegation.
Parse/Platform/ParseClient.cs Added constructor overload for separate live query configuration and validation logic for live query server URIs.
Parse/Platform/Queries/ParseQuery.cs Added GetLive() method to create a live query from an existing query.
Parse/Utilities/ObjectServiceExtensions.cs Added extension methods for connecting and disconnecting from the live query server, plus minor whitespace cleanup.

Sequence Diagram(s)

Live Query Subscription Flow

sequenceDiagram
    participant ClientApp
    participant ParseQuery
    participant ParseLiveQuery
    participant ParseLiveQueryController
    participant IWebSocketClient
    participant Parse Server

    ClientApp->>ParseQuery: GetLive()
    ParseQuery->>ParseLiveQuery: new ParseLiveQuery(...)
    ClientApp->>ParseLiveQuery: SubscribeAsync()
    ParseLiveQuery->>ParseLiveQueryController: SubscribeAsync(liveQuery)
    ParseLiveQueryController->>IWebSocketClient: OpenAsync(serverUri)
    IWebSocketClient->>Parse Server: Establish WebSocket Connection
    ParseLiveQueryController->>IWebSocketClient: SendAsync(subscribe message)
    IWebSocketClient->>Parse Server: Send subscribe message
    Parse Server-->>IWebSocketClient: Send live query events (create, update, etc.)
    IWebSocketClient-->>ParseLiveQueryController: MessageReceived event
    ParseLiveQueryController-->>ParseLiveQuerySubscription: OnCreate/OnUpdate/...
    ParseLiveQuerySubscription-->>ClientApp: Raise Create/Update/... event
Loading

Live Query Connection Management

sequenceDiagram
    participant ClientApp
    participant IServiceHub
    participant IParseLiveQueryController

    ClientApp->>IServiceHub: ConnectLiveQueryServerAsync(onError)
    IServiceHub->>IParseLiveQueryController: ConnectAsync()
    IParseLiveQueryController->>IWebSocketClient: OpenAsync(serverUri)
    IWebSocketClient->>Parse Server: Establish WebSocket Connection
    Parse Server-->>IWebSocketClient: Connection confirmation
    IWebSocketClient-->>IParseLiveQueryController: MessageReceived event

    ClientApp->>IServiceHub: DisconnectLiveQueryServerAsync()
    IServiceHub->>IParseLiveQueryController: CloseAsync()
    IParseLiveQueryController->>IWebSocketClient: CloseAsync()
    IWebSocketClient->>Parse Server: Close WebSocket Connection
Loading

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai auto-generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 20

🧹 Nitpick comments (5)
Parse/Abstractions/Infrastructure/Execution/IWebSocketClient.cs (2)

19-19: Consider adding connection state visibility.

The interface lacks a way to query the current connection state, which could be useful for clients to determine if they need to reconnect or if operations are valid.

Consider adding a property like:

+ /// <summary>
+ /// Gets a value indicating whether the WebSocket connection is currently open.
+ /// </summary>
+ bool IsConnected { get; }

48-48: Missing cancellation token default parameter.

The SendAsync method lacks a default value for the cancellation token parameter, which is inconsistent with the other methods.

Apply this diff for consistency:

- public Task SendAsync(string message, CancellationToken cancellationToken);
+ public Task SendAsync(string message, CancellationToken cancellationToken = default);
Parse/Platform/Queries/ParseQuery.cs (1)

915-919: Add XML documentation for the new method.

The GetLive() method lacks XML documentation, which is inconsistent with the rest of the codebase that has comprehensive documentation.

Add XML documentation:

+/// <summary>
+/// Creates a live query from this query that can be used to receive real-time updates
+/// when objects matching the query are created, updated, or deleted.
+/// </summary>
+/// <returns>A new ParseLiveQuery instance configured with this query's parameters.</returns>
 public ParseLiveQuery<T> GetLive()
Parse/Platform/LiveQueries/ParseLiveQueryDualEventArgs.cs (1)

19-22: Documentation comment is redundant.

The constructor's XML documentation largely repeats information already available in the class-level documentation and property documentation.

Consider simplifying or removing the redundant constructor documentation:

- /// <summary>
- /// Represents the event arguments provided to Live Query event handlers in the Parse platform.
- /// This class provides information about the current and original state of the Parse object
- /// involved in the Live Query operation.
- /// </summary>
+ /// <summary>
+ /// Initializes a new instance with the current and original object states.
+ /// </summary>
Parse/Platform/LiveQueries/ParseLiveQueryEventArgs.cs (1)

20-25: Fix documentation inconsistency in constructor comment.

The constructor documentation mentions "current and original state of the Parse object" but the class only contains a single Object property representing the current state. This inconsistency should be resolved.

    /// <summary>
-    /// Represents the event arguments provided to Live Query event handlers in the Parse platform.
-    /// This class provides information about the current and original state of the Parse object
-    /// involved in the Live Query operation.
+    /// Initializes a new instance of the ParseLiveQueryEventArgs class with the specified Parse object.
    /// </summary>
+    /// <param name="current">The current state of the Parse object associated with the event.</param>
    internal ParseLiveQueryEventArgs(ParseObject current) => Object = current;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a9c1855 and 4b83d23.

📒 Files selected for processing (21)
  • Parse.sln (2 hunks)
  • Parse/Abstractions/Infrastructure/CustomServiceHub.cs (4 hunks)
  • Parse/Abstractions/Infrastructure/Execution/IWebSocketClient.cs (1 hunks)
  • Parse/Abstractions/Infrastructure/IMutableServiceHub.cs (3 hunks)
  • Parse/Abstractions/Infrastructure/IServiceHub.cs (3 hunks)
  • Parse/Abstractions/Platform/LiveQueries/IParseLiveQueryController.cs (1 hunks)
  • Parse/Abstractions/Platform/LiveQueries/IParseLiveQuerySubscription.cs (1 hunks)
  • Parse/Infrastructure/Execution/TextWebSocketClient.cs (1 hunks)
  • Parse/Infrastructure/LateInitializedMutableServiceHub.cs (5 hunks)
  • Parse/Infrastructure/MutableServiceHub.cs (6 hunks)
  • Parse/Infrastructure/OrchestrationServiceHub.cs (4 hunks)
  • Parse/Infrastructure/ServiceHub.cs (4 hunks)
  • Parse/Platform/LiveQueries/ParseLiveQuery.cs (1 hunks)
  • Parse/Platform/LiveQueries/ParseLiveQueryController.cs (1 hunks)
  • Parse/Platform/LiveQueries/ParseLiveQueryDualEventArgs.cs (1 hunks)
  • Parse/Platform/LiveQueries/ParseLiveQueryErrorEventArgs.cs (1 hunks)
  • Parse/Platform/LiveQueries/ParseLiveQueryEventArgs.cs (1 hunks)
  • Parse/Platform/LiveQueries/ParseLiveQuerySubscription.cs (1 hunks)
  • Parse/Platform/ParseClient.cs (1 hunks)
  • Parse/Platform/Queries/ParseQuery.cs (1 hunks)
  • Parse/Utilities/ObjectServiceExtensions.cs (5 hunks)
🔇 Additional comments (20)
Parse/Abstractions/Infrastructure/Execution/IWebSocketClient.cs (1)

29-38: Ensure proper error handling in implementations.

While the interface design is solid, implementations should handle common WebSocket scenarios like connection failures, network timeouts, and invalid URIs. Consider documenting expected exceptions in XML comments.

The interface methods should specify what exceptions can be thrown. Consider updating the documentation to include common exceptions like InvalidOperationException for operations on closed connections or ArgumentException for invalid URIs.

Parse.sln (1)

20-21: LGTM! Solution file properly configured.

The addition of the ParseApp project follows standard Visual Studio solution conventions with proper GUID assignment and build configurations for both Debug and Release modes.

Also applies to: 36-39

Parse/Abstractions/Infrastructure/CustomServiceHub.cs (1)

8-8: LGTM! Consistent implementation following established patterns.

The additions properly extend the service hub abstraction to include live query capabilities. The new properties follow the same delegation pattern as existing properties and maintain consistency with the class design.

Also applies to: 35-35, 47-47, 67-67

Parse/Platform/LiveQueries/ParseLiveQueryErrorEventArgs.cs (1)

8-51: Well-designed event args class with comprehensive documentation.

The class follows established EventArgs patterns with immutable properties and an internal constructor for controlled instantiation. The documentation is thorough and clearly explains each property's purpose.

Parse/Infrastructure/OrchestrationServiceHub.cs (2)

9-9: Good addition of required using statement.

The using statement for live query abstractions is correctly added to support the new properties.


37-37: Well-integrated live query service properties.

The new properties (WebSocketClient, LiveQueryController, LiveQueryServerConnectionData) follow the established orchestration pattern perfectly, using the same Custom ?? Default fallback approach as existing services.

Also applies to: 49-50, 69-70

Parse/Abstractions/Infrastructure/IServiceHub.cs (2)

10-10: Correct addition of live query abstractions using statement.

The using statement is properly added to support the new live query interface types.


30-30: Well-positioned live query service properties in interface.

The new properties are logically positioned within the interface structure and follow consistent naming conventions with existing services.

Also applies to: 43-43, 50-50

Parse/Infrastructure/ServiceHub.cs (2)

10-10: Correct addition of required using statements.

Both abstraction and platform using statements for live queries are properly added to support the new implementations.

Also applies to: 24-24


42-42: Excellent implementation of live query services with proper dependency injection.

The live query services are well-integrated following the established ServiceHub pattern:

  • LiveQueryServerConnectionData as a simple property
  • WebSocketClient with lazy initialization of TextWebSocketClient
  • LiveQueryController with proper dependency injection of WebSocketClient and Decoder

The dependency hierarchy is logical and the lazy initialization pattern is maintained consistently.

Also applies to: 55-55, 62-62

Parse/Infrastructure/LateInitializedMutableServiceHub.cs (1)

75-79: LGTM! Proper lazy initialization pattern implemented.

The WebSocketClient and LiveQueryController properties follow the established lazy initialization pattern correctly, with appropriate dependency ordering (WebSocketClient is initialized before LiveQueryController which depends on it).

Also applies to: 111-115, 178-178

Parse/Abstractions/Infrastructure/IMutableServiceHub.cs (1)

22-22: Clean interface extension for live query support.

The additions follow the established pattern of the mutable service hub interface, properly extending functionality without breaking existing contracts.

Also applies to: 35-35, 42-42

Parse/Utilities/ObjectServiceExtensions.cs (1)

291-317: Well-designed extension methods for live query management.

The ConnectLiveQueryServerAsync and DisconnectLiveQueryServerAsync methods provide a clean API for managing live query connections with appropriate optional parameters and proper async patterns.

Parse/Platform/ParseClient.cs (1)

100-159: Constructor implementation follows established patterns.

The new constructor overload properly handles separate configurations for the main server and live query server, maintaining consistency with the existing constructor design.

Parse/Infrastructure/MutableServiceHub.cs (1)

39-39: Proper integration of live query services.

The MutableServiceHub correctly extends the service initialization pattern to include live query functionality, with appropriate dependency ordering and consistent null-coalescing initialization.

Also applies to: 52-52, 59-59, 73-76, 93-93, 100-100

Parse/Abstractions/Platform/LiveQueries/IParseLiveQuerySubscription.cs (1)

9-77: Well-designed interface with comprehensive event coverage.

The interface provides a clean abstraction for live query subscriptions with proper event handling for all object lifecycle events and appropriate async method signatures with cancellation token support.

Parse/Abstractions/Platform/LiveQueries/IParseLiveQueryController.cs (1)

8-127: Excellent interface design with comprehensive functionality.

The interface provides a complete contract for live query management with proper async patterns, exception documentation, and comprehensive method coverage for all live query operations.

Parse/Platform/LiveQueries/ParseLiveQuery.cs (1)

18-96: Well-structured live query implementation with modern C# patterns.

The class design is clean with proper encapsulation, immutable composition pattern, and modern C# features like collection expressions in MergeWatchers.

Parse/Platform/LiveQueries/ParseLiveQuerySubscription.cs (1)

14-51: Well-implemented subscription class with proper event handling.

The class correctly implements the interface with proper event definitions and event handler methods that safely invoke events with null checks.

Also applies to: 98-142

Parse/Platform/LiveQueries/ParseLiveQueryController.cs (1)

26-26: Make ClientId setter private to ensure immutability.

The ClientId is only set once during connection establishment and should not be modified externally.

-private string ClientId { get; set; }
+private string ClientId { get; private set; }

Likely an incorrect or invalid review comment.

Comment on lines 75 to 103
private async Task ListenForMessages(CancellationToken cancellationToken)
{
byte[] buffer = new byte[1024 * 4];

try
{
while (!cancellationToken.IsCancellationRequested &&
webSocket.State == WebSocketState.Open)
{
WebSocketReceiveResult result = await webSocket.ReceiveAsync(
new ArraySegment<byte>(buffer),
cancellationToken);

if (result.MessageType == WebSocketMessageType.Close)
{
await CloseAsync(cancellationToken);
break;
}

string message = Encoding.UTF8.GetString(buffer, 0, result.Count);
MessageReceived?.Invoke(this, message);
}
}
catch (OperationCanceledException ex)
{
// Normal cancellation, no need to handle
Debug.WriteLine($"ClientWebsocket connection was closed: {ex.Message}");
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve exception handling and make buffer size configurable.

The method only catches OperationCanceledException but other exceptions like WebSocketException could occur and should be handled. Also consider making the buffer size configurable.

private async Task ListenForMessages(CancellationToken cancellationToken)
{
-    byte[] buffer = new byte[1024 * 4];
+    byte[] buffer = new byte[BufferSize]; // Make BufferSize a configurable property

    try
    {
        // ... existing code ...
    }
    catch (OperationCanceledException ex)
    {
        // Normal cancellation, no need to handle
        Debug.WriteLine($"ClientWebsocket connection was closed: {ex.Message}");
    }
+    catch (WebSocketException ex)
+    {
+        Debug.WriteLine($"WebSocket error: {ex.Message}");
+        // Consider raising an error event or reconnection logic
+    }
+    catch (Exception ex)
+    {
+        Debug.WriteLine($"Unexpected error in WebSocket listener: {ex.Message}");
+        // Handle unexpected exceptions
+    }
}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In Parse/Infrastructure/Execution/TextWebSocketClient.cs between lines 75 and
103, the ListenForMessages method currently only catches
OperationCanceledException, missing other potential exceptions like
WebSocketException that should be handled to avoid unhandled crashes. Update the
exception handling to also catch WebSocketException and handle it appropriately,
such as logging or cleanup. Additionally, refactor the buffer allocation to use
a configurable buffer size instead of a fixed 4KB size, allowing flexibility for
different use cases.

ParseLiveQueryErrorEventArgs errorArgs = new ParseLiveQueryErrorEventArgs(
Convert.ToInt32(message["code"]),
message["error"] as string,
(bool) message["reconnect"]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add safe casting to prevent InvalidCastException.

Direct casting to bool will throw if the value is null or not a boolean.

-                    (bool) message["reconnect"]);
+                    message.TryGetValue("reconnect", out object reconnectValue) && reconnectValue is bool reconnect && reconnect);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
(bool) message["reconnect"]);
message.TryGetValue("reconnect", out object reconnectValue)
&& reconnectValue is bool reconnect
&& reconnect);
🤖 Prompt for AI Agents
In Parse/Platform/LiveQueries/ParseLiveQueryController.cs at line 160, the code
directly casts message["reconnect"] to bool, which can throw an
InvalidCastException if the value is null or not a boolean. Replace the direct
cast with a safe casting approach such as using the 'as' operator with a
nullable bool or checking the type before casting, and provide a default value
if the cast fails to prevent exceptions.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
Parse/Platform/LiveQueries/ParseLiveQuery.cs (1)

108-109: Fix cancellation token usage in SubscribeAsync.

The method ignores the implicit cancellation token and uses CancellationToken.None, which prevents callers from cancelling the subscription operation.

The method should accept a cancellation token parameter:

-public async Task<IParseLiveQuerySubscription> SubscribeAsync() =>
-    await Services.LiveQueryController.SubscribeAsync(this, CancellationToken.None);
+public async Task<IParseLiveQuerySubscription> SubscribeAsync(CancellationToken cancellationToken = default) =>
+    await Services.LiveQueryController.SubscribeAsync(this, cancellationToken);
🧹 Nitpick comments (1)
Parse/Platform/LiveQueries/ParseLiveQuery.cs (1)

63-77: Simplify KeyWatchers assignment logic.

The assignment at line 71 is unnecessary since it's conditionally overwritten at line 75. This makes the code harder to follow.

 private ParseLiveQuery(ParseLiveQuery<T> source, IEnumerable<string> watchedKeys = null)
 {
     ArgumentNullException.ThrowIfNull(source);

     Services = source.Services;
     ClassName = source.ClassName;
     Filters = source.Filters;
     KeySelections = source.KeySelections;
-    KeyWatchers = source.KeyWatchers;

-    if (watchedKeys is not null)
-    {
-        KeyWatchers = new ReadOnlyCollection<string>(MergeWatchers(watchedKeys).ToList());
-    }
+    KeyWatchers = watchedKeys is not null 
+        ? new ReadOnlyCollection<string>(MergeWatchers(watchedKeys).ToList())
+        : source.KeyWatchers;
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4b83d23 and 65c73e4.

📒 Files selected for processing (2)
  • Parse/Platform/LiveQueries/ParseLiveQuery.cs (1 hunks)
  • Parse/Platform/Queries/ParseQuery.cs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • Parse/Platform/Queries/ParseQuery.cs
🔇 Additional comments (2)
Parse/Platform/LiveQueries/ParseLiveQuery.cs (2)

79-79: LGTM!

The method correctly uses modern C# collection expression syntax and handles null values appropriately.


90-98: LGTM!

The method correctly builds query parameters with appropriate null checks to avoid adding empty collections.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (6)
Parse/Infrastructure/Execution/TextWebSocketClient.cs (2)

82-82: Make buffer size configurable.

The buffer size is hardcoded to 4KB, which limits flexibility for different use cases.

Add a configurable property:

+private int BufferSize { get; set; } = 1024 * 4;

 private async Task ListenForMessages(CancellationToken cancellationToken)
 {
-    byte[] buffer = new byte[1024 * 4];
+    byte[] buffer = new byte[BufferSize];

167-171: Add WebSocket state validation.

The method should verify the WebSocket is open before sending to avoid exceptions.

 public async Task SendAsync(string message, CancellationToken cancellationToken = default)
 {
     ArgumentNullException.ThrowIfNull(webSocket);
+    if (webSocket.State != WebSocketState.Open)
+    {
+        throw new InvalidOperationException("WebSocket is not connected or not in an open state.");
+    }
     await webSocket.SendAsync(Encoding.UTF8.GetBytes(message), WebSocketMessageType.Text, true, cancellationToken);
 }
Parse/Platform/LiveQueries/ParseLiveQuerySubscription.cs (2)

78-79: Fix cancellation token usage.

The method ignores the cancellationToken parameter.

 public async Task UpdateAsync<T1>(ParseLiveQuery<T1> liveQuery, CancellationToken cancellationToken = default) where T1 : ParseObject =>
-    await Services.LiveQueryController.UpdateSubscriptionAsync(liveQuery, RequestId, cancellationToken);
+    await Services.LiveQueryController.UpdateSubscriptionAsync(liveQuery, RequestId, cancellationToken);

Also consider simplifying the generic parameter since the class is already generic:

-public async Task UpdateAsync<T1>(ParseLiveQuery<T1> liveQuery, CancellationToken cancellationToken = default) where T1 : ParseObject =>
+public async Task UpdateAsync(ParseLiveQuery<T> liveQuery, CancellationToken cancellationToken = default) =>

88-89: Fix cancellation token usage in CancelAsync.

The method ignores the cancellationToken parameter.

 public async Task CancelAsync(CancellationToken cancellationToken = default) =>
-    await Services.LiveQueryController.UnsubscribeAsync(RequestId, cancellationToken);
+    await Services.LiveQueryController.UnsubscribeAsync(RequestId, cancellationToken);
Parse/Platform/LiveQueries/ParseLiveQuery.cs (1)

39-42: Add null validation for critical constructor parameters.

The constructor should validate serviceHub and className parameters.

 internal ParseLiveQuery(IServiceHub serviceHub, string className, IDictionary<string, object> filters, IEnumerable<string> selectedKeys = null, IEnumerable<string> watchedKeys = null)
 {
+    ArgumentNullException.ThrowIfNull(serviceHub);
+    ArgumentException.ThrowIfNullOrWhiteSpace(className);
     ArgumentNullException.ThrowIfNull(filters);
Parse/Platform/LiveQueries/ParseLiveQueryController.cs (1)

262-262: Add safe casting to prevent InvalidCastException.

Direct casting to bool could throw if the value is null or not a boolean.

-            (bool) message["reconnect"]);
+            message.TryGetValue("reconnect", out object reconnectValue) && reconnectValue is bool reconnect ? reconnect : false);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 340f6fb and 7e66bb6.

📒 Files selected for processing (7)
  • Parse/Infrastructure/Execution/TextWebSocketClient.cs (1 hunks)
  • Parse/Platform/LiveQueries/ParseLiveQuery.cs (1 hunks)
  • Parse/Platform/LiveQueries/ParseLiveQueryController.cs (1 hunks)
  • Parse/Platform/LiveQueries/ParseLiveQueryDualEventArgs.cs (1 hunks)
  • Parse/Platform/LiveQueries/ParseLiveQueryEventArgs.cs (1 hunks)
  • Parse/Platform/LiveQueries/ParseLiveQuerySubscription.cs (1 hunks)
  • Parse/Platform/ParseClient.cs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • Parse/Platform/LiveQueries/ParseLiveQueryDualEventArgs.cs
  • Parse/Platform/LiveQueries/ParseLiveQueryEventArgs.cs
  • Parse/Platform/ParseClient.cs
🧰 Additional context used
🧬 Code Graph Analysis (1)
Parse/Infrastructure/Execution/TextWebSocketClient.cs (1)
Parse/Platform/LiveQueries/ParseLiveQueryController.cs (9)
  • Task (297-305)
  • Task (307-308)
  • Task (310-323)
  • Task (350-389)
  • Task (391-416)
  • Task (440-458)
  • Task (479-488)
  • Task (505-514)
  • Task (525-533)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
Parse/Infrastructure/Execution/TextWebSocketClient.cs (2)

183-187: Add WebSocket state validation to match documented exceptions.

The method correctly handles null checks but doesn't validate the WebSocket state before sending, which could result in exceptions that don't match the documented behavior.

public async Task SendAsync(string message, CancellationToken cancellationToken = default)
{
    ArgumentNullException.ThrowIfNull(webSocket);
+    
+    if (webSocket.State != WebSocketState.Open)
+    {
+        throw new InvalidOperationException($"WebSocket is not in Open state. Current state: {webSocket.State}");
+    }
+    
    await webSocket.SendAsync(Encoding.UTF8.GetBytes(message), WebSocketMessageType.Text, true, cancellationToken);
}

55-67: Address remaining race condition in connection logic.

While the WebSocket initialization is properly synchronized, the state check and connection attempt are still not atomic. Multiple threads could pass the state check simultaneously and attempt to connect concurrently.

Consider this approach to ensure atomic connection logic:

public async Task OpenAsync(string serverUri, CancellationToken cancellationToken = default)
{
+    ClientWebSocket webSocketToConnect = null;
+    
    lock (connectionLock)
    {
        webSocket ??= new ClientWebSocket();
+        
+        if (webSocket.State != WebSocketState.Open && webSocket.State != WebSocketState.Connecting)
+        {
+            webSocketToConnect = webSocket;
+        }
    }

-    if (webSocket.State != WebSocketState.Open && webSocket.State != WebSocketState.Connecting)
+    if (webSocketToConnect != null)
    {
-        await webSocket.ConnectAsync(new Uri(serverUri), cancellationToken);
+        await webSocketToConnect.ConnectAsync(new Uri(serverUri), cancellationToken);
        StartListening(cancellationToken);
    }
}
🧹 Nitpick comments (2)
Parse/Infrastructure/Execution/TextWebSocketClient.cs (2)

77-78: Remove unnecessary null-forgiving operator.

The null-forgiving operator (!) is unnecessary and potentially misleading since the null-conditional operator (?.) can return null.

-public async Task CloseAsync(CancellationToken cancellationToken = default) =>
-    await webSocket?.CloseAsync(WebSocketCloseStatus.NormalClosure, String.Empty, cancellationToken)!;
+public async Task CloseAsync(CancellationToken cancellationToken = default)
+{
+    if (webSocket != null)
+    {
+        await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, String.Empty, cancellationToken);
+    }
+}

80-137: Excellent exception handling and partial message support!

The method properly handles various exception types and correctly manages partial WebSocket messages. However, consider making the buffer size configurable for different use cases.

Consider adding a configurable buffer size:

+private const int DefaultBufferSize = 1024 * 4;
+private readonly int bufferSize;

+// Add to constructor or make it a property
+public int BufferSize { get; init; } = DefaultBufferSize;

private async Task ListenForMessages(CancellationToken cancellationToken)
{
-    byte[] buffer = new byte[1024 * 4];
+    byte[] buffer = new byte[BufferSize];
    // ... rest of the method
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7e66bb6 and 84f7060.

📒 Files selected for processing (2)
  • Parse/Infrastructure/Execution/TextWebSocketClient.cs (1 hunks)
  • Parse/Platform/LiveQueries/ParseLiveQuery.cs (1 hunks)
🔇 Additional comments (11)
Parse/Platform/LiveQueries/ParseLiveQuery.cs (9)

1-9: LGTM - Clean imports and namespace declaration.

The using statements are appropriate and well-organized, importing necessary System libraries and Parse-specific abstractions for live query functionality.


12-18: LGTM - Well-documented generic class declaration.

The XML documentation clearly explains the purpose and constraints of the ParseLiveQuery class. The generic constraint where T : ParseObject is appropriate for the Parse SDK context.


20-37: LGTM - Well-designed immutable properties.

The properties follow good immutability practices with get-only accessors and appropriate access levels. Using ReadOnlyCollection<string> for collections ensures the class remains immutable after construction.


39-58: LGTM - Robust constructor with proper validation.

The constructor properly validates all critical parameters and handles optional collections correctly. The use of ToList() prevents potential deferred execution issues with the input enumerables.


65-79: LGTM - Well-designed composition constructor.

The private constructor for query composition properly copies all properties from the source and allows extending watched keys. Good validation and immutability design.


83-90: LGTM - Clean fluent API method.

The Watch method provides a clean fluent interface for adding watched keys, maintaining immutability by returning a new instance.


92-100: LGTM - Correct parameter building logic.

The BuildParameters method correctly constructs the dictionary needed for live query operations, properly handling optional collections and converting them to arrays as expected by the Parse Server API.


102-111: LGTM - Proper async subscription with cancellation support.

The SubscribeAsync method correctly accepts a cancellation token parameter and passes it through to the controller. This addresses the previous review concern about cancellation token usage.


81-81: Verify C# 12 collection expression compatibility.

The code uses C# 12 collection expression syntax [..] which may not be compatible with older target frameworks. Ensure your project targets .NET 8+ or C# 12+.

#!/bin/bash
# Description: Check the target framework and C# language version in project files
# Expected: Confirm .NET 8+ or C# 12+ support for collection expressions

echo "Checking .csproj files for target framework and language version..."
fd -e csproj --exec cat {} \; | grep -E "(TargetFramework|LangVersion)"

echo -e "\nChecking Directory.Build.props for global settings..."
find . -name "Directory.Build.props" -exec cat {} \;
Parse/Infrastructure/Execution/TextWebSocketClient.cs (2)

11-44: LGTM! Good class structure with proper thread safety.

The class is well-structured with appropriate encapsulation, proper field declarations, and includes a connection lock for thread safety. The use of events for message notification follows good event-driven design patterns.


143-169: Well-implemented listener management with proper fault monitoring.

The method correctly prevents multiple concurrent listeners and properly monitors the listening task for faults without causing application crashes. The fault handling approach is much improved.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
Parse/Platform/LiveQueries/ParseLiveQueryController.cs (1)

564-564: Consider potential deadlock in Dispose method.

Using GetAwaiter().GetResult() in a disposal context could potentially cause deadlocks in certain synchronization contexts, though this is a common pattern for dispose methods.

Consider using ConfigureAwait(false) to reduce deadlock risk:

- CloseAsync().GetAwaiter().GetResult();
+ CloseAsync().ConfigureAwait(false).GetAwaiter().GetResult();
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 84f7060 and 834ff89.

📒 Files selected for processing (15)
  • Parse/Abstractions/Infrastructure/CustomServiceHub.cs (4 hunks)
  • Parse/Abstractions/Infrastructure/ILiveQueryServerConnectionData.cs (1 hunks)
  • Parse/Abstractions/Infrastructure/IMutableServiceHub.cs (3 hunks)
  • Parse/Abstractions/Infrastructure/IServiceHub.cs (3 hunks)
  • Parse/Abstractions/Platform/LiveQueries/IParseLiveQueryController.cs (1 hunks)
  • Parse/Abstractions/Platform/LiveQueries/IParseLiveQuerySubscription.cs (1 hunks)
  • Parse/Infrastructure/Execution/TextWebSocketClient.cs (1 hunks)
  • Parse/Infrastructure/LateInitializedMutableServiceHub.cs (3 hunks)
  • Parse/Infrastructure/LiveQueryServerConnectionData.cs (1 hunks)
  • Parse/Infrastructure/MutableServiceHub.cs (6 hunks)
  • Parse/Infrastructure/OrchestrationServiceHub.cs (4 hunks)
  • Parse/Infrastructure/ServiceHub.cs (4 hunks)
  • Parse/Platform/LiveQueries/ParseLiveQueryController.cs (1 hunks)
  • Parse/Platform/ParseClient.cs (1 hunks)
  • Parse/Utilities/ObjectServiceExtensions.cs (5 hunks)
✅ Files skipped from review due to trivial changes (1)
  • Parse/Infrastructure/LiveQueryServerConnectionData.cs
🚧 Files skipped from review as they are similar to previous changes (12)
  • Parse/Infrastructure/OrchestrationServiceHub.cs
  • Parse/Abstractions/Infrastructure/CustomServiceHub.cs
  • Parse/Utilities/ObjectServiceExtensions.cs
  • Parse/Abstractions/Infrastructure/IMutableServiceHub.cs
  • Parse/Abstractions/Infrastructure/IServiceHub.cs
  • Parse/Infrastructure/ServiceHub.cs
  • Parse/Platform/ParseClient.cs
  • Parse/Abstractions/Platform/LiveQueries/IParseLiveQueryController.cs
  • Parse/Infrastructure/Execution/TextWebSocketClient.cs
  • Parse/Abstractions/Platform/LiveQueries/IParseLiveQuerySubscription.cs
  • Parse/Infrastructure/LateInitializedMutableServiceHub.cs
  • Parse/Infrastructure/MutableServiceHub.cs
🔇 Additional comments (1)
Parse/Platform/LiveQueries/ParseLiveQueryController.cs (1)

20-124: Excellent implementation of previous feedback.

The class now properly implements thread safety with volatile state field, ConcurrentDictionary collections, Interlocked.Increment for request IDs, and a proper IDisposable pattern. The async patterns have been correctly implemented with TaskCompletionSource instead of blocking calls.

string clientId = message["clientId"] as string;
if (clientId != ClientId)
return;
int requestId = Convert.ToInt32(message["requestId"]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add safe conversion for message fields to prevent exceptions.

The code uses Convert.ToInt32() and Convert.ToBoolean() directly on dictionary values without null checking, which could throw exceptions if the values are null or invalid.

Consider using safe conversion patterns:

- int requestId = Convert.ToInt32(message["requestId"]);
+ if (!message.TryGetValue("requestId", out object requestIdObj) || !int.TryParse(requestIdObj?.ToString(), out int requestId))
+     return;

- Convert.ToBoolean(message["reconnect"])
+ message.TryGetValue("reconnect", out object reconnectObj) && bool.TryParse(reconnectObj?.ToString(), out bool reconnect) && reconnect

Also applies to: 189-189, 209-209, 229-229, 249-249, 262-264, 273-273, 285-285

🤖 Prompt for AI Agents
In Parse/Platform/LiveQueries/ParseLiveQueryController.cs around line 174 and
similarly at lines 189, 209, 229, 249, 262-264, 273, and 285, the code uses
Convert.ToInt32() and Convert.ToBoolean() directly on dictionary values without
checking for null or invalid inputs, which can cause exceptions. Update these
lines to safely convert the values by first checking if the dictionary contains
the key and if the value is not null, then attempt to parse the value using
int.TryParse or bool.TryParse as appropriate, providing a default value if
parsing fails to prevent exceptions.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
Parse/Platform/LiveQueries/ParseLiveQueryController.cs (1)

174-174: Use safe type conversion to prevent runtime exceptions.

The direct use of Convert.ToInt32() and Convert.ToBoolean() without null checking can throw exceptions if the message values are null or invalid.

Apply safe conversion patterns as previously suggested:

- int requestId = Convert.ToInt32(message["requestId"]);
+ if (!message.TryGetValue("requestId", out object requestIdObj) || !int.TryParse(requestIdObj?.ToString(), out int requestId))
+     return;

Also applies to lines 189, 209, 229, 249, 262, 264, 273, 285.

🧹 Nitpick comments (1)
Parse/Platform/LiveQueries/ParseLiveQueryController.cs (1)

169-257: Extract repeated client ID validation into a helper method.

Multiple event processing methods duplicate the same client ID validation pattern, violating DRY principles.

Consider extracting this into a helper method:

private bool ValidateClientMessage(IDictionary<string, object> message, out int requestId)
{
    requestId = 0;
    
    if (!(message.TryGetValue("clientId", out object clientIdObj) && 
          clientIdObj is string clientId && clientId == ClientId))
        return false;
        
    return message.TryGetValue("requestId", out object requestIdObj) && 
           int.TryParse(requestIdObj?.ToString(), out requestId);
}

Then update each event processing method:

- string clientId = message["clientId"] as string;
- if (clientId != ClientId)
-     return;
- int requestId = Convert.ToInt32(message["requestId"]);
+ if (!ValidateClientMessage(message, out int requestId))
+     return;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 834ff89 and bd36b7d.

📒 Files selected for processing (2)
  • Parse/Abstractions/Infrastructure/ILiveQueryServerConnectionData.cs (1 hunks)
  • Parse/Platform/LiveQueries/ParseLiveQueryController.cs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • Parse/Abstractions/Infrastructure/ILiveQueryServerConnectionData.cs
🧰 Additional context used
🧬 Code Graph Analysis (1)
Parse/Platform/LiveQueries/ParseLiveQueryController.cs (6)
Parse/Platform/LiveQueries/ParseLiveQueryErrorEventArgs.cs (2)
  • ParseLiveQueryErrorEventArgs (8-51)
  • ParseLiveQueryErrorEventArgs (45-50)
Parse/Platform/LiveQueries/ParseLiveQuery.cs (6)
  • IDictionary (92-100)
  • Task (110-111)
  • ParseLiveQuery (18-112)
  • ParseLiveQuery (39-58)
  • ParseLiveQuery (65-79)
  • ParseLiveQuery (90-90)
Parse/Platform/ParseClient.cs (7)
  • IDictionary (185-196)
  • IDictionary (198-201)
  • ParseClient (19-220)
  • ParseClient (58-58)
  • ParseClient (66-98)
  • ParseClient (107-159)
  • ParseClient (164-164)
Parse/Platform/LiveQueries/ParseLiveQuerySubscription.cs (9)
  • OnDelete (141-142)
  • OnLeave (130-133)
  • OnUpdate (118-121)
  • OnEnter (107-110)
  • OnCreate (98-99)
  • Task (78-79)
  • Task (88-89)
  • ParseLiveQuerySubscription (14-143)
  • ParseLiveQuerySubscription (58-63)
Parse/Abstractions/Platform/LiveQueries/IParseLiveQueryController.cs (5)
  • Task (41-41)
  • Task (64-64)
  • Task (84-84)
  • Task (101-101)
  • Task (114-114)
Parse/Infrastructure/Execution/TextWebSocketClient.cs (4)
  • Task (57-74)
  • Task (84-90)
  • Task (92-149)
  • Task (195-203)
🔇 Additional comments (3)
Parse/Platform/LiveQueries/ParseLiveQueryController.cs (3)

20-124: Class structure and constructor implementation look excellent.

The implementation properly addresses thread safety concerns with ConcurrentDictionary collections, volatile state field, and uses Interlocked operations for request ID generation. The constructor is well-designed with clear parameter requirements.


352-534: Excellent async implementation with proper resource management.

The public API methods demonstrate best practices with TaskCompletionSource usage, proper cancellation token handling, and comprehensive resource cleanup. The refactored SendAndWaitForSignalAsync helper eliminates code duplication effectively.


544-567: Proper IDisposable pattern implementation.

The dispose pattern correctly implements standard .NET practices with appropriate resource cleanup and proper handling of the async CloseAsync call within the synchronous dispose context.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant