stitchkit 0.110.6 → 0.111.1

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.
Files changed (88) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/dist/agent-runtime/sandbox-broker.d.ts.map +1 -1
  3. package/dist/application/grammy-update-admission.d.ts.map +1 -1
  4. package/dist/browser/stream.d.ts +3 -2
  5. package/dist/browser/stream.d.ts.map +1 -1
  6. package/dist/contract/runtime-context.d.ts +3 -1
  7. package/dist/contract/runtime-context.d.ts.map +1 -1
  8. package/dist/entrypoints/agent-runtime/sandbox.js +13 -27
  9. package/dist/entrypoints/application/grammy.js +11 -2
  10. package/dist/entrypoints/bin/upgrade-cli.d.ts.map +1 -1
  11. package/dist/entrypoints/bin/upgrade-cli.js +123 -29
  12. package/dist/entrypoints/cli/publish.js +1 -1
  13. package/dist/entrypoints/cli.js +2 -2
  14. package/dist/entrypoints/http.d.ts +2 -0
  15. package/dist/entrypoints/http.d.ts.map +1 -1
  16. package/dist/entrypoints/http.js +5 -1
  17. package/dist/entrypoints/index.d.ts +1 -0
  18. package/dist/entrypoints/index.d.ts.map +1 -1
  19. package/dist/entrypoints/index.js +5 -1
  20. package/dist/entrypoints/node.d.ts +1 -0
  21. package/dist/entrypoints/node.d.ts.map +1 -1
  22. package/dist/entrypoints/node.js +3 -1
  23. package/dist/entrypoints/observability.js +24 -9
  24. package/dist/entrypoints/remote.js +1 -1
  25. package/dist/entrypoints/server.d.ts +1 -0
  26. package/dist/entrypoints/server.d.ts.map +1 -1
  27. package/dist/entrypoints/server.js +24 -3
  28. package/dist/entrypoints/telegram.js +3 -3
  29. package/dist/entrypoints/testing.js +1 -1
  30. package/dist/entrypoints/tools/connections.js +16 -31
  31. package/dist/entrypoints/tools/mcp.js +1 -1
  32. package/dist/entrypoints/tools.js +40 -8
  33. package/dist/entrypoints/voice.js +17 -10
  34. package/dist/{index-s6d8zsha.js → index-2qymcsq2.js} +66 -121
  35. package/dist/index-5efrpfmj.js +55 -0
  36. package/dist/{index-s2eybn7b.js → index-c5374vpt.js} +10 -9
  37. package/dist/{index-jk44mbqb.js → index-c5at3ykd.js} +3 -57
  38. package/dist/index-cjbn98zt.js +68 -0
  39. package/dist/{index-3h0ztg0z.js → index-cxpcdswt.js} +5 -21
  40. package/dist/{index-s0xbnpfh.js → index-erv5shjq.js} +1 -1
  41. package/dist/{index-n7hrpngg.js → index-v2eyj2ws.js} +1 -1
  42. package/dist/internal/bounded-body.d.ts +44 -0
  43. package/dist/internal/bounded-body.d.ts.map +1 -0
  44. package/dist/internal/bounded-lines.d.ts +1 -2
  45. package/dist/internal/bounded-lines.d.ts.map +1 -1
  46. package/dist/internal/secure-fetch.d.ts.map +1 -1
  47. package/dist/internal/stream-chunks.d.ts +13 -0
  48. package/dist/internal/stream-chunks.d.ts.map +1 -0
  49. package/dist/internal/upgrade-affects.d.ts +5 -0
  50. package/dist/internal/upgrade-affects.d.ts.map +1 -1
  51. package/dist/internal/upgrade-plan.d.ts +18 -3
  52. package/dist/internal/upgrade-plan.d.ts.map +1 -1
  53. package/dist/internal/upgrade-usage.d.ts +21 -5
  54. package/dist/internal/upgrade-usage.d.ts.map +1 -1
  55. package/dist/observability/audit.d.ts +10 -0
  56. package/dist/observability/audit.d.ts.map +1 -1
  57. package/dist/observability/event.d.ts +5 -0
  58. package/dist/observability/event.d.ts.map +1 -1
  59. package/dist/server/contract-stream.d.ts.map +1 -1
  60. package/dist/server/multipart.d.ts.map +1 -1
  61. package/dist/server/oauth/endpoints.d.ts.map +1 -1
  62. package/dist/server/oauth/provider.d.ts +5 -0
  63. package/dist/server/oauth/provider.d.ts.map +1 -1
  64. package/dist/server/raw.d.ts +6 -3
  65. package/dist/server/raw.d.ts.map +1 -1
  66. package/dist/server/request-body.d.ts.map +1 -1
  67. package/dist/server/request-completion.d.ts.map +1 -1
  68. package/dist/server/request-ingress.d.ts +31 -0
  69. package/dist/server/request-ingress.d.ts.map +1 -0
  70. package/dist/server/unix-client-body.d.ts.map +1 -1
  71. package/dist/tools/connections/limits.d.ts.map +1 -1
  72. package/dist/tools/mcp/handler.d.ts.map +1 -1
  73. package/dist/voice/speech-queue.d.ts +6 -1
  74. package/dist/voice/speech-queue.d.ts.map +1 -1
  75. package/llms/http.txt +3 -0
  76. package/llms/observability.txt +19 -2
  77. package/llms/server.2.txt +4 -0
  78. package/llms/server.5.txt +2 -1
  79. package/llms/server.txt +42 -0
  80. package/llms/stitchkit.2.txt +28 -0
  81. package/llms/stitchkit.txt +3 -3
  82. package/llms/tools.3.txt +1 -1
  83. package/llms/tools.txt +4 -0
  84. package/llms/{upgrading-0.101.0-0.110.0.txt → upgrading-0.101.0-0.111.0.txt} +29 -3
  85. package/llms/upgrading.txt +8 -2
  86. package/llms-full.txt +136 -8
  87. package/llms.txt +6 -6
  88. package/package.json +1 -1
@@ -27,6 +27,11 @@ import {
27
27
  bindContract,
28
28
  mediaTypeEssence
29
29
  } from "./index-pzw0fpek.js";
30
+ import {
31
+ readBoundedChunks,
32
+ concatChunks,
33
+ readBoundedBody2
34
+ } from "./index-5efrpfmj.js";
30
35
  import {
31
36
  assertPositiveSafeInteger
32
37
  } from "./index-0s4xebbg.js";
@@ -54,6 +59,10 @@ import {
54
59
  getClientInfo2,
55
60
  parseQueryParams
56
61
  } from "./index-kw1ws0cs.js";
62
+ import {
63
+ abortReason,
64
+ untilAborted
65
+ } from "./index-a5qr2rj6.js";
57
66
  import {
58
67
  isUnsafeKey,
59
68
  safeJsonParse
@@ -418,27 +427,10 @@ async function collectFile(part, stream) {
418
427
  });
419
428
  }
420
429
  async function readText(stream, maxBytes) {
421
- const chunks = [];
422
- const reader = stream.getReader();
423
- let total = 0;
424
- while (true) {
425
- const result = await reader.read();
426
- if (result.done)
427
- break;
428
- total += result.value.length;
429
- if (total > maxBytes) {
430
- await reader.cancel();
431
- badRequest2(`Multipart text field exceeds ${maxBytes} bytes`);
432
- }
433
- chunks.push(result.value);
434
- }
435
- const value = new Uint8Array(total);
436
- let offset = 0;
437
- for (const chunk of chunks) {
438
- value.set(chunk, offset);
439
- offset += chunk.length;
440
- }
441
- return decoder.decode(value);
430
+ const result = await readBoundedChunks(stream, { maxBytes });
431
+ if (result.kind === "over-limit")
432
+ badRequest2(`Multipart text field exceeds ${maxBytes} bytes`);
433
+ return decoder.decode(concatChunks(result.chunks, result.total));
442
434
  }
443
435
  function required(policy) {
444
436
  return policy.required !== false;
@@ -896,63 +888,16 @@ function mergeContextContribution(ctx, value, scope) {
896
888
  }
897
889
 
898
890
  // src/server/request-body.ts
899
- function requestAbortReason(req) {
900
- return req.signal.reason ?? new DOMException("The connection was closed", "AbortError");
901
- }
902
- function throwIfRequestAborted(req) {
903
- if (req.signal.aborted)
904
- throw requestAbortReason(req);
905
- }
906
891
  async function readRequestText(req, maxBytes) {
907
- throwIfRequestAborted(req);
892
+ if (req.signal.aborted)
893
+ throw abortReason(req.signal);
908
894
  if (maxBytes === undefined)
909
895
  return req.text();
910
896
  assertPositiveSafeInteger("maxJsonBodyBytes", maxBytes, Error);
911
- const reader = req.body?.getReader();
912
- if (!reader)
913
- return "";
914
- const chunks = [];
915
- let total = 0;
916
- let rejectAbortedRead = (_reason) => {
917
- return;
918
- };
919
- const abortedRead = new Promise((_resolve, reject) => {
920
- rejectAbortedRead = reject;
921
- });
922
- const onAbort = () => {
923
- const reason = requestAbortReason(req);
924
- rejectAbortedRead(reason);
925
- reader.cancel(reason).catch(() => {
926
- return;
927
- });
928
- };
929
- req.signal.addEventListener("abort", onAbort, { once: true });
930
- if (req.signal.aborted)
931
- onAbort();
932
- try {
933
- while (true) {
934
- const { done, value } = await Promise.race([reader.read(), abortedRead]);
935
- if (done)
936
- break;
937
- total += value.byteLength;
938
- if (total > maxBytes) {
939
- await reader.cancel();
940
- badRequest2(`JSON body exceeds the ${maxBytes}-byte limit`);
941
- }
942
- chunks.push(value);
943
- }
944
- } finally {
945
- req.signal.removeEventListener("abort", onAbort);
946
- if (!req.signal.aborted)
947
- reader.releaseLock();
948
- }
949
- const bytes = new Uint8Array(total);
950
- let offset = 0;
951
- for (const chunk of chunks) {
952
- bytes.set(chunk, offset);
953
- offset += chunk.byteLength;
954
- }
955
- return new TextDecoder().decode(bytes);
897
+ const result = await readBoundedChunks(req.body, { maxBytes, signal: req.signal });
898
+ if (result.kind === "over-limit")
899
+ badRequest2(`JSON body exceeds the ${maxBytes}-byte limit`);
900
+ return new TextDecoder().decode(concatChunks(result.chunks, result.total));
956
901
  }
957
902
 
958
903
  // src/server/context.ts
@@ -1057,15 +1002,6 @@ function encodedBytes(value, format) {
1057
1002
  `;
1058
1003
  return new TextEncoder().encode(framing).byteLength;
1059
1004
  }
1060
- function waitForAbort(signal) {
1061
- return new Promise((_resolve, reject) => {
1062
- const rejectAborted = () => reject(signal.reason ?? new Error("Stream aborted"));
1063
- if (signal.aborted)
1064
- rejectAborted();
1065
- else
1066
- signal.addEventListener("abort", rejectAborted, { once: true });
1067
- });
1068
- }
1069
1005
  function contractStreamResponse(request, context, source, descriptor, operationAbort) {
1070
1006
  const format = descriptor.format ?? "ndjson";
1071
1007
  const maxFrameBytes = descriptor.maxFrameBytes ?? DEFAULT_CONTRACT_STREAM_FRAME_BYTES2;
@@ -1104,12 +1040,11 @@ function contractStreamResponse(request, context, source, descriptor, operationA
1104
1040
  const frames = async function* () {
1105
1041
  if (closing)
1106
1042
  return;
1107
- const aborted = waitForAbort(operationAbort.signal);
1108
1043
  let terminalSeen = descriptor.terminal === undefined;
1109
1044
  try {
1110
1045
  for (;; ) {
1111
1046
  pendingNext = Promise.resolve(iterator.next());
1112
- const next = await Promise.race([pendingNext, aborted]);
1047
+ const next = await untilAborted(pendingNext, operationAbort.signal);
1113
1048
  if (next.done)
1114
1049
  break;
1115
1050
  const parsed = descriptor.item.safeParse(next.value);
@@ -1979,13 +1914,25 @@ function logOutgoing(entry) {
1979
1914
 
1980
1915
  // src/server/request-completion.ts
1981
1916
  var BODY_METHODS = new Set(["POST", "PUT", "PATCH", "DELETE"]);
1917
+ var DEFAULT_PAYLOAD_LOG_BYTES = 1024 * 1024;
1918
+ async function readPayload(clone, maxBytes) {
1919
+ try {
1920
+ const result = await readBoundedChunks(clone.body, { maxBytes, signal: clone.signal });
1921
+ if (result.kind === "over-limit")
1922
+ return { value: undefined, truncated: true };
1923
+ return {
1924
+ value: JSON.parse(new TextDecoder().decode(concatChunks(result.chunks, result.total))),
1925
+ truncated: false
1926
+ };
1927
+ } catch {
1928
+ return { value: undefined, truncated: false };
1929
+ }
1930
+ }
1982
1931
  function openRequestCompletion(state, window) {
1983
1932
  const { logConfig, customLogger, useDefaultLog, observability } = state;
1984
1933
  const { req, url, traceId, startedAt, ipAddress } = window;
1985
1934
  const shouldLogRequest = logConfig !== null && shouldLog(url.pathname, req.method) && !shouldSkipLog(logConfig, req, url);
1986
- const payload = observability?.includePayload && BODY_METHODS.has(req.method) ? req.clone().json().catch(() => {
1987
- return;
1988
- }) : undefined;
1935
+ const payloadRead = observability?.includePayload && BODY_METHODS.has(req.method) ? readPayload(req.clone(), observability.payloadMaxBytes ?? state.config.maxJsonBodyBytes ?? DEFAULT_PAYLOAD_LOG_BYTES) : undefined;
1989
1936
  const logFormat = resolveLogFormat(logConfig?.format);
1990
1937
  let reqLog;
1991
1938
  if (shouldLogRequest && useDefaultLog) {
@@ -2026,7 +1973,10 @@ function openRequestCompletion(state, window) {
2026
1973
  context,
2027
1974
  statusCode: status,
2028
1975
  durationMs,
2029
- payload,
1976
+ ...payloadRead && {
1977
+ payload: payloadRead.then((read) => read.value),
1978
+ payloadTruncated: payloadRead.then((read) => read.truncated)
1979
+ },
2030
1980
  ...outcome !== undefined && { outcome }
2031
1981
  });
2032
1982
  } catch {}
@@ -2367,6 +2317,23 @@ function bindProcessSignals2(handle, options = {}) {
2367
2317
  return { promise, close };
2368
2318
  }
2369
2319
 
2320
+ // src/server/request-ingress.ts
2321
+ function defineRequestIngress2(name) {
2322
+ const values = new WeakMap;
2323
+ return {
2324
+ name,
2325
+ attach(request, value) {
2326
+ if (values.has(request)) {
2327
+ throw new TypeError(`[stitchkit] request ingress ${JSON.stringify(name)} is already attached to this request`);
2328
+ }
2329
+ values.set(request, value);
2330
+ },
2331
+ from(source) {
2332
+ return source.req && values.get(source.req);
2333
+ }
2334
+ };
2335
+ }
2336
+
2370
2337
  // src/server/websocket.ts
2371
2338
  function webSocketLane2(lane) {
2372
2339
  const { match, handlers } = lane;
@@ -2630,39 +2597,17 @@ import {
2630
2597
 
2631
2598
  // src/server/unix-client-body.ts
2632
2599
  async function readBoundedRequestBody(request, maxBytes) {
2633
- const body = request.body;
2634
- if (!body)
2600
+ if (!request.body)
2635
2601
  return;
2602
+ const tooLarge = () => new UnixClientTransportError2("UNIX_REQUEST_TOO_LARGE", `Unix request body exceeds the ${maxBytes} byte limit`, "not-dispatched");
2636
2603
  const declared = request.headers.get("content-length");
2637
- if (declared !== null && Number(declared) > maxBytes) {
2638
- throw new UnixClientTransportError2("UNIX_REQUEST_TOO_LARGE", `Unix request body exceeds the ${maxBytes} byte limit`, "not-dispatched");
2639
- }
2640
- const reader = body.getReader();
2641
- const chunks = [];
2642
- let total = 0;
2643
- try {
2644
- for (;; ) {
2645
- if (request.signal.aborted)
2646
- throw request.signal.reason;
2647
- const chunk = await reader.read();
2648
- if (chunk.done)
2649
- break;
2650
- total += chunk.value.byteLength;
2651
- if (total > maxBytes) {
2652
- throw new UnixClientTransportError2("UNIX_REQUEST_TOO_LARGE", `Unix request body exceeds the ${maxBytes} byte limit`, "not-dispatched");
2653
- }
2654
- chunks.push(chunk.value);
2655
- }
2656
- } finally {
2657
- reader.releaseLock();
2658
- }
2659
- const result = new Uint8Array(total);
2660
- let offset = 0;
2661
- for (const chunk of chunks) {
2662
- result.set(chunk, offset);
2663
- offset += chunk.byteLength;
2664
- }
2665
- return result;
2604
+ if (declared !== null && Number(declared) > maxBytes)
2605
+ throw tooLarge();
2606
+ return readBoundedBody2(request.body, {
2607
+ maxBytes,
2608
+ signal: request.signal,
2609
+ limitError: tooLarge
2610
+ });
2666
2611
  }
2667
2612
  function incomingResponseBody(response, maxBytes, onDone) {
2668
2613
  let finished = false;
@@ -3474,4 +3419,4 @@ function createUnixClientTransport2(config) {
3474
3419
  };
3475
3420
  }
3476
3421
 
3477
- export { contextContributionDelta, prepareContextContribution, mergeContextContribution, parseMultipart2, DEFAULT_STREAM_HEARTBEAT_MS2, streamingRoute2, ndjsonRoute2, sseRoute2, createHandler2, createScopedImplementRegistry2, implementRegistry2, createImplementRegistry2, bindProcessSignals2, webSocketLane2, composeWebSocketHandlers2, createSocketIOServer2, socketIoLane2, UnixClientTransportError2, createUnixClientTransport2 };
3422
+ export { contextContributionDelta, prepareContextContribution, mergeContextContribution, parseMultipart2, DEFAULT_STREAM_HEARTBEAT_MS2, streamingRoute2, ndjsonRoute2, sseRoute2, createHandler2, createScopedImplementRegistry2, implementRegistry2, createImplementRegistry2, bindProcessSignals2, defineRequestIngress2, webSocketLane2, composeWebSocketHandlers2, createSocketIOServer2, socketIoLane2, UnixClientTransportError2, createUnixClientTransport2 };
@@ -0,0 +1,55 @@
1
+ import {
2
+ chunksOf
3
+ } from "./index-cjbn98zt.js";
4
+ import {
5
+ assertPositiveSafeInteger
6
+ } from "./index-0s4xebbg.js";
7
+ import {
8
+ abortReason
9
+ } from "./index-a5qr2rj6.js";
10
+
11
+ // src/internal/bounded-body.ts
12
+ async function readBoundedChunks(source, options) {
13
+ options.signal?.throwIfAborted();
14
+ const chunks = [];
15
+ let total = 0;
16
+ if (source === null)
17
+ return { kind: "complete", chunks, total };
18
+ for await (const chunk of chunksOf(source, options.signal)) {
19
+ if (!(chunk instanceof Uint8Array)) {
20
+ throw new TypeError("A bounded body source must yield bytes (Uint8Array chunks)");
21
+ }
22
+ total += chunk.byteLength;
23
+ options.onChunk?.(chunk);
24
+ if (total > options.maxBytes)
25
+ return { kind: "over-limit", total };
26
+ chunks.push(chunk);
27
+ }
28
+ return { kind: "complete", chunks, total };
29
+ }
30
+ function concatChunks(chunks, total) {
31
+ const bytes = new Uint8Array(total);
32
+ let offset = 0;
33
+ for (const chunk of chunks) {
34
+ bytes.set(chunk, offset);
35
+ offset += chunk.byteLength;
36
+ }
37
+ return bytes;
38
+ }
39
+ async function readBoundedBody2(source, options) {
40
+ assertPositiveSafeInteger("maxBytes", options.maxBytes);
41
+ let result;
42
+ try {
43
+ result = await readBoundedChunks(source, options);
44
+ } catch (error) {
45
+ const { signal, abortError } = options;
46
+ if (signal?.aborted && abortError)
47
+ throw abortError(abortReason(signal));
48
+ throw error;
49
+ }
50
+ if (result.kind === "over-limit")
51
+ throw options.limitError(result.total);
52
+ return concatChunks(result.chunks, result.total);
53
+ }
54
+
55
+ export { readBoundedChunks, concatChunks, readBoundedBody2 };
@@ -36,6 +36,9 @@ import {
36
36
  runWithRequestContext2,
37
37
  getRequestContext2
38
38
  } from "./index-r6v4mc67.js";
39
+ import {
40
+ untilAborted
41
+ } from "./index-a5qr2rj6.js";
39
42
  import {
40
43
  AppError2
41
44
  } from "./index-j9gxbztg.js";
@@ -971,10 +974,7 @@ function createMcpHandler2(config) {
971
974
  const buildPrepared = prepareStaticSurface(config);
972
975
  const authByCarrier = new WeakMap;
973
976
  let closed = false;
974
- let resolveClosed;
975
- const closedSignal = new Promise((resolve) => {
976
- resolveClosed = resolve;
977
- });
977
+ const closing = new AbortController;
978
978
  const sdkHandler = createSdkMcpHandler(({ authInfo }) => {
979
979
  const resolved = authInfo ? authByCarrier.get(authInfo) : undefined;
980
980
  if (!resolved) {
@@ -1011,10 +1011,11 @@ function createMcpHandler2(config) {
1011
1011
  const originRejection = originValidationResponse(request, [...allowedOrigins]);
1012
1012
  if (originRejection)
1013
1013
  return observeRejection(request, originRejection);
1014
- const auth = await Promise.race([
1015
- Promise.resolve(config.auth(request)),
1016
- closedSignal.then(() => null)
1017
- ]);
1014
+ const auth = await untilAborted(Promise.resolve(config.auth(request)), closing.signal).catch((error) => {
1015
+ if (closing.signal.aborted)
1016
+ return null;
1017
+ throw error;
1018
+ });
1018
1019
  if (closed) {
1019
1020
  return observeRejection(request, jsonRpcError(-32000, "MCP handler is closed", 503));
1020
1021
  }
@@ -1036,7 +1037,7 @@ function createMcpHandler2(config) {
1036
1037
  if (closed)
1037
1038
  return;
1038
1039
  closed = true;
1039
- resolveClosed?.();
1040
+ closing.abort();
1040
1041
  await sdkHandler.close();
1041
1042
  }
1042
1043
  };
@@ -14,10 +14,6 @@ import {
14
14
  withUploadProgress,
15
15
  parseApiErrorBody
16
16
  } from "./index-p6apg0jg.js";
17
- import {
18
- abortReason,
19
- untilAborted
20
- } from "./index-a5qr2rj6.js";
21
17
  import {
22
18
  inputIsQuery
23
19
  } from "./index-xhzq2qvj.js";
@@ -25,6 +21,9 @@ import {
25
21
  ApiError2,
26
22
  refuseLocally
27
23
  } from "./index-kfjkb396.js";
24
+ import {
25
+ chunksOf
26
+ } from "./index-cjbn98zt.js";
28
27
  import {
29
28
  assertPositiveSafeInteger
30
29
  } from "./index-0s4xebbg.js";
@@ -131,59 +130,6 @@ function createBoundedLineDecoder(maxLineBytes, requireFinalNewline = false) {
131
130
  const { push, end } = createLineSplitter(maxLineBytes, requireFinalNewline);
132
131
  return { push, end };
133
132
  }
134
- async function* chunksOf(source, signal) {
135
- signal?.throwIfAborted();
136
- const stream = source instanceof Response ? source.body : source;
137
- if (stream === null)
138
- return;
139
- if (stream instanceof ReadableStream) {
140
- const reader = stream.getReader();
141
- const abort = () => {
142
- if (signal)
143
- reader.cancel(abortReason(signal)).catch(() => {
144
- return;
145
- });
146
- };
147
- signal?.addEventListener("abort", abort, { once: true });
148
- try {
149
- for (;; ) {
150
- const chunk = await reader.read();
151
- signal?.throwIfAborted();
152
- if (chunk.done)
153
- return;
154
- yield chunk.value;
155
- }
156
- } finally {
157
- signal?.removeEventListener("abort", abort);
158
- await reader.cancel().catch(() => {
159
- return;
160
- });
161
- reader.releaseLock();
162
- }
163
- }
164
- const iterator = stream[Symbol.asyncIterator]();
165
- let finished = false;
166
- try {
167
- for (;; ) {
168
- const pending = iterator.next();
169
- const next = await (signal ? untilAborted(pending, signal) : pending);
170
- if (next.done) {
171
- finished = true;
172
- return;
173
- }
174
- yield next.value;
175
- }
176
- } finally {
177
- if (!finished && signal?.aborted)
178
- iterator.return?.().catch(() => {
179
- return;
180
- });
181
- else if (!finished)
182
- await iterator.return?.().catch(() => {
183
- return;
184
- });
185
- }
186
- }
187
133
  async function* readBoundedUtf8Lines(source, maxLineBytes, requireFinalNewline = false, signal) {
188
134
  const splitter = createLineSplitter(maxLineBytes, requireFinalNewline);
189
135
  for await (const chunk of chunksOf(source, signal)) {
@@ -0,0 +1,68 @@
1
+ import {
2
+ abortReason,
3
+ untilAborted
4
+ } from "./index-a5qr2rj6.js";
5
+
6
+ // src/internal/stream-chunks.ts
7
+ async function* chunksOf(source, signal) {
8
+ signal?.throwIfAborted();
9
+ const stream = source instanceof Response ? source.body : source;
10
+ if (stream === null)
11
+ return;
12
+ if (stream instanceof ReadableStream) {
13
+ const reader = stream.getReader();
14
+ const abort = () => {
15
+ if (signal)
16
+ reader.cancel(abortReason(signal)).catch(() => {
17
+ return;
18
+ });
19
+ };
20
+ signal?.addEventListener("abort", abort, { once: true });
21
+ try {
22
+ for (;; ) {
23
+ const pending = reader.read();
24
+ const chunk = await (signal ? untilAborted(pending, signal) : pending);
25
+ signal?.throwIfAborted();
26
+ if (chunk.done)
27
+ return;
28
+ yield chunk.value;
29
+ }
30
+ } finally {
31
+ signal?.removeEventListener("abort", abort);
32
+ if (signal?.aborted) {
33
+ reader.cancel(abortReason(signal)).catch(() => {
34
+ return;
35
+ });
36
+ } else {
37
+ await reader.cancel().catch(() => {
38
+ return;
39
+ });
40
+ reader.releaseLock();
41
+ }
42
+ }
43
+ }
44
+ const iterator = stream[Symbol.asyncIterator]();
45
+ let finished = false;
46
+ try {
47
+ for (;; ) {
48
+ const pending = iterator.next();
49
+ const next = await (signal ? untilAborted(pending, signal) : pending);
50
+ if (next.done) {
51
+ finished = true;
52
+ return;
53
+ }
54
+ yield next.value;
55
+ }
56
+ } finally {
57
+ if (!finished && signal?.aborted)
58
+ iterator.return?.().catch(() => {
59
+ return;
60
+ });
61
+ else if (!finished)
62
+ await iterator.return?.().catch(() => {
63
+ return;
64
+ });
65
+ }
66
+ }
67
+
68
+ export { chunksOf };
@@ -1,6 +1,9 @@
1
1
  import {
2
2
  raceAbort
3
3
  } from "./index-b0kddvzw.js";
4
+ import {
5
+ readBoundedChunks
6
+ } from "./index-5efrpfmj.js";
4
7
 
5
8
  // src/internal/secure-fetch.ts
6
9
  import { lookup } from "node:dns/promises";
@@ -259,27 +262,8 @@ async function fetchPinnedDocument(start, options) {
259
262
  throw new Error("too many redirects");
260
263
  }
261
264
  async function readCapped(res, max) {
262
- const reader = res.body?.getReader();
263
- if (!reader)
264
- return Buffer.alloc(0);
265
- const chunks = [];
266
- let total = 0;
267
- try {
268
- while (true) {
269
- const { done, value } = await reader.read();
270
- if (done)
271
- break;
272
- total += value.length;
273
- if (total > max) {
274
- await reader.cancel();
275
- return null;
276
- }
277
- chunks.push(value);
278
- }
279
- } finally {
280
- reader.releaseLock();
281
- }
282
- return Buffer.concat(chunks);
265
+ const result = await readBoundedChunks(res.body, { maxBytes: max });
266
+ return result.kind === "over-limit" ? null : Buffer.concat(result.chunks);
283
267
  }
284
268
 
285
269
  export { fetchGuarded, PrivateAddressRefusal, fetchPinnedDocument, readCapped };
@@ -5,7 +5,7 @@ import {
5
5
  fetchGuarded,
6
6
  PrivateAddressRefusal,
7
7
  readCapped
8
- } from "./index-3h0ztg0z.js";
8
+ } from "./index-cxpcdswt.js";
9
9
  import {
10
10
  writeFileAtomicSync2
11
11
  } from "./index-07dcf2yn.js";
@@ -26,7 +26,7 @@ import {
26
26
  import {
27
27
  fetchGuarded,
28
28
  readCapped
29
- } from "./index-3h0ztg0z.js";
29
+ } from "./index-cxpcdswt.js";
30
30
  import {
31
31
  isWithinDir2
32
32
  } from "./index-5cc6r990.js";
@@ -0,0 +1,44 @@
1
+ import { type StreamByteSource } from './stream-chunks.js';
2
+ /** How a bounded read of a byte source ended, other than by an abort. */
3
+ export type BoundedChunksResult = {
4
+ readonly kind: 'complete';
5
+ readonly chunks: Uint8Array[];
6
+ readonly total: number;
7
+ }
8
+ /** The source crossed `maxBytes`; `total` counts the chunk that crossed it. */
9
+ | {
10
+ readonly kind: 'over-limit';
11
+ readonly total: number;
12
+ };
13
+ export interface BoundedChunksOptions {
14
+ readonly maxBytes: number;
15
+ readonly signal?: AbortSignal;
16
+ /** Called with every chunk read, including the one that crosses the limit. */
17
+ readonly onChunk?: (chunk: Uint8Array) => void;
18
+ }
19
+ /**
20
+ * Read a source up to `maxBytes`. The read stops at the first chunk that crosses the limit, so
21
+ * memory stays bounded by the limit plus one chunk, and the source is cancelled. A `null` source
22
+ * is empty. An abort rejects with the signal's reason, before the first read as well as during
23
+ * one; it is checked ahead of the empty-source answer.
24
+ */
25
+ export declare function readBoundedChunks(source: StreamByteSource | null, options: BoundedChunksOptions): Promise<BoundedChunksResult>;
26
+ /** One byte array from the chunks of a finished read. */
27
+ export declare function concatChunks(chunks: readonly Uint8Array[], total: number): Uint8Array<ArrayBuffer>;
28
+ export interface BoundedBodyOptions {
29
+ /** The most bytes the body may hold; a body of exactly `maxBytes` is read, one byte more is not. */
30
+ maxBytes: number;
31
+ /** Stops the read; the source is cancelled and the read rejects with `abortError` or the signal's reason. */
32
+ signal?: AbortSignal;
33
+ /** The error to throw when the body crosses `maxBytes`; `observedBytes` counts the chunk that crossed it. */
34
+ limitError: (observedBytes: number) => unknown;
35
+ /** The error to throw when `signal` aborts; the signal's reason when omitted. */
36
+ abortError?: (reason: unknown) => unknown;
37
+ }
38
+ /**
39
+ * The whole body as bytes, never reading past `maxBytes` plus one chunk. A `null` body is an empty
40
+ * result. The source is cancelled when the limit is crossed and when the signal aborts, and the
41
+ * caller picks both errors, so a limit and a cancellation stay different outcomes.
42
+ */
43
+ export declare function readBoundedBody(source: StreamByteSource | null, options: BoundedBodyOptions): Promise<Uint8Array<ArrayBuffer>>;
44
+ //# sourceMappingURL=bounded-body.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bounded-body.d.ts","sourceRoot":"","sources":["../../src/internal/bounded-body.ts"],"names":[],"mappings":"AACA,OAAO,EAAY,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAGlE,yEAAyE;AACzE,MAAM,MAAM,mBAAmB,GAC3B;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE;AACtF,+EAA+E;GAC7E;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5D,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;CAChD;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,gBAAgB,GAAG,IAAI,EAC/B,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,mBAAmB,CAAC,CAiB9B;AAED,yDAAyD;AACzD,wBAAgB,YAAY,CAC1B,MAAM,EAAE,SAAS,UAAU,EAAE,EAC7B,KAAK,EAAE,MAAM,GACZ,UAAU,CAAC,WAAW,CAAC,CAQzB;AAED,MAAM,WAAW,kBAAkB;IACjC,oGAAoG;IACpG,QAAQ,EAAE,MAAM,CAAC;IACjB,6GAA6G;IAC7G,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,6GAA6G;IAC7G,UAAU,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC;IAC/C,iFAAiF;IACjF,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC;CAC3C;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,gBAAgB,GAAG,IAAI,EAC/B,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAYlC"}
@@ -1,3 +1,4 @@
1
+ import { type StreamByteSource } from './stream-chunks.js';
1
2
  export declare const DEFAULT_STREAM_LINE_BYTES: number;
2
3
  /**
3
4
  * A line grew past its byte limit. The reader throws as soon as the retained bytes cross the
@@ -24,8 +25,6 @@ export declare class StreamTruncatedLineError extends SyntaxError {
24
25
  /** Bytes of the unterminated final line. */
25
26
  lineBytes: number);
26
27
  }
27
- /** Bytes a line reader accepts: a `Response` body, a web stream, or any async byte iterable. */
28
- export type StreamByteSource = Response | ReadableStream<Uint8Array> | AsyncIterable<Uint8Array>;
29
28
  /**
30
29
  * Push-driven bounded line splitter for sources that deliver bytes through a callback, such as
31
30
  * a `Bun.listen`/`Bun.connect` socket's `data` handler. Lines are split on `\n` in bytes and
@@ -1 +1 @@
1
- {"version":3,"file":"bounded-lines.d.ts","sourceRoot":"","sources":["../../src/internal/bounded-lines.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,yBAAyB,QAAc,CAAC;AAErD;;;GAGG;AACH,qBAAa,oBAAqB,SAAQ,UAAU;IAGhD,qCAAqC;IACrC,QAAQ,CAAC,UAAU,EAAE,MAAM;IAC3B,yFAAyF;IACzF,QAAQ,CAAC,SAAS,EAAE,MAAM;IAL5B,SAAkB,IAAI,0BAA0B;IAChD;IACE,qCAAqC;IAC5B,UAAU,EAAE,MAAM;IAC3B,yFAAyF;IAChF,SAAS,EAAE,MAAM,EAG3B;CACF;AAED,iGAAiG;AACjG,qBAAa,wBAAyB,SAAQ,WAAW;IAGrD,4CAA4C;IAC5C,QAAQ,CAAC,SAAS,EAAE,MAAM;IAH5B,SAAkB,IAAI,8BAA8B;IACpD;IACE,4CAA4C;IACnC,SAAS,EAAE,MAAM,EAG3B;CACF;AAED,gGAAgG;AAChG,MAAM,MAAM,gBAAgB,GACxB,QAAQ,GACR,cAAc,CAAC,UAAU,CAAC,GAC1B,aAAa,CAAC,UAAU,CAAC,CAAC;AAE9B;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,0EAA0E;IAC1E,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,EAAE,CAAC;IAClC;;;OAGG;IACH,GAAG,IAAI,MAAM,EAAE,CAAC;CACjB;AAiFD,wBAAgB,wBAAwB,CACtC,YAAY,CAAC,EAAE,MAAM,EACrB,mBAAmB,UAAQ,GAC1B,kBAAkB,CAGpB;AAkDD,6EAA6E;AAC7E,wBAAuB,oBAAoB,CACzC,MAAM,EAAE,gBAAgB,EACxB,YAAY,CAAC,EAAE,MAAM,EACrB,mBAAmB,UAAQ,EAC3B,MAAM,CAAC,EAAE,WAAW,GACnB,cAAc,CAAC,MAAM,CAAC,CAcxB"}
1
+ {"version":3,"file":"bounded-lines.d.ts","sourceRoot":"","sources":["../../src/internal/bounded-lines.ts"],"names":[],"mappings":"AACA,OAAO,EAAY,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAElE,eAAO,MAAM,yBAAyB,QAAc,CAAC;AAErD;;;GAGG;AACH,qBAAa,oBAAqB,SAAQ,UAAU;IAGhD,qCAAqC;IACrC,QAAQ,CAAC,UAAU,EAAE,MAAM;IAC3B,yFAAyF;IACzF,QAAQ,CAAC,SAAS,EAAE,MAAM;IAL5B,SAAkB,IAAI,0BAA0B;IAChD;IACE,qCAAqC;IAC5B,UAAU,EAAE,MAAM;IAC3B,yFAAyF;IAChF,SAAS,EAAE,MAAM,EAG3B;CACF;AAED,iGAAiG;AACjG,qBAAa,wBAAyB,SAAQ,WAAW;IAGrD,4CAA4C;IAC5C,QAAQ,CAAC,SAAS,EAAE,MAAM;IAH5B,SAAkB,IAAI,8BAA8B;IACpD;IACE,4CAA4C;IACnC,SAAS,EAAE,MAAM,EAG3B;CACF;AAED;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,0EAA0E;IAC1E,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,EAAE,CAAC;IAClC;;;OAGG;IACH,GAAG,IAAI,MAAM,EAAE,CAAC;CACjB;AAiFD,wBAAgB,wBAAwB,CACtC,YAAY,CAAC,EAAE,MAAM,EACrB,mBAAmB,UAAQ,GAC1B,kBAAkB,CAGpB;AAED,6EAA6E;AAC7E,wBAAuB,oBAAoB,CACzC,MAAM,EAAE,gBAAgB,EACxB,YAAY,CAAC,EAAE,MAAM,EACrB,mBAAmB,UAAQ,EAC3B,MAAM,CAAC,EAAE,WAAW,GACnB,cAAc,CAAC,MAAM,CAAC,CAcxB"}
@@ -1 +1 @@
1
- {"version":3,"file":"secure-fetch.d.ts","sourceRoot":"","sources":["../../src/internal/secure-fetch.ts"],"names":[],"mappings":"AAkEA,iFAAiF;AACjF,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CA8D/C;AAED;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAEnF;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kEAAkE;IAClE,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,wBAAsB,YAAY,CAChC,KAAK,EAAE,GAAG,EACV,YAAY,EAAE,OAAO,EACrB,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,QAAQ,CAAC,CAiDnB;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,6DAA6D;IAC7D,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,UAAU,CAAC;IACjB,GAAG,EAAE,GAAG,CAAC;CACV;AAED;;;;;;;;GAQG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,IAAI,2BAA2B;CACzC;AA8CD;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,GAAG,EACV,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAAC,sBAAsB,CAAC,CAkFjC;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAoBnF"}
1
+ {"version":3,"file":"secure-fetch.d.ts","sourceRoot":"","sources":["../../src/internal/secure-fetch.ts"],"names":[],"mappings":"AAmEA,iFAAiF;AACjF,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CA8D/C;AAED;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAEnF;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kEAAkE;IAClE,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,wBAAsB,YAAY,CAChC,KAAK,EAAE,GAAG,EACV,YAAY,EAAE,OAAO,EACrB,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,QAAQ,CAAC,CAiDnB;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,6DAA6D;IAC7D,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,UAAU,CAAC;IACjB,GAAG,EAAE,GAAG,CAAC;CACV;AAED;;;;;;;;GAQG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,IAAI,2BAA2B;CACzC;AA8CD;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,GAAG,EACV,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAAC,sBAAsB,CAAC,CAkFjC;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAGnF"}
@@ -0,0 +1,13 @@
1
+ /** Bytes a stream reader accepts: a `Response` body, a web stream, or any async byte iterable. */
2
+ export type StreamByteSource = Response | ReadableStream<Uint8Array> | AsyncIterable<Uint8Array>;
3
+ /**
4
+ * Every chunk of a byte source: the chunk loop the line readers and the bounded body readers
5
+ * share. The source is cancelled or returned on every exit path.
6
+ *
7
+ * An abort ends the loop with the signal's reason even while a read is pending: the pending read
8
+ * is cancelled but not waited for, because a transport stream may leave it pending for good. For
9
+ * the same reason a lock is not released after an abort — releasing it under a pending read throws
10
+ * on some runtimes — and the cancellation is not awaited.
11
+ */
12
+ export declare function chunksOf(source: StreamByteSource, signal?: AbortSignal): AsyncGenerator<Uint8Array>;
13
+ //# sourceMappingURL=stream-chunks.d.ts.map