@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/README.md +87 -2
- package/dist/core/data-channels.d.ts.map +1 -1
- package/dist/core/message-target.d.ts +1 -15
- package/dist/core/message-target.d.ts.map +1 -1
- package/dist/http/http-send-recieve.d.ts +27 -14
- package/dist/http/http-send-recieve.d.ts.map +1 -1
- package/dist/index.js +250 -121
- package/dist/relay-sw.js +848 -66
- package/dist/sw/sw-dispatcher.d.ts.map +1 -1
- package/dist/sw-worker.js +848 -64
- package/dist/sw.js +879 -72
- package/package.json +4 -3
- package/src/core/data-channels.ts +42 -4
- package/src/core/message-target.ts +8 -18
- package/src/http/http-send-recieve.ts +27 -14
- package/src/sw/sw-dispatcher.ts +26 -1
- package/LICENSE +0 -21
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region ../webrun-streams/
|
|
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
|
-
|
|
32
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* `
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
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
|
-
|
|
145
|
-
|
|
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
|
|
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
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
|
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
|
|
248
|
-
s.
|
|
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
|
-
|
|
643
|
-
|
|
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({
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
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)
|
|
651
|
-
|
|
652
|
-
|
|
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
|
-
}
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
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
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
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) =>
|
|
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/
|
|
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-
|
|
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
|
-
*
|
|
2312
|
-
*
|
|
2313
|
-
*
|
|
2314
|
-
*
|
|
2315
|
-
*
|
|
2316
|
-
* `
|
|
2317
|
-
* `
|
|
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
|
-
*
|
|
2328
|
-
*
|
|
2329
|
-
*
|
|
2330
|
-
*
|
|
2331
|
-
*
|
|
2332
|
-
*
|
|
2333
|
-
* `
|
|
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 };
|