@statewalker/webrun-streams 0.1.1 → 0.2.1
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 +88 -9
- package/dist/collect.d.ts +7 -0
- package/dist/collect.d.ts.map +1 -0
- package/dist/duplex.d.ts +52 -0
- package/dist/duplex.d.ts.map +1 -0
- package/dist/emulate-mux.d.ts +39 -0
- package/dist/emulate-mux.d.ts.map +1 -0
- package/dist/errors.d.ts +8 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/flow-control.d.ts +71 -0
- package/dist/flow-control.d.ts.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/{index.mjs → index.js} +200 -52
- package/dist/jsonl.d.ts +5 -0
- package/dist/jsonl.d.ts.map +1 -0
- package/dist/lines.d.ts +5 -0
- package/dist/lines.d.ts.map +1 -0
- package/dist/map.d.ts +3 -0
- package/dist/map.d.ts.map +1 -0
- package/dist/new-async-generator.d.ts +70 -0
- package/dist/new-async-generator.d.ts.map +1 -0
- package/dist/normalize.d.ts +8 -0
- package/dist/normalize.d.ts.map +1 -0
- package/dist/readable-streams.d.ts +13 -0
- package/dist/readable-streams.d.ts.map +1 -0
- package/dist/recieve-iterator.d.ts +14 -0
- package/dist/recieve-iterator.d.ts.map +1 -0
- package/dist/send-iterator.d.ts +15 -0
- package/dist/send-iterator.d.ts.map +1 -0
- package/dist/text.d.ts +5 -0
- package/dist/text.d.ts.map +1 -0
- package/dist/to-chunks.d.ts +11 -0
- package/dist/to-chunks.d.ts.map +1 -0
- package/dist/uint32.d.ts +25 -0
- package/dist/uint32.d.ts.map +1 -0
- package/package.json +14 -7
- package/src/duplex.ts +59 -0
- package/src/emulate-mux.ts +100 -85
- package/src/flow-control.ts +146 -0
- package/src/index.ts +2 -0
- package/src/readable-streams.ts +49 -13
- package/src/uint32.ts +34 -0
- package/dist/index.d.mts +0 -244
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -15,7 +15,17 @@ Every higher-level package in the `webrun-*` family (and its consumers — scann
|
|
|
15
15
|
5. **Error (de)serialisation** for passing exceptions across structured-clone / JSON boundaries without losing stacks or extra fields.
|
|
16
16
|
6. **Line / JSONL / text codecs** so stream-processing code doesn't re-invent split/join/encode/decode in every consumer.
|
|
17
17
|
|
|
18
|
-
The MessagePack codec that previously rode along here is split out to [`@statewalker/webrun-msgpack`](../webrun-msgpack) so consumers that don't need framing don't pull in
|
|
18
|
+
The MessagePack codec that previously rode along here is split out to [`@statewalker/webrun-msgpack`](../webrun-msgpack) so consumers that don't need framing don't pull in a MessagePack implementation.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
npm install @statewalker/webrun-streams
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Zero runtime dependencies, zero peer dependencies. ESM only
|
|
27
|
+
(`"type": "module"`); runs in browsers, Node, Deno, Bun and Workers. This is the
|
|
28
|
+
foundation package — everything else in the workspace depends on it.
|
|
19
29
|
|
|
20
30
|
## How to use
|
|
21
31
|
|
|
@@ -239,14 +249,18 @@ const stop = serve(async function* handler(input) {
|
|
|
239
249
|
| `side` | `"initiator"` | Id allocation: initiator uses even ids, responder odd. Pick one per peer so they cannot collide. |
|
|
240
250
|
| `maxStreams` | `256` | Concurrent streams before new calls are refused. |
|
|
241
251
|
| `mtu` | `65536` | Largest payload per DATA frame; bigger chunks are split. |
|
|
242
|
-
| `maxStreamBuffer` | `8388608` |
|
|
252
|
+
| `maxStreamBuffer` | `8388608` | The credit this side advertises to the peer, in bytes, and the hard cap on inbound bytes one stream may hold undrained. A peer that honours credit never reaches the cap; one that ignores it has that stream torn down. |
|
|
243
253
|
|
|
244
254
|
### Flow control
|
|
245
255
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
stream
|
|
256
|
+
Receiver-advertised credit. Each side puts its `maxStreamBuffer` in the frame it
|
|
257
|
+
opens with — `OPEN` for the caller, the `ACK` answering it for the responder —
|
|
258
|
+
and the sender may only send what it has been granted. Both sides start at zero,
|
|
259
|
+
so a caller pays one round trip per stream before its first DATA frame and none
|
|
260
|
+
thereafter. The receiver grants more once its consumer has actually drained,
|
|
261
|
+
batched at half the window and flushed as soon as its queue empties. A sender
|
|
262
|
+
therefore cannot overrun the receiver's buffer, and `maxStreamBuffer` bounds only
|
|
263
|
+
peers that ignore the protocol.
|
|
250
264
|
|
|
251
265
|
Backpressure is **per-stream**, so a stalled stream does not block the others,
|
|
252
266
|
and it applies symmetrically in both directions.
|
|
@@ -254,6 +268,10 @@ and it applies symmetrically in both directions.
|
|
|
254
268
|
There is deliberately **no stall timeout**: a peer that never acknowledges
|
|
255
269
|
blocks that producer indefinitely, exactly as a TCP receiver that never reads
|
|
256
270
|
blocks its sender. `maxStreams` and `maxStreamBuffer` bound what that can cost.
|
|
271
|
+
The bound is per stream and there is no mux-wide budget, so the worst case is
|
|
272
|
+
`maxStreams × maxStreamBuffer` — 2 GiB at the defaults, against 16 MiB under the
|
|
273
|
+
one-frame-in-flight rule this replaced. Lower `maxStreamBuffer` if that matters
|
|
274
|
+
more than throughput.
|
|
257
275
|
|
|
258
276
|
### Behaviour on hostile input
|
|
259
277
|
|
|
@@ -263,6 +281,67 @@ rather than failing the connection — otherwise one malformed frame would tear
|
|
|
263
281
|
down every stream sharing the mux. A stream that exceeds `maxStreamBuffer` is
|
|
264
282
|
torn down on its own, with an error frame sent to the peer.
|
|
265
283
|
|
|
284
|
+
## Exports
|
|
285
|
+
|
|
286
|
+
Everything is exported from the package root.
|
|
287
|
+
|
|
288
|
+
### Seam types
|
|
289
|
+
|
|
290
|
+
| Export | Kind | Purpose |
|
|
291
|
+
| --- | --- | --- |
|
|
292
|
+
| `Duplex` | type | `(input) => AsyncGenerator<Uint8Array>` — one logical call. |
|
|
293
|
+
| `Connect<P>` | type | `(params) => Promise<{ call: Duplex; close() }>`. |
|
|
294
|
+
| `Serve<P>` | type | `(params, handler) => Promise<() => Promise<void>>`. |
|
|
295
|
+
| `ByteChannel` | type | `{ send, recv, closed, close }` — the minimum a transport must expose. |
|
|
296
|
+
| `ByteLike` | type | Accepted byte inputs before normalisation. |
|
|
297
|
+
| `emulateMux(channel, opts?)` | function | Multi-stream over a single `ByteChannel`. |
|
|
298
|
+
| `EmulateMuxOptions` | type | `maxStreams` (256), `mtu` (64 KiB), `maxStreamBuffer` (8 MiB), `side`. |
|
|
299
|
+
| `TransportClosedError` | class | Thrown when the transport closes with calls in flight. Catch via `instanceof` or `error.name`. |
|
|
300
|
+
|
|
301
|
+
The port multiplexing layer — `multiplexPort`, `PortMux`, `structuredCodec`,
|
|
302
|
+
`PortEnvelope` and the `MessageTarget` family of types — lives in
|
|
303
|
+
[`@statewalker/webrun-rpc`](../webrun-rpc#port-multiplexing), not here.
|
|
304
|
+
|
|
305
|
+
### Credit
|
|
306
|
+
|
|
307
|
+
| Export | Kind | Purpose |
|
|
308
|
+
| --- | --- | --- |
|
|
309
|
+
| `newCreditLedger(initial?)` | function | Sender-side credit: `reserve(upTo)` waits for any credit and returns how much it got (`upTo` must be >= 1; less rejects with a `RangeError`), `grant(units)` releases waiters in order, `fail(err)` unwinds them. Starts at zero unless told otherwise. |
|
|
310
|
+
| `CreditLedger` | type | `{ available, reserve, grant, fail }`. |
|
|
311
|
+
| `newCreditGrantor(window, threshold?)` | function | Receiver-side: `consumed(units, queueEmpty)` returns the credit to hand back, batched at `threshold` (default half the window) and flushed once the queue empties. |
|
|
312
|
+
| `CreditGrantor` | type | `{ consumed(units, queueEmpty): number }`. |
|
|
313
|
+
|
|
314
|
+
The unit is whatever the caller counts. `emulateMux` counts bytes; a value-
|
|
315
|
+
oriented caller would count values. Nothing in this module interprets it.
|
|
316
|
+
|
|
317
|
+
### Collectors and codecs
|
|
318
|
+
|
|
319
|
+
| Export | Purpose |
|
|
320
|
+
| --- | --- |
|
|
321
|
+
| `collect` / `collectBytes` / `collectString` | Drain an async iterable to an array / `Uint8Array` / `string`. |
|
|
322
|
+
| `encodeText` / `decodeText` | UTF-8 `string` ↔ `Uint8Array` streams. |
|
|
323
|
+
| `splitLines` / `joinLines` | Cross-chunk-safe line splitting and rejoining. |
|
|
324
|
+
| `encodeJsonl` / `decodeJsonl` | JSON values ↔ `\n`-delimited string stream. |
|
|
325
|
+
| `map` | Stream-map over an `AsyncIterable<T>`. |
|
|
326
|
+
| `toChunks` / `normalizeToUint8Array` | Coerce assorted byte-ish inputs into `Uint8Array` chunks. |
|
|
327
|
+
|
|
328
|
+
### Iterator plumbing
|
|
329
|
+
|
|
330
|
+
| Export | Purpose |
|
|
331
|
+
| --- | --- |
|
|
332
|
+
| `newAsyncGenerator` | Backpressure-aware queue turning `next`/`done` callbacks into an async generator. |
|
|
333
|
+
| `sendIterator` / `recieveIterator` | Ship an async iterator across any transport. |
|
|
334
|
+
| `IteratorChunk` | type — the `{ done, value, error }` chunk envelope they exchange. |
|
|
335
|
+
| `ChunkSender` / `ChunkReceiver` / `ReceiverInstaller` | types — the transport-side callbacks those two are wired to. |
|
|
336
|
+
| `toReadableStream` / `fromReadableStream` | `AsyncIterator<Uint8Array>` ↔ WHATWG `ReadableStream<Uint8Array>`. |
|
|
337
|
+
|
|
338
|
+
### Errors
|
|
339
|
+
|
|
340
|
+
| Export | Purpose |
|
|
341
|
+
| --- | --- |
|
|
342
|
+
| `serializeError` / `deserializeError` | Preserve `message`, `stack` and custom fields across JSON / structured-clone boundaries. |
|
|
343
|
+
| `SerializedError` | type — the wire shape those two produce and consume. |
|
|
344
|
+
|
|
266
345
|
## Internals
|
|
267
346
|
|
|
268
347
|
### `newAsyncGenerator` — backpressure queue
|
|
@@ -332,17 +411,17 @@ strict one-way converters: no queuing strategy tricks, no transform.
|
|
|
332
411
|
|
|
333
412
|
**Zero runtime dependencies.**
|
|
334
413
|
|
|
335
|
-
Dev: TypeScript, vitest,
|
|
414
|
+
Dev: TypeScript, vitest, rolldown, rimraf, `@types/node`
|
|
336
415
|
(catalog versions from the monorepo root).
|
|
337
416
|
|
|
338
417
|
## Scripts
|
|
339
418
|
|
|
340
419
|
```sh
|
|
341
420
|
pnpm test # vitest run
|
|
342
|
-
pnpm run build #
|
|
421
|
+
pnpm run build # rolldown + tsc --emitDeclarationOnly (ships src + dist)
|
|
343
422
|
pnpm lint # biome check
|
|
344
423
|
```
|
|
345
424
|
|
|
346
425
|
## License
|
|
347
426
|
|
|
348
|
-
MIT © statewalker
|
|
427
|
+
MIT © statewalker — see [LICENSE](../../LICENSE).
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Collect all items from an async iterable into an array. */
|
|
2
|
+
export declare function collect<T>(input: AsyncIterable<T>): Promise<T[]>;
|
|
3
|
+
/** Concatenate all Uint8Array chunks into a single Uint8Array. */
|
|
4
|
+
export declare function collectBytes(input: AsyncIterable<Uint8Array>): Promise<Uint8Array>;
|
|
5
|
+
/** Concatenate all string chunks into a single string. */
|
|
6
|
+
export declare function collectString(input: AsyncIterable<string>): Promise<string>;
|
|
7
|
+
//# sourceMappingURL=collect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collect.d.ts","sourceRoot":"","sources":["../src/collect.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,wBAAsB,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAItE;AAED,kEAAkE;AAClE,wBAAsB,YAAY,CAAC,KAAK,EAAE,aAAa,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,CAexF;AAED,0DAA0D;AAC1D,wBAAsB,aAAa,CAAC,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAIjF"}
|
package/dist/duplex.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The transport seam every `webrun-streams-*` adapter and the conformance suite
|
|
3
|
+
* are written against.
|
|
4
|
+
*
|
|
5
|
+
* These types live here rather than beside an implementation on purpose: the
|
|
6
|
+
* emulated multiplexer that once declared them is scheduled for deletion, and a
|
|
7
|
+
* seam that disappears with its first implementation is not a seam.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Canonical seam for the webrun-streams transport family. A `Duplex` carries
|
|
11
|
+
* one logical call: caller emits an iterable of bytes as input, peer yields an
|
|
12
|
+
* async generator of bytes as output. Same shape on both sides — an in-process
|
|
13
|
+
* test can wire `const caller = handler` and run without any transport.
|
|
14
|
+
*
|
|
15
|
+
* Iterator semantics carry every signal:
|
|
16
|
+
* - Consumer `.return()` on the output → producer's `finally` runs.
|
|
17
|
+
* - Producer `throw` → consumer's `for await` throws.
|
|
18
|
+
* - Normal exhaustion on either side → matching end on the other side.
|
|
19
|
+
*/
|
|
20
|
+
export type Duplex = (input: AsyncIterable<Uint8Array> | Iterable<Uint8Array>) => AsyncGenerator<Uint8Array>;
|
|
21
|
+
/**
|
|
22
|
+
* Adapter-side factory that stands up a transport connection and yields a
|
|
23
|
+
* caller `Duplex`. One `Connect` invocation owns one transport; each call
|
|
24
|
+
* to the resolved `call` opens a new sub-stream on it.
|
|
25
|
+
*
|
|
26
|
+
* A caller must either drain the returned generator or `.return()` it.
|
|
27
|
+
* Dropping the reference without doing either emits no observable signal:
|
|
28
|
+
* the abandoned consumer never acknowledges inbound data, so the peer's
|
|
29
|
+
* outbound pump blocks awaiting that acknowledgement, no end-of-stream is
|
|
30
|
+
* ever exchanged, and both peers hold the stream's slot open. There is no
|
|
31
|
+
* way for the transport to detect this — an unreferenced generator is not
|
|
32
|
+
* observable — so it is the consumer's obligation.
|
|
33
|
+
*/
|
|
34
|
+
export type Connect<P> = (params: P) => Promise<{
|
|
35
|
+
call: Duplex;
|
|
36
|
+
close: () => Promise<void>;
|
|
37
|
+
}>;
|
|
38
|
+
/**
|
|
39
|
+
* Adapter-side factory that registers a handler `Duplex` against a transport.
|
|
40
|
+
* Returns an idempotent teardown.
|
|
41
|
+
*/
|
|
42
|
+
export type Serve<P> = (params: P, handler: Duplex) => Promise<() => Promise<void>>;
|
|
43
|
+
/**
|
|
44
|
+
* Thrown by `emulateMux` and adapters when the underlying transport closes
|
|
45
|
+
* while one or more `Duplex` calls are in flight. Consumers can catch by
|
|
46
|
+
* `instanceof TransportClosedError` or by checking `error.name`.
|
|
47
|
+
*/
|
|
48
|
+
export declare class TransportClosedError extends Error {
|
|
49
|
+
readonly name = "TransportClosedError";
|
|
50
|
+
constructor(message?: string);
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=duplex.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duplex.d.ts","sourceRoot":"","sources":["../src/duplex.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;;;;;;;GAUG;AACH,MAAM,MAAM,MAAM,GAAG,CACnB,KAAK,EAAE,aAAa,CAAC,UAAU,CAAC,GAAG,QAAQ,CAAC,UAAU,CAAC,KACpD,cAAc,CAAC,UAAU,CAAC,CAAC;AAEhC;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,KAAK,OAAO,CAAC;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,MAAM,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;AAEpF;;;;GAIG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,SAAkB,IAAI,0BAA0B;IAChD,YAAY,OAAO,SAAqB,EAEvC;CACF"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type Duplex } from "./duplex.js";
|
|
2
|
+
/**
|
|
3
|
+
* The minimum a transport must expose to be wrapped by `emulateMux`. Inbound
|
|
4
|
+
* bytes are surfaced as an async iterable; outbound is imperative `send`. All
|
|
5
|
+
* five message-oriented transports (WebSocket, LiveKit data channel, PeerJS
|
|
6
|
+
* DataConnection, MessagePort, in-process pipe) match this shape after a thin
|
|
7
|
+
* wrapper.
|
|
8
|
+
*/
|
|
9
|
+
export type ByteChannel = {
|
|
10
|
+
send(bytes: Uint8Array): void;
|
|
11
|
+
recv: AsyncIterable<Uint8Array>;
|
|
12
|
+
closed: Promise<void>;
|
|
13
|
+
close(): void;
|
|
14
|
+
};
|
|
15
|
+
export interface EmulateMuxOptions {
|
|
16
|
+
maxStreams?: number;
|
|
17
|
+
mtu?: number;
|
|
18
|
+
/**
|
|
19
|
+
* Cap on inbound bytes one stream may hold for a consumer that has not
|
|
20
|
+
* drained them. Exceeding it tears down that stream alone.
|
|
21
|
+
*
|
|
22
|
+
* It is also the credit this side advertises to the peer, so it must be at
|
|
23
|
+
* least 1 — a window of 0 authorises nothing and hangs the peer forever —
|
|
24
|
+
* and it travels in a uint32, so a value above `MAX_UINT32` (4 GiB - 1) is
|
|
25
|
+
* advertised as `MAX_UINT32`.
|
|
26
|
+
*/
|
|
27
|
+
maxStreamBuffer?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Stream-id allocation side. Initiator uses even ids (2, 4, …); responder
|
|
30
|
+
* uses odd ids (1, 3, …). Pick one per peer so allocations don't collide.
|
|
31
|
+
*/
|
|
32
|
+
side?: "initiator" | "responder";
|
|
33
|
+
}
|
|
34
|
+
export declare function emulateMux(channel: ByteChannel, opts?: EmulateMuxOptions): {
|
|
35
|
+
call: Duplex;
|
|
36
|
+
serve: (handler: Duplex) => () => Promise<void>;
|
|
37
|
+
close: () => Promise<void>;
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=emulate-mux.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emulate-mux.d.ts","sourceRoot":"","sources":["../src/emulate-mux.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAwB,MAAM,aAAa,CAAC;AAUhE;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,IAAI,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IAChC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,KAAK,IAAI,IAAI,CAAC;CACf,CAAC;AA4CF,MAAM,WAAW,iBAAiB;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;;;;OAQG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,IAAI,CAAC,EAAE,WAAW,GAAG,WAAW,CAAC;CAClC;AAED,wBAAgB,UAAU,CACxB,OAAO,EAAE,WAAW,EACpB,IAAI,GAAE,iBAAsB,GAC3B;IACD,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B,CAuVA"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface SerializedError {
|
|
2
|
+
message: string;
|
|
3
|
+
stack?: string;
|
|
4
|
+
[key: string]: unknown;
|
|
5
|
+
}
|
|
6
|
+
export declare function serializeError(error: unknown): SerializedError;
|
|
7
|
+
export declare function deserializeError(error: SerializedError | string): Error;
|
|
8
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,CAY9D;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,GAAG,KAAK,CAGvE"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credit-based flow control, as a pair of pure state machines with no I/O.
|
|
3
|
+
*
|
|
4
|
+
* The sender holds a {@link CreditLedger}: it reserves credit before it puts
|
|
5
|
+
* anything on the wire, and stalls at zero. The receiver holds a
|
|
6
|
+
* {@link CreditGrantor}: it counts what its consumer has actually drained and
|
|
7
|
+
* says when to hand the sender more.
|
|
8
|
+
*
|
|
9
|
+
* A sender therefore cannot overrun a receiver's buffer, because it was never
|
|
10
|
+
* granted permission to. That is the property a sender-side window cannot
|
|
11
|
+
* offer: the receiver's capacity is not knowable to the sender unless the
|
|
12
|
+
* receiver states it.
|
|
13
|
+
*
|
|
14
|
+
* **The unit is opaque.** This module never interprets the numbers it counts.
|
|
15
|
+
* `emulateMux` passes byte counts and advertises `maxStreamBuffer`; the RPC
|
|
16
|
+
* tier passes 1 per value and advertises a maximum in-flight value count.
|
|
17
|
+
* Nothing here depends on which.
|
|
18
|
+
*/
|
|
19
|
+
export interface CreditLedger {
|
|
20
|
+
/** Units authorised by the peer and not yet reserved. */
|
|
21
|
+
readonly available: number;
|
|
22
|
+
/**
|
|
23
|
+
* Reserve up to `upTo` units, resolving with how many were actually
|
|
24
|
+
* granted — at least 1, never more than `upTo`. The caller sends exactly
|
|
25
|
+
* that much and calls `reserve` again for the rest.
|
|
26
|
+
*
|
|
27
|
+
* `upTo` must itself be at least 1; anything less rejects with a
|
|
28
|
+
* `RangeError` rather than resolving with 0, which would be a silent no-op
|
|
29
|
+
* that also consumed a waiter slot.
|
|
30
|
+
*
|
|
31
|
+
* Returning a partial amount rather than waiting for the full request is
|
|
32
|
+
* what makes the ledger deadlock-free: a peer that advertises less than
|
|
33
|
+
* one `upTo` still makes progress, one short piece at a time.
|
|
34
|
+
*
|
|
35
|
+
* Rejects if {@link fail} is called.
|
|
36
|
+
*/
|
|
37
|
+
reserve(upTo: number): Promise<number>;
|
|
38
|
+
/** The peer authorised `units` more. */
|
|
39
|
+
grant(units: number): void;
|
|
40
|
+
/** Reject every pending and future reservation — transport or stream is gone. */
|
|
41
|
+
fail(err: Error): void;
|
|
42
|
+
}
|
|
43
|
+
export declare function newCreditLedger(initial?: number): CreditLedger;
|
|
44
|
+
export interface CreditGrantor {
|
|
45
|
+
/**
|
|
46
|
+
* Record that the consumer drained `units`, and whether the receive queue
|
|
47
|
+
* is now empty. Returns the credit to hand back to the peer, or `0` to stay
|
|
48
|
+
* silent and keep accumulating.
|
|
49
|
+
*/
|
|
50
|
+
consumed(units: number, queueEmpty: boolean): number;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Grants are batched: replenishing on every chunk while the receiver is
|
|
54
|
+
* behind would reinvent the per-frame ACK this change exists to remove.
|
|
55
|
+
* `threshold` is the fraction of the window that must drain before a grant is
|
|
56
|
+
* emitted.
|
|
57
|
+
*
|
|
58
|
+
* The batch is flushed unconditionally once the receive queue is empty, even
|
|
59
|
+
* below the threshold, so the receiver never sits on credit it owes. Note what
|
|
60
|
+
* this is and is not: paired with a {@link CreditLedger}, a sender blocks only
|
|
61
|
+
* at *exactly* zero credit, and at that point the receiver holds the entire
|
|
62
|
+
* window as `pending` — above any threshold at or below the whole window — so
|
|
63
|
+
* the threshold alone cannot deadlock that pairing. The flush is what keeps
|
|
64
|
+
* that from being an argument about a global accounting identity: it is
|
|
65
|
+
* locally decidable from one boolean, and it returns owed credit now rather
|
|
66
|
+
* than at the next threshold crossing. It costs an extra frame only when the
|
|
67
|
+
* consumer is keeping pace, which is exactly when the sender is not blocked
|
|
68
|
+
* and the frame is cheap.
|
|
69
|
+
*/
|
|
70
|
+
export declare function newCreditGrantor(window: number, threshold?: number): CreditGrantor;
|
|
71
|
+
//# sourceMappingURL=flow-control.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flow-control.d.ts","sourceRoot":"","sources":["../src/flow-control.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,WAAW,YAAY;IAC3B,yDAAyD;IACzD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACvC,wCAAwC;IACxC,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,iFAAiF;IACjF,IAAI,CAAC,GAAG,EAAE,KAAK,GAAG,IAAI,CAAC;CACxB;AAQD,wBAAgB,eAAe,CAAC,OAAO,SAAI,GAAG,YAAY,CAqDzD;AAED,MAAM,WAAW,aAAa;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,GAAG,MAAM,CAAC;CACtD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,SAAM,GAAG,aAAa,CAa/E"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export * from "./collect.js";
|
|
2
|
+
export * from "./duplex.js";
|
|
3
|
+
export * from "./emulate-mux.js";
|
|
4
|
+
export * from "./errors.js";
|
|
5
|
+
export * from "./flow-control.js";
|
|
6
|
+
export * from "./jsonl.js";
|
|
7
|
+
export * from "./lines.js";
|
|
8
|
+
export * from "./map.js";
|
|
9
|
+
export * from "./new-async-generator.js";
|
|
10
|
+
export * from "./normalize.js";
|
|
11
|
+
export * from "./readable-streams.js";
|
|
12
|
+
export * from "./recieve-iterator.js";
|
|
13
|
+
export * from "./send-iterator.js";
|
|
14
|
+
export * from "./text.js";
|
|
15
|
+
export * from "./to-chunks.js";
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,0BAA0B,CAAC;AACzC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC"}
|