@statewalker/webrun-streams 0.1.0 → 0.2.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.
Files changed (44) hide show
  1. package/README.md +197 -13
  2. package/dist/collect.d.ts +7 -0
  3. package/dist/collect.d.ts.map +1 -0
  4. package/dist/duplex.d.ts +52 -0
  5. package/dist/duplex.d.ts.map +1 -0
  6. package/dist/emulate-mux.d.ts +39 -0
  7. package/dist/emulate-mux.d.ts.map +1 -0
  8. package/dist/errors.d.ts +8 -0
  9. package/dist/errors.d.ts.map +1 -0
  10. package/dist/flow-control.d.ts +71 -0
  11. package/dist/flow-control.d.ts.map +1 -0
  12. package/dist/index.d.ts +16 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/{index.mjs → index.js} +251 -56
  15. package/dist/jsonl.d.ts +5 -0
  16. package/dist/jsonl.d.ts.map +1 -0
  17. package/dist/lines.d.ts +5 -0
  18. package/dist/lines.d.ts.map +1 -0
  19. package/dist/map.d.ts +3 -0
  20. package/dist/map.d.ts.map +1 -0
  21. package/dist/new-async-generator.d.ts +70 -0
  22. package/dist/new-async-generator.d.ts.map +1 -0
  23. package/dist/normalize.d.ts +8 -0
  24. package/dist/normalize.d.ts.map +1 -0
  25. package/dist/readable-streams.d.ts +13 -0
  26. package/dist/readable-streams.d.ts.map +1 -0
  27. package/dist/recieve-iterator.d.ts +14 -0
  28. package/dist/recieve-iterator.d.ts.map +1 -0
  29. package/dist/send-iterator.d.ts +15 -0
  30. package/dist/send-iterator.d.ts.map +1 -0
  31. package/dist/text.d.ts +5 -0
  32. package/dist/text.d.ts.map +1 -0
  33. package/dist/to-chunks.d.ts +11 -0
  34. package/dist/to-chunks.d.ts.map +1 -0
  35. package/dist/uint32.d.ts +25 -0
  36. package/dist/uint32.d.ts.map +1 -0
  37. package/package.json +13 -8
  38. package/src/duplex.ts +59 -0
  39. package/src/emulate-mux.ts +188 -74
  40. package/src/flow-control.ts +146 -0
  41. package/src/index.ts +2 -0
  42. package/src/readable-streams.ts +49 -13
  43. package/src/uint32.ts +34 -0
  44. package/LICENSE +0 -21
@@ -1,21 +1,44 @@
1
+ /**
2
+ * The iterator ↔ `ReadableStream` boundary.
3
+ *
4
+ * Both adapters must carry CANCELLATION, not just data: a response body leaves
5
+ * a handler as a `ReadableStream`, crosses a transport as an iterator, and
6
+ * becomes a `ReadableStream` again at the caller — so when the caller walks
7
+ * away, the only path back to the handler's producer runs through both of
8
+ * these functions. Teardown that stops at an adapter leaves a producer running
9
+ * for ever.
10
+ */
11
+
1
12
  export function toReadableStream(it: AsyncIterator<Uint8Array>): ReadableStream<Uint8Array> {
2
13
  return new ReadableStream<Uint8Array>({
14
+ /**
15
+ * One chunk per pull. An earlier version drained the whole iterator inside
16
+ * a single `pull`, which defeated the stream's own backpressure (every
17
+ * chunk was enqueued as fast as the producer could make them, however slow
18
+ * the reader was) and left no point between chunks at which a cancellation
19
+ * could take effect.
20
+ */
3
21
  async pull(controller) {
4
- let handled = false;
5
22
  try {
6
- while (true) {
7
- const slot = await it.next();
8
- if (!slot || slot.done) break;
9
- const value = (await slot.value) as Uint8Array;
10
- controller.enqueue(value);
23
+ const slot = await it.next();
24
+ if (!slot || slot.done) {
25
+ controller.close();
26
+ return;
11
27
  }
28
+ controller.enqueue((await slot.value) as Uint8Array);
12
29
  } catch (error) {
13
- handled = true;
14
30
  controller.error(error);
15
- } finally {
16
- if (!handled) controller.close();
17
31
  }
18
32
  },
33
+ /**
34
+ * Release the source. NOT awaited: `.return()` on an async generator that
35
+ * is parked awaiting its own source is queued behind that pending
36
+ * `next()`, so awaiting it here would hang `reader.cancel()` on exactly
37
+ * the producers that most need cancelling.
38
+ */
39
+ cancel(reason) {
40
+ void Promise.resolve(it.return?.(reason)).catch(() => {});
41
+ },
19
42
  });
20
43
  }
21
44
 
@@ -23,9 +46,22 @@ export async function* fromReadableStream(
23
46
  stream: ReadableStream<Uint8Array>,
24
47
  ): AsyncGenerator<Uint8Array, void, unknown> {
25
48
  const reader = stream.getReader();
26
- while (true) {
27
- const { done, value } = await reader.read();
28
- if (done) break;
29
- if (value !== undefined) yield value;
49
+ let drained = false;
50
+ try {
51
+ while (true) {
52
+ const { done, value } = await reader.read();
53
+ if (done) {
54
+ drained = true;
55
+ break;
56
+ }
57
+ if (value !== undefined) yield value;
58
+ }
59
+ } finally {
60
+ // A consumer that stops early (`break`, `.return()`, an error) must cancel
61
+ // the source, or whatever fills it keeps filling it. A stream that ended
62
+ // on its own is merely released — cancelling it would be a lie to any
63
+ // `cancel()` hook watching for an abandoned reader.
64
+ if (drained) reader.releaseLock();
65
+ else await reader.cancel().catch(() => {});
30
66
  }
31
67
  }
package/src/uint32.ts ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Credit payloads are a single big-endian uint32, matching the frame header's
3
+ * byte order.
4
+ *
5
+ * Deliberately **not** re-exported from `index.ts`: this is an internal codec
6
+ * for the `emulateMux` wire format, not a compatibility commitment. Tests
7
+ * import it by path.
8
+ */
9
+ /** The largest credit a single frame can advertise or grant: 2^32 - 1. */
10
+ export const MAX_UINT32 = 0xffffffff;
11
+
12
+ /**
13
+ * Clamps rather than wraps. `n >>> 0` is the obvious spelling and it is wrong
14
+ * here: 2^32 becomes **0**, so a 4 GiB window advertises *zero credit* and the
15
+ * peer stalls forever with no error — the exact silent hang credit exists to
16
+ * remove. 2^32 + 5 becomes 5, which looks like a working window and is worse.
17
+ * Anything above the ceiling is advertised as the ceiling.
18
+ */
19
+ export function encodeUint32(n: number): Uint8Array {
20
+ const bytes = new Uint8Array(4);
21
+ const clamped = Number.isFinite(n) ? Math.min(MAX_UINT32, Math.max(0, Math.floor(n))) : 0;
22
+ new DataView(bytes.buffer).setUint32(0, clamped, false);
23
+ return bytes;
24
+ }
25
+
26
+ /**
27
+ * Returns `undefined` rather than a garbage number when the payload is too
28
+ * short, so a truncated frame — or one from a peer predating credit — is
29
+ * detectable at the call site instead of silently granting nonsense.
30
+ */
31
+ export function decodeUint32(bytes: Uint8Array): number | undefined {
32
+ if (bytes.byteLength < 4) return undefined;
33
+ return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(0, false);
34
+ }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2022-2026 statewalker
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.