@statewalker/webrun-rpc 0.4.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/README.md +549 -0
- package/dist/byte-channel.d.ts +15 -0
- package/dist/byte-channel.d.ts.map +1 -0
- package/dist/call-bidi.d.ts +25 -0
- package/dist/call-bidi.d.ts.map +1 -0
- package/dist/call-port.d.ts +37 -0
- package/dist/call-port.d.ts.map +1 -0
- package/dist/cancel-channel.d.ts +15 -0
- package/dist/cancel-channel.d.ts.map +1 -0
- package/dist/close-signal.d.ts +36 -0
- package/dist/close-signal.d.ts.map +1 -0
- package/dist/connect-serve.d.ts +104 -0
- package/dist/connect-serve.d.ts.map +1 -0
- package/dist/duplex-over-port.d.ts +49 -0
- package/dist/duplex-over-port.d.ts.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1180 -0
- package/dist/io-handle.d.ts +17 -0
- package/dist/io-handle.d.ts.map +1 -0
- package/dist/io-send.d.ts +26 -0
- package/dist/io-send.d.ts.map +1 -0
- package/dist/listen-bidi.d.ts +11 -0
- package/dist/listen-bidi.d.ts.map +1 -0
- package/dist/listen-port.d.ts +16 -0
- package/dist/listen-port.d.ts.map +1 -0
- package/dist/message-target.d.ts +16 -0
- package/dist/message-target.d.ts.map +1 -0
- package/dist/multiplex-port.d.ts +12 -0
- package/dist/multiplex-port.d.ts.map +1 -0
- package/dist/port-types.d.ts +86 -0
- package/dist/port-types.d.ts.map +1 -0
- package/dist/recieve.d.ts +35 -0
- package/dist/recieve.d.ts.map +1 -0
- package/dist/send.d.ts +23 -0
- package/dist/send.d.ts.map +1 -0
- package/dist/structured-codec.d.ts +12 -0
- package/dist/structured-codec.d.ts.map +1 -0
- package/dist/through-abort.d.ts +8 -0
- package/dist/through-abort.d.ts.map +1 -0
- package/dist/transfer-port-mux.d.ts +39 -0
- package/dist/transfer-port-mux.d.ts.map +1 -0
- package/dist/virtual-port.d.ts +19 -0
- package/dist/virtual-port.d.ts.map +1 -0
- package/package.json +51 -0
- package/src/byte-channel.ts +109 -0
- package/src/call-bidi.ts +60 -0
- package/src/call-port.ts +119 -0
- package/src/cancel-channel.ts +42 -0
- package/src/close-signal.ts +43 -0
- package/src/connect-serve.ts +208 -0
- package/src/duplex-over-port.ts +471 -0
- package/src/index.ts +29 -0
- package/src/io-handle.ts +40 -0
- package/src/io-send.ts +70 -0
- package/src/listen-bidi.ts +31 -0
- package/src/listen-port.ts +47 -0
- package/src/message-target.ts +18 -0
- package/src/multiplex-port.ts +134 -0
- package/src/port-types.ts +80 -0
- package/src/recieve.ts +89 -0
- package/src/send.ts +60 -0
- package/src/structured-codec.ts +30 -0
- package/src/through-abort.ts +32 -0
- package/src/transfer-port-mux.ts +106 -0
- package/src/virtual-port.ts +71 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-port AbortSignal that fires when the underlying transport adapter
|
|
3
|
+
* (libp2p stream, LiveKit participant, WebSocket, …) detects closure.
|
|
4
|
+
*
|
|
5
|
+
* Native `MessagePort` does NOT surface close events to either entangled
|
|
6
|
+
* half — neither a `close` event, nor a `messageerror`, nor any signal at
|
|
7
|
+
* all. Closing one half just silently stops message delivery on the other.
|
|
8
|
+
* A WHATWG proposal exists (https://github.com/whatwg/html/issues/1766) but
|
|
9
|
+
* no browser ships it.
|
|
10
|
+
*
|
|
11
|
+
* This module is the work-around: `bindBytesToPort` creates an
|
|
12
|
+
* `AbortController` per port and aborts it when `transport.onClose` fires.
|
|
13
|
+
* Consumers (`callPort`, `recieve`, `send`, application code) look up the
|
|
14
|
+
* signal via `getPortCloseSignal(port)` and combine it into their own
|
|
15
|
+
* abort plumbing — typically with `AbortSignal.any([userSignal, closeSignal])`
|
|
16
|
+
* — so pending awaits reject cleanly instead of hanging until a per-call
|
|
17
|
+
* timeout fires.
|
|
18
|
+
*
|
|
19
|
+
* Ports created via raw `new MessageChannel()` (i.e., without going through
|
|
20
|
+
* `bindBytesToPort`) return `undefined` from `getPortCloseSignal` — there's
|
|
21
|
+
* no underlying transport so there's nothing to track.
|
|
22
|
+
*/
|
|
23
|
+
import type { MessageTarget } from "./message-target.js";
|
|
24
|
+
/**
|
|
25
|
+
* Internal: register a port's close signal. Called once by `bindBytesToPort`.
|
|
26
|
+
*
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
export declare function setPortCloseSignal(port: MessageTarget, signal: AbortSignal): void;
|
|
30
|
+
/**
|
|
31
|
+
* Return the AbortSignal that fires when `port`'s transport closes, or
|
|
32
|
+
* `undefined` if `port` is not transport-backed (e.g., a raw
|
|
33
|
+
* `MessageChannel().port1`).
|
|
34
|
+
*/
|
|
35
|
+
export declare function getPortCloseSignal(port: MessageTarget): AbortSignal | undefined;
|
|
36
|
+
//# sourceMappingURL=close-signal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"close-signal.d.ts","sourceRoot":"","sources":["../src/close-signal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAIzD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI,CAEjF;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,aAAa,GAAG,WAAW,GAAG,SAAS,CAE/E"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { Connect, Serve } from "@statewalker/webrun-streams";
|
|
2
|
+
import type { MessageTarget } from "./message-target.js";
|
|
3
|
+
import type { PortCodec, PortMux } from "./port-types.js";
|
|
4
|
+
/** What a mux calls when the peer opens a port. `false` rejects it. */
|
|
5
|
+
export type OnPort = (port: MessageTarget, meta?: unknown) => boolean | undefined;
|
|
6
|
+
/**
|
|
7
|
+
* A source of ports, built around the handler that will answer them.
|
|
8
|
+
*
|
|
9
|
+
* WHY A FACTORY AND NOT A `PortMux`. Every mux decides accept-or-reject
|
|
10
|
+
* **synchronously**, inside the `open` envelope, and tells the peer on the
|
|
11
|
+
* spot — `multiplexPort` posts `{type:"close", reason:"rejected"}` before
|
|
12
|
+
* returning. There is no "decide later", and layer 1 refuses to queue what it
|
|
13
|
+
* cannot deliver ("Drop, never queue").
|
|
14
|
+
*
|
|
15
|
+
* So a mux handed over already built has a window between its construction and
|
|
16
|
+
* its handler being attached, and a port arriving in that window is rejected
|
|
17
|
+
* outright — measured: zero messages delivered, and the peer told `rejected`
|
|
18
|
+
* for a call that was merely early. A factory closes the window by
|
|
19
|
+
* construction: the mux cannot exist before the thing that answers it.
|
|
20
|
+
*
|
|
21
|
+
* `connect` passes no handler, which is how a caller declines inbound ports.
|
|
22
|
+
* The same factory therefore serves both sides, and whichever of them called
|
|
23
|
+
* it owns the mux and closes it.
|
|
24
|
+
*/
|
|
25
|
+
export type PortMuxFactory = (onPort?: OnPort) => PortMux | Promise<PortMux>;
|
|
26
|
+
export interface PortParams {
|
|
27
|
+
/**
|
|
28
|
+
* Where ports come from. See {@link PortMuxFactory}, and `overPipe` /
|
|
29
|
+
* `overPorts` for the two this package ships.
|
|
30
|
+
*
|
|
31
|
+
* This used to be a `MessageTarget` plus `side` and a credit window, which
|
|
32
|
+
* forced one strategy — an id table — on every transport. A libp2p
|
|
33
|
+
* connection multiplexes already, so that stacked two multiplexers with no
|
|
34
|
+
* way to tell which one stalled; a transferable boundary moves real ports
|
|
35
|
+
* and needs no table at all. The strategy belongs to whoever knows the
|
|
36
|
+
* transport, which is never this file.
|
|
37
|
+
*/
|
|
38
|
+
mux: PortMuxFactory;
|
|
39
|
+
/** Per-stream inactivity timeout in ms, reset by any chunk in either direction.
|
|
40
|
+
*
|
|
41
|
+
* Unset — the default — means no timeout at all: a slow consumer is
|
|
42
|
+
* throttled, never failed. Set it on the side that must not hang. */
|
|
43
|
+
timeout?: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* One port source in, one caller `Duplex` out.
|
|
47
|
+
*
|
|
48
|
+
* A call is a port: the mux allocates one, `duplexOverPort` runs the single
|
|
49
|
+
* invocation on it, and closing the stream closes the port. No stream ids, no
|
|
50
|
+
* framing and no credit accounting live here — the mux owns the first, and
|
|
51
|
+
* `duplexOverPort` owns the one-chunk window that makes memory bounded.
|
|
52
|
+
*/
|
|
53
|
+
export declare const connect: Connect<PortParams>;
|
|
54
|
+
/**
|
|
55
|
+
* One port source in, `handler` serving every call that arrives on it.
|
|
56
|
+
*
|
|
57
|
+
* Each inbound port is one invocation, so the factory's `onPort` is the accept
|
|
58
|
+
* loop: it installs `handler` on the new port and nothing else. The returned
|
|
59
|
+
* teardown abandons the streams still running *before* dropping the mux, so
|
|
60
|
+
* the peer's callers reject with "the peer abandoned the stream" instead of
|
|
61
|
+
* parking forever on a port that has silently gone inert — layer 1's close is
|
|
62
|
+
* not observable to layer 2, which is why the notice has to be posted
|
|
63
|
+
* deliberately.
|
|
64
|
+
*/
|
|
65
|
+
export declare const serve: Serve<PortParams>;
|
|
66
|
+
export interface OverPipeOptions {
|
|
67
|
+
/** How envelopes are placed on the pipe. `structuredCodec` for a `MessagePort`. */
|
|
68
|
+
codec: PortCodec;
|
|
69
|
+
/**
|
|
70
|
+
* Id parity. The initiator allocates even ids, the responder odd, so both
|
|
71
|
+
* ends may open concurrently with no negotiation. The two ends of one pair
|
|
72
|
+
* must disagree, or their ids collide.
|
|
73
|
+
*/
|
|
74
|
+
side?: "initiator" | "responder";
|
|
75
|
+
/** Ceiling on concurrently open ports. Bounds the id table only. */
|
|
76
|
+
maxPorts?: number;
|
|
77
|
+
/**
|
|
78
|
+
* Largest payload one message may carry, if the pipe imposes a limit.
|
|
79
|
+
* Bodies are split to fit. Leave at least 256 bytes of margin below the
|
|
80
|
+
* transport's real ceiling — see `PortMuxOptions.maxMessageSize`.
|
|
81
|
+
*/
|
|
82
|
+
maxMessageSize?: number;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Ports over ONE PIPE OF BYTES — a `MessagePort`, a worker, a WebSocket.
|
|
86
|
+
*
|
|
87
|
+
* There is no second port to be had, so `multiplexPort`'s id table invents
|
|
88
|
+
* them. Use this when the transport gives you exactly one channel.
|
|
89
|
+
*/
|
|
90
|
+
export declare function overPipe(pipe: MessageTarget, options: OverPipeOptions): PortMuxFactory;
|
|
91
|
+
/**
|
|
92
|
+
* Ports from a source that already has them — a transferable boundary, or a
|
|
93
|
+
* transport that multiplexes on its own (libp2p's yamux, say).
|
|
94
|
+
*
|
|
95
|
+
* A pass-through, so an adapter that builds its own `PortMux` plugs in without
|
|
96
|
+
* this package learning what that transport is. It exists to make the
|
|
97
|
+
* three-way choice legible at the call site rather than to do work:
|
|
98
|
+
*
|
|
99
|
+
* ```ts
|
|
100
|
+
* await serve({ mux: overPorts((onPort) => libp2pPortMux({ node, onPort })) }, handler);
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
export declare function overPorts(factory: PortMuxFactory): PortMuxFactory;
|
|
104
|
+
//# sourceMappingURL=connect-serve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connect-serve.d.ts","sourceRoot":"","sources":["../src/connect-serve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAU,KAAK,EAAE,MAAM,6BAA6B,CAAC;AAM1E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAU1D,uEAAuE;AACvE,MAAM,MAAM,MAAM,GAAG,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,OAAO,GAAG,SAAS,CAAC;AAElF;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,MAAM,CAAC,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAE7E,MAAM,WAAW,UAAU;IACzB;;;;;;;;;;OAUG;IACH,GAAG,EAAE,cAAc,CAAC;IACpB;;;yEAGqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,OAAO,EAAE,OAAO,CAAC,UAAU,CA0BvC,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,KAAK,EAAE,KAAK,CAAC,UAAU,CAqDnC,CAAC;AAEF,MAAM,WAAW,eAAe;IAC9B,mFAAmF;IACnF,KAAK,EAAE,SAAS,CAAC;IACjB;;;;OAIG;IACH,IAAI,CAAC,EAAE,WAAW,GAAG,WAAW,CAAC;IACjC,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,GAAG,cAAc,CAEtF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,cAAc,GAAG,cAAc,CAEjE"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type Duplex } from "@statewalker/webrun-streams";
|
|
2
|
+
import type { MessageTarget } from "./message-target.js";
|
|
3
|
+
/**
|
|
4
|
+
* The `type` of the out-of-band notice a side posts when it abandons a stream.
|
|
5
|
+
*
|
|
6
|
+
* Layer 1's `close` is not observable to layer 2 — a closed virtual port drops
|
|
7
|
+
* its listeners silently and is indistinguishable from a working port nobody
|
|
8
|
+
* is answering — so the peer would otherwise wait forever. Exported because
|
|
9
|
+
* tests and adapters assert on it.
|
|
10
|
+
*/
|
|
11
|
+
export declare const STREAM_ABORT = "webrun-rpc:stream-abort";
|
|
12
|
+
export interface DuplexOverPortOptions {
|
|
13
|
+
/**
|
|
14
|
+
* Largest payload one chunk may carry, from `PortMux.maxMessageSize` (spec
|
|
15
|
+
* D10). Bodies are split to fit with `toChunks`. Unset means no limit and no
|
|
16
|
+
* splitting.
|
|
17
|
+
*/
|
|
18
|
+
maxMessageSize?: number;
|
|
19
|
+
/**
|
|
20
|
+
* Inactivity timeout for the whole stream, in ms: the clock is reset by any
|
|
21
|
+
* chunk in either direction, and elapsing aborts the stream. Unset — the
|
|
22
|
+
* default — means no timeout at all (spec D8): a slow consumer is throttled,
|
|
23
|
+
* never failed. Any finite default would reintroduce F5 at a different
|
|
24
|
+
* threshold.
|
|
25
|
+
*/
|
|
26
|
+
timeout?: number;
|
|
27
|
+
/** Logging function; defaults to a no-op. */
|
|
28
|
+
log?: (...args: unknown[]) => void;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* One port in, one `Duplex` out (spec D9).
|
|
32
|
+
*
|
|
33
|
+
* The returned `Duplex` runs a single stream on `port`: the caller's `input`
|
|
34
|
+
* is sent chunk by chunk with `callPort`, and the handler's output arrives the
|
|
35
|
+
* same way on the other channel. Within each direction the next chunk is never
|
|
36
|
+
* sent until the previous one has been delivered *and* pulled past by the
|
|
37
|
+
* consumer (spec D11) — the reply to a chunk call *is* the confirmation, and
|
|
38
|
+
* `listenPort` withholds it until then.
|
|
39
|
+
*
|
|
40
|
+
* A stream port carries exactly one invocation. To make several calls, open
|
|
41
|
+
* several ports: `mux.openPort({ kind: "stream" })` per call.
|
|
42
|
+
*/
|
|
43
|
+
export declare function duplexOverPort(port: MessageTarget, options?: DuplexOverPortOptions): Duplex;
|
|
44
|
+
/**
|
|
45
|
+
* Installs `handler` as the serving side of one stream on `port`. Returns an
|
|
46
|
+
* idempotent teardown that abandons the stream and notifies the peer.
|
|
47
|
+
*/
|
|
48
|
+
export declare function serveDuplexOverPort(port: MessageTarget, handler: Duplex, options?: DuplexOverPortOptions): () => void;
|
|
49
|
+
//# sourceMappingURL=duplex-over-port.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duplex-over-port.d.ts","sourceRoot":"","sources":["../src/duplex-over-port.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,MAAM,EAOZ,MAAM,6BAA6B,CAAC;AAGrC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGzD;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,4BAA4B,CAAC;AActD,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;CACpC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,GAAE,qBAA0B,GAAG,MAAM,CAE/F;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,qBAA0B,GAClC,MAAM,IAAI,CA6BZ"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export { byteChannelFromMessagePort } from "./byte-channel.js";
|
|
2
|
+
export * from "./call-bidi.js";
|
|
3
|
+
export * from "./call-port.js";
|
|
4
|
+
export * from "./cancel-channel.js";
|
|
5
|
+
export { getPortCloseSignal, setPortCloseSignal } from "./close-signal.js";
|
|
6
|
+
export { connect, type OnPort, type OverPipeOptions, overPipe, overPorts, type PortMuxFactory, type PortParams, serve, } from "./connect-serve.js";
|
|
7
|
+
export * from "./duplex-over-port.js";
|
|
8
|
+
export * from "./io-handle.js";
|
|
9
|
+
export * from "./io-send.js";
|
|
10
|
+
export * from "./listen-bidi.js";
|
|
11
|
+
export * from "./listen-port.js";
|
|
12
|
+
export * from "./message-target.js";
|
|
13
|
+
export { DEFAULT_MAX_PORTS, multiplexPort } from "./multiplex-port.js";
|
|
14
|
+
export type { PortCodec, PortEnvelope, PortMux, PortMuxOptions } from "./port-types.js";
|
|
15
|
+
export * from "./recieve.js";
|
|
16
|
+
export * from "./send.js";
|
|
17
|
+
export { structuredCodec } from "./structured-codec.js";
|
|
18
|
+
export * from "./transfer-port-mux.js";
|
|
19
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAC/D,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC3E,OAAO,EACL,OAAO,EACP,KAAK,MAAM,EACX,KAAK,eAAe,EACpB,QAAQ,EACR,SAAS,EACT,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,GACN,MAAM,oBAAoB,CAAC;AAC5B,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACvE,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACxF,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,cAAc,wBAAwB,CAAC"}
|