@statewalker/webrun-http-browser 0.12.2 → 0.12.3

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
@@ -2230,7 +2230,10 @@ async function httpFetch(call, env, body, options = {}) {
2230
2230
  }
2231
2231
  const onAbort = () => void release();
2232
2232
  signal?.addEventListener("abort", onAbort, { once: true });
2233
- drain(output).finally(() => signal?.removeEventListener("abort", onAbort));
2233
+ options.keepAlive;
2234
+ drain(output).finally(() => {
2235
+ signal?.removeEventListener("abort", onAbort);
2236
+ });
2234
2237
  };
2235
2238
  let decoded;
2236
2239
  try {
@@ -2626,7 +2629,8 @@ async function fetchOverDuplex(call, request, options = {}) {
2626
2629
  }
2627
2630
  const { envelope, body: respBody, release, finish } = await httpFetch(call, env, body, {
2628
2631
  ...options,
2629
- signal: request.signal ?? void 0
2632
+ signal: request.signal ?? void 0,
2633
+ keepAlive: request
2630
2634
  });
2631
2635
  const responseInit = {
2632
2636
  status: envelope.status,
@@ -2638,7 +2642,7 @@ async function fetchOverDuplex(call, request, options = {}) {
2638
2642
  else await release?.();
2639
2643
  return new Response(null, responseInit);
2640
2644
  }
2641
- return new Response(responseBody(respBody, request.signal, release), responseInit);
2645
+ return new Response(responseBody(respBody, request, release), responseInit);
2642
2646
  }
2643
2647
  /**
2644
2648
  * Wrap a `(Request) => Promise<Response>` handler as a `Duplex` so it can be
@@ -2791,7 +2795,9 @@ const CANCEL_GRACE_MS = 1e3;
2791
2795
  * A cancel abandons the call too — unless the read in flight comes back with the
2792
2796
  * body's end, which finishes it instead (see `cancel`).
2793
2797
  */
2794
- function responseBody(body, signal, release) {
2798
+ function responseBody(body, request, release) {
2799
+ const keep = { request };
2800
+ const signal = request.signal ?? void 0;
2795
2801
  const it = body[Symbol.asyncIterator]();
2796
2802
  let controller;
2797
2803
  let ended = false;
@@ -2799,6 +2805,7 @@ function responseBody(body, signal, release) {
2799
2805
  const end = () => {
2800
2806
  ended = true;
2801
2807
  signal?.removeEventListener("abort", onAbort);
2808
+ keep.request = void 0;
2802
2809
  };
2803
2810
  const onAbort = () => {
2804
2811
  if (ended) return;
@@ -3789,12 +3796,6 @@ async function sendChunks(port, channelName, output, { maxMessageSize, log }, si
3789
3796
  const producer = output;
3790
3797
  const returnProducer = () => void Promise.resolve(producer.return?.()).catch(() => {});
3791
3798
  if (framed !== output) signal.addEventListener("abort", returnProducer, { once: true });
3792
- let onAborted = () => {};
3793
- const aborted = new Promise((resolve) => {
3794
- onAborted = () => resolve();
3795
- });
3796
- if (signal.aborted) onAborted();
3797
- else signal.addEventListener("abort", onAborted, { once: true });
3798
3799
  try {
3799
3800
  await sendIterator(async ({ done, value, error }) => {
3800
3801
  if (signal.aborted) return;
@@ -3816,7 +3817,7 @@ async function sendChunks(port, channelName, output, { maxMessageSize, log }, si
3816
3817
  });
3817
3818
  touch();
3818
3819
  if (done && !error) onSent();
3819
- if (portBufferedAmount(port) > 0) await Promise.race([whenPortReady(port), aborted]);
3820
+ if (portBufferedAmount(port) > 0) await whenReadyOrAborted(port, signal);
3820
3821
  return;
3821
3822
  }
3822
3823
  await callPort(port, chunk, {
@@ -3832,10 +3833,25 @@ async function sendChunks(port, channelName, output, { maxMessageSize, log }, si
3832
3833
  throw err;
3833
3834
  } finally {
3834
3835
  signal.removeEventListener("abort", returnProducer);
3835
- signal.removeEventListener("abort", onAborted);
3836
3836
  }
3837
3837
  }
3838
3838
  /**
3839
+ * A pipelined sender waits only for room in the window, or for the abort. The
3840
+ * abort listener lives for this one wait: a promise held for the whole stream
3841
+ * would keep one reaction per wait until the stream ends.
3842
+ */
3843
+ function whenReadyOrAborted(port, signal) {
3844
+ if (signal.aborted) return Promise.resolve();
3845
+ return new Promise((resolve) => {
3846
+ const settle = () => {
3847
+ signal.removeEventListener("abort", settle);
3848
+ resolve();
3849
+ };
3850
+ signal.addEventListener("abort", settle, { once: true });
3851
+ whenPortReady(port).then(settle, settle);
3852
+ });
3853
+ }
3854
+ /**
3839
3855
  * One virtual port.
3840
3856
  *
3841
3857
  * `deliver` and `markClosed` are deliberately not on `port`: the consumer holds
@@ -4833,7 +4849,12 @@ function trackedLink(send, release) {
4833
4849
  finish();
4834
4850
  return response;
4835
4851
  }
4836
- return new Response(guardBody(response.body, lost, () => lostReason, finish, request.signal), {
4852
+ failers.delete(fail);
4853
+ const body = guardBody(response.body, finish, request);
4854
+ fail = body.fail;
4855
+ if (closed) fail(lostReason);
4856
+ else if (!finished) failers.add(fail);
4857
+ return new Response(body.stream, {
4837
4858
  status: response.status,
4838
4859
  statusText: response.statusText,
4839
4860
  headers: response.headers
@@ -4852,56 +4873,62 @@ function trackedLink(send, release) {
4852
4873
  return asyncDisposable(link, () => link.close());
4853
4874
  }
4854
4875
  /**
4855
- * A response body that reports its end (done, cancelled, failed or aborted) and
4856
- * fails with the link's reason when the link is lost mid-stream. An abort of the
4857
- * request's signal ends it at once, read or not: the exchange is over (the layer
4858
- * below has given its port back), so the request leaves `inflight` then.
4876
+ * A response body that reports its end (done, cancelled, failed or aborted).
4877
+ * `fail(error)` ends it with `error` at once — the link calls it when it is lost
4878
+ * mid-stream. An abort of the request's signal does the same with the signal's
4879
+ * reason, read or not: the exchange is over (the layer below has given its port
4880
+ * back), so the request leaves `inflight` then. A read waits on the reader
4881
+ * alone, so nothing outlives the chunk it brings.
4859
4882
  */
4860
- function guardBody(body, lost, reason, finish, signal) {
4883
+ function guardBody(body, finish, request) {
4884
+ const keep = { request };
4885
+ const signal = request.signal ?? void 0;
4861
4886
  const reader = body.getReader();
4862
4887
  let controller;
4863
4888
  let ended = false;
4864
4889
  const end = () => {
4865
4890
  ended = true;
4866
4891
  signal?.removeEventListener("abort", onAbort);
4892
+ keep.request = void 0;
4867
4893
  finish();
4868
4894
  };
4869
- const onAbort = () => {
4895
+ const fail = (error) => {
4870
4896
  if (ended) return;
4871
4897
  end();
4872
4898
  reader.cancel().catch(() => {});
4873
4899
  try {
4874
- controller.error(abortError(signal));
4900
+ controller.error(error);
4875
4901
  } catch {}
4876
4902
  };
4877
- return new ReadableStream({
4878
- start(c) {
4879
- controller = c;
4880
- if (signal?.aborted) onAbort();
4881
- else signal?.addEventListener("abort", onAbort, { once: true });
4882
- },
4883
- async pull(controller) {
4884
- try {
4885
- const { done, value } = await Promise.race([reader.read(), lost]);
4886
- if (ended) return;
4887
- if (done) {
4888
- end();
4889
- controller.close();
4890
- return;
4903
+ const onAbort = () => fail(abortError(signal));
4904
+ return {
4905
+ stream: new ReadableStream({
4906
+ start(c) {
4907
+ controller = c;
4908
+ if (signal?.aborted) onAbort();
4909
+ else signal?.addEventListener("abort", onAbort, { once: true });
4910
+ },
4911
+ async pull(controller) {
4912
+ try {
4913
+ const { done, value } = await reader.read();
4914
+ if (ended) return;
4915
+ if (done) {
4916
+ end();
4917
+ controller.close();
4918
+ return;
4919
+ }
4920
+ controller.enqueue(value);
4921
+ } catch (error) {
4922
+ fail(error);
4891
4923
  }
4892
- controller.enqueue(value);
4893
- } catch (error) {
4894
- if (ended) return;
4924
+ },
4925
+ cancel(why) {
4895
4926
  end();
4896
- reader.cancel().catch(() => {});
4897
- controller.error(reason() ?? error);
4927
+ setTimeout(() => void reader.cancel(why).catch(() => {}), 0);
4898
4928
  }
4899
- },
4900
- async cancel(why) {
4901
- end();
4902
- await reader.cancel(why).catch(() => {});
4903
- }
4904
- });
4929
+ }),
4930
+ fail
4931
+ };
4905
4932
  }
4906
4933
  /** The signal's reason as an `Error` (`AbortError` when it gave none). */
4907
4934
  function abortError(signal) {
@@ -8077,7 +8104,10 @@ function serveHttpInWorker(handler, options = {}) {
8077
8104
  let WebSocketClass;
8078
8105
  return {
8079
8106
  fetch(input, init) {
8080
- const request = input instanceof Request && init === void 0 ? input : new Request(input instanceof Request ? input : new URL(`${input}`, baseUrl()), init);
8107
+ const request = input instanceof Request && init === void 0 ? input : new Request(input instanceof Request ? input : new URL(`${input}`, baseUrl()), {
8108
+ ...init,
8109
+ signal: init && "signal" in init ? init.signal : input instanceof Request ? input.signal : void 0
8110
+ });
8081
8111
  return endpoint.fetch(request);
8082
8112
  },
8083
8113
  connectWebSocket,
@@ -1 +1 @@
1
- {"version":3,"file":"http-link.d.ts","sourceRoot":"","sources":["../../src/link/http-link.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,kBAAkB,uBAAuB,CAAC;AAEvD,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAKD,iEAAiE;AACjE,MAAM,WAAW,QAAQ;IACvB,0EAA0E;IAC1E,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC1C;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,2CAA2C;IAC3C,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B;;;OAGG;IACH,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B;AAED;;;;GAIG;AACH,qBAAa,aAAc,SAAQ,KAAK;IACtC,YAAY,OAAO,SAAuD,EAGzE;CACF;AASD,qEAAqE;AACrE,wBAAsB,eAAe,CACnC,IAAI,EAAE,WAAW,EACjB,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAiC,EAAE,GAAE,eAAoB,GACnF,OAAO,CAAC,QAAQ,CAAC,CAcnB;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,EAC3D,EAAE,aAAa,EAAE,QAAQ,EAAE,aAAa,EAAE,GAAE,eAAoB,GAC/D,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CAiB9B;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,EAC7C,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GAClC,QAAQ,CAwEV"}
1
+ {"version":3,"file":"http-link.d.ts","sourceRoot":"","sources":["../../src/link/http-link.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,kBAAkB,uBAAuB,CAAC;AAEvD,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAKD,iEAAiE;AACjE,MAAM,WAAW,QAAQ;IACvB,0EAA0E;IAC1E,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC1C;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,2CAA2C;IAC3C,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B;;;OAGG;IACH,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B;AAED;;;;GAIG;AACH,qBAAa,aAAc,SAAQ,KAAK;IACtC,YAAY,OAAO,SAAuD,EAGzE;CACF;AASD,qEAAqE;AACrE,wBAAsB,eAAe,CACnC,IAAI,EAAE,WAAW,EACjB,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAiC,EAAE,GAAE,eAAoB,GACnF,OAAO,CAAC,QAAQ,CAAC,CAcnB;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,EAC3D,EAAE,aAAa,EAAE,QAAQ,EAAE,aAAa,EAAE,GAAE,eAAoB,GAC/D,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CAiB9B;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,EAC7C,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GAClC,QAAQ,CA4EV"}
package/dist/relay-sw.js CHANGED
@@ -1138,7 +1138,10 @@
1138
1138
  }
1139
1139
  const onAbort = () => void release();
1140
1140
  signal?.addEventListener("abort", onAbort, { once: true });
1141
- drain(output).finally(() => signal?.removeEventListener("abort", onAbort));
1141
+ options.keepAlive;
1142
+ drain(output).finally(() => {
1143
+ signal?.removeEventListener("abort", onAbort);
1144
+ });
1142
1145
  };
1143
1146
  let decoded;
1144
1147
  try {
@@ -1388,7 +1391,8 @@
1388
1391
  }
1389
1392
  const { envelope, body: respBody, release, finish } = await httpFetch(call, env, body, {
1390
1393
  ...options,
1391
- signal: request.signal ?? void 0
1394
+ signal: request.signal ?? void 0,
1395
+ keepAlive: request
1392
1396
  });
1393
1397
  const responseInit = {
1394
1398
  status: envelope.status,
@@ -1400,7 +1404,7 @@
1400
1404
  else await release?.();
1401
1405
  return new Response(null, responseInit);
1402
1406
  }
1403
- return new Response(responseBody(respBody, request.signal, release), responseInit);
1407
+ return new Response(responseBody(respBody, request, release), responseInit);
1404
1408
  }
1405
1409
  /**
1406
1410
  * How long a cancelled body waits for the read in flight before it abandons the call anyway.
@@ -1423,7 +1427,9 @@
1423
1427
  * A cancel abandons the call too — unless the read in flight comes back with the
1424
1428
  * body's end, which finishes it instead (see `cancel`).
1425
1429
  */
1426
- function responseBody(body, signal, release) {
1430
+ function responseBody(body, request, release) {
1431
+ const keep = { request };
1432
+ const signal = request.signal ?? void 0;
1427
1433
  const it = body[Symbol.asyncIterator]();
1428
1434
  let controller;
1429
1435
  let ended = false;
@@ -1431,6 +1437,7 @@
1431
1437
  const end = () => {
1432
1438
  ended = true;
1433
1439
  signal?.removeEventListener("abort", onAbort);
1440
+ keep.request = void 0;
1434
1441
  };
1435
1442
  const onAbort = () => {
1436
1443
  if (ended) return;
@@ -2394,12 +2401,6 @@
2394
2401
  const producer = output;
2395
2402
  const returnProducer = () => void Promise.resolve(producer.return?.()).catch(() => {});
2396
2403
  if (framed !== output) signal.addEventListener("abort", returnProducer, { once: true });
2397
- let onAborted = () => {};
2398
- const aborted = new Promise((resolve) => {
2399
- onAborted = () => resolve();
2400
- });
2401
- if (signal.aborted) onAborted();
2402
- else signal.addEventListener("abort", onAborted, { once: true });
2403
2404
  try {
2404
2405
  await sendIterator(async ({ done, value, error }) => {
2405
2406
  if (signal.aborted) return;
@@ -2421,7 +2422,7 @@
2421
2422
  });
2422
2423
  touch();
2423
2424
  if (done && !error) onSent();
2424
- if (portBufferedAmount(port) > 0) await Promise.race([whenPortReady(port), aborted]);
2425
+ if (portBufferedAmount(port) > 0) await whenReadyOrAborted(port, signal);
2425
2426
  return;
2426
2427
  }
2427
2428
  await callPort(port, chunk, {
@@ -2437,10 +2438,25 @@
2437
2438
  throw err;
2438
2439
  } finally {
2439
2440
  signal.removeEventListener("abort", returnProducer);
2440
- signal.removeEventListener("abort", onAborted);
2441
2441
  }
2442
2442
  }
2443
2443
  /**
2444
+ * A pipelined sender waits only for room in the window, or for the abort. The
2445
+ * abort listener lives for this one wait: a promise held for the whole stream
2446
+ * would keep one reaction per wait until the stream ends.
2447
+ */
2448
+ function whenReadyOrAborted(port, signal) {
2449
+ if (signal.aborted) return Promise.resolve();
2450
+ return new Promise((resolve) => {
2451
+ const settle = () => {
2452
+ signal.removeEventListener("abort", settle);
2453
+ resolve();
2454
+ };
2455
+ signal.addEventListener("abort", settle, { once: true });
2456
+ whenPortReady(port).then(settle, settle);
2457
+ });
2458
+ }
2459
+ /**
2444
2460
  * One virtual port.
2445
2461
  *
2446
2462
  * `deliver` and `markClosed` are deliberately not on `port`: the consumer holds
@@ -3366,7 +3382,12 @@
3366
3382
  finish();
3367
3383
  return response;
3368
3384
  }
3369
- return new Response(guardBody(response.body, lost, () => lostReason, finish, request.signal), {
3385
+ failers.delete(fail);
3386
+ const body = guardBody(response.body, finish, request);
3387
+ fail = body.fail;
3388
+ if (closed) fail(lostReason);
3389
+ else if (!finished) failers.add(fail);
3390
+ return new Response(body.stream, {
3370
3391
  status: response.status,
3371
3392
  statusText: response.statusText,
3372
3393
  headers: response.headers
@@ -3385,56 +3406,62 @@
3385
3406
  return asyncDisposable(link, () => link.close());
3386
3407
  }
3387
3408
  /**
3388
- * A response body that reports its end (done, cancelled, failed or aborted) and
3389
- * fails with the link's reason when the link is lost mid-stream. An abort of the
3390
- * request's signal ends it at once, read or not: the exchange is over (the layer
3391
- * below has given its port back), so the request leaves `inflight` then.
3409
+ * A response body that reports its end (done, cancelled, failed or aborted).
3410
+ * `fail(error)` ends it with `error` at once — the link calls it when it is lost
3411
+ * mid-stream. An abort of the request's signal does the same with the signal's
3412
+ * reason, read or not: the exchange is over (the layer below has given its port
3413
+ * back), so the request leaves `inflight` then. A read waits on the reader
3414
+ * alone, so nothing outlives the chunk it brings.
3392
3415
  */
3393
- function guardBody(body, lost, reason, finish, signal) {
3416
+ function guardBody(body, finish, request) {
3417
+ const keep = { request };
3418
+ const signal = request.signal ?? void 0;
3394
3419
  const reader = body.getReader();
3395
3420
  let controller;
3396
3421
  let ended = false;
3397
3422
  const end = () => {
3398
3423
  ended = true;
3399
3424
  signal?.removeEventListener("abort", onAbort);
3425
+ keep.request = void 0;
3400
3426
  finish();
3401
3427
  };
3402
- const onAbort = () => {
3428
+ const fail = (error) => {
3403
3429
  if (ended) return;
3404
3430
  end();
3405
3431
  reader.cancel().catch(() => {});
3406
3432
  try {
3407
- controller.error(abortError(signal));
3433
+ controller.error(error);
3408
3434
  } catch {}
3409
3435
  };
3410
- return new ReadableStream({
3411
- start(c) {
3412
- controller = c;
3413
- if (signal?.aborted) onAbort();
3414
- else signal?.addEventListener("abort", onAbort, { once: true });
3415
- },
3416
- async pull(controller) {
3417
- try {
3418
- const { done, value } = await Promise.race([reader.read(), lost]);
3419
- if (ended) return;
3420
- if (done) {
3421
- end();
3422
- controller.close();
3423
- return;
3436
+ const onAbort = () => fail(abortError(signal));
3437
+ return {
3438
+ stream: new ReadableStream({
3439
+ start(c) {
3440
+ controller = c;
3441
+ if (signal?.aborted) onAbort();
3442
+ else signal?.addEventListener("abort", onAbort, { once: true });
3443
+ },
3444
+ async pull(controller) {
3445
+ try {
3446
+ const { done, value } = await reader.read();
3447
+ if (ended) return;
3448
+ if (done) {
3449
+ end();
3450
+ controller.close();
3451
+ return;
3452
+ }
3453
+ controller.enqueue(value);
3454
+ } catch (error) {
3455
+ fail(error);
3424
3456
  }
3425
- controller.enqueue(value);
3426
- } catch (error) {
3427
- if (ended) return;
3457
+ },
3458
+ cancel(why) {
3428
3459
  end();
3429
- reader.cancel().catch(() => {});
3430
- controller.error(reason() ?? error);
3460
+ setTimeout(() => void reader.cancel(why).catch(() => {}), 0);
3431
3461
  }
3432
- },
3433
- async cancel(why) {
3434
- end();
3435
- await reader.cancel(why).catch(() => {});
3436
- }
3437
- });
3462
+ }),
3463
+ fail
3464
+ };
3438
3465
  }
3439
3466
  /** The signal's reason as an `Error` (`AbortError` when it gave none). */
3440
3467
  function abortError(signal) {
@@ -1137,7 +1137,10 @@ async function httpFetch(call, env, body, options = {}) {
1137
1137
  }
1138
1138
  const onAbort = () => void release();
1139
1139
  signal?.addEventListener("abort", onAbort, { once: true });
1140
- drain(output).finally(() => signal?.removeEventListener("abort", onAbort));
1140
+ options.keepAlive;
1141
+ drain(output).finally(() => {
1142
+ signal?.removeEventListener("abort", onAbort);
1143
+ });
1141
1144
  };
1142
1145
  let decoded;
1143
1146
  try {
@@ -1387,7 +1390,8 @@ async function fetchOverDuplex(call, request, options = {}) {
1387
1390
  }
1388
1391
  const { envelope, body: respBody, release, finish } = await httpFetch(call, env, body, {
1389
1392
  ...options,
1390
- signal: request.signal ?? void 0
1393
+ signal: request.signal ?? void 0,
1394
+ keepAlive: request
1391
1395
  });
1392
1396
  const responseInit = {
1393
1397
  status: envelope.status,
@@ -1399,7 +1403,7 @@ async function fetchOverDuplex(call, request, options = {}) {
1399
1403
  else await release?.();
1400
1404
  return new Response(null, responseInit);
1401
1405
  }
1402
- return new Response(responseBody(respBody, request.signal, release), responseInit);
1406
+ return new Response(responseBody(respBody, request, release), responseInit);
1403
1407
  }
1404
1408
  /**
1405
1409
  * How long a cancelled body waits for the read in flight before it abandons the call anyway.
@@ -1422,7 +1426,9 @@ const CANCEL_GRACE_MS = 1e3;
1422
1426
  * A cancel abandons the call too — unless the read in flight comes back with the
1423
1427
  * body's end, which finishes it instead (see `cancel`).
1424
1428
  */
1425
- function responseBody(body, signal, release) {
1429
+ function responseBody(body, request, release) {
1430
+ const keep = { request };
1431
+ const signal = request.signal ?? void 0;
1426
1432
  const it = body[Symbol.asyncIterator]();
1427
1433
  let controller;
1428
1434
  let ended = false;
@@ -1430,6 +1436,7 @@ function responseBody(body, signal, release) {
1430
1436
  const end = () => {
1431
1437
  ended = true;
1432
1438
  signal?.removeEventListener("abort", onAbort);
1439
+ keep.request = void 0;
1433
1440
  };
1434
1441
  const onAbort = () => {
1435
1442
  if (ended) return;
@@ -2393,12 +2400,6 @@ async function sendChunks(port, channelName, output, { maxMessageSize, log }, si
2393
2400
  const producer = output;
2394
2401
  const returnProducer = () => void Promise.resolve(producer.return?.()).catch(() => {});
2395
2402
  if (framed !== output) signal.addEventListener("abort", returnProducer, { once: true });
2396
- let onAborted = () => {};
2397
- const aborted = new Promise((resolve) => {
2398
- onAborted = () => resolve();
2399
- });
2400
- if (signal.aborted) onAborted();
2401
- else signal.addEventListener("abort", onAborted, { once: true });
2402
2403
  try {
2403
2404
  await sendIterator(async ({ done, value, error }) => {
2404
2405
  if (signal.aborted) return;
@@ -2420,7 +2421,7 @@ async function sendChunks(port, channelName, output, { maxMessageSize, log }, si
2420
2421
  });
2421
2422
  touch();
2422
2423
  if (done && !error) onSent();
2423
- if (portBufferedAmount(port) > 0) await Promise.race([whenPortReady(port), aborted]);
2424
+ if (portBufferedAmount(port) > 0) await whenReadyOrAborted(port, signal);
2424
2425
  return;
2425
2426
  }
2426
2427
  await callPort(port, chunk, {
@@ -2436,10 +2437,25 @@ async function sendChunks(port, channelName, output, { maxMessageSize, log }, si
2436
2437
  throw err;
2437
2438
  } finally {
2438
2439
  signal.removeEventListener("abort", returnProducer);
2439
- signal.removeEventListener("abort", onAborted);
2440
2440
  }
2441
2441
  }
2442
2442
  /**
2443
+ * A pipelined sender waits only for room in the window, or for the abort. The
2444
+ * abort listener lives for this one wait: a promise held for the whole stream
2445
+ * would keep one reaction per wait until the stream ends.
2446
+ */
2447
+ function whenReadyOrAborted(port, signal) {
2448
+ if (signal.aborted) return Promise.resolve();
2449
+ return new Promise((resolve) => {
2450
+ const settle = () => {
2451
+ signal.removeEventListener("abort", settle);
2452
+ resolve();
2453
+ };
2454
+ signal.addEventListener("abort", settle, { once: true });
2455
+ whenPortReady(port).then(settle, settle);
2456
+ });
2457
+ }
2458
+ /**
2443
2459
  * One virtual port.
2444
2460
  *
2445
2461
  * `deliver` and `markClosed` are deliberately not on `port`: the consumer holds
@@ -3365,7 +3381,12 @@ function trackedLink(send, release) {
3365
3381
  finish();
3366
3382
  return response;
3367
3383
  }
3368
- return new Response(guardBody(response.body, lost, () => lostReason, finish, request.signal), {
3384
+ failers.delete(fail);
3385
+ const body = guardBody(response.body, finish, request);
3386
+ fail = body.fail;
3387
+ if (closed) fail(lostReason);
3388
+ else if (!finished) failers.add(fail);
3389
+ return new Response(body.stream, {
3369
3390
  status: response.status,
3370
3391
  statusText: response.statusText,
3371
3392
  headers: response.headers
@@ -3384,56 +3405,62 @@ function trackedLink(send, release) {
3384
3405
  return asyncDisposable(link, () => link.close());
3385
3406
  }
3386
3407
  /**
3387
- * A response body that reports its end (done, cancelled, failed or aborted) and
3388
- * fails with the link's reason when the link is lost mid-stream. An abort of the
3389
- * request's signal ends it at once, read or not: the exchange is over (the layer
3390
- * below has given its port back), so the request leaves `inflight` then.
3408
+ * A response body that reports its end (done, cancelled, failed or aborted).
3409
+ * `fail(error)` ends it with `error` at once — the link calls it when it is lost
3410
+ * mid-stream. An abort of the request's signal does the same with the signal's
3411
+ * reason, read or not: the exchange is over (the layer below has given its port
3412
+ * back), so the request leaves `inflight` then. A read waits on the reader
3413
+ * alone, so nothing outlives the chunk it brings.
3391
3414
  */
3392
- function guardBody(body, lost, reason, finish, signal) {
3415
+ function guardBody(body, finish, request) {
3416
+ const keep = { request };
3417
+ const signal = request.signal ?? void 0;
3393
3418
  const reader = body.getReader();
3394
3419
  let controller;
3395
3420
  let ended = false;
3396
3421
  const end = () => {
3397
3422
  ended = true;
3398
3423
  signal?.removeEventListener("abort", onAbort);
3424
+ keep.request = void 0;
3399
3425
  finish();
3400
3426
  };
3401
- const onAbort = () => {
3427
+ const fail = (error) => {
3402
3428
  if (ended) return;
3403
3429
  end();
3404
3430
  reader.cancel().catch(() => {});
3405
3431
  try {
3406
- controller.error(abortError(signal));
3432
+ controller.error(error);
3407
3433
  } catch {}
3408
3434
  };
3409
- return new ReadableStream({
3410
- start(c) {
3411
- controller = c;
3412
- if (signal?.aborted) onAbort();
3413
- else signal?.addEventListener("abort", onAbort, { once: true });
3414
- },
3415
- async pull(controller) {
3416
- try {
3417
- const { done, value } = await Promise.race([reader.read(), lost]);
3418
- if (ended) return;
3419
- if (done) {
3420
- end();
3421
- controller.close();
3422
- return;
3435
+ const onAbort = () => fail(abortError(signal));
3436
+ return {
3437
+ stream: new ReadableStream({
3438
+ start(c) {
3439
+ controller = c;
3440
+ if (signal?.aborted) onAbort();
3441
+ else signal?.addEventListener("abort", onAbort, { once: true });
3442
+ },
3443
+ async pull(controller) {
3444
+ try {
3445
+ const { done, value } = await reader.read();
3446
+ if (ended) return;
3447
+ if (done) {
3448
+ end();
3449
+ controller.close();
3450
+ return;
3451
+ }
3452
+ controller.enqueue(value);
3453
+ } catch (error) {
3454
+ fail(error);
3423
3455
  }
3424
- controller.enqueue(value);
3425
- } catch (error) {
3426
- if (ended) return;
3456
+ },
3457
+ cancel(why) {
3427
3458
  end();
3428
- reader.cancel().catch(() => {});
3429
- controller.error(reason() ?? error);
3459
+ setTimeout(() => void reader.cancel(why).catch(() => {}), 0);
3430
3460
  }
3431
- },
3432
- async cancel(why) {
3433
- end();
3434
- await reader.cancel(why).catch(() => {});
3435
- }
3436
- });
3461
+ }),
3462
+ fail
3463
+ };
3437
3464
  }
3438
3465
  /** The signal's reason as an `Error` (`AbortError` when it gave none). */
3439
3466
  function abortError(signal) {
@@ -1 +1 @@
1
- {"version":3,"file":"worker-service.d.ts","sourceRoot":"","sources":["../../src/sw/worker-service.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACzB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,KAAK,YAAY,EAEjB,KAAK,sBAAsB,EAC5B,MAAM,uBAAuB,CAAC;AAG/B,MAAM,WAAW,wBACf,SAAQ,IAAI,CAAC,sBAAsB,EAAE,eAAe,GAAG,WAAW,CAAC;IACnE,mFAAmF;IACnF,KAAK,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,kBAAkB,GAAG,qBAAqB,CAAC,CAAC;IACtE,8DAA8D;IAC9D,SAAS,CAAC,EAAE,qBAAqB,CAAC;CACnC;AAED,yDAAyD;AACzD,MAAM,WAAW,iBAAiB;IAChC;;;;;;OAMG;IACH,KAAK,CAAC,KAAK,EAAE,WAAW,GAAG,GAAG,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IACvE;;;OAGG;IACH,gBAAgB,CACd,GAAG,EAAE,MAAM,GAAG,GAAG,EACjB,SAAS,CAAC,EAAE,MAAM,EAAE,EACpB,OAAO,CAAC,EAAE,WAAW,GACpB,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAChC,0DAA0D;IAC1D,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAC;IACvC,0CAA0C;IAC1C,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,8CAA8C;IAC9C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,YAAY,EACrB,OAAO,GAAE,wBAA6B,GACrC,iBAAiB,CAoFnB"}
1
+ {"version":3,"file":"worker-service.d.ts","sourceRoot":"","sources":["../../src/sw/worker-service.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACzB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,KAAK,YAAY,EAEjB,KAAK,sBAAsB,EAC5B,MAAM,uBAAuB,CAAC;AAG/B,MAAM,WAAW,wBACf,SAAQ,IAAI,CAAC,sBAAsB,EAAE,eAAe,GAAG,WAAW,CAAC;IACnE,mFAAmF;IACnF,KAAK,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,kBAAkB,GAAG,qBAAqB,CAAC,CAAC;IACtE,8DAA8D;IAC9D,SAAS,CAAC,EAAE,qBAAqB,CAAC;CACnC;AAED,yDAAyD;AACzD,MAAM,WAAW,iBAAiB;IAChC;;;;;;OAMG;IACH,KAAK,CAAC,KAAK,EAAE,WAAW,GAAG,GAAG,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IACvE;;;OAGG;IACH,gBAAgB,CACd,GAAG,EAAE,MAAM,GAAG,GAAG,EACjB,SAAS,CAAC,EAAE,MAAM,EAAE,EACpB,OAAO,CAAC,EAAE,WAAW,GACpB,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAChC,0DAA0D;IAC1D,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAC;IACvC,0CAA0C;IAC1C,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,8CAA8C;IAC9C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,YAAY,EACrB,OAAO,GAAE,wBAA6B,GACrC,iBAAiB,CA+FnB"}