@statewalker/webrun-http-browser 0.3.4 → 0.4.2

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/index.js CHANGED
@@ -1,4 +1,4 @@
1
- //#region ../webrun-streams/src/collect.ts
1
+ //#region ../webrun-streams/dist/index.js
2
2
  /** Collect all items from an async iterable into an array. */
3
3
  async function collect(input) {
4
4
  const items = [];
@@ -28,8 +28,17 @@ async function collectString(input) {
28
28
  for await (const chunk of input) result += chunk;
29
29
  return result;
30
30
  }
31
- //#endregion
32
- //#region ../webrun-streams/src/errors.ts
31
+ /**
32
+ * Thrown by `emulateMux` and adapters when the underlying transport closes
33
+ * while one or more `Duplex` calls are in flight. Consumers can catch by
34
+ * `instanceof TransportClosedError` or by checking `error.name`.
35
+ */
36
+ var TransportClosedError = class extends Error {
37
+ name = "TransportClosedError";
38
+ constructor(message = "transport closed") {
39
+ super(message);
40
+ }
41
+ };
33
42
  function serializeError(error) {
34
43
  if (error instanceof Error) {
35
44
  const out = {
@@ -53,19 +62,113 @@ function deserializeError(error) {
53
62
  const payload = typeof error === "string" ? { message: error } : error;
54
63
  return Object.assign(new Error(payload.message), payload);
55
64
  }
56
- //#endregion
57
- //#region ../webrun-streams/src/emulate-mux.ts
65
+ function newCreditLedger(initial = 0) {
66
+ let available = initial;
67
+ let failure;
68
+ const waiters = [];
69
+ const pump = () => {
70
+ while (waiters.length > 0 && available > 0) {
71
+ const next = waiters[0];
72
+ if (!next) return;
73
+ waiters.shift();
74
+ const granted = Math.min(next.upTo, available);
75
+ available -= granted;
76
+ next.resolve(granted);
77
+ }
78
+ };
79
+ return {
80
+ get available() {
81
+ return available;
82
+ },
83
+ reserve(upTo) {
84
+ if (failure) return Promise.reject(failure);
85
+ if (!(upTo >= 1)) return Promise.reject(/* @__PURE__ */ new RangeError(`newCreditLedger: reserve(${upTo}) — upTo must be at least 1`));
86
+ if (waiters.length === 0 && available > 0) {
87
+ const granted = Math.min(upTo, available);
88
+ available -= granted;
89
+ return Promise.resolve(granted);
90
+ }
91
+ return new Promise((resolve, reject) => {
92
+ waiters.push({
93
+ upTo,
94
+ resolve,
95
+ reject
96
+ });
97
+ });
98
+ },
99
+ grant(units) {
100
+ if (failure) return;
101
+ available += units;
102
+ pump();
103
+ },
104
+ fail(err) {
105
+ failure ??= err;
106
+ while (waiters.length > 0) waiters.shift()?.reject(err);
107
+ }
108
+ };
109
+ }
58
110
  /**
59
- * Thrown by `emulateMux` and adapters when the underlying transport closes
60
- * while one or more `Duplex` calls are in flight. Consumers can catch by
61
- * `instanceof TransportClosedError` or by checking `error.name`.
111
+ * Grants are batched: replenishing on every chunk while the receiver is
112
+ * behind would reinvent the per-frame ACK this change exists to remove.
113
+ * `threshold` is the fraction of the window that must drain before a grant is
114
+ * emitted.
115
+ *
116
+ * The batch is flushed unconditionally once the receive queue is empty, even
117
+ * below the threshold, so the receiver never sits on credit it owes. Note what
118
+ * this is and is not: paired with a {@link CreditLedger}, a sender blocks only
119
+ * at *exactly* zero credit, and at that point the receiver holds the entire
120
+ * window as `pending` — above any threshold at or below the whole window — so
121
+ * the threshold alone cannot deadlock that pairing. The flush is what keeps
122
+ * that from being an argument about a global accounting identity: it is
123
+ * locally decidable from one boolean, and it returns owed credit now rather
124
+ * than at the next threshold crossing. It costs an extra frame only when the
125
+ * consumer is keeping pace, which is exactly when the sender is not blocked
126
+ * and the frame is cheap.
62
127
  */
63
- var TransportClosedError = class extends Error {
64
- name = "TransportClosedError";
65
- constructor(message = "transport closed") {
66
- super(message);
67
- }
68
- };
128
+ function newCreditGrantor(window, threshold = .5) {
129
+ const trigger = Math.max(1, Math.floor(window * threshold));
130
+ let pending = 0;
131
+ return { consumed(units, queueEmpty) {
132
+ pending += units;
133
+ if (pending === 0) return 0;
134
+ if (pending < trigger && !queueEmpty) return 0;
135
+ const grant = pending;
136
+ pending = 0;
137
+ return grant;
138
+ } };
139
+ }
140
+ /**
141
+ * Credit payloads are a single big-endian uint32, matching the frame header's
142
+ * byte order.
143
+ *
144
+ * Deliberately **not** re-exported from `index.ts`: this is an internal codec
145
+ * for the `emulateMux` wire format, not a compatibility commitment. Tests
146
+ * import it by path.
147
+ */
148
+ /** The largest credit a single frame can advertise or grant: 2^32 - 1. */
149
+ const MAX_UINT32 = 4294967295;
150
+ /**
151
+ * Clamps rather than wraps. `n >>> 0` is the obvious spelling and it is wrong
152
+ * here: 2^32 becomes **0**, so a 4 GiB window advertises *zero credit* and the
153
+ * peer stalls forever with no error — the exact silent hang credit exists to
154
+ * remove. 2^32 + 5 becomes 5, which looks like a working window and is worse.
155
+ * Anything above the ceiling is advertised as the ceiling.
156
+ */
157
+ function encodeUint32(n) {
158
+ const bytes = /* @__PURE__ */ new Uint8Array(4);
159
+ const clamped = Number.isFinite(n) ? Math.min(MAX_UINT32, Math.max(0, Math.floor(n))) : 0;
160
+ new DataView(bytes.buffer).setUint32(0, clamped, false);
161
+ return bytes;
162
+ }
163
+ /**
164
+ * Returns `undefined` rather than a garbage number when the payload is too
165
+ * short, so a truncated frame — or one from a peer predating credit — is
166
+ * detectable at the call site instead of silently granting nonsense.
167
+ */
168
+ function decodeUint32(bytes) {
169
+ if (bytes.byteLength < 4) return void 0;
170
+ return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(0, false);
171
+ }
69
172
  const TYPE_OPEN = 1;
70
173
  const TYPE_DATA = 2;
71
174
  const TYPE_ACK = 3;
@@ -81,6 +184,7 @@ function emulateMux(channel, opts = {}) {
81
184
  const maxStreams = opts.maxStreams ?? DEFAULT_MAX_STREAMS;
82
185
  const mtu = opts.mtu ?? DEFAULT_MTU;
83
186
  const maxStreamBuffer = opts.maxStreamBuffer ?? DEFAULT_MAX_STREAM_BUFFER;
187
+ if (!(maxStreamBuffer >= 1)) throw new RangeError(`emulateMux: maxStreamBuffer must be at least 1, got ${maxStreamBuffer}`);
84
188
  const side = opts.side ?? "initiator";
85
189
  const streams = /* @__PURE__ */ new Map();
86
190
  let nextLocalId = side === "initiator" ? 2 : 1;
@@ -101,13 +205,9 @@ function emulateMux(channel, opts = {}) {
101
205
  const teardownStream = (s, err) => {
102
206
  if (s.closed) return;
103
207
  s.closed = true;
104
- const resolve = s.resolveAck;
105
- const reject = s.rejectAck;
106
- s.resolveAck = null;
107
- s.rejectAck = null;
108
- if (reject && err) reject(err);
109
- else resolve?.();
208
+ s.outboundCredit.fail(err ?? new TransportClosedError("emulateMux: stream closed"));
110
209
  s.doneIn(err);
210
+ s.cancelInput?.();
111
211
  streams.delete(s.id);
112
212
  };
113
213
  /**
@@ -141,8 +241,8 @@ function emulateMux(channel, opts = {}) {
141
241
  }),
142
242
  pushIn: queue.push,
143
243
  doneIn: queue.done,
144
- resolveAck: null,
145
- rejectAck: null,
244
+ outboundCredit: newCreditLedger(0),
245
+ grantor: newCreditGrantor(maxStreamBuffer),
146
246
  closed: false,
147
247
  inDone: false,
148
248
  outDone: false,
@@ -151,20 +251,24 @@ function emulateMux(channel, opts = {}) {
151
251
  return state;
152
252
  };
153
253
  const pumpOutbound = async (s, input) => {
254
+ const iterator = input[Symbol.asyncIterator]?.() ?? input[Symbol.iterator]();
255
+ s.cancelInput = () => {
256
+ iterator.return?.(void 0);
257
+ };
154
258
  try {
155
- for await (const chunk of input) {
259
+ for (;;) {
260
+ const next = await iterator.next();
261
+ if (next.done === true) break;
262
+ const chunk = next.value;
156
263
  if (s.closed || muxClosed) return;
157
264
  if (chunk.byteLength === 0) continue;
158
265
  let off = 0;
159
266
  while (off < chunk.byteLength) {
160
267
  if (s.closed || muxClosed) return;
161
- const end = Math.min(off + mtu, chunk.byteLength);
162
- const piece = chunk.subarray(off, end);
163
- sendFrame(s.id, TYPE_DATA, piece);
164
- await new Promise((resolve, reject) => {
165
- s.resolveAck = resolve;
166
- s.rejectAck = reject;
167
- });
268
+ const take = await s.outboundCredit.reserve(Math.min(mtu, chunk.byteLength - off));
269
+ if (s.closed || muxClosed) return;
270
+ const end = off + take;
271
+ sendFrame(s.id, TYPE_DATA, chunk.subarray(off, end));
168
272
  off = end;
169
273
  }
170
274
  }
@@ -222,6 +326,9 @@ function emulateMux(channel, opts = {}) {
222
326
  }
223
327
  const s = createStream(id);
224
328
  streams.set(id, s);
329
+ const advertised = decodeUint32(payload);
330
+ if (advertised !== void 0) s.outboundCredit.grant(advertised);
331
+ sendFrame(id, TYPE_ACK, encodeUint32(maxStreamBuffer));
225
332
  runHandler(s, handler);
226
333
  return;
227
334
  }
@@ -239,15 +346,15 @@ function emulateMux(channel, opts = {}) {
239
346
  const copy = payload.byteLength === 0 ? payload : new Uint8Array(payload);
240
347
  s.pushIn(copy).then((handled) => {
241
348
  s.queuedBytes -= copy.byteLength;
242
- if (handled && !s.closed && !muxClosed) sendFrame(id, TYPE_ACK);
349
+ if (!handled || s.closed || muxClosed) return;
350
+ const grant = s.grantor.consumed(copy.byteLength, s.queuedBytes === 0);
351
+ if (grant > 0) sendFrame(id, TYPE_ACK, encodeUint32(grant));
243
352
  });
244
353
  return;
245
354
  }
246
355
  case TYPE_ACK: {
247
- const r = s.resolveAck;
248
- s.resolveAck = null;
249
- s.rejectAck = null;
250
- r?.();
356
+ const granted = decodeUint32(payload);
357
+ if (granted !== void 0) s.outboundCredit.grant(granted);
251
358
  return;
252
359
  }
253
360
  case TYPE_END:
@@ -284,7 +391,7 @@ function emulateMux(channel, opts = {}) {
284
391
  nextLocalId += 2;
285
392
  const s = createStream(id);
286
393
  streams.set(id, s);
287
- sendFrame(id, TYPE_OPEN);
394
+ sendFrame(id, TYPE_OPEN, encodeUint32(maxStreamBuffer));
288
395
  pumpOutbound(s, input);
289
396
  return s.inbound;
290
397
  };
@@ -424,8 +531,6 @@ function decodeError(buf) {
424
531
  return new Error(textDecoder.decode(buf));
425
532
  }
426
533
  }
427
- //#endregion
428
- //#region ../webrun-streams/src/lines.ts
429
534
  /** Split a stream of string chunks into individual lines (delimited by \n). */
430
535
  async function* splitLines(input) {
431
536
  let buffer = "";
@@ -444,14 +549,10 @@ async function* splitLines(input) {
444
549
  async function* joinLines(input) {
445
550
  for await (const line of input) yield `${line}\n`;
446
551
  }
447
- //#endregion
448
- //#region ../webrun-streams/src/map.ts
449
552
  /** Apply a sync or async function to each item in a stream. */
450
553
  async function* map(input, fn) {
451
554
  for await (const item of input) yield await fn(item);
452
555
  }
453
- //#endregion
454
- //#region ../webrun-streams/src/jsonl.ts
455
556
  /** Encode each value as a JSON line (terminated by \n). */
456
557
  async function* encodeJsonl(input) {
457
558
  yield* map(input, (item) => `${JSON.stringify(item)}\n`);
@@ -463,8 +564,6 @@ async function* decodeJsonl(input) {
463
564
  if (trimmed) yield JSON.parse(trimmed);
464
565
  }
465
566
  }
466
- //#endregion
467
- //#region ../webrun-streams/src/new-async-generator.ts
468
567
  /**
469
568
  * The newAsyncGenerator function creates async generators from callback-based initialization
470
569
  * functions, providing a bridge between imperative event handling and declarative async
@@ -617,8 +716,6 @@ async function* newAsyncGenerator(init, skipValues = false) {
617
716
  drainQueue();
618
717
  }
619
718
  }
620
- //#endregion
621
- //#region ../webrun-streams/src/normalize.ts
622
719
  const utf8 = new TextEncoder();
623
720
  /**
624
721
  * Coerce common byte-like inputs into a `Uint8Array`. Strings encode as UTF-8.
@@ -639,36 +736,65 @@ function describe(value) {
639
736
  if (t !== "object") return t;
640
737
  return value.constructor?.name ?? "object";
641
738
  }
642
- //#endregion
643
- //#region ../webrun-streams/src/readable-streams.ts
739
+ /**
740
+ * The iterator ↔ `ReadableStream` boundary.
741
+ *
742
+ * Both adapters must carry CANCELLATION, not just data: a response body leaves
743
+ * a handler as a `ReadableStream`, crosses a transport as an iterator, and
744
+ * becomes a `ReadableStream` again at the caller — so when the caller walks
745
+ * away, the only path back to the handler's producer runs through both of
746
+ * these functions. Teardown that stops at an adapter leaves a producer running
747
+ * for ever.
748
+ */
644
749
  function toReadableStream(it) {
645
- return new ReadableStream({ async pull(controller) {
646
- let handled = false;
647
- try {
648
- while (true) {
750
+ return new ReadableStream({
751
+ /**
752
+ * One chunk per pull. An earlier version drained the whole iterator inside
753
+ * a single `pull`, which defeated the stream's own backpressure (every
754
+ * chunk was enqueued as fast as the producer could make them, however slow
755
+ * the reader was) and left no point between chunks at which a cancellation
756
+ * could take effect.
757
+ */
758
+ async pull(controller) {
759
+ try {
649
760
  const slot = await it.next();
650
- if (!slot || slot.done) break;
651
- const value = await slot.value;
652
- controller.enqueue(value);
761
+ if (!slot || slot.done) {
762
+ controller.close();
763
+ return;
764
+ }
765
+ controller.enqueue(await slot.value);
766
+ } catch (error) {
767
+ controller.error(error);
653
768
  }
654
- } catch (error) {
655
- handled = true;
656
- controller.error(error);
657
- } finally {
658
- if (!handled) controller.close();
769
+ },
770
+ /**
771
+ * Release the source. NOT awaited: `.return()` on an async generator that
772
+ * is parked awaiting its own source is queued behind that pending
773
+ * `next()`, so awaiting it here would hang `reader.cancel()` on exactly
774
+ * the producers that most need cancelling.
775
+ */
776
+ cancel(reason) {
777
+ Promise.resolve(it.return?.(reason)).catch(() => {});
659
778
  }
660
- } });
779
+ });
661
780
  }
662
781
  async function* fromReadableStream(stream) {
663
782
  const reader = stream.getReader();
664
- while (true) {
665
- const { done, value } = await reader.read();
666
- if (done) break;
667
- if (value !== void 0) yield value;
783
+ let drained = false;
784
+ try {
785
+ while (true) {
786
+ const { done, value } = await reader.read();
787
+ if (done) {
788
+ drained = true;
789
+ break;
790
+ }
791
+ if (value !== void 0) yield value;
792
+ }
793
+ } finally {
794
+ if (drained) reader.releaseLock();
795
+ else await reader.cancel().catch(() => {});
668
796
  }
669
797
  }
670
- //#endregion
671
- //#region ../webrun-streams/src/recieve-iterator.ts
672
798
  /**
673
799
  * Inverse of {@link sendIterator}: turns a sequence of `{done, value, error}`
674
800
  * chunks (delivered to the supplied callback by `installer`) into an async
@@ -691,8 +817,6 @@ function recieveIterator(installer) {
691
817
  };
692
818
  });
693
819
  }
694
- //#endregion
695
- //#region ../webrun-streams/src/send-iterator.ts
696
820
  /**
697
821
  * Drain an async iterator into a sink that consumes one chunk at a time.
698
822
  *
@@ -716,8 +840,6 @@ async function sendIterator(send, it) {
716
840
  });
717
841
  }
718
842
  }
719
- //#endregion
720
- //#region ../webrun-streams/src/text.ts
721
843
  /** Decode Uint8Array chunks to string chunks via TextDecoder, handling split multi-byte characters. */
722
844
  async function* decodeText(input) {
723
845
  const decoder = new TextDecoder("utf-8", { fatal: false });
@@ -733,8 +855,6 @@ async function* encodeText(input) {
733
855
  const encoder = new TextEncoder();
734
856
  for await (const str of input) yield encoder.encode(str);
735
857
  }
736
- //#endregion
737
- //#region ../webrun-streams/src/to-chunks.ts
738
858
  const DEFAULT_CHUNK_SIZE = 16384;
739
859
  /**
740
860
  * Curried `Duplex`-shaped transformer that splits incoming `Uint8Array`s into
@@ -877,11 +997,14 @@ async function* sendStream(communicationPort, input, params = {}) {
877
997
  params
878
998
  }, [messageChannel.port2]);
879
999
  const channel = newStreamChannel(messageChannel.port1);
1000
+ let drained = false;
880
1001
  try {
881
1002
  await channel.start();
882
1003
  channel.sendAll(input);
883
1004
  yield* channel.recieveAll();
1005
+ drained = true;
884
1006
  } finally {
1007
+ if (!drained) channel.cancel();
885
1008
  await channel.close();
886
1009
  }
887
1010
  }
@@ -909,11 +1032,33 @@ function newStreamChannel(port) {
909
1032
  const notifyAll = async (data) => {
910
1033
  for (const listener of listeners) await listener(data);
911
1034
  };
1035
+ /**
1036
+ * Release whatever we are sending. NOT awaited: `.return()` on an async
1037
+ * generator parked awaiting its own source is queued behind that pending
1038
+ * `next()`, so awaiting it here would block the message handler — and the
1039
+ * chunk that would unblock it can only arrive through that same handler.
1040
+ */
1041
+ const cancelOutgoing = () => {
1042
+ for (const it of [...iterators]) {
1043
+ const iterable = it;
1044
+ Promise.resolve(iterable.return?.()).catch(() => {});
1045
+ }
1046
+ };
912
1047
  const channel = newInvokationChannel({
913
1048
  port,
914
- handler: (data) => notifyAll(data)
1049
+ handler: (data) => {
1050
+ const message = data;
1051
+ if (message?.cancel) {
1052
+ cancelOutgoing();
1053
+ return;
1054
+ }
1055
+ return notifyAll(message);
1056
+ }
915
1057
  });
916
1058
  const start = () => channel.start();
1059
+ const cancel = () => {
1060
+ channel.invoke({ cancel: true }).catch(() => {});
1061
+ };
917
1062
  const close = async () => {
918
1063
  await notifyAll({ done: true });
919
1064
  for (const it of [...iterators]) await it.return?.();
@@ -940,6 +1085,7 @@ function newStreamChannel(port) {
940
1085
  return {
941
1086
  start,
942
1087
  close,
1088
+ cancel,
943
1089
  recieveAll,
944
1090
  sendAll
945
1091
  };
@@ -979,7 +1125,7 @@ function newRegistry(onError = console.error) {
979
1125
  });
980
1126
  }
981
1127
  //#endregion
982
- //#region ../webrun-http-streams/src/bytes.ts
1128
+ //#region ../webrun-http-streams/dist/index.js
983
1129
  const CR = 13;
984
1130
  const LF = 10;
985
1131
  const EMPTY = /* @__PURE__ */ new Uint8Array(0);
@@ -1104,8 +1250,6 @@ var ByteReader = class {
1104
1250
  }
1105
1251
  }
1106
1252
  };
1107
- //#endregion
1108
- //#region ../webrun-http-streams/src/http1/errors.ts
1109
1253
  /**
1110
1254
  * Raised for any byte sequence this codec refuses to interpret. Every case is
1111
1255
  * a refusal to guess: HTTP/1.1 parsers that guess are how request smuggling
@@ -1114,8 +1258,6 @@ var ByteReader = class {
1114
1258
  var HttpParseError = class extends Error {
1115
1259
  name = "HttpParseError";
1116
1260
  };
1117
- //#endregion
1118
- //#region ../webrun-http-streams/src/envelope.ts
1119
1261
  const NEWLINE = 10;
1120
1262
  /** Mirrors the HTTP/1.1 codec's default `maxHeaderBytes`. */
1121
1263
  const MAX_ENVELOPE_BYTES = 65536;
@@ -1199,8 +1341,6 @@ const jsonEnvelopeCodec = {
1199
1341
  decodeRequest: (input) => decodeMessage(input),
1200
1342
  decodeResponse: (input) => decodeMessage(input)
1201
1343
  };
1202
- //#endregion
1203
- //#region ../webrun-http-streams/src/http1/headers.ts
1204
1344
  const TCHAR = /* @__PURE__ */ new Set("!#$%&'*+-.^_`|~0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
1205
1345
  const SP = 32;
1206
1346
  const HTAB = 9;
@@ -1376,8 +1516,6 @@ function resolveFraming(headers, version) {
1376
1516
  };
1377
1517
  return { kind: "none" };
1378
1518
  }
1379
- //#endregion
1380
- //#region ../webrun-http-streams/src/http1/chunked.ts
1381
1519
  const CRLF = new Uint8Array([13, 10]);
1382
1520
  const LAST_CHUNK = new Uint8Array([
1383
1521
  48,
@@ -1444,8 +1582,6 @@ async function* decodeChunked(reader, maxLineBytes) {
1444
1582
  }
1445
1583
  }
1446
1584
  }
1447
- //#endregion
1448
- //#region ../webrun-http-streams/src/http1/decode.ts
1449
1585
  const VERSIONS = /* @__PURE__ */ new Set(["HTTP/1.1", "HTTP/1.0"]);
1450
1586
  const ABSOLUTE_FORM = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//;
1451
1587
  /**
@@ -1583,8 +1719,6 @@ async function decodeResponse(input, opts, method) {
1583
1719
  toHttpParseError(err);
1584
1720
  }
1585
1721
  }
1586
- //#endregion
1587
- //#region ../webrun-http-streams/src/http1/encode.ts
1588
1722
  /** Headers the codec owns: a caller-supplied copy is dropped and re-derived. */
1589
1723
  const REQUEST_OWNED = [
1590
1724
  "host",
@@ -1680,8 +1814,6 @@ async function* encodeResponse(env, body, _opts, requestMethod) {
1680
1814
  }
1681
1815
  yield* emitBody(body, declared);
1682
1816
  }
1683
- //#endregion
1684
- //#region ../webrun-http-streams/src/http1/index.ts
1685
1817
  function newHttpCodec(options = {}) {
1686
1818
  const opts = {
1687
1819
  scheme: options.scheme ?? "http",
@@ -1698,8 +1830,6 @@ function newHttpCodec(options = {}) {
1698
1830
  };
1699
1831
  }
1700
1832
  const httpCodec = newHttpCodec();
1701
- //#endregion
1702
- //#region ../webrun-http-streams/src/sniff.ts
1703
1833
  /**
1704
1834
  * Dispatches on byte 0. The formats are self-identifying — a JSON envelope
1705
1835
  * always begins `{`, which is not a token character and so can never begin an
@@ -1745,8 +1875,6 @@ function newSniffingCodec(options) {
1745
1875
  }
1746
1876
  };
1747
1877
  }
1748
- //#endregion
1749
- //#region ../webrun-http-streams/src/codec-default.ts
1750
1878
  /**
1751
1879
  * Writes HTTP/1.1; accepts HTTP/1.1 or the legacy JSON envelope. Used whenever
1752
1880
  * no `codec` option is supplied.
@@ -1755,8 +1883,6 @@ const defaultCodec = newSniffingCodec({
1755
1883
  write: httpCodec,
1756
1884
  accept: [httpCodec, jsonEnvelopeCodec]
1757
1885
  });
1758
- //#endregion
1759
- //#region ../webrun-http-streams/src/http-data.ts
1760
1886
  /** Carries the serialized JS error between two webrun peers. */
1761
1887
  const PEER_ERROR_HEADER = "x-webrun-error";
1762
1888
  /** Hard cap on the peer-error header (M3): see `encodeErrorResponse`. */
@@ -1865,8 +1991,6 @@ function httpServe(handler, options = {}) {
1865
1991
  yield* replyCodec.encodeResponse(result.envelope, result.body, { method: decoded.envelope.method });
1866
1992
  };
1867
1993
  }
1868
- //#endregion
1869
- //#region ../webrun-http-streams/src/request-streams.ts
1870
1994
  /**
1871
1995
  * The one place that knows a runtime may not implement request body streams,
1872
1996
  * and the buffering both fallbacks need. `fetch.ts` and `http-stubs.ts` each
@@ -1935,8 +2059,6 @@ async function collectBytes$1(source) {
1935
2059
  }
1936
2060
  return out;
1937
2061
  }
1938
- //#endregion
1939
- //#region ../webrun-http-streams/src/http-stubs.ts
1940
2062
  const NULL_BODY_STATUSES = /* @__PURE__ */ new Set([
1941
2063
  101,
1942
2064
  103,
@@ -2032,8 +2154,6 @@ function newHttpServerStub(handler) {
2032
2154
  };
2033
2155
  };
2034
2156
  }
2035
- //#endregion
2036
- //#region ../webrun-http-streams/src/fetch.ts
2037
2157
  /**
2038
2158
  * Connection-scoped headers. The codec surfaces them verbatim (decision 12),
2039
2159
  * but they are meaningless to a `Request`/`Response`, and re-emitting them
@@ -2184,8 +2304,6 @@ function abortReason(signal) {
2184
2304
  err.name = "AbortError";
2185
2305
  return err;
2186
2306
  }
2187
- //#endregion
2188
- //#region ../webrun-http-streams/src/duplex-site-builder.ts
2189
2307
  /**
2190
2308
  * Host a `SiteHandler` over a `Connect/Serve` pair. The `start(serve, params)`
2191
2309
  * call hands the handler to any `webrun-streams-*` adapter's `serve`.
@@ -2193,7 +2311,7 @@ function abortReason(signal) {
2193
2311
  * ```ts
2194
2312
  * import { SiteBuilder } from "@statewalker/webrun-site-builder";
2195
2313
  * import { DuplexSiteBuilder } from "@statewalker/webrun-http-streams";
2196
- * import { serve } from "@statewalker/webrun-streams-port";
2314
+ * import { serve } from "@statewalker/webrun-rpc";
2197
2315
  *
2198
2316
  * const handler = new SiteBuilder()
2199
2317
  * .setEndpoint("/api/time", () => new Response(new Date().toISOString()))
@@ -2224,8 +2342,6 @@ var DuplexSiteBuilder = class {
2224
2342
  return serve(params, serveFetchOverDuplex(async (req) => handler(req), { codec: this.#codec }));
2225
2343
  }
2226
2344
  };
2227
- //#endregion
2228
- //#region ../webrun-http-streams/src/http-error.ts
2229
2345
  var HttpError = class HttpError extends Error {
2230
2346
  status;
2231
2347
  statusText;
@@ -2308,13 +2424,21 @@ async function httpFromIterator(iterable) {
2308
2424
  };
2309
2425
  }
2310
2426
  /**
2311
- * @deprecated For new code, prefer the `MessagePort`-based stack from
2312
- * `@statewalker/webrun-http-port`. Once a `MessagePort` is established between
2313
- * page and worker, `httpServe(port, handler)` provides equivalent semantics
2314
- * with `callBidi` multiplexing, full-duplex streaming, and `AbortSignal`.
2315
- * This helper remains for existing ServiceWorker setups that still consume the
2316
- * `MessageTarget` surface; it will be reimplemented on top of
2317
- * `webrun-http-port` in a follow-up release.
2427
+ * Serve an `HttpHandler` over a `MessageTarget`, using this package's own
2428
+ * `handleStreams` transport.
2429
+ *
2430
+ * @deprecated Prefer the port stack in `@statewalker/webrun-rpc`: open a port
2431
+ * (`multiplexPort` over one pipe, or `transferPortMux` where the platform can
2432
+ * transfer a real `MessagePort`), turn it into a `Duplex` with
2433
+ * `serveDuplexOverPort`, and serve HTTP on that with
2434
+ * `httpServe(handler, options)` from `@statewalker/webrun-http-streams`.
2435
+ *
2436
+ * That path has backpressure; **this one does not.** `sendStream`'s chunk
2437
+ * sender discards the promise it is given, so a fast producer over a slow
2438
+ * consumer accumulates without bound. It also has no per-stream timeout and no
2439
+ * chunking to a transport's message ceiling.
2440
+ *
2441
+ * Kept for existing ServiceWorker setups built on the `MessageTarget` surface.
2318
2442
  */
2319
2443
  function handleHttpRequests(communicationPort, handler) {
2320
2444
  const serverStub = newHttpServerStub(handler);
@@ -2324,13 +2448,18 @@ function handleHttpRequests(communicationPort, handler) {
2324
2448
  });
2325
2449
  }
2326
2450
  /**
2327
- * @deprecated For new code, prefer the `MessagePort`-based stack from
2328
- * `@statewalker/webrun-http-port/fetch`. Once the page and SW share a
2329
- * `MessagePort`, `fetchOverPort(port, request)` provides the same
2330
- * `Request → Response` semantics with multiplexing via `callBidi`, JSONL
2331
- * envelope framing, and native `AbortSignal` support. This helper remains for
2332
- * existing ServiceWorker setups; it will be reimplemented on top of
2333
- * `webrun-http-port` in a follow-up release.
2451
+ * Ship a `Request` over a `MessageTarget` and await the `Response`, using this
2452
+ * package's own `sendStream` transport.
2453
+ *
2454
+ * @deprecated Prefer the port stack in `@statewalker/webrun-rpc`: open a port
2455
+ * (`multiplexPort` over one pipe, or `transferPortMux` where the platform can
2456
+ * transfer a real `MessagePort`), turn it into a `Duplex` with
2457
+ * `duplexOverPort`, and drive HTTP over it with `httpFetch` from
2458
+ * `@statewalker/webrun-http-streams`.
2459
+ *
2460
+ * Same caveat as {@link handleHttpRequests}: the transport underneath this
2461
+ * helper has no backpressure, no per-stream timeout, and no chunking to a
2462
+ * transport's message ceiling. Kept for existing ServiceWorker setups.
2334
2463
  */
2335
2464
  async function sendHttpRequest(communicationPort, request) {
2336
2465
  return await newHttpClientStub(async (req) => {
@@ -2551,4 +2680,4 @@ async function registerConnectionsHandler({ key, handler, communicationPort }) {
2551
2680
  return cleanup;
2552
2681
  }
2553
2682
  //#endregion
2554
- export { DuplexSiteBuilder, HttpError, HttpParseError, PEER_ERROR_HEADER, TransportClosedError, callChannel, callHttpService, collect, collectBytes, collectString, decodeJsonl, decodeMessage, decodeText, defaultCodec, deserializeError, emulateMux, encodeJsonl, encodeMessage, encodeText, fetchOverDuplex, fromReadableStream, getRelayWindowMessageHandler, handleChannelCalls, handleHttpRequests, handleStreams, httpCodec, httpFetch, httpServe, initHttpService, initServiceWorker, initializeConnection, joinLines, jsonEnvelopeCodec, map, newAsyncGenerator, newHttpClientStub, newHttpCodec, newHttpServerStub, newInvokationChannel, newRegistry, newRemoteRelayChannel, newServiceWorkerPort, newSniffingCodec, normalizeToUint8Array, recieveIterator, registerConnectionsHandler, sendHttpRequest, sendIterator, sendStream, serializeError, serveFetchOverDuplex, splitLines, splitServiceUrl, toChunks, toReadableStream };
2683
+ export { DuplexSiteBuilder, HttpError, HttpParseError, PEER_ERROR_HEADER, TransportClosedError, callChannel, callHttpService, collect, collectBytes, collectString, decodeJsonl, decodeMessage, decodeText, defaultCodec, deserializeError, emulateMux, encodeJsonl, encodeMessage, encodeText, fetchOverDuplex, fromReadableStream, getRelayWindowMessageHandler, handleChannelCalls, handleHttpRequests, handleStreams, httpCodec, httpFetch, httpServe, initHttpService, initServiceWorker, initializeConnection, joinLines, jsonEnvelopeCodec, map, newAsyncGenerator, newCreditGrantor, newCreditLedger, newHttpClientStub, newHttpCodec, newHttpServerStub, newInvokationChannel, newRegistry, newRemoteRelayChannel, newServiceWorkerPort, newSniffingCodec, normalizeToUint8Array, recieveIterator, registerConnectionsHandler, sendHttpRequest, sendIterator, sendStream, serializeError, serveFetchOverDuplex, splitLines, splitServiceUrl, toChunks, toReadableStream };