WIP: Refactor of new server#865
Conversation
WalkthroughThis 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
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)
Possibly related PRs
✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed 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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 31
🧹 Nitpick comments (14)
components/log-viewer-webui/client/src/App.tsx (6)
3-5: Consider cleaning up commented codeRather 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 codeThis commented code line for an unused collection should be removed.
20-28: Query implementation looks good but consider extracting to a separate functionThe 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 codeRemove the commented debug logging code before submitting the final PR.
63-65: Clean up commented componentsRemove these commented components if they are no longer needed.
48-62: Consider using proper semantic HTML and stylingThe 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
MongoReplicaCollectionReactiveCursorbut the parameter type isMongoCollectionReactiveCursor.-* @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: UseObject.prototype.hasOwnProperty.callinstead ofObject.hasOwn.Per the coding guidelines for
**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}preferred format would befalse == <expression>rather than!<expression>. Similarly, for consistency and broader compatibility, useObject.prototype.hasOwnProperty.callinstead ofObject.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 whenresponse.datais 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 failuresWhen
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 guidelineCoding-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
⛔ Files ignored due to path filters (2)
components/log-viewer-webui/client/package-lock.jsonis excluded by!**/package-lock.jsoncomponents/log-viewer-webui/server/package-lock.jsonis 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}: - Preferfalse == <expression>rather than!<expression>.
components/log-viewer-webui/client/src/mongoCDCLib/MongoCollection.tscomponents/log-viewer-webui/client/vite.config.tscomponents/log-viewer-webui/server/src/app.tscomponents/log-viewer-webui/client/src/App.tsxcomponents/log-viewer-webui/client/src/mongoCDCLib/MongoCollectionCursor.tscomponents/log-viewer-webui/client/src/mongoCDCLib/useTracker.tsxcomponents/log-viewer-webui/client/src/mongoCDCLib/MongoCollectionReactiveCursor.tscomponents/log-viewer-webui/server/src/plugins/MongoSocketIoServer/utils.tscomponents/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.tscomponents/log-viewer-webui/server/src/plugins/MongoSocketIoServer/MongoWatcherCollection.tscomponents/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 communicationThe 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 goodThe 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 implementedThe 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 addedThe import statement for the new MongoDB Socket.IO server plugin is correctly added.
58-62: FastifyMongoServer registration looks goodThe 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 goodThe imports for the new MongoDB collection and reactive tracker hook are appropriate for implementing the reactive query functionality.
10-10: Collection initialization looks goodThe 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 pathThe scripts
lint:checkandlint:fixinvoke bareeslint, 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:unsubcribeFromWatcher➔unsubscribeFromWatcherThe current spelling leaks into the public API of
MongoWatcherCollectionand makes grepping harder.- const lastSubscriber = collection.unsubcribeFromWatcher(queryId, socket.id); + const lastSubscriber = collection.unsubscribeFromWatcher(queryId, socket.id);Confirm and rename the method in
MongoWatcherCollection.tsas well.
| <div> | ||
| {JSON.stringify(r)} | ||
| </div> | ||
| ))} | ||
|
|
There was a problem hiding this comment.
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.
| {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)
| () => collection.find({ | ||
| _id: { | ||
| $gte: 1, | ||
| $lte: 5, | ||
| }, | ||
| }, {sort: {start_time: -1}}), | ||
| [] | ||
| ); | ||
|
|
There was a problem hiding this comment.
🛠️ 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.
| 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), []); |
| dependencies: object[] = [] | ||
| ): unknown[] => { |
There was a problem hiding this comment.
🛠️ 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.
| 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; | ||
| } | ||
|
|
There was a problem hiding this comment.
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.
| 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({}); | |
| } |
| 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()); | ||
| } |
There was a problem hiding this comment.
🛠️ 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.
| 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); | |
| } | |
| } | |
| } | |
| } |
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (3)
components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts (3)
47-48:⚠️ Potential issueAdd the missing third generic placeholder (
ServerSideEvents) to theServertypeThe
socket.ioServerconstructor expects four generic parameters in the order
<ListenEvents, EmitEvents, ServerSideEvents, SocketData>.
By supplying only three,SocketDatais currently interpreted asServerSideEvents, sosocket.datais typed asany, 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 issueAck the client’s
collection::initcall on successThe 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 suggestionRemove
queryIdfrom#subscribedQueryIdsMapafter a successful unsubscribe
#unsubscribecleans 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!shorthandGuidelines 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
📒 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}: - Preferfalse == <expression>rather than!<expression>.
components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (8)
components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/typings.ts (8)
13-16: Consider renamingErrinterface for clarityThe interface name
Erris somewhat ambiguous. A more descriptive name likeErrorResponsewould 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 redundantThe callback response type
Response<{data: Document[]}>contains nested data structures -Success<T>already includes adataproperty. Consider simplifying toResponse<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 aliasFollowing 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 commentThere'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 callbacksThe 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 handlersWhile 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-likeThe 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_MSis 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
📒 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}: - Preferfalse == <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)
There was a problem hiding this comment.
Actionable comments posted: 28
♻️ Duplicate comments (4)
components/log-viewer-webui/client/src/App.tsx (2)
25-27:⚠️ Potential issueUsing 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 issueUsing 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-cliententry – echoing earlier feedback
socket.io-client@4.xships its own typings;@types/socket.io-client@3targets 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::initnever ACKs success – client callbacks hang indefinitelyAfter 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
⛔ Files ignored due to path filters (2)
components/log-viewer-webui/client/package-lock.jsonis excluded by!**/package-lock.jsoncomponents/log-viewer-webui/server/package-lock.jsonis 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}: - Preferfalse == <expression>rather than!<expression>.
components/log-viewer-webui/client/src/main.tsxcomponents/log-viewer-webui/server/src/app.tscomponents/log-viewer-webui/client/src/api/socket/useCursor.tsxcomponents/log-viewer-webui/client/vite.config.tscomponents/log-viewer-webui/client/src/api/socket/MongoCursorSocket.tscomponents/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.tscomponents/log-viewer-webui/client/src/App.tsxcomponents/log-viewer-webui/common/index.tscomponents/log-viewer-webui/server/src/plugins/MongoSocketIoServer/typings.tscomponents/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.tscomponents/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 updatesThe 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 directoryIncluding 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 structureThe import of the new FastifyMongoServer plugin follows the existing import pattern and organization.
58-62: Plugin registration follows consistent patternThe 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" fiLength of output: 165
Proxy target port verified
The.envfile specifiesPORT=3007, which matches the proxy configuration incomponents/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 goodThe imports are correctly structured, properly importing the necessary socket.io components and shared types from the common module.
14-19: Clean class documentation and structureThe class documentation clearly describes the purpose of the class with good JSDoc format.
47-53: Properly structured find methodThe 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.logfor 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
serverUrlparameter (or read fromimport.meta.env.VITE_SOCKET_URL).- Replace
console.logwithlogger.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 fieldsThe class uses modern JavaScript private fields with good naming conventions.
25-29: Clean constructor implementationThe 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 definitionsThe Error, Success, and Response type definitions are clearly structured and documented.
61-66: Socket data interface looks goodThe SocketData interface is well-defined with a clear purpose.
68-75: Export list is well organizedAll types and interfaces are properly exported.
components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts (1)
310-317: Call-site still references the misspelledunsubcribemethodOnce 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 |
There was a problem hiding this comment.
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:
- Using environment variables supplied at runtime
- Using a secrets management system
- At minimum, adding the
.envfile to.gitignore
| //<StrictMode> | ||
| <AppComponent/> | ||
| </StrictMode> | ||
| //</StrictMode> |
There was a problem hiding this comment.
🛠️ 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:
- Address those issues directly rather than disabling the wrapper
- 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.
| //<StrictMode> | |
| <AppComponent/> | |
| </StrictMode> | |
| //</StrictMode> | |
| <StrictMode> | |
| <AppComponent/> | |
| </StrictMode> |
| "baseUrl": "../", | ||
| "paths": { | ||
| "@common/*": ["../common/*"] // Map imports from "@common/*" to the shared folder | ||
| }, |
There was a problem hiding this comment.
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.
| "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, |
There was a problem hiding this comment.
🧹 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:
- Keep all individual flags for clarity
- Use just "strict: true" and remove individual flags
- 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.
| "strict": true, |
| host: "0.0.0.0", | ||
| port: 8081, |
There was a problem hiding this comment.
💡 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 mdLength 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:8080tohttp://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.
| } 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); | ||
| } |
There was a problem hiding this comment.
🧹 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.
| } 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); | |
| } |
| 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; | ||
| } |
There was a problem hiding this comment.
Misspelled & misleading unsubcribe method breaks public API semantics
- Typo: “unsubcribe” → “unsubscribe”.
- JSDoc says “True if connection is last subcriber”, yet the method returns
trueafter the change-stream is closed – i.e., when no subscribers remain. - Returning
falsewhen 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.
| const CLIENT_UPDATE_TIMEOUT_MS = 500; | ||
|
|
||
| /** |
There was a problem hiding this comment.
🧹 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.
| async #collectionExists (collectionName: string): Promise<boolean> { | ||
| const collections = await this.#mongoDb.listCollections().toArray(); | ||
| return collections.some((collection) => collection.name === collectionName); | ||
| } | ||
|
|
There was a problem hiding this comment.
🧹 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.
| #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]); | ||
| } |
There was a problem hiding this comment.
🧹 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.
| #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 | |
| ]); | |
| } |
|
can this be now closed? |
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
breaking change.
Validation performed
Summary by CodeRabbit
New Features
Improvements
Chores