@mcpwarp/ws-mixer 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/stream.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * MixerStream: one ws-mixer byte stream, exposed as a Node Duplex.
3
- * Mirrors `go/wsmixer/stream.go`'s state machine, credit accounting and
3
+ * Mirrors `ws-mixer-go/wsmixer/stream.go`'s state machine, credit accounting and
4
4
  * half-close semantics (WIRE.md section 2.5-2.6). The JS SDK is always
5
5
  * the answering peer: it never opens streams, only receives OPEN from the
6
6
  * server and reads/writes/closes/resets what arrives.
@@ -30,8 +30,10 @@ export interface StreamHost {
30
30
  sendData(streamId: number, chunk: Uint8Array): Promise<void>;
31
31
  /** Send a control-priority frame (WINDOW/CLOSE/RESET) immediately. */
32
32
  sendControlFrame(frame: Uint8Array): void;
33
- /** Called when the stream is fully closed so the connection can retire its id. */
33
+ /** Called when the stream is fully closed so the connection can retire its id (a connection teardown still reaches it until it is destroyed). */
34
34
  retireStream(streamId: number): void;
35
+ /** Called when the stream starts holding a write callback, so the connection holds a retired stream strongly until that callback is settled (a no-op for one not yet retired). Optional: a host that tracks no retired streams has nothing to pin. */
36
+ pinRetired?(streamId: number): void;
35
37
  }
36
38
  export declare class MixerStream extends Duplex {
37
39
  readonly id: number;
@@ -48,7 +50,7 @@ export declare class MixerStream extends Duplex {
48
50
  /** Bytes queued via _write, waiting on credit; drives backpressure. */
49
51
  private writeQueue;
50
52
  private draining;
51
- /** Write callbacks (_write's own terminal-error branch, or drainWriteQueue's catch) whose error must wait until the read side has finished delivering buffered data and emitted 'end' -- see terminateNoThrow's doc comment. Non-null exactly while that wait is pending; flushed (and reset to null) from _destroy(), or from the 'end' listener drainWriteQueue's catch installs. */
53
+ /** Write callbacks (_write's own terminal-error branch, or failWrite()) whose error must wait until the read side has finished delivering buffered data and emitted 'end' -- see terminateNoThrow's doc comment. Non-null exactly while that wait is pending; flushed (and reset to null) from _destroy(), or from the 'end' listener failWrite() installs. */
52
54
  private heldWriteCallbacks;
53
55
  /** Set once this stream is RESET -- by the peer (handleReset), the SDK (abort()), or the app itself (reset()); undefined until then. Set even when no `'error'` listener is attached. */
54
56
  resetCode?: number;
@@ -62,6 +64,35 @@ export declare class MixerStream extends Duplex {
62
64
  _destroy(err: Error | null, callback: (error?: Error | null) => void): void;
63
65
  private pumpWriteQueue;
64
66
  private drainWriteQueue;
67
+ /**
68
+ * Settles a failed write's callback with `failure` -- now, or held until
69
+ * the read side can no longer be hurt by it. Calling a write callback with
70
+ * an error marks the READABLE side errored too (Node's own Writable/Duplex
71
+ * machinery), which drops whatever is still buffered and blocks 'end'.
72
+ *
73
+ * - terminateNoThrow is already holding write errors for the read side
74
+ * (`heldWriteCallbacks` non-null): join that hold -- see its doc comment.
75
+ * - The peer's CLOSE has arrived and 'end' hasn't fired yet, whatever this
76
+ * side's own write state (`half_closed_remote`, or `closed` after
77
+ * closeWrite()): the read side may still be delivering buffered data, so
78
+ * hold the callback until 'end', without relying on teardown to flush it
79
+ * -- there may be no teardown at all. A connection teardown that does
80
+ * come (it also reaches a stream closeWrite() retired) flushes it once
81
+ * nothing is left unread; so do destroy()/reset(). `failure` is the
82
+ * fallback for a flush with no terminalError of its own (always, on a
83
+ * stream CLOSE'd both ways -- see terminateNoThrow).
84
+ * - Otherwise (peer still sending, 'end' already fired or never will --
85
+ * destroyed, or already errored): fail it now.
86
+ *
87
+ * Node's Writable machinery re-emits a write callback's error as 'error',
88
+ * so -- same rationale as terminateNoThrow() -- the internal no-op listener
89
+ * is attached first if nothing else is listening: a failure is also
90
+ * reachable before terminateNoThrow() has run at all (the connection's
91
+ * writer failed this chunk's socket write, and teardown follows from the
92
+ * socket's own 'close'), and a dying connection must not crash a consumer
93
+ * that never attached its own 'error'.
94
+ */
95
+ private failWrite;
65
96
  /** Waits until send credit is available, then reserves and returns min(want, sendWindow, MAX_CHUNK). */
66
97
  private reserveSendCredit;
67
98
  private wakeSendWaiters;
@@ -83,16 +114,37 @@ export declare class MixerStream extends Duplex {
83
114
  * never entered it there.
84
115
  */
85
116
  read(size?: number): unknown;
117
+ /**
118
+ * Both directions CLOSE'd cleanly (closeWrite() + the peer's CLOSE, in
119
+ * either order): the stream is retired, so only a connection teardown
120
+ * (which may never come) would otherwise reach it, and 'close' needs a
121
+ * destroy() on an `autoDestroy: false` Duplex. Mirrors
122
+ * what autoDestroy itself would do: destroy once the readable has emitted
123
+ * 'end' (a tick later, like failWrite()'s hold, so every 'end' listener
124
+ * runs first and 'end' always precedes 'close'), and, when the write side
125
+ * was closed by end(), once 'finish' has fired too -- destroying inside
126
+ * _final() would suppress 'finish' and turn finished()/pipeline() into a
127
+ * premature close. A readable that already errored never emits 'end', so
128
+ * that destroys right away. A consumer that never reads never sees 'end',
129
+ * and so never 'close' -- same as any other Readable.
130
+ */
131
+ private destroyWhenDone;
86
132
  private maybeDeliverEOF;
87
- /** True once something is listening for `'error'`: gates attaching the internal no-op `'error'` listener (terminateNoThrow, drainWriteQueue). */
133
+ /** True once something is listening for `'error'`: gates attaching the internal no-op `'error'` listener (terminateNoThrow, failWrite). */
88
134
  private hasErrorListener;
89
- /** Sends CLOSE: "I will send no more DATA on this stream." end() is sugar for this via _final. */
135
+ /**
136
+ * Sends CLOSE: "I will send no more DATA on this stream." end() is sugar
137
+ * for this via _final. A write still waiting on send credit can never go
138
+ * out now, so it is woken to fail with STREAM_CLOSED (reserveSendCredit)
139
+ * instead of waiting on a WINDOW that, once this stream is retired, would
140
+ * never reach it.
141
+ */
90
142
  closeWrite(): void;
91
143
  /**
92
144
  * Sends CLOSE (if not already sent) and stops delivering further reads. If
93
145
  * the peer has not yet half-closed its own send side, a plain closeWrite()
94
146
  * would leave it writing into a window nobody drains, so close() also
95
- * RESETs with CANCEL in that case (mirrors go/wsmixer/stream.go Close()).
147
+ * RESETs with CANCEL in that case (mirrors ws-mixer-go/wsmixer/stream.go Close()).
96
148
  */
97
149
  close(): void;
98
150
  /**
@@ -114,7 +166,7 @@ export declare class MixerStream extends Duplex {
114
166
  * an SDK-detected violation (abort()), `reset()` does NOT emit `'reset'`.
115
167
  */
116
168
  reset(code: number | string, message?: string): void;
117
- /** Shared implementation behind reset()/abort(); returns false (no-op) if the stream was already closed. */
169
+ /** Shared implementation behind reset()/abort(); returns false (nothing sent) if the stream was already closed. */
118
170
  private applyReset;
119
171
  /**
120
172
  * Wraps this stream as Web Streams API `{ readable, writable }`, e.g. for
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcpwarp/ws-mixer",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "sideEffects": false,
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/",