Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 192 additions & 9 deletions components/log-viewer-webui/client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions components/log-viewer-webui/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-router": "^7.4.1",
"socket.io-client": "^4.8.1",
"vite-tsconfig-paths": "^5.1.4",

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)

Relocate vite-tsconfig-paths to devDependencies.
The vite-tsconfig-paths plugin is only needed at build time and for local development. Moving it to devDependencies will reduce production install size and clarify its usage scope.

"zustand": "^5.0.3"
},
"devDependencies": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {
ClientToServerEvents,
ServerToClientEvents,
} from "@common/index.js";
import {
io,
Socket,
} from "socket.io-client";

import {MongoCursorSocket} from "./MongoCursorSocket.js";


/**
* Socket connection to a MongoDB collection residing on a server. Class provides methods to
* query the collection.
*/
class MongoCollectionSocket {
private socket: Socket<ServerToClientEvents, ClientToServerEvents>;
Comment thread
davemarco marked this conversation as resolved.
Outdated

/**
* Initalizes socket connection to a MongoDB collection on the server.
*
* @param collectionName
*/
Comment on lines +20 to +24

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)

Fix typo and clarify JSDoc in constructor
There's a typo in the JSDoc (Initalizes → Initializes) and the @param lacks a description. Consider updating to:

- * Initalizes socket connection to a MongoDB collection on the server.
+ * Initializes the shared socket connection for a MongoDB collection on the server.
+ *
+ * @param collectionName - The name of the MongoDB collection to subscribe to.
📝 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
/**
* Initalizes socket connection to a MongoDB collection on the server.
*
* @param collectionName
*/
/**
* Initializes the shared socket connection for a MongoDB collection on the server.
*
* @param collectionName - The name of the MongoDB collection to subscribe to.
*/

constructor (collectionName: string) {
// eslint-disable-next-line no-warning-comments
// TODO: Add support for user provided domain name (i.e. io("https://server-domain.com")).
// Implementation could involve parsing server .env file and moving server .env to a
// common folder.

// eslint-disable-next-line no-warning-comments
// TODO: Current implementation creates a new socket connection for each collection. This
// could be problematic if the number of collections is large since browsers limit the
// number of web sockets per domain. An simple change is to use Socket.IO namespace
// feature, and having Socket.IO multiplex the socket per collection namespace. Another
// more involved option is to implement a shared socket.
// Namespace reference: https://socket.io/docs/v4/namespaces/
this.socket = io();
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
this.socket.emit("collection::init", {
collectionName: collectionName,
});
console.log(`MongoDB collection:${collectionName} initialized.`);
}
Comment on lines +25 to +29

@coderabbitai coderabbitai Bot May 9, 2025 •

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

Socket lifecycle is never closed – risk of resource leaks

MongoCollectionSocket opens a fresh socket per instance (io()), but there is no public disconnect()/close() method or automatic teardown logic (beforeunload, React unmount, etc.).
In long-running UIs that mount/unmount many components (or hot-reload during dev), the browser can easily exceed the ~6 WebSocket per-origin cap and silently drop newer ones.

Diff sketch:

+  /**
+   * Cleanly tears down the socket connection.
+   * Call this when the owning component unmounts.
+   */
+  public disconnect(): void {
+      if (this.socket.connected) {
+          this.socket.disconnect();
+      }
+  }

Expose this method (or share a singleton socket—see next comment) and invoke it from the consumer (e.g., inside useEffect cleanup that creates the collection object).


🏁 Script executed:

#!/bin/bash
# Inspect MongoCollectionSocket.ts and search for disconnect/close usage

FILE="components/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.ts"
echo "=== Contents of $FILE ==="
sed -n '1,200p' "$FILE"

echo
echo "=== Searching for 'disconnect' across the repo ==="
rg "disconnect" -n .

echo
echo "=== Searching for 'close' across the repo ==="
rg "close" -n .

Length of output: 66430


Add socket teardown to prevent connection leaks

The MongoCollectionSocket constructor creates a new io() connection but never closes it, which can exhaust the browser’s per-origin WebSocket limit in long-running or hot-reloaded UIs.

Please implement and expose a cleanup method and invoke it from the owning component’s unmount logic:

• File: components/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.ts
– After the existing methods, add:

/**
 * Cleanly disconnects the socket.
 * Call this when the owning component unmounts.
 */
public disconnect(): void {
  if (this.socket.connected) {
    this.socket.disconnect();
  }
}

• Consumer (e.g. in a React component):

const collectionSocket = new MongoCollectionSocket("myCollection");
useEffect(() => {
  return () => {
    collectionSocket.disconnect();
  };
}, []);

This ensures sockets are closed when no longer needed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

When the class is garbage collected, will it automatically disconnect?

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.

🧩 Analysis chain

I need to research this question about Socket.IO garbage collection behavior to give you an accurate answer.


🌐 Web query:

Does Socket.IO automatically disconnect when the socket object is garbage collected in JavaScript?

💡 Result:

Does Socket.IO Automatically Disconnect When the Socket Object Is Garbage Collected in JavaScript?

Summary:
No, Socket.IO does not automatically disconnect a socket when its JavaScript object is garbage collected. Explicit disconnection is required to properly close the network connection.


Detailed Explanation

1. Socket.IO Connection Lifecycle

  • Socket.IO maintains a live connection between the client and server, managed internally by its own event system and timers[1][4].
  • To close a connection intentionally, you must explicitly call socket.disconnect() on the client-side or server-side[5][8].

2. Garbage Collection in JavaScript

  • In JavaScript, objects are garbage collected when there are no more references to them, but this does not guarantee any cleanup of side effects or external resources (like network sockets).
  • The garbage collector only frees memory; it does not invoke any disconnection logic provided by Socket.IO.

3. Implicit vs. Explicit Disconnection

  • Explicit Disconnection:

    • Use socket.disconnect() to close the connection cleanly and emit the disconnect event[5][8].
    • Once disconnected, the socket will not try to reconnect (unless you explicitly call connect again)[5].
  • Garbage Collection Alone:

    • If the socket object is simply dereferenced and left for garbage collection, the underlying connection may persist until the OS or browser forcibly closes idle or orphaned connections (which could take time and may not be reliable)[1][3][4].
    • Socket.IO does not register a finalizer or destructor with the JavaScript garbage collector to disconnect itself automatically.

4. Memory and Resource Management

  • Relying on garbage collection to manage network connections is not safe, as network resources (like open TCP connections) can remain open, potentially causing memory leaks or exhausting server resources[9].
  • Proper practice is always to call .disconnect() when you are done using a socket.

5. Analogies and Related Technologies

  • Other networking libraries (e.g., in .NET or certain C++ environments) do provide explicit .close() or .dispose() methods, and recommend their use to ensure proper resource cleanup[6].
  • In JavaScript, such deterministic cleanup must be manually invoked.

Conclusion

Socket.IO does not automatically disconnect sockets when their JavaScript object is garbage collected.
To ensure connections are closed and resources are cleaned up, always explicitly call socket.disconnect() when you are finished with a socket object[5][8]. Garbage collection alone will not handle network disconnection.

Citations:


@davemarco No, Socket.IO connections are not automatically disconnected when the object is garbage collected. JavaScript's garbage collector only frees memory; it doesn't handle cleanup of external resources like network connections.

The socket connection will remain open even after the class instance is garbage collected, which can lead to:

  1. Exhaustion of the browser's WebSocket connection limit (typically ~6 per origin)
  2. Unnecessary server resource consumption
  3. Potential memory leaks

You should implement the suggested disconnect() method and ensure it's called when the connection is no longer needed (typically in component unmount logic):

/**
 * Cleanly disconnects the socket.
 * Call this when the owning component unmounts.
 */
public disconnect(): void {
  if (this.socket.connected) {
    this.socket.disconnect();
  }
}

This is standard practice for managing connection lifecycle in networking libraries.


✏️ Learnings added
Learnt from: davemarco
PR: y-scope/clp#892
File: components/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.ts:25-43
Timestamp: 2025-05-09T18:06:33.030Z
Learning: Socket.IO connections in JavaScript require explicit disconnection by calling socket.disconnect() and are not automatically cleaned up when the object is garbage collected.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

hmm i imagine this will be difficult to implement in the current useCusor hook. Will the connection automatically disconnect after a certain amount of no activity?

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.

🧩 Analysis chain

@davemarco Socket.IO does have a built-in ping/pong mechanism, but it doesn't automatically disconnect inactive connections by default. Here's how it works:

  1. Socket.IO has a "ping timeout" mechanism, but this is primarily to detect connection failures, not application inactivity.
  2. By default, pings are sent every 25 seconds, and if no pong is received within 20 seconds after a ping, the connection is considered broken.
  3. These timeouts detect network issues, not application inactivity - a silent but connected client will still maintain its connection.

Regarding implementation in useCursor:

You could modify the useCursor hook to handle the disconnection in its cleanup function:

// In useCursor.tsx
useEffect(() => {
  // ... existing subscription logic ...
  
  return () => {
    // Cleanup function when hook unmounts
    if (cursor) {
      cursor.unsubscribe();
      // If you had access to the MongoCollectionSocket instance:
      // collectionSocket.disconnect();
    }
  };
}, [dependencies]);

The challenge is getting access to the underlying MongoCollectionSocket instance from the cursor. You might need to refactor to either:

  1. Expose the socket through the cursor
  2. Use a singleton pattern or context for socket management
  3. Pass socket lifecycle responsibility to the component using the hook

(●__●)


🌐 Web query:

Does Socket.IO automatically disconnect inactive connections after a timeout period?

💡 Result:

Socket.IO and Automatic Disconnection on Inactivity

Summary:
Yes, Socket.IO does automatically disconnect inactive connections after a timeout period using a built-in heartbeat mechanism. This mechanism relies on periodic "ping" and "pong" messages to detect inactive or lost connections.


How the Timeout Works

  • Heartbeat Mechanism:
    The server sends a PING packet to each connected client at regular intervals defined by the pingInterval. The client must reply with a PONG within a time window defined by pingTimeout. If the server does not receive a PONG in time, it considers the connection closed due to inactivity and disconnects the client[4][7][9].

  • Default Values:

    • pingInterval: 25,000 ms (25 seconds)
    • pingTimeout: 20,000 ms (20 seconds)

    These can be adjusted in the server configuration[9].

  • Disconnection Reason:
    When a connection is disconnected due to inactivity, the reason is logged as "ping timeout"[4][7][9].

  • Both Client and Server:

    • If the client does not receive a ping from the server within pingInterval + pingTimeout, it also considers the connection lost and disconnects itself[4][7][9].

Example Configuration (Node.js):

const io = new Server({
  pingInterval: 25000,
  pingTimeout: 20000,
});

You can increase pingTimeout and pingInterval to allow for longer periods of inactivity before disconnecting, but setting these too high may delay the detection of truly lost connections[2][7][9].


Common Scenarios and Limitations

  • Browser/OS Power Saving:
    If a browser tab is minimized or a mobile app is backgrounded, timers may be throttled or network connections paused, leading to unexpected disconnects even if the user expects to remain connected[2][1][7].

  • Proxies, Load Balancers, and Network Devices:
    In some environments, proxies or load balancers may close inactive WebSocket connections after their own timeout period, potentially overriding Socket.IO's settings[2][8][10].


Key Takeaway

  • Socket.IO will automatically disconnect connections that are inactive beyond the configured timeout period, primarily as a safety and resource-saving feature, but this is highly configurable[4][7][9].
  • Unexpected disconnects can also occur due to factors outside of Socket.IO, such as browser power-saving, proxies, or network interruptions[2][8][10].

References to the Mechanism in Official Documentation:

“At a given interval (the pingInterval value sent in the handshake) the server sends a PING packet and the client has a few seconds (the pingTimeout value) to send a PONG packet back. If the server does not receive a PONG packet back, it will consider that the connection is closed.”[4]

“The server sends a ping packet every pingInterval ms, and if the client does not answer with a pong within pingTimeout ms, the server considers that the connection is closed.”[7]


Conclusion:
Socket.IO does automatically disconnect clients after a period of inactivity, as determined by its ping/pong heartbeat mechanism and the values of pingInterval and pingTimeout[4][7][9]. These settings can be customized to suit your application's needs.

Citations:


@davemarco Yes, Socket.IO has a built-in heartbeat mechanism, but it's designed to detect broken connections, not application inactivity:

  1. By default, the server sends a PING every 25 seconds
  2. Clients must respond with a PONG within 20 seconds
  3. If no PONG is received, the connection is closed with "ping timeout" reason

This mechanism only detects network failures. If your connection is healthy but idle, it will remain open indefinitely.

For implementing disconnection in useCursor, you have several options:

  1. Refactor to expose the socket: Modify your MongoCursorSocket to provide access to the underlying MongoCollectionSocket

    // In MongoCursorSocket.ts
    getCollection(): MongoCollectionSocket {
      return this._collection; // You would need to store this reference
    }
  2. Use a connection manager: Create a singleton that tracks all socket connections

  3. React Context provider: Manage connections at a higher level through React Context

The simplest approach would be to pass the collection instance to the cursor and expose a getter:

// When creating the cursor
const cursor = collectionSocket.find(query, options);
useEffect(() => {
  // ...subscription logic
  return () => {
    cursor.unsubscribe();
    cursor.getCollection().disconnect();
  }
}, []);

(x__x)


✏️ Learnings added
Learnt from: davemarco
PR: y-scope/clp#892
File: components/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.ts:25-43
Timestamp: 2025-05-09T18:21:05.824Z
Learning: Socket.IO has a built-in ping/pong mechanism that detects connection failures, but it doesn't automatically disconnect truly inactive but connected clients. By default, it sends pings every 25 seconds and expects pongs within 20 seconds, but this is for connection health monitoring, not application inactivity detection.

Learnt from: davemarco
PR: y-scope/clp#892
File: components/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.ts:25-43
Timestamp: 2025-05-09T18:21:05.824Z
Learning: Socket.IO has a built-in heartbeat mechanism that uses PING/PONG messages (default: 25s interval, 20s timeout) to detect broken connections, but this only detects network failures, not application inactivity. If a connection is healthy but idle, it will remain open indefinitely unless manually disconnected.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No this looks terrible. will namespace help?

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.

🧩 Analysis chain

@davemarco Yes, namespaces would help significantly! They're actually the perfect solution for this problem.

Socket.IO namespaces allow multiple logical connections to share a single physical WebSocket connection. This directly addresses your TODO comment about browser connection limits.

Here's how you could implement it:

// SocketManager.ts - Singleton to manage the socket connection
export class SocketManager {
  private static instance: SocketManager;
  private socket: Socket;
  private collectionSockets: Map<string, Socket> = new Map();
  
  private constructor() {
    this.socket = io();
  }
  
  public static getInstance(): SocketManager {
    if (!SocketManager.instance) {
      SocketManager.instance = new SocketManager();
    }
    return SocketManager.instance;
  }
  
  public getCollectionSocket(collectionName: string): Socket {
    if (!this.collectionSockets.has(collectionName)) {
      // Create a namespace for this collection
      const collectionSocket = this.socket.io(`/${collectionName}`);
      this.collectionSockets.set(collectionName, collectionSocket);
    }
    return this.collectionSockets.get(collectionName)!;
  }
  
  public disconnect(): void {
    if (this.socket.connected) {
      this.socket.disconnect();
      this.collectionSockets.clear();
    }
  }
}

// Modified MongoCollectionSocket.ts
class MongoCollectionSocket {
  private socket: Socket<ServerToClientEvents, ClientToServerEvents>;

  constructor(collectionName: string) {
    // Get the namespaced socket from the singleton manager
    this.socket = SocketManager.getInstance().getCollectionSocket(collectionName);
    this.socket.emit("collection::init", { collectionName });
    console.log(`MongoDB collection:${collectionName} initialized.`);
  }
  
  // Rest of your class...
}

Benefits:

  1. Single physical connection for all collections
  2. Centralized connection management
  3. App-level disconnect on shutdown rather than component-level
  4. No risk of hitting browser connection limits

This avoids the "terrible" approach of having to manage disconnection in every component that uses a collection.


🌐 Web query:

Do Socket.IO namespaces solve the connection disconnection management problem?

💡 Result:

Socket.IO namespaces help organize and separate different communication channels within a single Socket.IO connection. They provide a way to split the connection into multiple logical endpoints, each with its own event handlers and logic. However, namespaces alone do not fully solve connection and disconnection management problems.

Here's a more detailed explanation:

  1. Purpose of Namespaces: Namespaces allow you to segment the connection space, which can be useful for separating concerns within your application (e.g., chat, notifications, gaming). Each namespace can have its own event listeners and middleware.

  2. Connection Management: When a client connects, it connects to a specific namespace. Socket.IO manages the connection lifecycle for each namespace separately. This means you can handle events like connect, disconnect, and reconnections within each namespace independently.

  3. Disconnection Management: Namespaces can help you handle disconnections more granularly because each namespace triggers its own disconnect event. However, namespaces don't inherently solve challenges like detecting real connection drops, handling reconnections, or managing resources based on connection state. You still need to implement proper event handlers and logic to manage these cases.

  4. Limitations: If a client disconnects from the underlying transport (e.g., network issues), all namespaces for that client are affected since they share the underlying connection. So, namespaces don't isolate the physical connection—they just provide logical segmentation over it.

Summary:

  • Namespaces improve logical separation and event management per context.
  • They provide a framework to handle connection/disconnection events per namespace.
  • They do not independently solve all connection and disconnection management problems.
  • You still need to write application-level code for robust connection handling, reconnection strategies, and resource cleanup.

If you're looking to manage connection and disconnection issues comprehensively, consider combining namespaces with other Socket.IO features like rooms, middleware, and custom reconnection logic.


@davemarco Yes, Socket.IO namespaces would solve the primary issue mentioned in your TODO comment - they allow multiple logical connections to share a single physical WebSocket connection.

From the TODO in your code:

"An simple change is to use Socket.IO namespace feature, and having Socket.IO multiplex the socket per collection namespace."

Here's how namespaces help:

  1. Single physical connection: Instead of one connection per collection, all collections use the same underlying connection
  2. Avoid browser limits: Browsers typically limit connections to 6-8 per domain, namespaces bypass this
  3. Simpler architecture: Centralized connection management instead of per-component disconnection

Basic implementation approach:

// Create a singleton socket manager
export class SocketManager {
  private static instance: SocketManager;
  private socket: Socket;
  
  private constructor() {
    this.socket = io(); // Single connection
  }
  
  public static getInstance(): SocketManager {
    if (!SocketManager.instance) {
      SocketManager.instance = new SocketManager();
    }
    return SocketManager.instance;
  }
  
  public getCollectionNamespace(collectionName: string): Socket {
    return this.socket.io(`/${collectionName}`);
  }
  
  // Single disconnect point for application shutdown
  public disconnect(): void {
    this.socket.disconnect();
  }
}

This addresses both the connection limit issue and simplifies disconnection management by centralizing it.

Note that server-side changes would also be needed to properly handle these namespaces.


🧠 Learnings used
Learnt from: davemarco
PR: y-scope/clp#892
File: components/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.ts:32-38
Timestamp: 2025-05-09T18:05:42.788Z
Learning: When implementing Socket.IO namespaces or connection pooling in a client-server architecture (like the MongoDB collection socket system in the log-viewer-webui), coordinated changes are needed on both client and server sides, making it appropriate to track as a TODO rather than implement piecemeal.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I moved to shared socket which should fix the issue

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.

@davemarco Great! Using a shared socket singleton is the perfect solution for this issue. This implementation:

  1. Prevents creating multiple unnecessary connections
  2. Avoids hitting browser connection limits
  3. Centralizes connection management
  4. Eliminates the need for per-collection disconnection logic

The getSharedSocket() approach is ideal since it gives you all the benefits of connection pooling without the complexity of implementing namespaces. Nice work addressing this!

(^◡^)


🧠 Learnings used
Learnt from: davemarco
PR: y-scope/clp#892
File: components/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.ts:32-38
Timestamp: 2025-05-09T18:05:42.788Z
Learning: When implementing Socket.IO namespaces or connection pooling in a client-server architecture (like the MongoDB collection socket system in the log-viewer-webui), coordinated changes are needed on both client and server sides, making it appropriate to track as a TODO rather than implement piecemeal.

Comment on lines +27 to +29

@coderabbitai coderabbitai Bot May 9, 2025 •

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

Expose a deterministic teardown path for the collection-level socket

MongoCollectionSocket keeps only a reference returned by getSharedSocket().
Call-sites currently have no way to disconnect or remove listeners when the component tree unmounts (e.g., during hot-reload). Even though the underlying connection is shared, it is still good practice to provide a dispose() / disconnect() that simply delegates to the shared singleton. This allows React cleanup hooks (or unit tests) to release resources explicitly and avoids lingering listeners that keep collections in memory.

Example:

class MongoCollectionSocket {
   …
+  /**
+   * Releases any listeners added by this instance.
+   * No-op when the shared socket is already closed.
+   */
+  public disconnect(): void {
+      if (this.socket.connected) {
+          // Only detach collection-specific listeners here
+          // (do not close the underlying shared socket).
+          this.socket.removeAllListeners("collection::find::update");
+      }
+  }
}
📝 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
constructor (collectionName: string) {
this.socket = getSharedSocket();
this.collectionName = collectionName;
console.log(`MongoDB collection:${collectionName} initialized.`);
}
// components/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.ts
export class MongoCollectionSocket {
constructor (collectionName: string) {
this.socket = getSharedSocket();
this.collectionName = collectionName;
console.log(`MongoDB collection:${collectionName} initialized.`);
}
/**
* Releases any listeners added by this instance.
* No-op when the shared socket is already closed.
*/
public disconnect(): void {
if (this.socket.connected) {
// Only detach collection-specific listeners here
// (do not close the underlying shared socket).
this.socket.removeAllListeners("collection::find::update");
}
}
// …other methods…
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is only one socket. So the only way to get another socket should disconnect the previous socket. I think it should be okay

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.

@davemarco I understand your point about using a socket singleton pattern. Since the socket is shared and managed centrally, and since the unsubscription is handled in the useCursor hook, you're right that an explicit disconnect in the MongoCollectionSocket class may not be strictly necessary for the current implementation.

Given that the class isn't directly attaching listeners (those appear to be in the MongoCursorSocket class instead), your current approach should work fine as long as the hook's cleanup is thorough.


✏️ Learnings added
Learnt from: davemarco
PR: y-scope/clp#892
File: components/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.ts:27-31
Timestamp: 2025-05-09T19:15:26.158Z
Learning: For the MongoDB real-time updates implementation in components/log-viewer-webui/client/src/api/socket, a socket singleton pattern is used where a single shared socket connection is maintained rather than creating multiple connections. The socket lifecycle is managed centrally, with unsubscription handling in the useCursor React hook's cleanup function.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment on lines +25 to +29

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)

Replace console.log with proper logger
Using console.log for production code can make debugging and log management harder. Consider using a structured logging utility or debug namespace (e.g. debug, winston, or a custom logger) to control log levels and output channels.


/**
* Selects documents in collection and returns a cursor-like object.
*
* @param query
* @param options
* @return a `MongoCursorSocket`.
*/

find (query: object, options: object) {
return new MongoCursorSocket(
this.socket,
query,
options,
);
}
Comment on lines +41 to +46

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)

Strengthen typing & return signature of find()

query and options are declared as plain object, which forfeits type-safety and IDE autocompletion.
Consider parameterising the class (or at least the method) with generics, or constrain them to Record<string, unknown>.

In addition, the method relies on type inference for its return value. Being explicit helps consumers and documentation tools:

- find (query: object, options: object) {
-     return new MongoCursorSocket(
+ find<Q extends Record<string, unknown>, O extends Record<string, unknown>>(
+     query: Q,
+     options: O
+ ): MongoCursorSocket {
+     return new MongoCursorSocket(
          this.socket,
          this.collectionName,
          query,
          options,
      );
 }
📝 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
find (query: object, options: object) {
return new MongoCursorSocket(
this.socket,
this.collectionName,
query,
options,
);
}
find<Q extends Record<string, unknown>, O extends Record<string, unknown>>(
query: Q,
options: O
): MongoCursorSocket {
return new MongoCursorSocket(
this.socket,
this.collectionName,
query,
options,
);
}

}


export default MongoCollectionSocket;
Loading