feat: feat(cluster-protocol): publish the TypeScript cluster client with full v1 parity#800
feat: feat(cluster-protocol): publish the TypeScript cluster client with full v1 parity#800tomdps wants to merge 1 commit into
Conversation
… cluster client with full v1 parity
Greptile SummaryIntroduces a published TypeScript client for cluster protocol v1.
Confidence Score: 3/5The PR should not merge until synchronous request-send failures clean up pending state; the unbounded subscription queue should also receive overflow protection. Calls made on a closed transport register deferred requests before the frame write throws and never remove those entries, while inbound subscription events can accumulate without a client-side capacity bound. Files Needing Attention: src/cluster/transport/multiplexer.ts and src/cluster/transport/async-queue.ts
|
| Filename | Overview |
|---|---|
| src/cluster/transport/multiplexer.ts | Implements shared request routing and subscription dispatch, but synchronous send failures retain pending request entries. |
| src/cluster/transport/async-queue.ts | Decouples WebSocket handling from subscription consumers using an unbounded queue that lacks required overflow handling. |
| src/cluster/subscriptions/watch.ts | Implements cursor-based replay and cross-reconnect de-duplication consistently with the documented at-least-once protocol. |
| src/cluster/connect.ts | Creates a shared multiplexer and exposes unary and subscription APIs over one connected WebSocket. |
| src/cluster/transport/websocket-factory.ts | Supports injected WebSocket implementations and a lazy ws fallback on Node versions without a global WebSocket. |
| package.json | Adds the cluster export, build pipeline, runtime WebSocket dependency, and generated package artifacts. |
Sequence Diagram
sequenceDiagram
participant App
participant Client as ClusterClient
participant Mux as ConnectionMultiplexer
participant WS as WebSocket
App->>Client: unary call / subscribe
Client->>Mux: allocate shared request ID
Mux->>WS: JSON-RPC request
WS-->>Mux: response / notification
Mux-->>Client: resolve pending call / enqueue event
Client-->>App: result / async delivery
App->>Client: cancel or reconnect
Client->>Mux: cancellation notification or fresh subscription
Reviews (1): Last reviewed commit: "feat: implement #748 - feat(cluster-prot..." | Re-trigger Greptile
| this.pending.set(id, {kind: 'unary', resolve: deferred.resolve, reject: deferred.reject}); | ||
| const unregisterAbort = this.registerAbort(id, deferred.reject, signal); | ||
|
|
||
| this.sendRequest(id, method, params); |
There was a problem hiding this comment.
Pending requests survive send failures
When a unary call or subscription is started after the WebSocket leaves the OPEN state, the deferred is added to pending before writeFrame() throws. The failed entry is never removed, so repeated calls on the closed connection retain additional unreachable deferred state for the lifetime of the connection.
| public push(item: T): void { | ||
| if (this.closed) return; | ||
| const waiter = this.waiting.shift(); | ||
| if (waiter) { | ||
| waiter({value: item, done: false}); | ||
| return; | ||
| } | ||
| this.buffered.push(item); |
There was a problem hiding this comment.
Subscription queue has no bound
Inbound watch, logs, and attach notifications are appended without a capacity or overflow path when the consumer falls behind. Sustained delivery therefore grows the queue with every unread event, increasing memory use and retaining buffered deliveries even after close() while the stream remains referenced.
How this was verified: The notification router forwards peer-supplied events directly to push(), whose fallback unconditionally appends to an uncapped array.
Context Used: AGENTS.md (source)
|
External review found another release-blocking P1 plus a security-relevant P2: frame-send failure leaves an unreachable deferred in the transport-wide pending map, and the subscription async queue is unbounded for a slow consumer. The campaign acceptance bar prohibits unresolved P1/P0 findings, and the protocol client must preserve the Rust queue bound/overflow behavior. Closing this attempt for a fresh Zeroshot repair. |
Closes #748