Skip to content

WIP: Refactor of new server#865

Closed
davemarco wants to merge 9 commits into
y-scope:mainfrom
davemarco:newServer
Closed

WIP: Refactor of new server#865
davemarco wants to merge 9 commits into
y-scope:mainfrom
davemarco:newServer

Conversation

@davemarco

@davemarco davemarco commented Apr 29, 2025

Copy link
Copy Markdown
Contributor

Description

Refactor of server for style, and some logic changes. The PR also has unreviewed client code from abby. Just PRing this so abby can take a look at server code.

Largest change was removing reference counter on the mongoCollection. I think my change
is simpler and removes some metadata we were maintaining.

Also fixes the previous bug where things were not updating.

Checklist

  • The PR satisfies the contribution guidelines.
  • This is a breaking change and that has been indicated in the PR title, OR this isn't a
    breaking change.
  • Necessary docs have been updated, OR no docs need to be updated.

Validation performed

Summary by CodeRabbit

  • New Features

    • Introduced real-time, socket-based querying and live updates for MongoDB collections in the log viewer web UI.
    • Added toggleable UI sections to view live data from different MongoDB collections.
    • Implemented new backend plugin enabling reactive MongoDB queries over WebSockets.
  • Improvements

    • Enhanced TypeScript and ESLint configuration for better code quality and shared type safety.
    • Updated development and runtime dependencies for both client and server.
    • Improved Vite and TypeScript configuration for easier module resolution and local development.
  • Chores

    • Updated environment variables and server scripts for improved deployment and development workflow.

@davemarco davemarco requested a review from a team as a code owner April 29, 2025 16:23
@coderabbitai

coderabbitai Bot commented Apr 29, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

This change introduces a real-time, reactive MongoDB query system for the log-viewer web UI using Socket.IO. On the client side, new classes and hooks are added for connecting to MongoDB collections over sockets, subscribing to live query results, and integrating them into React components. On the server side, a Fastify plugin is implemented to handle Socket.IO connections, manage MongoDB change streams, and broadcast updates to subscribed clients. Shared TypeScript types for socket events are defined in a new common module. The build configurations and environment variables are updated to support these changes.

Changes

File(s) Change Summary
client/package.json, server/package.json Added dependencies for socket.io-client, MongoDB, ESLint plugins, and build scripts. Updated main entry points and scripts.
client/tsconfig/tsconfig.app.json, server/tsconfig.json Updated TypeScript configs to support shared common folder and path aliases.
common/index.ts Introduced shared TypeScript interfaces/types for socket event contracts between client and server.
client/src/api/socket/MongoCollectionSocket.ts, client/src/api/socket/MongoCursorSocket.ts Added classes for managing socket connections and cursor-like subscriptions to MongoDB collections.
client/src/api/socket/useCursor.tsx Added a React hook for managing reactive MongoDB cursor subscriptions.
client/src/App.tsx Refactored to use new socket-based data viewers and removed legacy UI/theming code.
client/src/main.tsx Disabled React StrictMode by commenting it out.
client/vite.config.ts Added Vite plugin for tsconfig paths, updated dev server port, host, and proxy settings for socket support.
server/.env Changed server port and set database password.
server/src/app.ts Registered new Fastify MongoDB Socket.IO plugin.
server/src/plugins/MongoSocketIoServer/index.ts, MongoWatcherCollection.ts, typings.ts, utils.ts Implemented Fastify plugin for real-time MongoDB over Socket.IO, including watcher management, type definitions, and utility functions.
client/src/mongoCDCLib/MongoCollectionReactiveCursor.ts Added a class for client-side management of reactive MongoDB cursors using sockets.

Sequence Diagram(s)

sequenceDiagram
    participant ReactApp
    participant MongoCollectionSocket
    participant MongoCursorSocket
    participant SocketIOClient
    participant FastifyServer
    participant SocketIOServer
    participant MongoWatcherCollection
    participant MongoDB

    ReactApp->>MongoCollectionSocket: new MongoCollectionSocket("collection")
    MongoCollectionSocket->>SocketIOClient: connect + emit "collection::init"
    SocketIOClient->>SocketIOServer: "collection::init"
    SocketIOServer->>MongoWatcherCollection: ensure collection exists

    ReactApp->>MongoCollectionSocket: .find(query, options)
    MongoCollectionSocket->>MongoCursorSocket: new MongoCursorSocket(socket, query, options)
    ReactApp->>MongoCursorSocket: .subscribe(onDataUpdate)
    MongoCursorSocket->>SocketIOClient: emit "collection::find::toReactiveArray"
    SocketIOClient->>SocketIOServer: "collection::find::toReactiveArray"
    SocketIOServer->>MongoWatcherCollection: createWatcher or subscribe
    MongoWatcherCollection->>MongoDB: watch + initial query
    MongoWatcherCollection-->>SocketIOServer: initialDocuments, queryId
    SocketIOServer-->>SocketIOClient: ack {queryId, initialDocuments}
    SocketIOClient-->>MongoCursorSocket: onDataUpdate(initialDocuments)
    MongoCursorSocket-->>ReactApp: onDataUpdate(initialDocuments)

    MongoWatcherCollection-->>MongoDB: listen for changes
    MongoWatcherCollection-->>SocketIOServer: on change, emit "collection::find::update"
    SocketIOServer-->>SocketIOClient: "collection::find::update" (queryId, updatedData)
    SocketIOClient-->>MongoCursorSocket: onDataUpdate(updatedData)
    MongoCursorSocket-->>ReactApp: onDataUpdate(updatedData)
Loading

Possibly related PRs

  • y-scope/clp#645: Also modifies the package.json of the log-viewer-webui-client, focusing on switching from Webpack to Vite and converting the codebase to TypeScript, which overlaps with dependency management changes in this PR.
✨ Finishing Touches
  • 📝 Generate Docstrings

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.
    • Generate unit testing code for this file.
    • 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 generate unit testing code for this file.
    • @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 generate unit testing code.
    • @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 docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 31

🧹 Nitpick comments (14)
components/log-viewer-webui/client/src/App.tsx (6)

3-5: Consider cleaning up commented code

Rather than commenting out old code, consider removing it completely if it's no longer needed, or add a TODO comment explaining why it's temporarily commented out.


12-12: Remove commented code

This commented code line for an unused collection should be removed.


20-28: Query implementation looks good but consider extracting to a separate function

The MongoDB reactive query is properly implemented with the useTracker hook. However, consider extracting the query to a separate function for better readability and testability.


40-46: Remove commented debug code

Remove the commented debug logging code before submitting the final PR.


63-65: Clean up commented components

Remove these commented components if they are no longer needed.


48-62: Consider using proper semantic HTML and styling

The current implementation simply displays JSON strings in divs. Consider using proper semantic HTML elements and adding some basic styling for better readability.

-        <div>
-            {results.map((r) => (
-                <div>
-                    {JSON.stringify(r)}
-                </div>
-            ))}
-            {results2.map((r) => (
-                <div>
-                    {JSON.stringify(r)}
-                </div>
-            ))}
-
-        </div>
+        <div className="results-container">
+            <section className="results-section">
+                <h2>Query 1 Results (IDs 1-10)</h2>
+                <ul>
+                    {results.map((r) => (
+                        <li key={r._id}>
+                            <pre>{JSON.stringify(r, null, 2)}</pre>
+                        </li>
+                    ))}
+                </ul>
+            </section>
+            <section className="results-section">
+                <h2>Query 2 Results (IDs 1-5)</h2>
+                <ul>
+                    {results2.map((r) => (
+                        <li key={r._id}>
+                            <pre>{JSON.stringify(r, null, 2)}</pre>
+                        </li>
+                    ))}
+                </ul>
+            </section>
+        </div>
🧰 Tools
🪛 Biome (1.9.4)

[error] 51-51: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.

(lint/correctness/useJsxKeyInIterable)


[error] 56-56: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.

(lint/correctness/useJsxKeyInIterable)

components/log-viewer-webui/client/src/mongoCDCLib/useTracker.tsx (2)

12-12: Fix the documentation reference to match the parameter type.

The JSDoc references MongoReplicaCollectionReactiveCursor but the parameter type is MongoCollectionReactiveCursor.

-* @param getCursor Function that returns a MongoReplicaCollectionReactiveCursor.
+* @param getCursor Function that returns a MongoCollectionReactiveCursor.

31-33: Consider implementing better error handling.

The current implementation only logs errors to the console without any recovery mechanism or user notification.

onError: (error: unknown) => {
    console.error("Error fetching data:", error);
+   // Consider adding a more robust error handling strategy here:
+   // - Set an error state that can be returned from the hook
+   // - Implement a retry mechanism 
+   // - Notify the user through a toast/notification system
},
components/log-viewer-webui/client/src/mongoCDCLib/MongoCollection.ts (1)

57-58: Implement non-reactive cursors as noted in TODO.

There's a TODO comment about adding support for non-reactive cursors that should be implemented.

Would you like me to provide an implementation for non-reactive cursors? This would allow for better optimization when real-time updates aren't needed.

components/log-viewer-webui/client/src/mongoCDCLib/MongoCollectionCursor.ts (1)

92-97: Improve error handling and type checking in callback.

The code could use better type checking for the response object.

- }, (response: {error?: Error; data?: Document[]}) => {
-     if (response.error) {
-         reject(response.error);
+ }, (response: unknown) => {
+     if (!response || typeof response !== 'object') {
+         reject(new Error('Invalid response from server'));
+         return;
+     }
+     
+     const typedResponse = response as {error?: Error; data?: Document[]};
+     if (typedResponse.error) {
+         reject(typedResponse.error);
components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/utils.ts (1)

26-30: Use Object.prototype.hasOwnProperty.call instead of Object.hasOwn.

Per the coding guidelines for **/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx} preferred format would be false == <expression> rather than !<expression>. Similarly, for consistency and broader compatibility, use Object.prototype.hasOwnProperty.call instead of Object.hasOwn.

for (const key in query) {
-   if (Object.hasOwn(query, key)) {
+   if (false === Object.prototype.hasOwnProperty.call(query, key)) {
+       continue;
+   }
+   
    changeStreamQuery[`fullDocument.${key}`] = query[key] as unknown;
}
components/log-viewer-webui/client/src/mongoCDCLib/MongoCollectionReactiveCursor.ts (1)

88-92: Type-guard can be simplified & made safer

if ("undefined" !== typeof response.data.queryId) will throw when response.data is undefined.
Use optional chaining:

- if ("undefined" !== typeof response.data.queryId) {
+ if (response.data?.queryId !== undefined) {
components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/MongoWatcherCollection.ts (1)

195-205: Lack of error propagation on fetch failures

When collection.find() fails, the error is logged but clients are never informed, leaving them silently stale.

Emit an error event so subscribers can react:

catch (error) {
    console.error("Error fetching data for update:", error);
    this.io.to(`${queryId}`).emit("collection::find::update", {
        queryId,
        error: (error as Error).message,
        data: [],
    });
}
components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts (1)

233-237: Conform to project boolean-comparison guideline

Coding-guideline: prefer false == <expr> rather than !<expr>.
Replace the negated truthy check:

-        if (!collectionName) {
+        if (false == collectionName) {
             this.#fastify.log.error("Collection name is undefined");
             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 329eb7f and d09a489.

⛔ Files ignored due to path filters (2)
  • components/log-viewer-webui/client/package-lock.json is excluded by !**/package-lock.json
  • components/log-viewer-webui/server/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (13)
  • components/log-viewer-webui/client/package.json (1 hunks)
  • components/log-viewer-webui/client/src/App.tsx (1 hunks)
  • components/log-viewer-webui/client/src/mongoCDCLib/MongoCollection.ts (1 hunks)
  • components/log-viewer-webui/client/src/mongoCDCLib/MongoCollectionCursor.ts (1 hunks)
  • components/log-viewer-webui/client/src/mongoCDCLib/MongoCollectionReactiveCursor.ts (1 hunks)
  • components/log-viewer-webui/client/src/mongoCDCLib/useTracker.tsx (1 hunks)
  • components/log-viewer-webui/client/vite.config.ts (1 hunks)
  • components/log-viewer-webui/server/package.json (1 hunks)
  • components/log-viewer-webui/server/src/app.ts (2 hunks)
  • components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/MongoWatcherCollection.ts (1 hunks)
  • components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts (1 hunks)
  • components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/typings.ts (1 hunks)
  • components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/utils.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == ` rather than `!`.

**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}: - Prefer false == <expression> rather than !<expression>.

  • components/log-viewer-webui/client/src/mongoCDCLib/MongoCollection.ts
  • components/log-viewer-webui/client/vite.config.ts
  • components/log-viewer-webui/server/src/app.ts
  • components/log-viewer-webui/client/src/App.tsx
  • components/log-viewer-webui/client/src/mongoCDCLib/MongoCollectionCursor.ts
  • components/log-viewer-webui/client/src/mongoCDCLib/useTracker.tsx
  • components/log-viewer-webui/client/src/mongoCDCLib/MongoCollectionReactiveCursor.ts
  • components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/utils.ts
  • components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts
  • components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/MongoWatcherCollection.ts
  • components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/typings.ts
🧬 Code Graph Analysis (2)
components/log-viewer-webui/client/src/mongoCDCLib/MongoCollection.ts (2)
components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts (1)
  • collectionName (133-136)
components/log-viewer-webui/client/src/mongoCDCLib/MongoCollectionReactiveCursor.ts (2)
  • ServerError (110-110)
  • MongoCollectionReactiveCursor (109-109)
components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/utils.ts (3)
components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/MongoWatcherCollection.ts (1)
  • queryParams (132-147)
components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts (1)
  • queryParams (196-214)
components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/typings.ts (2)
  • QueryParameters (137-137)
  • DbOptions (134-134)
🪛 Biome (1.9.4)
components/log-viewer-webui/client/src/App.tsx

[error] 51-51: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.

(lint/correctness/useJsxKeyInIterable)


[error] 56-56: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.

(lint/correctness/useJsxKeyInIterable)

🔇 Additional comments (9)
components/log-viewer-webui/server/package.json (1)

33-33: Socket.IO dependency added for real-time communication

The addition of Socket.IO as a dependency is appropriate for implementing the reactive MongoDB querying system over WebSockets described in the PR objectives.

components/log-viewer-webui/client/vite.config.ts (2)

9-10: Port and host configuration changes look good

The changes to bind to all network interfaces (0.0.0.0) and change the port to 8081 are appropriate to avoid port conflicts and improve accessibility.


18-22: WebSocket proxy configuration is properly implemented

The added proxy configuration for Socket.IO WebSocket connections is correctly implemented with the necessary options:

  • Target points to the server WebSocket endpoint
  • changeOrigin is enabled
  • WebSocket support is explicitly enabled

This configuration allows the client to communicate with the Socket.IO server through the Vite development server.

components/log-viewer-webui/server/src/app.ts (2)

11-11: New FastifyMongoServer import added

The import statement for the new MongoDB Socket.IO server plugin is correctly added.


58-62: FastifyMongoServer registration looks good

The plugin registration with the MongoDB connection parameters is properly implemented, using the existing configuration values from settings. This ensures consistency with the current MongoDB connection parameters.

components/log-viewer-webui/client/src/App.tsx (2)

6-7: New imports for MongoDB reactive functionality look good

The imports for the new MongoDB collection and reactive tracker hook are appropriate for implementing the reactive query functionality.


10-10: Collection initialization looks good

The MongoDB collection is properly initialized with the correct collection name.

components/log-viewer-webui/client/package.json (1)

27-41: ESLint script will fail without config path

The scripts lint:check and lint:fix invoke bare eslint, but no config file path is specified and the project is ESM ("type": "module"). ESLint 9 requires the config to be discoverable (eslint.config.js). Verify that such a file exists; if not, add one or pass --config.

components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts (1)

285-291: Typo: unsubcribeFromWatcherunsubscribeFromWatcher

The current spelling leaks into the public API of MongoWatcherCollection and makes grepping harder.

-        const lastSubscriber = collection.unsubcribeFromWatcher(queryId, socket.id);
+        const lastSubscriber = collection.unsubscribeFromWatcher(queryId, socket.id);

Confirm and rename the method in MongoWatcherCollection.ts as well.

Comment thread components/log-viewer-webui/client/src/App.tsx Outdated
Comment on lines +55 to +59
<div>
{JSON.stringify(r)}
</div>
))}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add key prop to iterated elements

Similar to the previous issue, add a key prop to these elements as well.

-            {results2.map((r) => (
-                <div>
-                    {JSON.stringify(r)}
-                </div>
-            ))}
+            {results2.map((r) => (
+                <div key={r._id}>
+                    {JSON.stringify(r)}
+                </div>
+            ))}
📝 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
{results2.map((r) => (
<div>
{JSON.stringify(r)}
</div>
))}
{results2.map((r) => (
<div key={r._id}>
{JSON.stringify(r)}
</div>
))}
🧰 Tools
🪛 Biome (1.9.4)

[error] 56-56: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.

(lint/correctness/useJsxKeyInIterable)

Comment thread components/log-viewer-webui/client/src/App.tsx Outdated
Comment on lines +30 to +38
() => collection.find({
_id: {
$gte: 1,
$lte: 5,
},
}, {sort: {start_time: -1}}),
[]
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Duplicate query pattern could be refactored

There's significant duplication between the two queries. Consider creating a reusable query function that accepts parameters for the ID range.

-    const results = useTracker(
-        () => collection.find({
-            _id: {
-                $gte: 1,
-                $lte: 10,
-            },
-        }, {sort: {start_time: -1}}),
-        []
-    );
-
-    const results2 = useTracker(
-        () => collection.find({
-            _id: {
-                $gte: 1,
-                $lte: 5,
-            },
-        }, {sort: {start_time: -1}}),
-        []
-    );
+    const createIdRangeQuery = (minId, maxId) => (
+        collection.find({
+            _id: {
+                $gte: minId,
+                $lte: maxId,
+            },
+        }, {sort: {start_time: -1}})
+    );
+
+    const results = useTracker(() => createIdRangeQuery(1, 10), []);
+    const results2 = useTracker(() => createIdRangeQuery(1, 5), []);
📝 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
const results2 = useTracker(
() => collection.find({
_id: {
$gte: 1,
$lte: 5,
},
}, {sort: {start_time: -1}}),
[]
);
// Extract common find query into a reusable function
const createIdRangeQuery = (minId, maxId) => (
collection.find({
_id: {
$gte: minId,
$lte: maxId,
},
}, { sort: { start_time: -1 } })
);
// Use the new helper for each tracker
const results = useTracker(() => createIdRangeQuery(1, 10), []);
const results2 = useTracker(() => createIdRangeQuery(1, 5), []);

Comment on lines +18 to +19
dependencies: object[] = []
): unknown[] => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve type safety with more specific types.

Using generic object[] for dependencies and unknown[] for return type reduces type safety.

- dependencies: object[] = []
+ dependencies: React.DependencyList = []
): unknown[] => {
+ ): Record<string, any>[] => {

Consider defining a proper type for your MongoDB documents instead of using unknown[] for better type safety across your application.

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

Comment on lines +151 to +169
const {collectionName} = requestArgs;
this.#fastify.log.info(
`Socket ${socket.id} requested init of collection ${collectionName}`
);

const exists = await this.#collectionExists(collectionName);
if (false === exists) {
this.#fastify.log.error(`Collection ${collectionName} does not exist in MongoDB`);
callback({
error: `Collection ${collectionName} does not exist in MongoDB`,
});

return;
}


(socket.data as SocketData).collectionName = collectionName;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Acknowledge successful collection initialisation

The client sends an ACK callback; on success we never invoke it, causing the caller to hang indefinitely.
Return an empty success response once the collection has been stored on the socket:

         (socket.data as SocketData).collectionName = collectionName;
+
+        // Notify caller that the initialisation succeeded
+        callback({});
📝 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
const {collectionName} = requestArgs;
this.#fastify.log.info(
`Socket ${socket.id} requested init of collection ${collectionName}`
);
const exists = await this.#collectionExists(collectionName);
if (false === exists) {
this.#fastify.log.error(`Collection ${collectionName} does not exist in MongoDB`);
callback({
error: `Collection ${collectionName} does not exist in MongoDB`,
});
return;
}
(socket.data as SocketData).collectionName = collectionName;
}
(socket.data as SocketData).collectionName = collectionName;
// Notify caller that the initialisation succeeded
callback({});
}

Comment thread components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts Outdated
Comment on lines +315 to +326
const subscribedQueryIds = this.#subscribedQueryIdsMap.get(socket.id);
if ("undefined" === typeof subscribedQueryIds ||
false === subscribedQueryIds.includes(queryId)
) {
this.#fastify.log.error(`Socket ${socket.id} is not subscribed to ${queryId}`);

return;
}

this.#unsubscribe(socket, queryId);
await socket.leave(queryId.toString());
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove queryId from subscription map after successful unsubscription

#unsubscribe cleans up watchers but does not mutate #subscribedQueryIdsMap.
Stale entries mean disconnect will attempt to unsubscribe again, producing spurious error logs.

         this.#unsubscribe(socket, queryId);
         await socket.leave(queryId.toString());
+
+        // House-keeping: drop the queryId for this socket
+        const ids = this.#subscribedQueryIdsMap.get(socket.id);
+        if ("undefined" !== typeof ids) {
+            const idx = ids.indexOf(queryId);
+            if (-1 !== idx) {
+                ids.splice(idx, 1);
+                if (0 === ids.length) {
+                    this.#subscribedQueryIdsMap.delete(socket.id);
+                }
+            }
+        }
📝 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
const subscribedQueryIds = this.#subscribedQueryIdsMap.get(socket.id);
if ("undefined" === typeof subscribedQueryIds ||
false === subscribedQueryIds.includes(queryId)
) {
this.#fastify.log.error(`Socket ${socket.id} is not subscribed to ${queryId}`);
return;
}
this.#unsubscribe(socket, queryId);
await socket.leave(queryId.toString());
}
const subscribedQueryIds = this.#subscribedQueryIdsMap.get(socket.id);
if ("undefined" === typeof subscribedQueryIds ||
false === subscribedQueryIds.includes(queryId)
) {
this.#fastify.log.error(`Socket ${socket.id} is not subscribed to ${queryId}`);
return;
}
this.#unsubscribe(socket, queryId);
await socket.leave(queryId.toString());
// House-keeping: drop the queryId for this socket
const ids = this.#subscribedQueryIdsMap.get(socket.id);
if ("undefined" !== typeof ids) {
const idx = ids.indexOf(queryId);
if (-1 !== idx) {
ids.splice(idx, 1);
if (0 === ids.length) {
this.#subscribedQueryIdsMap.delete(socket.id);
}
}
}
}

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (3)
components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts (3)

47-48: ⚠️ Potential issue

Add the missing third generic placeholder (ServerSideEvents) to the Server type

The socket.io Server constructor expects four generic parameters in the order
<ListenEvents, EmitEvents, ServerSideEvents, SocketData>.
By supplying only three, SocketData is currently interpreted as ServerSideEvents, so socket.data is typed as any, defeating the type-safety you were aiming for.

-    #io: Server<ClientToServerEvents, ServerToClientEvents, SocketData>;
+    #io: Server<ClientToServerEvents, ServerToClientEvents, Record<string, never>, SocketData>;-        this.#io = new Server<
-            ClientToServerEvents,
-            ServerToClientEvents,
-            SocketData
-        >(fastify.server);
+        this.#io = new Server<
+            ClientToServerEvents,
+            ServerToClientEvents,
+            Record<string, never>,
+            SocketData
+        >(fastify.server);

Also applies to: 70-74


150-171: ⚠️ Potential issue

Ack the client’s collection::init call on success

The client provides a callback but it is invoked only on error.
Without an acknowledgement, the caller hangs indefinitely, assuming the request is still pending.

         (socket.data as SocketData).collectionName = collectionName;
+
+        // Successful initialisation → notify client
+        callback({});

271-299: 🛠️ Refactor suggestion

Remove queryId from #subscribedQueryIdsMap after a successful unsubscribe

#unsubscribe cleans up watchers but leaves stale entries in #subscribedQueryIdsMap.
Subsequent disconnects produce spurious error logs and attempt double-unsubscription.

         this.#subscribeToQueryId(queryId, socket.id);
@@
-        const lastSubscriber = collection.unsubcribeFromWatcher(queryId, socket.id);
+        const lastSubscriber = collection.unsubcribeFromWatcher(queryId, socket.id);
+
+        // House-keeping: drop the queryId for this connection
+        const ids = this.#subscribedQueryIdsMap.get(socket.id);
+        if ("undefined" !== typeof ids) {
+            const idx = ids.indexOf(queryId);
+            if (-1 !== idx) {
+                ids.splice(idx, 1);
+                if (0 === ids.length) {
+                    this.#subscribedQueryIdsMap.delete(socket.id);
+                }
+            }
+        }
🧹 Nitpick comments (1)
components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts (1)

236-240: Follow project style-guide: avoid the ! shorthand

Guidelines specify false == <expression> over !<expression>.
Use an explicit comparison to keep style consistent and avoid accidental truthiness checks on empty strings.

-if (!collectionName) {
+if (false == collectionName) {
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between d09a489 and 2d95dec.

📒 Files selected for processing (2)
  • components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/MongoWatcherCollection.ts (1 hunks)
  • components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/MongoWatcherCollection.ts
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == ` rather than `!`.

**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}: - Prefer false == <expression> rather than !<expression>.

  • components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts

Comment thread components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (8)
components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/typings.ts (8)

13-16: Consider renaming Err interface for clarity

The interface name Err is somewhat ambiguous. A more descriptive name like ErrorResponse would better convey its purpose and improve code readability.

-interface Err {
+interface ErrorResponse {
    error: string;
    queryId?: number;
}

Also update the Response type accordingly:

-type Response<T> = Err | Success<T>;
+type Response<T> = ErrorResponse | Success<T>;

46-46: Nested data structure seems redundant

The callback response type Response<{data: Document[]}> contains nested data structures - Success<T> already includes a data property. Consider simplifying to Response<Document[]> for a cleaner design.

-        callback: (res: Response<{data: Document[]}>) => void) => void;
+        callback: (res: Response<Document[]>) => void) => void;

80-81: Empty interface can be replaced with a type alias

Following TypeScript best practices, empty interfaces should be replaced with type aliases using Record.

-interface InterServerEvents {
-}
+type InterServerEvents = Record<string, never>;
🧰 Tools
🪛 Biome (1.9.4)

[error] 79-81: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.

(lint/suspicious/noEmptyInterface)


100-100: Fix typo in comment

There's a typo in the comment: "repsent" should be "represent".

-/**
- * Unique ID to repsent each socket connection.
- */
+/**
+ * Unique ID to represent each socket connection.
+ */

46-46: Inconsistent Promise handling for socket.io callbacks

The event handlers with callbacks don't return Promises, except for "collection::find::unsubscribe". Consider standardizing the Promise handling across all event handlers for consistency.

Either make all event handlers return a Promise:

-    "collection::find::toArray": (
-        requestArgs: {
-            query: Filter<Document>;
-            options: FindOptions;
-        },
-        callback: (res: Response<{data: Document[]}>) => void) => void;
+    "collection::find::toArray": (
+        requestArgs: {
+            query: Filter<Document>;
+            options: FindOptions;
+        },
+        callback: (res: Response<{data: Document[]}>) => void) => Promise<void>;

Or remove the Promise return from "collection::find::unsubscribe":

-    "collection::find::unsubscribe": (
-        requestArgs: {
-            queryId: number;
-        }
-    ) => Promise<void>;
+    "collection::find::unsubscribe": (
+        requestArgs: {
+            queryId: number;
+        }
+    ) => void;

Also applies to: 52-52


33-58: Consider adding JSDoc comments to individual event handlers

While the ClientToServerEvents type has a general comment, each event handler would benefit from individual JSDoc comments explaining their specific purpose and behavior.

For example:

type ClientToServerEvents = {
    /**
     * Emitted when client disconnects from the server
     */
    "disconnect": () => void;
    
    /**
     * Initializes a collection for the client
     * @param requestArgs - Contains the collection name to initialize
     * @param callback - Called with success or error response
     */
    "collection::init": (
        requestArgs: {
            collectionName: string;
        },
        callback: (res: Response<void>) => void
    ) => void;
    
    // ... Add similar comments for other event handlers
};

33-58: Socket event naming could be more REST-like

The current event naming uses "::" separators. Consider using a more standard REST-like naming convention with "/" separators for better readability and consistency with common API patterns.

For example:

type ClientToServerEvents = {
-    "disconnect": () => void;
-    "collection::init": (
+    "disconnect": () => void;
+    "collection/init": (
        // ...
    ) => void;
-    "collection::find::toArray": (
+    "collection/find/toArray": (
        // ...
    ) => void;
    // ... update other event names similarly
};

interface ServerToClientEvents {
-    "collection::find::update": (respArgs: {
+    "collection/find/update": (respArgs: {
        // ...
    }) => void;
}

Also applies to: 63-68


127-127: Consider making the timeout configurable

CLIENT_UPDATE_TIMEOUT_MS is defined as a hardcoded constant. Consider allowing this value to be configurable, either through environment variables or constructor parameters in the main implementation class.

-const CLIENT_UPDATE_TIMEOUT_MS = 500;
+const DEFAULT_CLIENT_UPDATE_TIMEOUT_MS = 500;
+const CLIENT_UPDATE_TIMEOUT_MS = process.env.CLIENT_UPDATE_TIMEOUT_MS 
+    ? parseInt(process.env.CLIENT_UPDATE_TIMEOUT_MS, 10) 
+    : DEFAULT_CLIENT_UPDATE_TIMEOUT_MS;
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 2d95dec and 3a33446.

📒 Files selected for processing (3)
  • components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/MongoWatcherCollection.ts (1 hunks)
  • components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts (1 hunks)
  • components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/typings.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/MongoWatcherCollection.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == ` rather than `!`.

**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}: - Prefer false == <expression> rather than !<expression>.

  • components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/typings.ts
🪛 Biome (1.9.4)
components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/typings.ts

[error] 79-81: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.

(lint/suspicious/noEmptyInterface)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 28

♻️ Duplicate comments (4)
components/log-viewer-webui/client/src/App.tsx (2)

25-27: ⚠️ Potential issue

Using index as key in iterable

Using array index as key for iterated elements is not recommended, as it can lead to rendering issues when the array order changes. Consider using a unique identifier from the data instead.

{singleResult.map((r, index) => (
-    <div key={index}>{JSON.stringify(r)}</div>
+    <div key={r._id || index}>{JSON.stringify(r)}</div>
))}

45-47: ⚠️ Potential issue

Using index as key in iterable

Using array index as key for iterated elements is not recommended, as it can lead to rendering issues when the array order changes. Consider using a unique identifier from the data instead.

{singleResult.map((r, index) => (
-    <div key={index}>{JSON.stringify(r)}</div>
+    <div key={r._id || index}>{JSON.stringify(r)}</div>
))}
components/log-viewer-webui/client/package.json (1)

30-34: Redundant & incompatible @types/socket.io-client entry – echoing earlier feedback

socket.io-client@4.x ships its own typings; @types/socket.io-client@3 targets the v3 API and will clash with the bundled d.ts files.
Please drop the dev-dependency to prevent duplicate symbol errors.

This was already pointed out in a previous review.

components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts (1)

154-175: ⚠️ Potential issue

collection::init never ACKs success – client callbacks hang indefinitely

After validating and storing collectionName, the server should invoke the provided callback to acknowledge success.

         socket.data.collectionName = collectionName;
+
+        // Notify caller that initialisation succeeded
+        callback({});

Without this, the caller’s Promise will never resolve.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 3a33446 and 60b08ec.

⛔ Files ignored due to path filters (2)
  • components/log-viewer-webui/client/package-lock.json is excluded by !**/package-lock.json
  • components/log-viewer-webui/server/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (16)
  • components/log-viewer-webui/client/package.json (1 hunks)
  • components/log-viewer-webui/client/src/App.tsx (1 hunks)
  • components/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.ts (1 hunks)
  • components/log-viewer-webui/client/src/api/socket/MongoCursorSocket.ts (1 hunks)
  • components/log-viewer-webui/client/src/api/socket/useCursor.tsx (1 hunks)
  • components/log-viewer-webui/client/src/main.tsx (1 hunks)
  • components/log-viewer-webui/client/tsconfig/tsconfig.app.json (2 hunks)
  • components/log-viewer-webui/client/vite.config.ts (1 hunks)
  • components/log-viewer-webui/common/index.ts (1 hunks)
  • components/log-viewer-webui/server/.env (1 hunks)
  • components/log-viewer-webui/server/package.json (2 hunks)
  • components/log-viewer-webui/server/src/app.ts (2 hunks)
  • components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/MongoWatcherCollection.ts (1 hunks)
  • components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts (1 hunks)
  • components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/typings.ts (1 hunks)
  • components/log-viewer-webui/server/tsconfig.json (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == ` rather than `!`.

**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}: - Prefer false == <expression> rather than !<expression>.

  • components/log-viewer-webui/client/src/main.tsx
  • components/log-viewer-webui/server/src/app.ts
  • components/log-viewer-webui/client/src/api/socket/useCursor.tsx
  • components/log-viewer-webui/client/vite.config.ts
  • components/log-viewer-webui/client/src/api/socket/MongoCursorSocket.ts
  • components/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.ts
  • components/log-viewer-webui/client/src/App.tsx
  • components/log-viewer-webui/common/index.ts
  • components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/typings.ts
  • components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts
  • components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/MongoWatcherCollection.ts
🧬 Code Graph Analysis (3)
components/log-viewer-webui/client/src/api/socket/useCursor.tsx (2)
components/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.ts (1)
  • find (47-53)
components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/MongoWatcherCollection.ts (1)
  • find (191-202)
components/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.ts (3)
components/log-viewer-webui/common/index.ts (3)
  • ServerToClientEvents (73-73)
  • ClientToServerEvents (69-69)
  • Response (72-72)
components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts (1)
  • collectionName (141-144)
components/log-viewer-webui/client/src/api/socket/MongoCursorSocket.ts (1)
  • MongoCursorSocket (90-90)
components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/typings.ts (1)
components/log-viewer-webui/common/index.ts (4)
  • ClientToServerEvents (69-69)
  • ServerToClientEvents (73-73)
  • InterServerEvents (71-71)
  • SocketData (74-74)
🪛 Biome (1.9.4)
components/log-viewer-webui/client/tsconfig/tsconfig.app.json

[error] 21-21: expected , but instead found // Map imports from "@common/*" to the shared folder

Remove // Map imports from "@common/*" to the shared folder

(parse)

components/log-viewer-webui/client/src/App.tsx

[error] 62-62: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)


[error] 69-69: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)

components/log-viewer-webui/common/index.ts

[error] 57-59: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.

(lint/suspicious/noEmptyInterface)

🔇 Additional comments (31)
components/log-viewer-webui/server/.env (1)

6-6: Port change may require documentation and deployment updates

The server port has been changed from 3000 to 3007. While this appears to align with client-side configuration changes, ensure this change is properly documented for anyone maintaining or deploying the application.

components/log-viewer-webui/client/tsconfig/tsconfig.app.json (1)

4-5: Good practice: Including shared types directory

Including the common directory for shared types between client and server is an excellent practice for maintaining type consistency across the application.

components/log-viewer-webui/server/src/app.ts (2)

11-11: Import for new MongoDB Socket.IO plugin aligns with project structure

The import of the new FastifyMongoServer plugin follows the existing import pattern and organization.


58-62: Plugin registration follows consistent pattern

The registration of the FastifyMongoServer plugin correctly follows the same pattern as other plugins and properly uses the existing settings configuration for database connection parameters.

components/log-viewer-webui/server/tsconfig.json (3)

13-13: Clear comment for shared module configuration.

The added comment clarifies that the "outDir" setting also affects the compilation output for files from the common folder, improving clarity.


18-21: Good configuration for shared types.

Adding baseUrl and paths configuration enables importing shared types from the common folder using the "@common/*" alias, which creates a clean separation between shared and server-specific code.


41-42: Appropriate include paths for shared code.

Adding "../common/**/*" to the include array correctly enables TypeScript to process the shared files from the common directory, completing the path alias setup.

components/log-viewer-webui/client/vite.config.ts (5)

3-3: Added TypeScript path resolution support.

The vite-tsconfig-paths plugin aligns with the TypeScript path alias configurations, enabling consistent imports between source and build time.


7-7: Proper plugin configuration.

The tsconfigPaths plugin is correctly added to the plugins array, enabling the path aliases defined in tsconfig.


18-22: Added WebSocket proxy configuration for Socket.IO.

The proxy configuration for "/socket.io/" with WebSocket support is necessary for the new Socket.IO-based MongoDB subscription system.


24-26: Enabled file system access to common folder.

The fs.allow setting permits serving files from one directory level up, necessary for accessing the shared types in the common folder.


15-17:

Details

✅ Verification successful

Updated server port reference in proxy config.

The target port for the API proxy has been updated to 3007, which should match the server's new configuration in .env.

Let's confirm the server's configured port in the .env file:


🏁 Script executed:

#!/bin/bash
# Check if the server port is consistently set to 3007 in .env
if [ -f components/log-viewer-webui/server/.env ]; then
  echo "Server port in .env:"
  grep "PORT" components/log-viewer-webui/server/.env
else
  echo "components/log-viewer-webui/server/.env not found"
fi

Length of output: 165


Proxy target port verified
The .env file specifies PORT=3007, which matches the proxy configuration in components/log-viewer-webui/client/vite.config.ts (Lines 15–17). No further changes required.

components/log-viewer-webui/client/src/api/socket/useCursor.tsx (3)

15-18: Well-typed custom hook signature.

The hook properly defines its parameter and return types, making it clear how to use it in React components.


21-32: Good subscription setup with proper cleanup.

The effect creates a cursor, sets up an ignore flag for cleanup, and handles data updates correctly. Using "false === ignore" follows the coding guideline of preferring "false == " over "!".


34-38: Error handling in promise chain.

The subscription promise is handled properly with error catching, which is essential for asynchronous operations.

components/log-viewer-webui/server/package.json (5)

5-5: Updated main entry point to compiled output.

Changing the main entry point from TypeScript source to compiled JavaScript is appropriate for a Node.js package.


12-16: Enhanced development scripts.

The updated start script and new development scripts provide more flexibility for different development workflows.


34-36: Added MongoDB and Socket.IO dependencies.

These new dependencies are required for the MongoDB reactive query system using Socket.IO.


40-40: Updated Node.js type definitions.

Updating @types/node to a newer version ensures compatibility with modern Node.js features.


43-47: Added development dependencies.

The new development dependencies support various workflows including testing with tap and development server management with wait-on.

components/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.ts (4)

1-12: Imports look good

The imports are correctly structured, properly importing the necessary socket.io components and shared types from the common module.


14-19: Clean class documentation and structure

The class documentation clearly describes the purpose of the class with good JSDoc format.


47-53: Properly structured find method

The find method correctly creates and returns a new MongoCursorSocket instance with the appropriate parameters.


25-38:

Details

❓ Verification inconclusive

Unresolved TODO and development-only logging

The constructor has an unresolved TODO comment and uses a hardcoded socket connection strategy instead of the commented approach. Additionally, console.log statements should be removed or replaced with proper logging for production code.

constructor (collectionName: string) {
    // eslint-disable-next-line no-warning-comments
-   // TODO: use the server URL from the environment / constructor args
-   //this.socket = io(`/${collectionName}`);
    this.socket = io();
    this.socket.emit("collection::init", {
        collectionName: collectionName,
    }, (res: Response<void>) => {
        if ("error" in res) {
            console.error("Error initializing collection:", res.error);
        }
    });
-   console.log("MongoCollectionSocket initialized for collection:", collectionName);
}

Is this code intended for production, or is it a prototype? If intended for production, consider implementing proper logging and addressing the TODO comment for configurable server URLs.


Address unresolved TODO and switch to configurable URL + production logging

  • The constructor still contains a TODO for sourcing the server URL and defaults to a bare io() call.
  • It uses console.log for routine info—this should be replaced with your app’s logging framework (e.g. logger.info) or gated behind a debug flag.
  • Suggested approach:
    • Remove the inline TODO.
    • Accept a serverUrl parameter (or read from import.meta.env.VITE_SOCKET_URL).
    • Replace console.log with logger.info() and ensure error logging flows through the same framework.

Example diff:

constructor(
  collectionName: string,
- // eslint-disable-next-line no-warning-comments
- // TODO: use the server URL from the environment / constructor args
- // this.socket = io();
+ // Pull the socket URL from env or constructor
+ constructor(
+   collectionName: string,
+   serverUrl = import.meta.env.VITE_SOCKET_URL
+ ) {
+   this.socket = io(serverUrl);
   this.socket.emit("collection::init", { collectionName }, (res: Response<void>) => {
     if ("error" in res) {
       console.error("Error initializing collection:", res.error);
     }
   });
-  console.log("MongoCollectionSocket initialized for collection:", collectionName);
+  logger.info("MongoCollectionSocket initialized", { collectionName });
}

Please confirm whether this code is intended for production or remains a prototype.

components/log-viewer-webui/client/src/api/socket/MongoCursorSocket.ts (3)

13-19: Well-structured private fields

The class uses modern JavaScript private fields with good naming conventions.


25-29: Clean constructor implementation

The constructor correctly initializes the private fields with the provided parameters.


69-72: 🧹 Nitpick (assertive)

Use preferred comparison style

According to the coding guidelines, prefer false == <expression> rather than !<expression>.

-    if (this.#queryId === null) {
+    if (null === this.#queryId) {
         console.error("Attempted to unsubscribe, but no active subscription exists.");
         return;
     }

Likely an incorrect or invalid review comment.

components/log-viewer-webui/common/index.ts (3)

1-19: Well-structured error and success type definitions

The Error, Success, and Response type definitions are clearly structured and documented.


61-66: Socket data interface looks good

The SocketData interface is well-defined with a clear purpose.


68-75: Export list is well organized

All types and interfaces are properly exported.

components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts (1)

310-317: Call-site still references the misspelled unsubcribe method

Once the watcher method is renamed to unsubscribe, remember to adjust this call:

-        const lastSubscriber = collection.unsubcribe(queryId, socket.id);
+        const lastSubscriber = collection.unsubscribe(queryId, socket.id);

PORT=3007
CLP_DB_USER=clp-user
CLP_DB_PASS=
CLP_DB_PASS=mGltCMuLSaM

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Security concern: Credentials should not be hardcoded in version-controlled files

Storing database passwords directly in .env files that are committed to version control is a security vulnerability. Even if this is a development password, it's best practice to keep credentials out of the repository.

Consider:

  1. Using environment variables supplied at runtime
  2. Using a secrets management system
  3. At minimum, adding the .env file to .gitignore

Comment on lines +24 to +26
//<StrictMode>
<AppComponent/>
</StrictMode>
//</StrictMode>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider keeping React StrictMode enabled

Commenting out StrictMode removes important development-time checks that help identify potential problems. While this may temporarily solve issues with the new socket features, it can hide bugs that would surface later in production.

If there are specific problems with StrictMode:

  1. Address those issues directly rather than disabling the wrapper
  2. If temporary, add a TODO comment explaining why it's disabled and when it should be re-enabled
-    //<StrictMode>
+    <StrictMode>
         <AppComponent/>
-    //</StrictMode>
+    </StrictMode>

If there are specific issues with StrictMode and the new socket features, please document them.

📝 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
//<StrictMode>
<AppComponent/>
</StrictMode>
//</StrictMode>
<StrictMode>
<AppComponent/>
</StrictMode>

Comment on lines +19 to +22
"baseUrl": "../",
"paths": {
"@common/*": ["../common/*"] // Map imports from "@common/*" to the shared folder
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix syntax error: Missing comma before comment

The static analysis correctly identified a syntax error - the inline comment on line 21 needs a preceding comma to make this valid JSON.

    "baseUrl": "../",
    "paths": {
-      "@common/*": ["../common/*"]        // Map imports from "@common/*" to the shared folder
+      "@common/*": ["../common/*"],        // Map imports from "@common/*" to the shared folder
    },
📝 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
"baseUrl": "../",
"paths": {
"@common/*": ["../common/*"] // Map imports from "@common/*" to the shared folder
},
"baseUrl": "../",
"paths": {
"@common/*": ["../common/*"], // Map imports from "@common/*" to the shared folder
},
🧰 Tools
🪛 Biome (1.9.4)

[error] 21-21: expected , but instead found // Map imports from "@common/*" to the shared folder

Remove // Map imports from "@common/*" to the shared folder

(parse)

"strictPropertyInitialization": true,
"useUnknownInCatchVariables": true,
"strict": true
"strict": true,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Redundant "strict" flag.

The "strict: true" flag is redundant since all the individual strict flags (lines 23-37) are already explicitly set.

You can either:

  1. Keep all individual flags for clarity
  2. Use just "strict: true" and remove individual flags
  3. Remove the redundant "strict: true" line
-    "strict": true,
📝 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
"strict": true,

Comment on lines +9 to +10
host: "0.0.0.0",
port: 8081,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Server configuration changes need documentation.

The changes to host (now binding to all interfaces) and port (changing from 8080 to 8081) are significant and may affect how developers access the application.

These configuration changes should be documented in a README or similar document. You can use the following command to check if there's any documentation mentioning these ports:


🏁 Script executed:

#!/bin/bash
# Look for documentation that mentions the ports being changed
echo "Checking for documentation mentioning the old port 8080:"
rg "8080" --type md
echo -e "\nChecking for documentation mentioning the new port 8081:"
rg "8081" --type md
echo -e "\nChecking for documentation mentioning the server port 3007:"
rg "3007" --type md

Length of output: 518


Document server host and port changes

The server is now binding to all interfaces (0.0.0.0) on port 8081, but the project documentation still references the old default (localhost:8080). Please update the README (or equivalent docs) accordingly.

• In docs/README.md:

  • Change the example URL from http://localhost:8080 to http://localhost:8081.
  • Optionally note that the server now listens on all network interfaces (0.0.0.0).

Suggested diff:

- the address it binds to (usually http://localhost:8080).
+ the address it binds to (usually http://localhost:8081, accessible on all interfaces via 0.0.0.0).

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

Comment on lines +167 to +175
} else if (!emitTimeout) {
const delay = CLIENT_UPDATE_TIMEOUT_MS - (currentTime - lastEmitTime);
emitTimeout = setTimeout(async () => {
emitTimeout = null;
const data = await this.find(queryParameters);
emitUpdate(queryId, data);
lastEmitTime = Date.now();
}, delay);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Follow code-base convention – prefer false == emitTimeout instead of !emitTimeout

The project guideline for JS/TS files asks for false == <expression> rather than a negation.
Apply the rule here:

-            } else if (!emitTimeout) {
+            } else if (false == emitTimeout) {
📝 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
} else if (!emitTimeout) {
const delay = CLIENT_UPDATE_TIMEOUT_MS - (currentTime - lastEmitTime);
emitTimeout = setTimeout(async () => {
emitTimeout = null;
const data = await this.find(queryParameters);
emitUpdate(queryId, data);
lastEmitTime = Date.now();
}, delay);
}
} else if (false == emitTimeout) {
const delay = CLIENT_UPDATE_TIMEOUT_MS - (currentTime - lastEmitTime);
emitTimeout = setTimeout(async () => {
emitTimeout = null;
const data = await this.find(queryParameters);
emitUpdate(queryId, data);
lastEmitTime = Date.now();
}, delay);
}

Comment on lines +68 to +94
unsubcribe (queryId: number, connectionId: string): boolean {
const watcher = this.#queryIdtoWatcherMap.get(queryId);

if ("undefined" === typeof watcher) {
console.warn(`No watcher found for queryId ${queryId}`);

return false;
}
console.log(`Length ${watcher.subscribers}`);

if (1 < watcher.subscribers.length) {
// Remove the first instance of the connectionId from the subscribers list. Note
// there can be multiple instances of the same connectionId in the list.
const index = watcher.subscribers.indexOf(connectionId);
if (-1 !== index) {
watcher.subscribers.splice(index, 1);
}

return false;
}

watcher.changeStream.close().catch((err: unknown) => {
console.error(`Error closing watcher for queryId ${queryId}:`, err);
});
this.#queryIdtoWatcherMap.delete(queryId);
return true;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Misspelled & misleading unsubcribe method breaks public API semantics

  1. Typo: “unsubcribe” → “unsubscribe”.
  2. JSDoc says “True if connection is last subcriber”, yet the method returns true after the change-stream is closed – i.e., when no subscribers remain.
  3. Returning false when duplicates still exist but one instance was removed can cause the server to leak watchers.
-    unsubcribe (queryId: number, connectionId: string): boolean {
+    unsubscribe (queryId: number, connectionId: string): boolean {
@@
-        if (1 < watcher.subscribers.length) {
+        // Remove **all** occurrences of the same connection ID
+        watcher.subscribers = watcher.subscribers.filter((id) => id !== connectionId);
+        if (0 < watcher.subscribers.length) {
             return false;
         }

Remember to rename the call-sites (index.ts line 310).

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

Comment on lines +61 to +63
const CLIENT_UPDATE_TIMEOUT_MS = 500;

/**

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Hard-coded 0.5 s emit throttle may be too aggressive for large change bursts

Consider exposing CLIENT_UPDATE_TIMEOUT_MS as a constructor parameter or env variable so operators can tune real-time vs. throughput trade-offs without code changes.

Comment on lines +141 to +145
async #collectionExists (collectionName: string): Promise<boolean> {
const collections = await this.#mongoDb.listCollections().toArray();
return collections.some((collection) => collection.name === collectionName);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Inefficient collection existence check – cache or use db.collectionNames filter

listCollections().toArray() issues a full catalogue scan on each init.
Cache the result per server lifetime or query listCollections({name}) to avoid O(n) scans on every client request.

Comment on lines +184 to +194
#AddQueryIdToSubscribedList (queryId: QueryId, socketId: ConnectionId) {
const subscribedQueryIds = this.#subscribedQueryIdsMap.get(socketId);
if ("undefined" === typeof subscribedQueryIds) {
this.#subscribedQueryIdsMap.set(socketId, [queryId]);

return;
}

this.#subscribedQueryIdsMap.set(socketId, [...subscribedQueryIds,
queryId]);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Private method names should use lower-camelCase

#AddQueryIdToSubscribedList starts with a capital letter, which is reserved for classes/types in the code-base style-guide.

-    #AddQueryIdToSubscribedList (queryId: QueryId, socketId: ConnectionId) {
+    #addQueryIdToSubscribedList (queryId: QueryId, socketId: ConnectionId) {

Update all invocations (e.g., line 280).

📝 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
#AddQueryIdToSubscribedList (queryId: QueryId, socketId: ConnectionId) {
const subscribedQueryIds = this.#subscribedQueryIdsMap.get(socketId);
if ("undefined" === typeof subscribedQueryIds) {
this.#subscribedQueryIdsMap.set(socketId, [queryId]);
return;
}
this.#subscribedQueryIdsMap.set(socketId, [...subscribedQueryIds,
queryId]);
}
#addQueryIdToSubscribedList (queryId: QueryId, socketId: ConnectionId) {
const subscribedQueryIds = this.#subscribedQueryIdsMap.get(socketId);
if ("undefined" === typeof subscribedQueryIds) {
this.#subscribedQueryIdsMap.set(socketId, [queryId]);
return;
}
this.#subscribedQueryIdsMap.set(socketId, [
...subscribedQueryIds,
queryId
]);
}

@junhaoliao

Copy link
Copy Markdown
Member

can this be now closed?

@davemarco davemarco closed this Jun 4, 2025
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.

3 participants