@statewalker/webrun-rpc 0.5.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.
@@ -1 +1 @@
1
- {"version":3,"file":"listen-port.d.ts","sourceRoot":"","sources":["../src/listen-port.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,MAAM,WAAW,iBAAiB;IAChC,+EAA+E;IAC/E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6CAA6C;IAC7C,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;CACpC;AAED,MAAM,MAAM,WAAW,CAAC,OAAO,GAAG,OAAO,EAAE,OAAO,GAAG,OAAO,IAAI,CAC9D,MAAM,EAAE,OAAO,KACZ,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,OAAO,GAAG,OAAO,EAAE,OAAO,GAAG,OAAO,EAC7D,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,WAAW,CAAC,OAAO,EAAE,OAAO,CAAC,EACtC,EAAE,WAAgB,EAAE,GAAc,EAAE,GAAE,iBAAsB,GAC3D,MAAM,IAAI,CAsBZ"}
1
+ {"version":3,"file":"listen-port.d.ts","sourceRoot":"","sources":["../src/listen-port.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,MAAM,WAAW,iBAAiB;IAChC,+EAA+E;IAC/E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6CAA6C;IAC7C,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;CACpC;AAED,MAAM,MAAM,WAAW,CAAC,OAAO,GAAG,OAAO,EAAE,OAAO,GAAG,OAAO,IAAI,CAC9D,MAAM,EAAE,OAAO,KACZ,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,OAAO,GAAG,OAAO,EAAE,OAAO,GAAG,OAAO,EAC7D,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,WAAW,CAAC,OAAO,EAAE,OAAO,CAAC,EACtC,EAAE,WAAgB,EAAE,GAAc,EAAE,GAAE,iBAAsB,GAC3D,MAAM,IAAI,CA6BZ"}
@@ -8,9 +8,22 @@ export interface MessageSource {
8
8
  /** An object we can post messages to (with optional transferable list). */
9
9
  export interface MessageSink {
10
10
  postMessage(message: unknown, transfer?: Transferable[]): void;
11
+ /**
12
+ * Optional: bytes posted and not yet written out, as a WebSocket's or an
13
+ * `RTCDataChannel`'s `bufferedAmount`. Informational.
14
+ */
15
+ readonly bufferedAmount?: number;
16
+ /**
17
+ * Optional: resolves once the pipe can take more, at once when it can now.
18
+ * A multiplexer with `frameSize` waits on it between frames, which is what
19
+ * lets frames of different ports take turns instead of piling up in the
20
+ * pipe's own buffer.
21
+ */
22
+ whenDrained?(): Promise<void>;
11
23
  }
12
24
  /** Full-duplex message target: both sends and receives. */
13
25
  export interface MessageTarget extends MessageSource, MessageSink {
14
- close?(): void | Promise<void>;
26
+ /** `reason`, where the target keeps one, becomes its close signal's reason. */
27
+ close?(reason?: unknown): void | Promise<void>;
15
28
  }
16
29
  //# sourceMappingURL=message-target.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"message-target.d.ts","sourceRoot":"","sources":["../src/message-target.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE5E,yDAAyD;AACzD,MAAM,WAAW,aAAa;IAC5B,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAC;IACnE,mBAAmB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAC;IACtE,KAAK,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChC;AAED,2EAA2E;AAC3E,MAAM,WAAW,WAAW;IAC1B,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC;CAChE;AAED,2DAA2D;AAC3D,MAAM,WAAW,aAAc,SAAQ,aAAa,EAAE,WAAW;IAC/D,KAAK,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChC"}
1
+ {"version":3,"file":"message-target.d.ts","sourceRoot":"","sources":["../src/message-target.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE5E,yDAAyD;AACzD,MAAM,WAAW,aAAa;IAC5B,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAC;IACnE,mBAAmB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAC;IACtE,KAAK,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChC;AAED,2EAA2E;AAC3E,MAAM,WAAW,WAAW;IAC1B,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC;IAC/D;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC;;;;;OAKG;IACH,WAAW,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B;AAED,2DAA2D;AAC3D,MAAM,WAAW,aAAc,SAAQ,aAAa,EAAE,WAAW;IAC/D,+EAA+E;IAC/E,KAAK,CAAC,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChD"}
@@ -1,5 +1,5 @@
1
1
  import type { MessageTarget } from "./message-target.js";
2
- import type { PortMux, PortMuxOptions } from "./port-types.js";
2
+ import type { PortInfo, PortMux, PortMuxOptions } from "./port-types.js";
3
3
  /**
4
4
  * The message of the error that fails the calls still in flight on a
5
5
  * multiplexer when it is closed (`PortMux.close()`).
@@ -7,11 +7,20 @@ import type { PortMux, PortMuxOptions } from "./port-types.js";
7
7
  export declare const MUX_CLOSED_MESSAGE = "webrun-rpc: the multiplexer was closed";
8
8
  /** Ceiling on concurrently open virtual ports. Bounds the id table only. */
9
9
  export declare const DEFAULT_MAX_PORTS = 1024;
10
+ /**
11
+ * What a message costs a port's window when the codec cannot say
12
+ * (`PortCodec.encode`): its bytes and string lengths, found in its values up
13
+ * to 4 levels deep (at most 256 values), and at least 64. Both ends compute it
14
+ * from the same structured value, so they agree.
15
+ */
16
+ export declare function estimateMessageSize(payload: unknown): number;
10
17
  /**
11
18
  * The default `PortMux`: emulates multiplexing over a single port.
12
19
  *
13
20
  * A transport that already multiplexes natively supplies its own `PortMux`
14
21
  * instead — this implementation is for transports that offer one pipe.
15
22
  */
16
- export declare function multiplexPort(port: MessageTarget, options: PortMuxOptions): PortMux;
23
+ export declare function multiplexPort(port: MessageTarget, options: PortMuxOptions): PortMux & {
24
+ ports(): PortInfo[];
25
+ };
17
26
  //# sourceMappingURL=multiplex-port.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"multiplex-port.d.ts","sourceRoot":"","sources":["../src/multiplex-port.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAgB,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAG7E;;;GAGG;AACH,eAAO,MAAM,kBAAkB,2CAA2C,CAAC;AAE3E,4EAA4E;AAC5E,eAAO,MAAM,iBAAiB,OAAO,CAAC;AAEtC;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAwHnF"}
1
+ {"version":3,"file":"multiplex-port.d.ts","sourceRoot":"","sources":["../src/multiplex-port.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,KAAK,EAGV,QAAQ,EACR,OAAO,EACP,cAAc,EACf,MAAM,iBAAiB,CAAC;AAGzB;;;GAGG;AACH,eAAO,MAAM,kBAAkB,2CAA2C,CAAC;AAY3E,4EAA4E;AAC5E,eAAO,MAAM,iBAAiB,OAAO,CAAC;AAEtC;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAc5D;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,cAAc,GACtB,OAAO,GAAG;IAAE,KAAK,IAAI,QAAQ,EAAE,CAAA;CAAE,CAqnBnC"}
@@ -0,0 +1,27 @@
1
+ import type { MessageTarget } from "./message-target.js";
2
+ /**
3
+ * The send side of a port with flow control (`PortMuxOptions.window`): what
4
+ * waits in its queue, and when it has drained.
5
+ */
6
+ export interface PortSendState {
7
+ /** Bytes queued on this port, waiting for the peer's credit. */
8
+ bufferedAmount(): number;
9
+ /** Resolves once at most `below` bytes are queued (default 0). */
10
+ ready(below?: number): Promise<void>;
11
+ }
12
+ /** @internal */
13
+ export declare const nextTask: (fn: () => void) => void;
14
+ /** @internal Registered by the multiplexer for each port with a window. */
15
+ export declare function setPortSendState(port: MessageTarget, state: PortSendState): void;
16
+ /**
17
+ * Bytes queued on `port` because its window is spent (0 for a port without
18
+ * flow control). A sender that should not queue without bound waits for
19
+ * `whenPortReady` before posting more.
20
+ */
21
+ export declare function portBufferedAmount(port: MessageTarget): number;
22
+ /**
23
+ * Resolves once at most `below` bytes are queued on `port`, always on a later
24
+ * task (for a port without flow control, on the next one).
25
+ */
26
+ export declare function whenPortReady(port: MessageTarget, below?: number): Promise<void>;
27
+ //# sourceMappingURL=port-credit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"port-credit.d.ts","sourceRoot":"","sources":["../src/port-credit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,gEAAgE;IAChE,cAAc,IAAI,MAAM,CAAC;IACzB,kEAAkE;IAClE,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACtC;AASD,gBAAgB;AAChB,eAAO,MAAM,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,KAAK,IAenC,CAAC;AAIT,2EAA2E;AAC3E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,aAAa,GAAG,IAAI,CAEhF;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAE9D;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,aAAa,EAAE,KAAK,SAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAE3E"}
@@ -2,14 +2,17 @@ import type { MessageTarget } from "./message-target.js";
2
2
  /**
3
3
  * What a multiplexer exchanges over the underlying port.
4
4
  *
5
- * Three types and nothing more. There is no DATA/ACK split, no credit and no
6
- * error type: `close` carries an opaque `reason` that layer 1 never inspects,
7
- * because stream semantics belong above this layer.
5
+ * `open`, `message` and `close`, plus `credit` (a port's window) and `frame`
6
+ * (a piece of a large message, with `frameSize`). There is no DATA/ACK split
7
+ * and no error type: `close` carries an opaque `reason` that layer 1 never
8
+ * inspects, because stream semantics belong above this layer.
8
9
  */
9
10
  export type PortEnvelope = {
10
11
  type: "open";
11
12
  id: number;
12
13
  meta?: unknown;
14
+ window?: number;
15
+ exact?: boolean;
13
16
  } | {
14
17
  type: "message";
15
18
  id: number;
@@ -18,6 +21,28 @@ export type PortEnvelope = {
18
21
  type: "close";
19
22
  id: number;
20
23
  reason?: unknown;
24
+ }
25
+ /** Per-port flow control (`window`): the receiver has consumed `bytes` more. */
26
+ | {
27
+ type: "credit";
28
+ id: number;
29
+ bytes: number;
30
+ exact?: boolean;
31
+ }
32
+ /** Fair framing (`frameSize`): one piece of an encoded `message` envelope. */
33
+ | {
34
+ type: "frame";
35
+ id: number;
36
+ data: Uint8Array;
37
+ last?: boolean;
38
+ }
39
+ /** Keepalive (`keepalive`): the mux's own liveness probe and its answer, on an id no port takes. */
40
+ | {
41
+ type: "ping";
42
+ id: number;
43
+ } | {
44
+ type: "pong";
45
+ id: number;
21
46
  };
22
47
  /**
23
48
  * How an envelope reaches the wire.
@@ -32,6 +57,15 @@ export interface PortCodec {
32
57
  post(port: MessageTarget, envelope: PortEnvelope, transfer?: Transferable[]): void;
33
58
  /** Recover an envelope from a message event, or `undefined` to ignore it. */
34
59
  read(event: MessageEvent): PortEnvelope | undefined;
60
+ /**
61
+ * Optional, for codecs whose wire form is bytes, with `decode`: the bytes
62
+ * `post` would place on the port. A port with a window then counts real
63
+ * bytes instead of an estimate, posts them as they are (encoding once), and
64
+ * can split them into frames (`frameSize`).
65
+ */
66
+ encode?(envelope: PortEnvelope): Uint8Array;
67
+ /** Optional, with `encode`: an envelope back from its bytes, or `undefined`. */
68
+ decode?(data: unknown): PortEnvelope | undefined;
35
69
  }
36
70
  export interface PortMuxOptions {
37
71
  /** How envelopes are placed on the underlying port. */
@@ -73,13 +107,113 @@ export interface PortMuxOptions {
73
107
  * delivered a body as zero bytes with no error on either side.
74
108
  */
75
109
  maxMessageSize?: number;
110
+ /**
111
+ * The transport's life. When it aborts, the mux closes with
112
+ * `signal.reason`: every port's close signal aborts and the calls in flight
113
+ * on them reject at once. Wire it to whatever reports the pipe's death — a
114
+ * worker's `exit`, a libp2p stream's `close`, a liveness lock — because a
115
+ * pipe that dies silently otherwise leaves every port open, forever.
116
+ */
117
+ signal?: AbortSignal;
118
+ /**
119
+ * Per-port flow control, in bytes (off when unset). A port this mux opens
120
+ * asks for it in its `open`; the peer's mux then applies it to that port in
121
+ * both directions. Each side may have at most `window` bytes of a port's
122
+ * messages delivered but not yet consumed by the other side; what exceeds
123
+ * it waits in the port's send queue (`portBufferedAmount`,
124
+ * `whenPortReady`). A message counts as consumed once every listener has
125
+ * returned, or, for a listener that returns a promise, once it settles: a
126
+ * slow consumer holds its own port's budget, and no other port's.
127
+ *
128
+ * A message's size is its byte length (`ArrayBuffer`, typed arrays), a
129
+ * string's length, or for an object the sizes of its values (4 levels deep,
130
+ * at most 256 values), and at least 64 — or, when both ends' codecs
131
+ * implement `encode` / `decode`, the encoded bytes. A message larger than
132
+ * the window is sent when nothing else is outstanding.
133
+ *
134
+ * Safe against an older peer: neither end holds back until the other has
135
+ * shown it reads credit (the responder's zero-byte ack, then the opener's
136
+ * answer to it), so a peer that never sends credit runs unwindowed.
137
+ */
138
+ window?: number;
139
+ /**
140
+ * Fair framing, in bytes (off when unset). Every port's messages leave in
141
+ * turn, one item per port, whenever the pipe has room (its `whenDrained`),
142
+ * and a message whose encoding is larger than `frameSize` goes as frames of
143
+ * that size, reassembled by the peer. A small message then waits for one
144
+ * frame of another port's large message, not for all of it. It needs
145
+ * `window` and codecs with `encode` / `decode` on both ends, agreed in the
146
+ * window's `open` and acknowledgement (`exact`); otherwise messages go
147
+ * whole. 16-64 KiB is a reasonable range.
148
+ */
149
+ frameSize?: number;
150
+ /**
151
+ * Liveness of the peer (off when unset). Every `intervalMs` the mux pings
152
+ * and the peer answers; anything from it counts as an answer. After `misses`
153
+ * pings in a row (default 3) with nothing back from a peer that has answered
154
+ * before, the mux closes with `webrun-rpc: the peer did not answer …`: the
155
+ * peer died or froze. A tick taken while this end's own pipe reports a
156
+ * backlog (`bufferedAmount`) is not counted. Every current mux answers pings,
157
+ * with or without this option; an older one never does and is never closed
158
+ * for it — nor is a peer that dies before its first answer, so wire
159
+ * `signal` too where the transport can report death.
160
+ */
161
+ keepalive?: {
162
+ intervalMs: number;
163
+ misses?: number;
164
+ };
165
+ /**
166
+ * Leak detection, for development (off when unset). A port its consumer
167
+ * dropped without `close()` is reported here once the garbage collector
168
+ * reclaims it. If it has no listener left it is freed on both ends, as a
169
+ * close would free it; one that still has a listener is still receiving, so
170
+ * it is reported and left open. A listener that closes over its own port
171
+ * keeps it reachable from the mux, so such a port is never collected, and
172
+ * never reported. Each port costs a `FinalizationRegistry` entry, and when
173
+ * the collector runs is up to the runtime.
174
+ */
175
+ onLeak?: (info: PortInfo) => void;
176
+ }
177
+ /** What `PortMux.ports()` reports for one open port. */
178
+ export interface PortInfo {
179
+ id: number;
180
+ meta?: unknown;
181
+ /** `Date.now()` when the port was opened. */
182
+ openedAt: number;
183
+ /** `Date.now()` of the last message in either direction. */
184
+ lastActivity: number;
185
+ /**
186
+ * The window in force, once the peer acknowledged it. Absent on a port that
187
+ * asked for one from a peer that never answers credit (an older version).
188
+ */
189
+ window?: number;
190
+ /** With a window: bytes waiting in this port's send queue. */
191
+ queued?: number;
192
+ /** With a window: bytes sent and not yet granted back by the peer. */
193
+ outstanding?: number;
194
+ /**
195
+ * With a window: bytes delivered to this side's listeners and not yet
196
+ * consumed. A value that stays up names a listener whose promise never
197
+ * settles, which holds the port's window spent.
198
+ */
199
+ unconsumed?: number;
200
+ }
201
+ export interface OpenPortOptions {
202
+ /** Closes the port when it aborts, so the port lives no longer than its user. */
203
+ signal?: AbortSignal;
76
204
  }
77
205
  /** One port in, many ports out. */
78
206
  export interface PortMux {
79
207
  /** Allocate a port, announce it, and return the local end. */
80
- openPort(meta?: unknown): Promise<MessageTarget>;
81
- /** Close every virtual port, then release the underlying port. */
82
- close(): Promise<void>;
208
+ openPort(meta?: unknown, options?: OpenPortOptions): Promise<MessageTarget>;
209
+ /**
210
+ * Close every port, then release the underlying port. With
211
+ * `multiplexPort`, `reason` becomes the reason of every port's close signal
212
+ * (`getPortCloseSignal`); `transferPortMux`'s real ports have none.
213
+ */
214
+ close(reason?: unknown): Promise<void>;
215
+ /** The ports open now, to find who holds a full table. */
216
+ ports?(): PortInfo[];
83
217
  /** See {@link PortMuxOptions.maxMessageSize}. */
84
218
  readonly maxMessageSize?: number;
85
219
  }
@@ -1 +1 @@
1
- {"version":3,"file":"port-types.d.ts","sourceRoot":"","sources":["../src/port-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,GAC5C;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAEpD;;;;;;;GAOG;AACH,MAAM,WAAW,SAAS;IACxB,iDAAiD;IACjD,IAAI,CAAC,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,YAAY,EAAE,QAAQ,CAAC,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC;IACnF,6EAA6E;IAC7E,IAAI,CAAC,KAAK,EAAE,YAAY,GAAG,YAAY,GAAG,SAAS,CAAC;CACrD;AAED,MAAM,WAAW,cAAc;IAC7B,uDAAuD;IACvD,KAAK,EAAE,SAAS,CAAC;IACjB;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,OAAO,GAAG,SAAS,CAAC;IACtE;;;OAGG;IACH,IAAI,CAAC,EAAE,WAAW,GAAG,WAAW,CAAC;IACjC;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;;;;;;;;;OAeG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,mCAAmC;AACnC,MAAM,WAAW,OAAO;IACtB,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IACjD,kEAAkE;IAClE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,iDAAiD;IACjD,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC"}
1
+ {"version":3,"file":"port-types.d.ts","sourceRoot":"","sources":["../src/port-types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * `gen`, except that `return()` and `throw()` take effect at once.
3
+ *
4
+ * On an async generator both are queued behind a `next()` that is still
5
+ * pending: a consumer that gives up on a stream while it waits for the peer's
6
+ * next chunk — an aborted fetch whose response head has not come, say — cannot
7
+ * end it until that chunk arrives, which for a peer that never answers is
8
+ * never. The stream's `finally` (which closes its port and tells the peer)
9
+ * then never runs, and on a multiplexer the port is kept for good.
10
+ *
11
+ * So `abandon` runs first, synchronously: it must make the pending `next()`
12
+ * settle (abort the stream), which then resolves done, after which the
13
+ * generator's own `return()` / `throw()` proceeds as usual. On a generator
14
+ * suspended at a `yield` it is the same ending, a step earlier. `abandon` runs once, whichever comes first.
15
+ */
16
+ export declare function returningAtOnce<T>(gen: AsyncGenerator<T>, abandon: (reason?: unknown) => void): AsyncGenerator<T>;
17
+ //# sourceMappingURL=return-at-once.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"return-at-once.d.ts","sourceRoot":"","sources":["../src/return-at-once.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAC/B,GAAG,EAAE,cAAc,CAAC,CAAC,CAAC,EACtB,OAAO,EAAE,CAAC,MAAM,CAAC,EAAE,OAAO,KAAK,IAAI,GAClC,cAAc,CAAC,CAAC,CAAC,CA8BnB"}
@@ -1 +1 @@
1
- {"version":3,"file":"structured-codec.d.ts","sourceRoot":"","sources":["../src/structured-codec.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAgB,MAAM,iBAAiB,CAAC;AAU/D;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,EAAE,SAU7B,CAAC"}
1
+ {"version":3,"file":"structured-codec.d.ts","sourceRoot":"","sources":["../src/structured-codec.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAgB,MAAM,iBAAiB,CAAC;AAkB/D;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,EAAE,SAU7B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"transfer-port-mux.d.ts","sourceRoot":"","sources":["../src/transfer-port-mux.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE/C,8EAA8E;AAC9E,eAAO,MAAM,aAAa,6BAA6B,CAAC;AAExD,MAAM,WAAW,sBAAsB;IACrC;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,OAAO,GAAG,SAAS,CAAC;IACtE,8EAA8E;IAC9E,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,aAAa,EACrB,OAAO,GAAE,sBAA2B,GACnC,OAAO,CA8DT"}
1
+ {"version":3,"file":"transfer-port-mux.d.ts","sourceRoot":"","sources":["../src/transfer-port-mux.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAmB,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAEhE,8EAA8E;AAC9E,eAAO,MAAM,aAAa,6BAA6B,CAAC;AAExD,MAAM,WAAW,sBAAsB;IACrC;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,OAAO,GAAG,SAAS,CAAC;IACtE,8EAA8E;IAC9E,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,aAAa,EACrB,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAsFT"}
@@ -2,8 +2,12 @@ import type { MessageTarget } from "./message-target.js";
2
2
  export interface VirtualPortHandle {
3
3
  /** The consumer-facing end. Indistinguishable from a real `MessagePort`. */
4
4
  port: MessageTarget;
5
- /** Multiplexer-only: hand an inbound payload to the consumer's listeners. */
6
- deliver(payload: unknown): void;
5
+ /**
6
+ * Multiplexer-only: hand an inbound payload to the consumer's listeners.
7
+ * Returns a promise when a listener returned one (settled once they all
8
+ * have), so flow control can count the message as consumed only then.
9
+ */
10
+ deliver(payload: unknown, size?: number): Promise<unknown> | undefined;
7
11
  /**
8
12
  * Multiplexer-only: the port is finished; drop listeners and go inert.
9
13
  * `reason` becomes the reason of the port's close signal (see
@@ -11,6 +15,8 @@ export interface VirtualPortHandle {
11
15
  */
12
16
  markClosed(reason?: unknown): void;
13
17
  isClosed(): boolean;
18
+ /** Multiplexer-only: the consumer still listens, so the port is in use. */
19
+ hasListeners(): boolean;
14
20
  }
15
21
  /**
16
22
  * One virtual port.
@@ -1 +1 @@
1
- {"version":3,"file":"virtual-port.d.ts","sourceRoot":"","sources":["../src/virtual-port.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAmB,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAE1E,MAAM,WAAW,iBAAiB;IAChC,4EAA4E;IAC5E,IAAI,EAAE,aAAa,CAAC;IACpB,6EAA6E;IAC7E,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IAChC;;;;OAIG;IACH,UAAU,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACnC,QAAQ,IAAI,OAAO,CAAC;CACrB;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,YAAY,EAAE,KAAK,IAAI,EAC3D,YAAY,EAAE,CAAC,MAAM,CAAC,EAAE,OAAO,KAAK,IAAI,GACvC,iBAAiB,CA6DnB"}
1
+ {"version":3,"file":"virtual-port.d.ts","sourceRoot":"","sources":["../src/virtual-port.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAmB,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAE1E,MAAM,WAAW,iBAAiB;IAChC,4EAA4E;IAC5E,IAAI,EAAE,aAAa,CAAC;IACpB;;;;OAIG;IACH,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC;IACvE;;;;OAIG;IACH,UAAU,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACnC,QAAQ,IAAI,OAAO,CAAC;IACpB,2EAA2E;IAC3E,YAAY,IAAI,OAAO,CAAC;CACzB;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,YAAY,EAAE,KAAK,IAAI,EAC3D,YAAY,EAAE,CAAC,MAAM,CAAC,EAAE,OAAO,KAAK,IAAI,GACvC,iBAAiB,CAqFnB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statewalker/webrun-rpc",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Ports and RPC over them: a port multiplexer, and typed request/response and streaming primitives over any MessageTarget.",
@@ -30,7 +30,7 @@
30
30
  "src"
31
31
  ],
32
32
  "dependencies": {
33
- "@statewalker/webrun-streams": "^0.2.2"
33
+ "@statewalker/webrun-streams": "^0.2.3"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@biomejs/biome": "^2.5.15",
@@ -39,7 +39,7 @@
39
39
  "rolldown": "^1.2.12",
40
40
  "typescript": "^7.0.2",
41
41
  "vitest": "^5.0.3",
42
- "@statewalker/webrun-streams-conformance": "^0.3.2"
42
+ "@statewalker/webrun-streams-conformance": "^0.3.3"
43
43
  },
44
44
  "sideEffects": false,
45
45
  "publishConfig": {
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Makes `target` disposable with `await using`: installs `dispose` as its
3
+ * `[Symbol.asyncDispose]`, where the runtime defines the symbol. Hand-written
4
+ * iterators lose what a native generator gets from `AsyncIteratorPrototype`;
5
+ * this gives it back. A no-op where the symbol is missing. Kept off the
6
+ * declared types, which would require consumers to compile with the
7
+ * `esnext.disposable` lib.
8
+ */
9
+ export function asyncDisposable<T extends object>(target: T, dispose: () => Promise<unknown>): T {
10
+ // Read here, not at module level: a top-level read is code a bundler keeps.
11
+ const asyncDispose = (Symbol as { asyncDispose?: symbol }).asyncDispose;
12
+ if (asyncDispose && !(asyncDispose in target)) {
13
+ Object.defineProperty(target, asyncDispose, {
14
+ value: async () => {
15
+ await dispose();
16
+ },
17
+ configurable: true,
18
+ writable: true,
19
+ });
20
+ }
21
+ return target;
22
+ }
@@ -22,7 +22,7 @@
22
22
  */
23
23
  import type { MessageTarget } from "./message-target.js";
24
24
 
25
- const closeSignals = new WeakMap<MessageTarget, AbortSignal>();
25
+ const closeSignals = /* @__PURE__ */ new WeakMap<MessageTarget, AbortSignal>();
26
26
 
27
27
  /**
28
28
  * Internal: register a port's close signal. Called once by `bindBytesToPort`.
@@ -6,7 +6,8 @@ import {
6
6
  } from "./duplex-over-port.js";
7
7
  import type { MessageTarget } from "./message-target.js";
8
8
  import { multiplexPort } from "./multiplex-port.js";
9
- import type { PortCodec, PortMux } from "./port-types.js";
9
+ import type { PortCodec, PortMux, PortMuxOptions } from "./port-types.js";
10
+ import { returningAtOnce } from "./return-at-once.js";
10
11
 
11
12
  /**
12
13
  * Announced with every port this pair opens. Layer 1 never reads `meta` — it is
@@ -74,19 +75,34 @@ export const connect: Connect<PortParams> = async ({ mux: factory, timeout }) =>
74
75
  const mux = await factory();
75
76
  const streamOptions: DuplexOverPortOptions = { maxMessageSize: mux.maxMessageSize, timeout };
76
77
 
77
- const call: Duplex = (input) =>
78
+ const call: Duplex = (input) => {
79
+ let stream: AsyncGenerator<Uint8Array> | undefined;
80
+ let abandoned = false;
78
81
  // The port is opened on the consumer's first pull, not when `call` is
79
82
  // invoked. A caller that builds a stream and then drops it without
80
83
  // iterating therefore costs the peer nothing — under eager opening it
81
84
  // would burn a port (and over libp2p, a whole stream) for a call that
82
85
  // never arrives.
83
- (async function* () {
86
+ const outer = (async function* () {
84
87
  const port = await mux.openPort(STREAM_META);
88
+ if (abandoned) {
89
+ // Given up while the port was being opened.
90
+ await port.close?.();
91
+ return;
92
+ }
93
+ stream = duplexOverPort(port, streamOptions)(input);
85
94
  // `yield*` and not a hand-rolled pump: delegation already forwards
86
95
  // `return()` and `throw()` into `duplexOverPort`'s generator, which is
87
96
  // exactly the cancellation path `Duplex` specifies.
88
- yield* duplexOverPort(port, streamOptions)(input);
97
+ yield* stream;
89
98
  })();
99
+ // Delegation forwards them only once the pending `next()` settles, though:
100
+ // giving up while the peer has not answered must reach the stream now.
101
+ return returningAtOnce(outer, () => {
102
+ abandoned = true;
103
+ void stream?.return(undefined).catch(() => {});
104
+ });
105
+ };
90
106
 
91
107
  return {
92
108
  call,
@@ -179,6 +195,16 @@ export interface OverPipeOptions {
179
195
  * transport's real ceiling — see `PortMuxOptions.maxMessageSize`.
180
196
  */
181
197
  maxMessageSize?: number;
198
+ /** The pipe's life: aborting it closes the mux. See `PortMuxOptions.signal`. */
199
+ signal?: AbortSignal;
200
+ /** Experimental per-port flow control in bytes — see `PortMuxOptions.window`. */
201
+ window?: number;
202
+ /** Fair framing in bytes — see `PortMuxOptions.frameSize`. */
203
+ frameSize?: number;
204
+ /** Liveness of the peer by ping/pong — see `PortMuxOptions.keepalive`. */
205
+ keepalive?: { intervalMs: number; misses?: number };
206
+ /** Development aid: report ports dropped without `close()` — see `PortMuxOptions.onLeak`. */
207
+ onLeak?: PortMuxOptions["onLeak"];
182
208
  }
183
209
 
184
210
  /**
@@ -12,6 +12,7 @@ import { callPort, NO_TIMEOUT } from "./call-port.js";
12
12
  import { getPortCloseSignal } from "./close-signal.js";
13
13
  import { listenPort } from "./listen-port.js";
14
14
  import type { MessageTarget } from "./message-target.js";
15
+ import { returningAtOnce } from "./return-at-once.js";
15
16
  import { throughAbort } from "./through-abort.js";
16
17
 
17
18
  /**
@@ -177,7 +178,7 @@ function runCallerSide(
177
178
  // the consumer's contract is the inbound half.
178
179
  });
179
180
 
180
- return (async function* () {
181
+ const stream = (async function* () {
181
182
  try {
182
183
  yield* inbound.stream;
183
184
  } finally {
@@ -200,6 +201,13 @@ function runCallerSide(
200
201
  }
201
202
  }
202
203
  })();
204
+ // A caller that gives up while it waits for the peer's next chunk ends the
205
+ // stream now: the abort settles that wait, and the `finally` above runs.
206
+ return returningAtOnce(stream, () => {
207
+ if (!controller.signal.aborted) {
208
+ controller.abort(new Error("webrun-rpc: the caller abandoned the stream"));
209
+ }
210
+ });
203
211
  }
204
212
 
205
213
  /**
package/src/index.ts CHANGED
@@ -22,7 +22,15 @@ export * from "./listen-bidi.js";
22
22
  export * from "./listen-port.js";
23
23
  export * from "./message-target.js";
24
24
  export { DEFAULT_MAX_PORTS, MUX_CLOSED_MESSAGE, multiplexPort } from "./multiplex-port.js";
25
- export type { PortCodec, PortEnvelope, PortMux, PortMuxOptions } from "./port-types.js";
25
+ export { type PortSendState, portBufferedAmount, whenPortReady } from "./port-credit.js";
26
+ export type {
27
+ OpenPortOptions,
28
+ PortCodec,
29
+ PortEnvelope,
30
+ PortInfo,
31
+ PortMux,
32
+ PortMuxOptions,
33
+ } from "./port-types.js";
26
34
  export * from "./recieve.js";
27
35
  export * from "./send.js";
28
36
  export { structuredCodec } from "./structured-codec.js";
@@ -42,6 +42,13 @@ export function listenPort<TParams = unknown, TResult = unknown>(
42
42
  }
43
43
  port.postMessage({ callId, channelName, type, result, error });
44
44
  };
45
- port.addEventListener("message", onMessage);
46
- return () => port.removeEventListener("message", onMessage);
45
+ // A request counts as consumed (for a port's window) once it is handed to its
46
+ // handler, not when the handler finishes: a handler that waits for a later
47
+ // call on the same port (long polls, subscriptions) must not hold the window
48
+ // that later call needs.
49
+ const listener = (event: MessageEvent): void => {
50
+ void onMessage(event);
51
+ };
52
+ port.addEventListener("message", listener);
53
+ return () => port.removeEventListener("message", listener);
47
54
  }
@@ -10,9 +10,22 @@ export interface MessageSource {
10
10
  /** An object we can post messages to (with optional transferable list). */
11
11
  export interface MessageSink {
12
12
  postMessage(message: unknown, transfer?: Transferable[]): void;
13
+ /**
14
+ * Optional: bytes posted and not yet written out, as a WebSocket's or an
15
+ * `RTCDataChannel`'s `bufferedAmount`. Informational.
16
+ */
17
+ readonly bufferedAmount?: number;
18
+ /**
19
+ * Optional: resolves once the pipe can take more, at once when it can now.
20
+ * A multiplexer with `frameSize` waits on it between frames, which is what
21
+ * lets frames of different ports take turns instead of piling up in the
22
+ * pipe's own buffer.
23
+ */
24
+ whenDrained?(): Promise<void>;
13
25
  }
14
26
 
15
27
  /** Full-duplex message target: both sends and receives. */
16
28
  export interface MessageTarget extends MessageSource, MessageSink {
17
- close?(): void | Promise<void>;
29
+ /** `reason`, where the target keeps one, becomes its close signal's reason. */
30
+ close?(reason?: unknown): void | Promise<void>;
18
31
  }