@statewalker/webrun-http-browser 0.12.1 → 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.
@@ -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"}
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) {