@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/sw-worker.js CHANGED
@@ -1312,7 +1312,10 @@
1312
1312
  }
1313
1313
  const onAbort = () => void release();
1314
1314
  signal?.addEventListener("abort", onAbort, { once: true });
1315
- drain(output).finally(() => signal?.removeEventListener("abort", onAbort));
1315
+ options.keepAlive;
1316
+ drain(output).finally(() => {
1317
+ signal?.removeEventListener("abort", onAbort);
1318
+ });
1316
1319
  };
1317
1320
  let decoded;
1318
1321
  try {
@@ -1675,7 +1678,8 @@
1675
1678
  }
1676
1679
  const { envelope, body: respBody, release, finish } = await httpFetch(call, env, body, {
1677
1680
  ...options,
1678
- signal: request.signal ?? void 0
1681
+ signal: request.signal ?? void 0,
1682
+ keepAlive: request
1679
1683
  });
1680
1684
  const responseInit = {
1681
1685
  status: envelope.status,
@@ -1687,7 +1691,7 @@
1687
1691
  else await release?.();
1688
1692
  return new Response(null, responseInit);
1689
1693
  }
1690
- return new Response(responseBody(respBody, request.signal, release), responseInit);
1694
+ return new Response(responseBody(respBody, request, release), responseInit);
1691
1695
  }
1692
1696
  /**
1693
1697
  * Wrap a `(Request) => Promise<Response>` handler as a `Duplex` so it can be
@@ -1840,7 +1844,9 @@
1840
1844
  * A cancel abandons the call too — unless the read in flight comes back with the
1841
1845
  * body's end, which finishes it instead (see `cancel`).
1842
1846
  */
1843
- function responseBody(body, signal, release) {
1847
+ function responseBody(body, request, release) {
1848
+ const keep = { request };
1849
+ const signal = request.signal ?? void 0;
1844
1850
  const it = body[Symbol.asyncIterator]();
1845
1851
  let controller;
1846
1852
  let ended = false;
@@ -1848,6 +1854,7 @@
1848
1854
  const end = () => {
1849
1855
  ended = true;
1850
1856
  signal?.removeEventListener("abort", onAbort);
1857
+ keep.request = void 0;
1851
1858
  };
1852
1859
  const onAbort = () => {
1853
1860
  if (ended) return;
@@ -2879,12 +2886,6 @@
2879
2886
  const producer = output;
2880
2887
  const returnProducer = () => void Promise.resolve(producer.return?.()).catch(() => {});
2881
2888
  if (framed !== output) signal.addEventListener("abort", returnProducer, { once: true });
2882
- let onAborted = () => {};
2883
- const aborted = new Promise((resolve) => {
2884
- onAborted = () => resolve();
2885
- });
2886
- if (signal.aborted) onAborted();
2887
- else signal.addEventListener("abort", onAborted, { once: true });
2888
2889
  try {
2889
2890
  await sendIterator(async ({ done, value, error }) => {
2890
2891
  if (signal.aborted) return;
@@ -2906,7 +2907,7 @@
2906
2907
  });
2907
2908
  touch();
2908
2909
  if (done && !error) onSent();
2909
- if (portBufferedAmount(port) > 0) await Promise.race([whenPortReady(port), aborted]);
2910
+ if (portBufferedAmount(port) > 0) await whenReadyOrAborted(port, signal);
2910
2911
  return;
2911
2912
  }
2912
2913
  await callPort(port, chunk, {
@@ -2922,10 +2923,25 @@
2922
2923
  throw err;
2923
2924
  } finally {
2924
2925
  signal.removeEventListener("abort", returnProducer);
2925
- signal.removeEventListener("abort", onAborted);
2926
2926
  }
2927
2927
  }
2928
2928
  /**
2929
+ * A pipelined sender waits only for room in the window, or for the abort. The
2930
+ * abort listener lives for this one wait: a promise held for the whole stream
2931
+ * would keep one reaction per wait until the stream ends.
2932
+ */
2933
+ function whenReadyOrAborted(port, signal) {
2934
+ if (signal.aborted) return Promise.resolve();
2935
+ return new Promise((resolve) => {
2936
+ const settle = () => {
2937
+ signal.removeEventListener("abort", settle);
2938
+ resolve();
2939
+ };
2940
+ signal.addEventListener("abort", settle, { once: true });
2941
+ whenPortReady(port).then(settle, settle);
2942
+ });
2943
+ }
2944
+ /**
2929
2945
  * One virtual port.
2930
2946
  *
2931
2947
  * `deliver` and `markClosed` are deliberately not on `port`: the consumer holds
@@ -3923,7 +3939,12 @@
3923
3939
  finish();
3924
3940
  return response;
3925
3941
  }
3926
- return new Response(guardBody(response.body, lost, () => lostReason, finish, request.signal), {
3942
+ failers.delete(fail);
3943
+ const body = guardBody(response.body, finish, request);
3944
+ fail = body.fail;
3945
+ if (closed) fail(lostReason);
3946
+ else if (!finished) failers.add(fail);
3947
+ return new Response(body.stream, {
3927
3948
  status: response.status,
3928
3949
  statusText: response.statusText,
3929
3950
  headers: response.headers
@@ -3942,56 +3963,62 @@
3942
3963
  return asyncDisposable(link, () => link.close());
3943
3964
  }
3944
3965
  /**
3945
- * A response body that reports its end (done, cancelled, failed or aborted) and
3946
- * fails with the link's reason when the link is lost mid-stream. An abort of the
3947
- * request's signal ends it at once, read or not: the exchange is over (the layer
3948
- * below has given its port back), so the request leaves `inflight` then.
3966
+ * A response body that reports its end (done, cancelled, failed or aborted).
3967
+ * `fail(error)` ends it with `error` at once — the link calls it when it is lost
3968
+ * mid-stream. An abort of the request's signal does the same with the signal's
3969
+ * reason, read or not: the exchange is over (the layer below has given its port
3970
+ * back), so the request leaves `inflight` then. A read waits on the reader
3971
+ * alone, so nothing outlives the chunk it brings.
3949
3972
  */
3950
- function guardBody(body, lost, reason, finish, signal) {
3973
+ function guardBody(body, finish, request) {
3974
+ const keep = { request };
3975
+ const signal = request.signal ?? void 0;
3951
3976
  const reader = body.getReader();
3952
3977
  let controller;
3953
3978
  let ended = false;
3954
3979
  const end = () => {
3955
3980
  ended = true;
3956
3981
  signal?.removeEventListener("abort", onAbort);
3982
+ keep.request = void 0;
3957
3983
  finish();
3958
3984
  };
3959
- const onAbort = () => {
3985
+ const fail = (error) => {
3960
3986
  if (ended) return;
3961
3987
  end();
3962
3988
  reader.cancel().catch(() => {});
3963
3989
  try {
3964
- controller.error(abortError(signal));
3990
+ controller.error(error);
3965
3991
  } catch {}
3966
3992
  };
3967
- return new ReadableStream({
3968
- start(c) {
3969
- controller = c;
3970
- if (signal?.aborted) onAbort();
3971
- else signal?.addEventListener("abort", onAbort, { once: true });
3972
- },
3973
- async pull(controller) {
3974
- try {
3975
- const { done, value } = await Promise.race([reader.read(), lost]);
3976
- if (ended) return;
3977
- if (done) {
3978
- end();
3979
- controller.close();
3980
- return;
3993
+ const onAbort = () => fail(abortError(signal));
3994
+ return {
3995
+ stream: new ReadableStream({
3996
+ start(c) {
3997
+ controller = c;
3998
+ if (signal?.aborted) onAbort();
3999
+ else signal?.addEventListener("abort", onAbort, { once: true });
4000
+ },
4001
+ async pull(controller) {
4002
+ try {
4003
+ const { done, value } = await reader.read();
4004
+ if (ended) return;
4005
+ if (done) {
4006
+ end();
4007
+ controller.close();
4008
+ return;
4009
+ }
4010
+ controller.enqueue(value);
4011
+ } catch (error) {
4012
+ fail(error);
3981
4013
  }
3982
- controller.enqueue(value);
3983
- } catch (error) {
3984
- if (ended) return;
4014
+ },
4015
+ cancel(why) {
3985
4016
  end();
3986
- reader.cancel().catch(() => {});
3987
- controller.error(reason() ?? error);
4017
+ setTimeout(() => void reader.cancel(why).catch(() => {}), 0);
3988
4018
  }
3989
- },
3990
- async cancel(why) {
3991
- end();
3992
- await reader.cancel(why).catch(() => {});
3993
- }
3994
- });
4019
+ }),
4020
+ fail
4021
+ };
3995
4022
  }
3996
4023
  /** The signal's reason as an `Error` (`AbortError` when it gave none). */
3997
4024
  function abortError(signal) {
package/dist/sw.js CHANGED
@@ -1183,7 +1183,10 @@ async function httpFetch(call, env, body, options = {}) {
1183
1183
  }
1184
1184
  const onAbort = () => void release();
1185
1185
  signal?.addEventListener("abort", onAbort, { once: true });
1186
- drain(output).finally(() => signal?.removeEventListener("abort", onAbort));
1186
+ options.keepAlive;
1187
+ drain(output).finally(() => {
1188
+ signal?.removeEventListener("abort", onAbort);
1189
+ });
1187
1190
  };
1188
1191
  let decoded;
1189
1192
  try {
@@ -1546,7 +1549,8 @@ async function fetchOverDuplex(call, request, options = {}) {
1546
1549
  }
1547
1550
  const { envelope, body: respBody, release, finish } = await httpFetch(call, env, body, {
1548
1551
  ...options,
1549
- signal: request.signal ?? void 0
1552
+ signal: request.signal ?? void 0,
1553
+ keepAlive: request
1550
1554
  });
1551
1555
  const responseInit = {
1552
1556
  status: envelope.status,
@@ -1558,7 +1562,7 @@ async function fetchOverDuplex(call, request, options = {}) {
1558
1562
  else await release?.();
1559
1563
  return new Response(null, responseInit);
1560
1564
  }
1561
- return new Response(responseBody(respBody, request.signal, release), responseInit);
1565
+ return new Response(responseBody(respBody, request, release), responseInit);
1562
1566
  }
1563
1567
  /**
1564
1568
  * Wrap a `(Request) => Promise<Response>` handler as a `Duplex` so it can be
@@ -1711,7 +1715,9 @@ const CANCEL_GRACE_MS = 1e3;
1711
1715
  * A cancel abandons the call too — unless the read in flight comes back with the
1712
1716
  * body's end, which finishes it instead (see `cancel`).
1713
1717
  */
1714
- function responseBody(body, signal, release) {
1718
+ function responseBody(body, request, release) {
1719
+ const keep = { request };
1720
+ const signal = request.signal ?? void 0;
1715
1721
  const it = body[Symbol.asyncIterator]();
1716
1722
  let controller;
1717
1723
  let ended = false;
@@ -1719,6 +1725,7 @@ function responseBody(body, signal, release) {
1719
1725
  const end = () => {
1720
1726
  ended = true;
1721
1727
  signal?.removeEventListener("abort", onAbort);
1728
+ keep.request = void 0;
1722
1729
  };
1723
1730
  const onAbort = () => {
1724
1731
  if (ended) return;
@@ -2547,12 +2554,6 @@ async function sendChunks(port, channelName, output, { maxMessageSize, log }, si
2547
2554
  const producer = output;
2548
2555
  const returnProducer = () => void Promise.resolve(producer.return?.()).catch(() => {});
2549
2556
  if (framed !== output) signal.addEventListener("abort", returnProducer, { once: true });
2550
- let onAborted = () => {};
2551
- const aborted = new Promise((resolve) => {
2552
- onAborted = () => resolve();
2553
- });
2554
- if (signal.aborted) onAborted();
2555
- else signal.addEventListener("abort", onAborted, { once: true });
2556
2557
  try {
2557
2558
  await sendIterator(async ({ done, value, error }) => {
2558
2559
  if (signal.aborted) return;
@@ -2574,7 +2575,7 @@ async function sendChunks(port, channelName, output, { maxMessageSize, log }, si
2574
2575
  });
2575
2576
  touch();
2576
2577
  if (done && !error) onSent();
2577
- if (portBufferedAmount(port) > 0) await Promise.race([whenPortReady(port), aborted]);
2578
+ if (portBufferedAmount(port) > 0) await whenReadyOrAborted(port, signal);
2578
2579
  return;
2579
2580
  }
2580
2581
  await callPort(port, chunk, {
@@ -2590,10 +2591,25 @@ async function sendChunks(port, channelName, output, { maxMessageSize, log }, si
2590
2591
  throw err;
2591
2592
  } finally {
2592
2593
  signal.removeEventListener("abort", returnProducer);
2593
- signal.removeEventListener("abort", onAborted);
2594
2594
  }
2595
2595
  }
2596
2596
  /**
2597
+ * A pipelined sender waits only for room in the window, or for the abort. The
2598
+ * abort listener lives for this one wait: a promise held for the whole stream
2599
+ * would keep one reaction per wait until the stream ends.
2600
+ */
2601
+ function whenReadyOrAborted(port, signal) {
2602
+ if (signal.aborted) return Promise.resolve();
2603
+ return new Promise((resolve) => {
2604
+ const settle = () => {
2605
+ signal.removeEventListener("abort", settle);
2606
+ resolve();
2607
+ };
2608
+ signal.addEventListener("abort", settle, { once: true });
2609
+ whenPortReady(port).then(settle, settle);
2610
+ });
2611
+ }
2612
+ /**
2597
2613
  * One virtual port.
2598
2614
  *
2599
2615
  * `deliver` and `markClosed` are deliberately not on `port`: the consumer holds
@@ -3591,7 +3607,12 @@ function trackedLink(send, release) {
3591
3607
  finish();
3592
3608
  return response;
3593
3609
  }
3594
- return new Response(guardBody(response.body, lost, () => lostReason, finish, request.signal), {
3610
+ failers.delete(fail);
3611
+ const body = guardBody(response.body, finish, request);
3612
+ fail = body.fail;
3613
+ if (closed) fail(lostReason);
3614
+ else if (!finished) failers.add(fail);
3615
+ return new Response(body.stream, {
3595
3616
  status: response.status,
3596
3617
  statusText: response.statusText,
3597
3618
  headers: response.headers
@@ -3610,56 +3631,62 @@ function trackedLink(send, release) {
3610
3631
  return asyncDisposable(link, () => link.close());
3611
3632
  }
3612
3633
  /**
3613
- * A response body that reports its end (done, cancelled, failed or aborted) and
3614
- * fails with the link's reason when the link is lost mid-stream. An abort of the
3615
- * request's signal ends it at once, read or not: the exchange is over (the layer
3616
- * below has given its port back), so the request leaves `inflight` then.
3634
+ * A response body that reports its end (done, cancelled, failed or aborted).
3635
+ * `fail(error)` ends it with `error` at once — the link calls it when it is lost
3636
+ * mid-stream. An abort of the request's signal does the same with the signal's
3637
+ * reason, read or not: the exchange is over (the layer below has given its port
3638
+ * back), so the request leaves `inflight` then. A read waits on the reader
3639
+ * alone, so nothing outlives the chunk it brings.
3617
3640
  */
3618
- function guardBody(body, lost, reason, finish, signal) {
3641
+ function guardBody(body, finish, request) {
3642
+ const keep = { request };
3643
+ const signal = request.signal ?? void 0;
3619
3644
  const reader = body.getReader();
3620
3645
  let controller;
3621
3646
  let ended = false;
3622
3647
  const end = () => {
3623
3648
  ended = true;
3624
3649
  signal?.removeEventListener("abort", onAbort);
3650
+ keep.request = void 0;
3625
3651
  finish();
3626
3652
  };
3627
- const onAbort = () => {
3653
+ const fail = (error) => {
3628
3654
  if (ended) return;
3629
3655
  end();
3630
3656
  reader.cancel().catch(() => {});
3631
3657
  try {
3632
- controller.error(abortError(signal));
3658
+ controller.error(error);
3633
3659
  } catch {}
3634
3660
  };
3635
- return new ReadableStream({
3636
- start(c) {
3637
- controller = c;
3638
- if (signal?.aborted) onAbort();
3639
- else signal?.addEventListener("abort", onAbort, { once: true });
3640
- },
3641
- async pull(controller) {
3642
- try {
3643
- const { done, value } = await Promise.race([reader.read(), lost]);
3644
- if (ended) return;
3645
- if (done) {
3646
- end();
3647
- controller.close();
3648
- return;
3661
+ const onAbort = () => fail(abortError(signal));
3662
+ return {
3663
+ stream: new ReadableStream({
3664
+ start(c) {
3665
+ controller = c;
3666
+ if (signal?.aborted) onAbort();
3667
+ else signal?.addEventListener("abort", onAbort, { once: true });
3668
+ },
3669
+ async pull(controller) {
3670
+ try {
3671
+ const { done, value } = await reader.read();
3672
+ if (ended) return;
3673
+ if (done) {
3674
+ end();
3675
+ controller.close();
3676
+ return;
3677
+ }
3678
+ controller.enqueue(value);
3679
+ } catch (error) {
3680
+ fail(error);
3649
3681
  }
3650
- controller.enqueue(value);
3651
- } catch (error) {
3652
- if (ended) return;
3682
+ },
3683
+ cancel(why) {
3653
3684
  end();
3654
- reader.cancel().catch(() => {});
3655
- controller.error(reason() ?? error);
3685
+ setTimeout(() => void reader.cancel(why).catch(() => {}), 0);
3656
3686
  }
3657
- },
3658
- async cancel(why) {
3659
- end();
3660
- await reader.cancel(why).catch(() => {});
3661
- }
3662
- });
3687
+ }),
3688
+ fail
3689
+ };
3663
3690
  }
3664
3691
  /** The signal's reason as an `Error` (`AbortError` when it gave none). */
3665
3692
  function abortError(signal) {
@@ -6928,7 +6955,10 @@ function serveHttpInWorker(handler, options = {}) {
6928
6955
  let WebSocketClass;
6929
6956
  return {
6930
6957
  fetch(input, init) {
6931
- const request = input instanceof Request && init === void 0 ? input : new Request(input instanceof Request ? input : new URL(`${input}`, baseUrl()), init);
6958
+ const request = input instanceof Request && init === void 0 ? input : new Request(input instanceof Request ? input : new URL(`${input}`, baseUrl()), {
6959
+ ...init,
6960
+ signal: init && "signal" in init ? init.signal : input instanceof Request ? input.signal : void 0
6961
+ });
6932
6962
  return endpoint.fetch(request);
6933
6963
  },
6934
6964
  connectWebSocket,
package/dist/worker.js CHANGED
@@ -1124,7 +1124,10 @@ async function httpFetch(call, env, body, options = {}) {
1124
1124
  }
1125
1125
  const onAbort = () => void release();
1126
1126
  signal?.addEventListener("abort", onAbort, { once: true });
1127
- drain(output).finally(() => signal?.removeEventListener("abort", onAbort));
1127
+ options.keepAlive;
1128
+ drain(output).finally(() => {
1129
+ signal?.removeEventListener("abort", onAbort);
1130
+ });
1128
1131
  };
1129
1132
  let decoded;
1130
1133
  try {
@@ -1432,7 +1435,8 @@ async function fetchOverDuplex(call, request, options = {}) {
1432
1435
  }
1433
1436
  const { envelope, body: respBody, release, finish } = await httpFetch(call, env, body, {
1434
1437
  ...options,
1435
- signal: request.signal ?? void 0
1438
+ signal: request.signal ?? void 0,
1439
+ keepAlive: request
1436
1440
  });
1437
1441
  const responseInit = {
1438
1442
  status: envelope.status,
@@ -1444,7 +1448,7 @@ async function fetchOverDuplex(call, request, options = {}) {
1444
1448
  else await release?.();
1445
1449
  return new Response(null, responseInit);
1446
1450
  }
1447
- return new Response(responseBody(respBody, request.signal, release), responseInit);
1451
+ return new Response(responseBody(respBody, request, release), responseInit);
1448
1452
  }
1449
1453
  /**
1450
1454
  * Wrap a `(Request) => Promise<Response>` handler as a `Duplex` so it can be
@@ -1597,7 +1601,9 @@ const CANCEL_GRACE_MS = 1e3;
1597
1601
  * A cancel abandons the call too — unless the read in flight comes back with the
1598
1602
  * body's end, which finishes it instead (see `cancel`).
1599
1603
  */
1600
- function responseBody(body, signal, release) {
1604
+ function responseBody(body, request, release) {
1605
+ const keep = { request };
1606
+ const signal = request.signal ?? void 0;
1601
1607
  const it = body[Symbol.asyncIterator]();
1602
1608
  let controller;
1603
1609
  let ended = false;
@@ -1605,6 +1611,7 @@ function responseBody(body, signal, release) {
1605
1611
  const end = () => {
1606
1612
  ended = true;
1607
1613
  signal?.removeEventListener("abort", onAbort);
1614
+ keep.request = void 0;
1608
1615
  };
1609
1616
  const onAbort = () => {
1610
1617
  if (ended) return;
@@ -2433,12 +2440,6 @@ async function sendChunks(port, channelName, output, { maxMessageSize, log }, si
2433
2440
  const producer = output;
2434
2441
  const returnProducer = () => void Promise.resolve(producer.return?.()).catch(() => {});
2435
2442
  if (framed !== output) signal.addEventListener("abort", returnProducer, { once: true });
2436
- let onAborted = () => {};
2437
- const aborted = new Promise((resolve) => {
2438
- onAborted = () => resolve();
2439
- });
2440
- if (signal.aborted) onAborted();
2441
- else signal.addEventListener("abort", onAborted, { once: true });
2442
2443
  try {
2443
2444
  await sendIterator(async ({ done, value, error }) => {
2444
2445
  if (signal.aborted) return;
@@ -2460,7 +2461,7 @@ async function sendChunks(port, channelName, output, { maxMessageSize, log }, si
2460
2461
  });
2461
2462
  touch();
2462
2463
  if (done && !error) onSent();
2463
- if (portBufferedAmount(port) > 0) await Promise.race([whenPortReady(port), aborted]);
2464
+ if (portBufferedAmount(port) > 0) await whenReadyOrAborted(port, signal);
2464
2465
  return;
2465
2466
  }
2466
2467
  await callPort(port, chunk, {
@@ -2476,10 +2477,25 @@ async function sendChunks(port, channelName, output, { maxMessageSize, log }, si
2476
2477
  throw err;
2477
2478
  } finally {
2478
2479
  signal.removeEventListener("abort", returnProducer);
2479
- signal.removeEventListener("abort", onAborted);
2480
2480
  }
2481
2481
  }
2482
2482
  /**
2483
+ * A pipelined sender waits only for room in the window, or for the abort. The
2484
+ * abort listener lives for this one wait: a promise held for the whole stream
2485
+ * would keep one reaction per wait until the stream ends.
2486
+ */
2487
+ function whenReadyOrAborted(port, signal) {
2488
+ if (signal.aborted) return Promise.resolve();
2489
+ return new Promise((resolve) => {
2490
+ const settle = () => {
2491
+ signal.removeEventListener("abort", settle);
2492
+ resolve();
2493
+ };
2494
+ signal.addEventListener("abort", settle, { once: true });
2495
+ whenPortReady(port).then(settle, settle);
2496
+ });
2497
+ }
2498
+ /**
2483
2499
  * One virtual port.
2484
2500
  *
2485
2501
  * `deliver` and `markClosed` are deliberately not on `port`: the consumer holds
@@ -3477,7 +3493,12 @@ function trackedLink(send, release) {
3477
3493
  finish();
3478
3494
  return response;
3479
3495
  }
3480
- return new Response(guardBody(response.body, lost, () => lostReason, finish, request.signal), {
3496
+ failers.delete(fail);
3497
+ const body = guardBody(response.body, finish, request);
3498
+ fail = body.fail;
3499
+ if (closed) fail(lostReason);
3500
+ else if (!finished) failers.add(fail);
3501
+ return new Response(body.stream, {
3481
3502
  status: response.status,
3482
3503
  statusText: response.statusText,
3483
3504
  headers: response.headers
@@ -3496,56 +3517,62 @@ function trackedLink(send, release) {
3496
3517
  return asyncDisposable(link, () => link.close());
3497
3518
  }
3498
3519
  /**
3499
- * A response body that reports its end (done, cancelled, failed or aborted) and
3500
- * fails with the link's reason when the link is lost mid-stream. An abort of the
3501
- * request's signal ends it at once, read or not: the exchange is over (the layer
3502
- * below has given its port back), so the request leaves `inflight` then.
3520
+ * A response body that reports its end (done, cancelled, failed or aborted).
3521
+ * `fail(error)` ends it with `error` at once — the link calls it when it is lost
3522
+ * mid-stream. An abort of the request's signal does the same with the signal's
3523
+ * reason, read or not: the exchange is over (the layer below has given its port
3524
+ * back), so the request leaves `inflight` then. A read waits on the reader
3525
+ * alone, so nothing outlives the chunk it brings.
3503
3526
  */
3504
- function guardBody(body, lost, reason, finish, signal) {
3527
+ function guardBody(body, finish, request) {
3528
+ const keep = { request };
3529
+ const signal = request.signal ?? void 0;
3505
3530
  const reader = body.getReader();
3506
3531
  let controller;
3507
3532
  let ended = false;
3508
3533
  const end = () => {
3509
3534
  ended = true;
3510
3535
  signal?.removeEventListener("abort", onAbort);
3536
+ keep.request = void 0;
3511
3537
  finish();
3512
3538
  };
3513
- const onAbort = () => {
3539
+ const fail = (error) => {
3514
3540
  if (ended) return;
3515
3541
  end();
3516
3542
  reader.cancel().catch(() => {});
3517
3543
  try {
3518
- controller.error(abortError(signal));
3544
+ controller.error(error);
3519
3545
  } catch {}
3520
3546
  };
3521
- return new ReadableStream({
3522
- start(c) {
3523
- controller = c;
3524
- if (signal?.aborted) onAbort();
3525
- else signal?.addEventListener("abort", onAbort, { once: true });
3526
- },
3527
- async pull(controller) {
3528
- try {
3529
- const { done, value } = await Promise.race([reader.read(), lost]);
3530
- if (ended) return;
3531
- if (done) {
3532
- end();
3533
- controller.close();
3534
- return;
3547
+ const onAbort = () => fail(abortError(signal));
3548
+ return {
3549
+ stream: new ReadableStream({
3550
+ start(c) {
3551
+ controller = c;
3552
+ if (signal?.aborted) onAbort();
3553
+ else signal?.addEventListener("abort", onAbort, { once: true });
3554
+ },
3555
+ async pull(controller) {
3556
+ try {
3557
+ const { done, value } = await reader.read();
3558
+ if (ended) return;
3559
+ if (done) {
3560
+ end();
3561
+ controller.close();
3562
+ return;
3563
+ }
3564
+ controller.enqueue(value);
3565
+ } catch (error) {
3566
+ fail(error);
3535
3567
  }
3536
- controller.enqueue(value);
3537
- } catch (error) {
3538
- if (ended) return;
3568
+ },
3569
+ cancel(why) {
3539
3570
  end();
3540
- reader.cancel().catch(() => {});
3541
- controller.error(reason() ?? error);
3571
+ setTimeout(() => void reader.cancel(why).catch(() => {}), 0);
3542
3572
  }
3543
- },
3544
- async cancel(why) {
3545
- end();
3546
- await reader.cancel(why).catch(() => {});
3547
- }
3548
- });
3573
+ }),
3574
+ fail
3575
+ };
3549
3576
  }
3550
3577
  /** The signal's reason as an `Error` (`AbortError` when it gave none). */
3551
3578
  function abortError(signal) {
@@ -4879,7 +4906,10 @@ function serveHttpInWorker(handler, options = {}) {
4879
4906
  let WebSocketClass;
4880
4907
  return {
4881
4908
  fetch(input, init) {
4882
- const request = input instanceof Request && init === void 0 ? input : new Request(input instanceof Request ? input : new URL(`${input}`, baseUrl()), init);
4909
+ const request = input instanceof Request && init === void 0 ? input : new Request(input instanceof Request ? input : new URL(`${input}`, baseUrl()), {
4910
+ ...init,
4911
+ signal: init && "signal" in init ? init.signal : input instanceof Request ? input.signal : void 0
4912
+ });
4883
4913
  return endpoint.fetch(request);
4884
4914
  },
4885
4915
  connectWebSocket,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statewalker/webrun-http-browser",
3
- "version": "0.12.2",
3
+ "version": "0.12.3",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "ServiceWorker-based HTTP and WebSocket server for browsers: handlers in pages and Dedicated Workers, same-origin and relay modes, streams with backpressure",
@@ -58,8 +58,8 @@
58
58
  ],
59
59
  "dependencies": {
60
60
  "idb-keyval": "^6.3.0",
61
- "@statewalker/webrun-http-streams": "^0.4.0",
62
- "@statewalker/webrun-rpc": "^0.10.0",
61
+ "@statewalker/webrun-http-streams": "^0.4.1",
62
+ "@statewalker/webrun-rpc": "^0.10.1",
63
63
  "@statewalker/webrun-streams": "^0.2.3"
64
64
  },
65
65
  "devDependencies": {