stitchkit 0.66.1 → 0.68.0

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 (93) hide show
  1. package/dist/agent-runtime.js +5 -6
  2. package/dist/application/admission.d.ts +131 -0
  3. package/dist/application/admission.d.ts.map +1 -0
  4. package/dist/application/channel.d.ts +106 -0
  5. package/dist/application/channel.d.ts.map +1 -0
  6. package/dist/application/graph.d.ts +1 -1
  7. package/dist/application/graph.d.ts.map +1 -1
  8. package/dist/application/kernel.d.ts.map +1 -1
  9. package/dist/application/latest-sink.d.ts.map +1 -1
  10. package/dist/application/resource.d.ts +72 -1
  11. package/dist/application/resource.d.ts.map +1 -1
  12. package/dist/application/server-resource.d.ts +26 -5
  13. package/dist/application/server-resource.d.ts.map +1 -1
  14. package/dist/application-grammy.js +2 -2
  15. package/dist/application-opentelemetry.js +1 -1
  16. package/dist/application.d.ts +4 -2
  17. package/dist/application.d.ts.map +1 -1
  18. package/dist/application.js +408 -24
  19. package/dist/browser/client.d.ts +2 -3
  20. package/dist/browser/client.d.ts.map +1 -1
  21. package/dist/browser/contract-stream.d.ts +5 -0
  22. package/dist/browser/contract-stream.d.ts.map +1 -0
  23. package/dist/browser/http.d.ts +3 -0
  24. package/dist/browser/http.d.ts.map +1 -1
  25. package/dist/browser/stream.d.ts +4 -1
  26. package/dist/browser/stream.d.ts.map +1 -1
  27. package/dist/browser/transport.d.ts +3 -0
  28. package/dist/browser/transport.d.ts.map +1 -0
  29. package/dist/cli.js +6 -7
  30. package/dist/contract/define.d.ts +34 -2
  31. package/dist/contract/define.d.ts.map +1 -1
  32. package/dist/contract/errors.d.ts +4 -0
  33. package/dist/contract/errors.d.ts.map +1 -1
  34. package/dist/contract/index.d.ts +2 -1
  35. package/dist/contract/index.d.ts.map +1 -1
  36. package/dist/contract/index.js +5 -1
  37. package/dist/contract/stream.d.ts +19 -0
  38. package/dist/contract/stream.d.ts.map +1 -0
  39. package/dist/{index-xxye8j3k.js → index-0w9abg87.js} +4 -0
  40. package/dist/index-2k4yrqkc.js +9 -0
  41. package/dist/index-69m2278y.js +115 -0
  42. package/dist/{index-eabpd4tb.js → index-6wzr93cg.js} +21 -2
  43. package/dist/{index-1pgeyyee.js → index-7pn02bgv.js} +4 -6
  44. package/dist/index-9ky9hhg3.js +484 -0
  45. package/dist/{index-wrhf06ak.js → index-avjd67h6.js} +8 -8
  46. package/dist/{index-413xk7ga.js → index-cq9q73nf.js} +50 -1
  47. package/dist/{index-6taryy00.js → index-dafax5md.js} +1 -1
  48. package/dist/{index-s1tywej8.js → index-nymn7wbg.js} +1103 -20
  49. package/dist/{index-y91zd0ch.js → index-pr0qsmjy.js} +3 -2
  50. package/dist/index-v5bayx1z.js +138 -0
  51. package/dist/{index-mfw1pec7.js → index-wqyrvhzz.js} +3 -3
  52. package/dist/{index-svqhcrrj.js → index-zd4v31v2.js} +156 -10
  53. package/dist/{index-x8v8b7rc.js → index-zpyj7hsv.js} +330 -58
  54. package/dist/index.js +270 -105
  55. package/dist/internal/bounded-lines.d.ts +4 -0
  56. package/dist/internal/bounded-lines.d.ts.map +1 -0
  57. package/dist/node.d.ts +1 -0
  58. package/dist/node.d.ts.map +1 -1
  59. package/dist/node.js +11 -8
  60. package/dist/observability/index.js +6 -7
  61. package/dist/remote.js +5 -6
  62. package/dist/server/contract-stream.d.ts +5 -0
  63. package/dist/server/contract-stream.d.ts.map +1 -0
  64. package/dist/server/create.d.ts.map +1 -1
  65. package/dist/server/implement.d.ts.map +1 -1
  66. package/dist/server/index.d.ts +1 -0
  67. package/dist/server/index.d.ts.map +1 -1
  68. package/dist/server/index.js +41 -283
  69. package/dist/server/openapi.d.ts.map +1 -1
  70. package/dist/server/process-signals.d.ts.map +1 -1
  71. package/dist/server/stream.d.ts +3 -1
  72. package/dist/server/stream.d.ts.map +1 -1
  73. package/dist/server/streaming-route.d.ts +2 -0
  74. package/dist/server/streaming-route.d.ts.map +1 -1
  75. package/dist/server/types.d.ts +13 -3
  76. package/dist/server/types.d.ts.map +1 -1
  77. package/dist/server/unix-client-body.d.ts +4 -0
  78. package/dist/server/unix-client-body.d.ts.map +1 -0
  79. package/dist/server/unix-client-bun.d.ts +13 -0
  80. package/dist/server/unix-client-bun.d.ts.map +1 -0
  81. package/dist/server/unix-client-error.d.ts +9 -0
  82. package/dist/server/unix-client-error.d.ts.map +1 -0
  83. package/dist/server/unix-client.d.ts +25 -0
  84. package/dist/server/unix-client.d.ts.map +1 -0
  85. package/dist/testing.js +7 -5
  86. package/dist/tools.js +14 -16
  87. package/llms-full.txt +594 -29
  88. package/package.json +1 -1
  89. package/dist/index-48ffdxgk.js +0 -6
  90. package/dist/index-pzyt11ch.js +0 -127
  91. package/dist/index-tvwcrx2d.js +0 -224
  92. package/dist/index-wd8g8z6e.js +0 -206
  93. package/dist/index-yr276yz0.js +0 -6
@@ -7,35 +7,34 @@ import {
7
7
  defaultSignalSource,
8
8
  guardSignalCallback,
9
9
  reportSignalError
10
- } from "./index-y91zd0ch.js";
10
+ } from "./index-pr0qsmjy.js";
11
11
  import {
12
12
  ShutdownOptionsSchema
13
13
  } from "./index-dk6e56g0.js";
14
14
  import {
15
- errorCode,
16
15
  extractIp,
17
16
  getClientInfo,
18
17
  getRequestContext,
19
- normalizeError,
20
18
  parseQueryParams,
21
- recordedErrorMessage,
22
19
  resolveSocketIp,
23
20
  resolveTraceId,
24
21
  runWithRequestContext,
25
22
  setRequestEndpoint,
26
- setRequestError,
27
- validateDeclaredOutput,
28
- zodIssues
29
- } from "./index-tvwcrx2d.js";
23
+ setRequestError
24
+ } from "./index-69m2278y.js";
30
25
  import {
26
+ DEFAULT_CONTRACT_STREAM_FRAME_BYTES,
27
+ errorCode,
31
28
  isUnsafeKey,
32
- safeJsonParse
33
- } from "./index-wd8g8z6e.js";
34
- import {
35
29
  joinRoutePath,
30
+ normalizeError,
36
31
  parseTrailingWildcard,
37
- resolveTraceContext
38
- } from "./index-pzyt11ch.js";
32
+ recordedErrorMessage,
33
+ resolveTraceContext,
34
+ safeJsonParse,
35
+ validateDeclaredOutput,
36
+ zodIssues
37
+ } from "./index-9ky9hhg3.js";
39
38
  import {
40
39
  isRecord,
41
40
  transportResult
@@ -43,7 +42,7 @@ import {
43
42
  import {
44
43
  AppError,
45
44
  badRequest
46
- } from "./index-xxye8j3k.js";
45
+ } from "./index-0w9abg87.js";
47
46
 
48
47
  // src/server/multipart.ts
49
48
  var DEFAULT_MAX_REQUEST_BYTES = 25 * 1024 * 1024;
@@ -470,6 +469,195 @@ async function parseMultipart(req, descriptor, fieldsSchema, receivers) {
470
469
  }
471
470
  }
472
471
 
472
+ // src/server/streaming-route.ts
473
+ var DEFAULT_STREAM_HEARTBEAT_MS = 5000;
474
+ var FRAMINGS = {
475
+ ndjson: {
476
+ contentType: "application/x-ndjson",
477
+ keepAlive: `
478
+ `,
479
+ frame: (value) => `${JSON.stringify(value)}
480
+ `
481
+ },
482
+ sse: {
483
+ contentType: "text/event-stream",
484
+ keepAlive: `: keep-alive
485
+
486
+ `,
487
+ frame: (value) => `data: ${JSON.stringify(value)}
488
+
489
+ `,
490
+ done: `data: [DONE]
491
+
492
+ `
493
+ }
494
+ };
495
+ function applyIdleTimeout(server, request, seconds) {
496
+ if (typeof server !== "object" || server === null)
497
+ return;
498
+ const timeout = Reflect.get(server, "timeout");
499
+ if (typeof timeout !== "function")
500
+ return;
501
+ try {
502
+ Reflect.apply(timeout, server, [request, seconds]);
503
+ } catch {}
504
+ }
505
+ function unrefTimer(timer) {
506
+ if (typeof timer !== "object" || timer === null)
507
+ return;
508
+ const unref = Reflect.get(timer, "unref");
509
+ if (typeof unref === "function")
510
+ Reflect.apply(unref, timer, []);
511
+ }
512
+ var MAX_BUFFERED_FRAMES = 16;
513
+ function responseHeaders(framing, extra) {
514
+ const headers = new Headers(extra);
515
+ headers.set("Content-Type", framing.contentType);
516
+ headers.set("Cache-Control", "no-cache");
517
+ headers.set("Connection", "keep-alive");
518
+ headers.set("X-Accel-Buffering", "no");
519
+ return headers;
520
+ }
521
+ function streamingRoute(options) {
522
+ const heartbeatMs = options.heartbeatMs ?? DEFAULT_STREAM_HEARTBEAT_MS;
523
+ if (!Number.isFinite(heartbeatMs) || heartbeatMs <= 0) {
524
+ throw new TypeError("heartbeatMs must be a finite positive number of milliseconds");
525
+ }
526
+ const idleTimeoutSeconds = options.idleTimeoutSeconds ?? 0;
527
+ if (!Number.isInteger(idleTimeoutSeconds) || idleTimeoutSeconds < 0) {
528
+ throw new TypeError("idleTimeoutSeconds must be a non-negative integer");
529
+ }
530
+ const framing = FRAMINGS[options.format ?? "ndjson"];
531
+ return {
532
+ method: options.method ?? "GET",
533
+ path: options.path,
534
+ handler: async (request, context) => {
535
+ applyIdleTimeout(context.server, request, idleTimeoutSeconds);
536
+ const departed = new AbortController;
537
+ const iterable = await options.source(request, { ...context, signal: departed.signal });
538
+ const iterator = iterable[Symbol.asyncIterator]();
539
+ const encoder2 = new TextEncoder;
540
+ let heartbeat = null;
541
+ let closed = false;
542
+ const stopHeartbeat = () => {
543
+ if (heartbeat === null)
544
+ return;
545
+ clearInterval(heartbeat);
546
+ heartbeat = null;
547
+ };
548
+ const release = () => {
549
+ if (closed)
550
+ return;
551
+ closed = true;
552
+ try {
553
+ options.onClose?.();
554
+ stopHeartbeat();
555
+ signalDemand();
556
+ departed.abort();
557
+ Promise.resolve(iterator.return?.(undefined)).catch(() => {
558
+ return;
559
+ });
560
+ } catch {}
561
+ };
562
+ let demand = null;
563
+ const signalDemand = () => {
564
+ const resolve = demand;
565
+ demand = null;
566
+ resolve?.();
567
+ };
568
+ const stream = new ReadableStream({
569
+ start(controller) {
570
+ const send = (text) => {
571
+ if (closed)
572
+ return false;
573
+ try {
574
+ controller.enqueue(encoder2.encode(text));
575
+ return true;
576
+ } catch {
577
+ release();
578
+ return false;
579
+ }
580
+ };
581
+ const finish = () => {
582
+ try {
583
+ controller.close();
584
+ } catch {}
585
+ };
586
+ if (request.signal.aborted) {
587
+ release();
588
+ finish();
589
+ return;
590
+ }
591
+ request.signal.addEventListener("abort", () => {
592
+ release();
593
+ finish();
594
+ });
595
+ send(framing.keepAlive);
596
+ heartbeat = setInterval(() => {
597
+ if ((controller.desiredSize ?? 1) > 0)
598
+ send(framing.keepAlive);
599
+ }, heartbeatMs);
600
+ unrefTimer(heartbeat);
601
+ const awaitDemand = async () => {
602
+ while (!closed && (controller.desiredSize ?? 1) <= 0) {
603
+ await new Promise((resolve) => {
604
+ demand = resolve;
605
+ });
606
+ }
607
+ };
608
+ let sinceYield = 0;
609
+ (async () => {
610
+ try {
611
+ for (;; ) {
612
+ await awaitDemand();
613
+ if (closed)
614
+ return;
615
+ const next = await iterator.next();
616
+ if (closed)
617
+ return;
618
+ if (next.done)
619
+ break;
620
+ if (!send(framing.frame(next.value)))
621
+ return;
622
+ sinceYield += 1;
623
+ if (sinceYield >= MAX_BUFFERED_FRAMES) {
624
+ sinceYield = 0;
625
+ await new Promise((resolve) => {
626
+ setTimeout(resolve, 0);
627
+ });
628
+ if (closed)
629
+ return;
630
+ }
631
+ }
632
+ if (framing.done)
633
+ send(framing.done);
634
+ } catch (error) {
635
+ if (!closed)
636
+ send(framing.frame(normalizeError(error).toJSON()));
637
+ } finally {
638
+ release();
639
+ finish();
640
+ }
641
+ })();
642
+ },
643
+ pull() {
644
+ signalDemand();
645
+ },
646
+ cancel() {
647
+ release();
648
+ }
649
+ }, { highWaterMark: MAX_BUFFERED_FRAMES });
650
+ return new Response(stream, { headers: responseHeaders(framing, options.headers) });
651
+ }
652
+ };
653
+ }
654
+ function ndjsonRoute(options) {
655
+ return streamingRoute({ ...options, format: "ndjson" });
656
+ }
657
+ function sseRoute(options) {
658
+ return streamingRoute({ ...options, format: "sse" });
659
+ }
660
+
473
661
  // src/server/request-body.ts
474
662
  function requestAbortReason(req) {
475
663
  return req.signal.reason ?? new DOMException("The connection was closed", "AbortError");
@@ -615,6 +803,97 @@ function buildErrorContext(req, url, traceId, clientIp) {
615
803
  };
616
804
  }
617
805
 
806
+ // src/server/contract-stream.ts
807
+ function encodedBytes(value, format) {
808
+ const json = JSON.stringify(value);
809
+ if (json === undefined)
810
+ throw new TypeError("Streaming item is not JSON serializable");
811
+ const framing = format === "sse" ? `data: ${json}
812
+
813
+ ` : `${json}
814
+ `;
815
+ return new TextEncoder().encode(framing).byteLength;
816
+ }
817
+ function waitForAbort(signal) {
818
+ return new Promise((_resolve, reject) => {
819
+ const rejectAborted = () => reject(signal.reason ?? new Error("Stream aborted"));
820
+ if (signal.aborted)
821
+ rejectAborted();
822
+ else
823
+ signal.addEventListener("abort", rejectAborted, { once: true });
824
+ });
825
+ }
826
+ function contractStreamResponse(request, context, source, descriptor, operationAbort) {
827
+ const format = descriptor.format ?? "ndjson";
828
+ const maxFrameBytes = descriptor.maxFrameBytes ?? DEFAULT_CONTRACT_STREAM_FRAME_BYTES;
829
+ let lifetime;
830
+ let lifetimeExpired = false;
831
+ if (descriptor.lifetimeMs !== undefined) {
832
+ lifetime = setTimeout(() => {
833
+ lifetimeExpired = true;
834
+ operationAbort.abort(new Error("Stream lifetime expired"));
835
+ }, descriptor.lifetimeMs);
836
+ lifetime.unref?.();
837
+ }
838
+ const frames = async function* () {
839
+ const iterator = source[Symbol.asyncIterator]();
840
+ const aborted = waitForAbort(operationAbort.signal);
841
+ let terminalSeen = descriptor.terminal === undefined;
842
+ try {
843
+ for (;; ) {
844
+ const next = await Promise.race([iterator.next(), aborted]);
845
+ if (next.done)
846
+ break;
847
+ const parsed = descriptor.item.safeParse(next.value);
848
+ if (!parsed.success) {
849
+ console.error("[stitchkit] contract stream item failed validation:", parsed.error);
850
+ throw new AppError("STREAM_ITEM_INVALID", "Stream item did not match its contract", 500);
851
+ }
852
+ const item = parsed.data;
853
+ if (descriptor.terminal?.safeParse(item).success)
854
+ terminalSeen = true;
855
+ const frame = { type: "data", data: item };
856
+ if (encodedBytes(frame, format) > maxFrameBytes) {
857
+ console.error(`[stitchkit] contract stream frame exceeded its ${maxFrameBytes} byte limit`);
858
+ throw new AppError("STREAM_FRAME_TOO_LARGE", "Stream item exceeded its declared frame limit", 500);
859
+ }
860
+ yield frame;
861
+ }
862
+ if (!terminalSeen) {
863
+ throw new AppError("STREAM_TERMINAL_MISSING", "Stream completed before its declared terminal item", 500);
864
+ }
865
+ yield { type: "end" };
866
+ } catch (error) {
867
+ if (operationAbort.signal.aborted && !lifetimeExpired)
868
+ return;
869
+ if (lifetimeExpired) {
870
+ yield {
871
+ type: "error",
872
+ error: new AppError("STREAM_LIFETIME_EXCEEDED", "Stream exceeded its declared lifetime", 408).toJSON().error
873
+ };
874
+ return;
875
+ }
876
+ yield { type: "error", error: normalizeError(error).toJSON().error };
877
+ } finally {
878
+ if (lifetime !== undefined)
879
+ clearTimeout(lifetime);
880
+ operationAbort.abort();
881
+ Promise.resolve(iterator.return?.(undefined)).catch(() => {
882
+ return;
883
+ });
884
+ }
885
+ };
886
+ const route = streamingRoute({
887
+ path: "/",
888
+ format,
889
+ heartbeatMs: descriptor.heartbeatMs,
890
+ idleTimeoutSeconds: descriptor.idleTimeoutSeconds,
891
+ source: () => frames(),
892
+ onClose: () => operationAbort.abort()
893
+ });
894
+ return Promise.resolve(route.handler(request, context));
895
+ }
896
+
618
897
  // src/server/logger.ts
619
898
  var c = {
620
899
  reset: "\x1B[0m",
@@ -1089,6 +1368,12 @@ function isClientClosedRequest(req, error) {
1089
1368
  return true;
1090
1369
  return req.signal.reason !== undefined && containsRequestAbortReason(error, req.signal.reason);
1091
1370
  }
1371
+ function isAsyncIterable(value) {
1372
+ if (typeof value !== "object" && typeof value !== "function" || value === null) {
1373
+ return false;
1374
+ }
1375
+ return typeof Reflect.get(value, Symbol.asyncIterator) === "function";
1376
+ }
1092
1377
  function createHandler(config) {
1093
1378
  const { cors, hooks, logging = false, observability, trustProxy = false } = config;
1094
1379
  if (cors)
@@ -1309,6 +1594,17 @@ function createHandler(config) {
1309
1594
  const responseMetadata = method.responseMeta ? createResponseMetadata() : undefined;
1310
1595
  if (responseMetadata)
1311
1596
  ctx.response = responseMetadata;
1597
+ const streamAbort = method.stream ? new AbortController : undefined;
1598
+ if (streamAbort) {
1599
+ if (req.signal.aborted)
1600
+ streamAbort.abort(req.signal.reason);
1601
+ else {
1602
+ req.signal.addEventListener("abort", () => streamAbort.abort(req.signal.reason), {
1603
+ once: true
1604
+ });
1605
+ }
1606
+ ctx.signal = streamAbort.signal;
1607
+ }
1312
1608
  let result = await method.handler(ctx);
1313
1609
  if (method.rawResponse) {
1314
1610
  if (!(result instanceof Response)) {
@@ -1323,6 +1619,16 @@ function createHandler(config) {
1323
1619
  complete(rawRes.status);
1324
1620
  return rawRes;
1325
1621
  }
1622
+ if (method.stream) {
1623
+ if (!streamAbort || !isAsyncIterable(result)) {
1624
+ streamAbort?.abort();
1625
+ throw new AppError("INTERNAL_SERVER_ERROR", `Streaming endpoint ${method.serviceName}.${method.key} must return an AsyncIterable`, 500);
1626
+ }
1627
+ const response = await contractStreamResponse(req, { params: pathParams, server, ipAddress: clientIp.socketIp }, result, method.stream, streamAbort);
1628
+ const withCors = applyCors(response, cors, req);
1629
+ complete(withCors.status);
1630
+ return withCors;
1631
+ }
1326
1632
  if (groupHooks?.afterHandle) {
1327
1633
  const transformed = await groupHooks.afterHandle(ctx, result, method);
1328
1634
  if (transformed !== undefined)
@@ -1347,14 +1653,14 @@ function createHandler(config) {
1347
1653
  if (method.outputSchema && (responseStatus === 204 || responseStatus === 205)) {
1348
1654
  throw new AppError("INTERNAL_SERVER_ERROR", `${method.serviceName}.${method.key} cannot combine output with bodyless status ${responseStatus}`, 500);
1349
1655
  }
1350
- const responseHeaders = new Headers(corsHeaders2(cors, req));
1351
- applyResponseMetadata(responseHeaders, responseMetadata, `${method.serviceName}.${method.key}`);
1656
+ const responseHeaders2 = new Headers(corsHeaders2(cors, req));
1657
+ applyResponseMetadata(responseHeaders2, responseMetadata, `${method.serviceName}.${method.key}`);
1352
1658
  if (!method.outputSchema) {
1353
- const empty = new Response(null, { status: responseStatus, headers: responseHeaders });
1659
+ const empty = new Response(null, { status: responseStatus, headers: responseHeaders2 });
1354
1660
  complete(responseStatus);
1355
1661
  return empty;
1356
1662
  }
1357
- const body = Response.json(result, { status: responseStatus, headers: responseHeaders });
1663
+ const body = Response.json(result, { status: responseStatus, headers: responseHeaders2 });
1358
1664
  complete(responseStatus);
1359
1665
  return body;
1360
1666
  } catch (err) {
@@ -1465,7 +1771,17 @@ function bindProcessSignals(handle, options = {}) {
1465
1771
  bound.add(handle);
1466
1772
  const signals = [...new Set(options.signals ?? DEFAULT_PROCESS_SIGNALS)];
1467
1773
  const source = options.signalSource ?? defaultSignalSource;
1468
- const budgets = ShutdownOptionsSchema.omit({ signal: true }).parse(options.shutdown ?? {});
1774
+ const requested = options.shutdown ?? {};
1775
+ const validated = ShutdownOptionsSchema.omit({ signal: true }).parse(requested);
1776
+ const budgets = {
1777
+ ...requested.gracePeriodMs !== undefined && { gracePeriodMs: validated.gracePeriodMs },
1778
+ ...requested.forceTimeoutMs !== undefined && {
1779
+ forceTimeoutMs: validated.forceTimeoutMs
1780
+ },
1781
+ ...requested.retryAfterSeconds !== undefined && {
1782
+ retryAfterSeconds: validated.retryAfterSeconds
1783
+ }
1784
+ };
1469
1785
  const controller = new AbortController;
1470
1786
  const handlers = [];
1471
1787
  let started = false;
@@ -2096,4 +2412,771 @@ function socketIoLane(websocket) {
2096
2412
  });
2097
2413
  }
2098
2414
 
2099
- export { parseMultipart, createHandler, bindProcessSignals, bindRealtimeServer, webSocketLane, composeWebSocketHandlers, createSocketIOServer, socketIoLane };
2415
+ // src/server/unix-client-error.ts
2416
+ class UnixClientTransportError extends Error {
2417
+ code;
2418
+ delivery;
2419
+ constructor(code, message, delivery = "possibly-dispatched", options) {
2420
+ super(message, options);
2421
+ this.code = code;
2422
+ this.delivery = delivery;
2423
+ this.name = "UnixClientTransportError";
2424
+ }
2425
+ }
2426
+
2427
+ // src/server/unix-client.ts
2428
+ import {
2429
+ Agent,
2430
+ request as httpRequest
2431
+ } from "node:http";
2432
+
2433
+ // src/server/unix-client-body.ts
2434
+ async function readBoundedRequestBody(request, maxBytes) {
2435
+ const body = request.body;
2436
+ if (!body)
2437
+ return;
2438
+ const declared = request.headers.get("content-length");
2439
+ if (declared !== null && Number(declared) > maxBytes) {
2440
+ throw new UnixClientTransportError("UNIX_REQUEST_TOO_LARGE", `Unix request body exceeds the ${maxBytes} byte limit`, "not-dispatched");
2441
+ }
2442
+ const reader = body.getReader();
2443
+ const chunks = [];
2444
+ let total = 0;
2445
+ try {
2446
+ for (;; ) {
2447
+ if (request.signal.aborted)
2448
+ throw request.signal.reason;
2449
+ const chunk = await reader.read();
2450
+ if (chunk.done)
2451
+ break;
2452
+ total += chunk.value.byteLength;
2453
+ if (total > maxBytes) {
2454
+ throw new UnixClientTransportError("UNIX_REQUEST_TOO_LARGE", `Unix request body exceeds the ${maxBytes} byte limit`, "not-dispatched");
2455
+ }
2456
+ chunks.push(chunk.value);
2457
+ }
2458
+ } finally {
2459
+ reader.releaseLock();
2460
+ }
2461
+ const result = new Uint8Array(total);
2462
+ let offset = 0;
2463
+ for (const chunk of chunks) {
2464
+ result.set(chunk, offset);
2465
+ offset += chunk.byteLength;
2466
+ }
2467
+ return result;
2468
+ }
2469
+ function boundedIncomingBody(response, maxBytes, onDone) {
2470
+ let finished = false;
2471
+ let received = 0;
2472
+ const finish = () => {
2473
+ if (finished)
2474
+ return;
2475
+ finished = true;
2476
+ onDone();
2477
+ };
2478
+ return new ReadableStream({
2479
+ start(controller) {
2480
+ response.pause();
2481
+ response.on("data", (chunk) => {
2482
+ if (finished)
2483
+ return;
2484
+ received += chunk.byteLength;
2485
+ if (received > maxBytes) {
2486
+ controller.error(new UnixClientTransportError("UNIX_RESPONSE_TOO_LARGE", `Unix response body exceeds the ${maxBytes} byte limit`, "response-received"));
2487
+ response.destroy();
2488
+ finish();
2489
+ return;
2490
+ }
2491
+ controller.enqueue(new Uint8Array(chunk));
2492
+ response.pause();
2493
+ });
2494
+ response.once("end", () => {
2495
+ if (finished)
2496
+ return;
2497
+ controller.close();
2498
+ finish();
2499
+ });
2500
+ response.once("aborted", () => {
2501
+ if (finished)
2502
+ return;
2503
+ controller.error(new UnixClientTransportError("UNIX_RESPONSE_ABORTED", "Unix response was aborted before completion", "response-received"));
2504
+ finish();
2505
+ });
2506
+ response.once("error", (error) => {
2507
+ if (finished)
2508
+ return;
2509
+ controller.error(new UnixClientTransportError("UNIX_RESPONSE_ABORTED", "Unix response failed before completion", "response-received", { cause: error }));
2510
+ finish();
2511
+ });
2512
+ },
2513
+ pull() {
2514
+ response.resume();
2515
+ },
2516
+ cancel() {
2517
+ response.destroy();
2518
+ finish();
2519
+ }
2520
+ }, { highWaterMark: 1 });
2521
+ }
2522
+
2523
+ // src/server/unix-client-bun.ts
2524
+ var MAX_BODY_CHUNK_BYTES = 64 * 1024;
2525
+ function isBunUnixRuntime(candidate) {
2526
+ if (typeof candidate !== "object" || candidate === null)
2527
+ return false;
2528
+ return typeof Reflect.get(candidate, "connect") === "function";
2529
+ }
2530
+ function runtimeBun() {
2531
+ const candidate = Reflect.get(globalThis, "Bun");
2532
+ return isBunUnixRuntime(candidate) ? candidate : undefined;
2533
+ }
2534
+ function hasBunUnixRuntime() {
2535
+ return runtimeBun() !== undefined;
2536
+ }
2537
+ function concatBytes2(left, right) {
2538
+ if (left.byteLength === 0)
2539
+ return right.slice();
2540
+ const combined = new Uint8Array(left.byteLength + right.byteLength);
2541
+ combined.set(left);
2542
+ combined.set(right, left.byteLength);
2543
+ return combined;
2544
+ }
2545
+ function indexOfSequence(bytes, sequence) {
2546
+ outer:
2547
+ for (let index = 0;index <= bytes.byteLength - sequence.length; index += 1) {
2548
+ for (let offset = 0;offset < sequence.length; offset += 1) {
2549
+ if (bytes[index + offset] !== sequence[offset])
2550
+ continue outer;
2551
+ }
2552
+ return index;
2553
+ }
2554
+ return -1;
2555
+ }
2556
+ function requestBytes(request, body) {
2557
+ const url = new URL(request.url);
2558
+ const headers = new Headers(request.headers);
2559
+ headers.set("host", url.host);
2560
+ headers.set("connection", "close");
2561
+ headers.set("accept-encoding", "identity");
2562
+ headers.delete("transfer-encoding");
2563
+ headers.delete("content-length");
2564
+ if (body !== undefined)
2565
+ headers.set("content-length", String(body.byteLength));
2566
+ const lines = [`${request.method} ${url.pathname}${url.search} HTTP/1.1`];
2567
+ for (const [name, value] of headers)
2568
+ lines.push(`${name}: ${value}`);
2569
+ const head = new TextEncoder().encode(`${lines.join(`\r
2570
+ `)}\r
2571
+ \r
2572
+ `);
2573
+ return body === undefined ? head : concatBytes2(head, body);
2574
+ }
2575
+ function parseContentLength(headers) {
2576
+ const raw = headers.get("content-length");
2577
+ if (raw === null)
2578
+ return;
2579
+ if (!/^\d+$/.test(raw))
2580
+ throw new Error("Unix response has an invalid Content-Length");
2581
+ const value = Number(raw);
2582
+ if (!Number.isSafeInteger(value)) {
2583
+ throw new Error("Unix response Content-Length is not a safe integer");
2584
+ }
2585
+ return value;
2586
+ }
2587
+ function bunUnixRequest(options) {
2588
+ const outgoing = requestBytes(options.request, options.body);
2589
+ const bun = runtimeBun();
2590
+ if (!bun)
2591
+ throw new Error("Bun Unix runtime is unavailable");
2592
+ let socket;
2593
+ let controller;
2594
+ let wire = new Uint8Array;
2595
+ let responseResolved = false;
2596
+ let headersReceived = false;
2597
+ let settled = false;
2598
+ let bodyComplete = false;
2599
+ let peerEnded = false;
2600
+ let chunked = false;
2601
+ let chunkRemaining;
2602
+ let expectChunkCrlf = false;
2603
+ let trailers = false;
2604
+ let remainingLength;
2605
+ let received = 0;
2606
+ let writeOffset = 0;
2607
+ let resolveResponse = () => {
2608
+ return;
2609
+ };
2610
+ let rejectResponse = () => {
2611
+ return;
2612
+ };
2613
+ const response = new Promise((resolve, reject) => {
2614
+ resolveResponse = resolve;
2615
+ rejectResponse = reject;
2616
+ });
2617
+ const transportFailure = (error) => {
2618
+ if (error instanceof UnixClientTransportError)
2619
+ return error;
2620
+ if (headersReceived) {
2621
+ return new UnixClientTransportError("UNIX_RESPONSE_ABORTED", "Unix response failed before completion", "response-received", { cause: error });
2622
+ }
2623
+ const dispatched = writeOffset > 0;
2624
+ return new UnixClientTransportError(dispatched ? "UNIX_DELIVERY_UNCERTAIN" : "UNIX_CONNECT_FAILED", dispatched ? "Unix request transport failed after dispatch may have begun" : "Unix socket connection failed before request dispatch", dispatched ? "possibly-dispatched" : "not-dispatched", { cause: error });
2625
+ };
2626
+ const clearRegistration = options.registerAbort(() => {
2627
+ fail(new UnixClientTransportError("UNIX_CLIENT_CLOSED", "Unix client transport is closed"));
2628
+ });
2629
+ const timer = setTimeout(() => {
2630
+ fail(new UnixClientTransportError("UNIX_HEADERS_TIMEOUT", `Unix response headers did not arrive within ${options.headersTimeoutMs}ms`));
2631
+ }, options.headersTimeoutMs);
2632
+ timer.unref();
2633
+ const removeRequestAbort = () => {
2634
+ options.request.signal.removeEventListener("abort", abortRequest);
2635
+ };
2636
+ const finish = (terminate = true) => {
2637
+ if (settled)
2638
+ return;
2639
+ settled = true;
2640
+ clearTimeout(timer);
2641
+ clearRegistration();
2642
+ removeRequestAbort();
2643
+ if (terminate)
2644
+ socket?.terminate();
2645
+ };
2646
+ function fail(error) {
2647
+ if (settled)
2648
+ return;
2649
+ if (responseResolved)
2650
+ controller?.error(error);
2651
+ else
2652
+ rejectResponse(error);
2653
+ finish();
2654
+ }
2655
+ function abortRequest() {
2656
+ fail(options.request.signal.reason ?? new DOMException("Request aborted", "AbortError"));
2657
+ }
2658
+ const takeWire = (bytes) => {
2659
+ const value = wire.slice(0, bytes);
2660
+ wire = wire.slice(bytes);
2661
+ return value;
2662
+ };
2663
+ const enqueue = (value) => {
2664
+ received += value.byteLength;
2665
+ if (received > options.maxResponseBytes) {
2666
+ fail(new UnixClientTransportError("UNIX_RESPONSE_TOO_LARGE", `Unix response body exceeds the ${options.maxResponseBytes} byte limit`, "response-received"));
2667
+ return;
2668
+ }
2669
+ controller?.enqueue(value);
2670
+ };
2671
+ const completeBody = () => {
2672
+ if (bodyComplete || settled)
2673
+ return;
2674
+ bodyComplete = true;
2675
+ controller?.close();
2676
+ finish();
2677
+ };
2678
+ const resumeForData = () => {
2679
+ if (settled || bodyComplete || peerEnded)
2680
+ return;
2681
+ socket?.resume();
2682
+ };
2683
+ const pumpChunked = () => {
2684
+ if (!controller || settled || bodyComplete)
2685
+ return;
2686
+ for (;; ) {
2687
+ if ((controller.desiredSize ?? 1) <= 0)
2688
+ return;
2689
+ if (trailers) {
2690
+ if (wire.byteLength >= 2 && wire[0] === 13 && wire[1] === 10) {
2691
+ takeWire(2);
2692
+ completeBody();
2693
+ return;
2694
+ }
2695
+ const trailerEnd = indexOfSequence(wire, [13, 10, 13, 10]);
2696
+ if (trailerEnd < 0) {
2697
+ if (wire.byteLength > options.maxHeaderBytes) {
2698
+ fail(new UnixClientTransportError("UNIX_HEADERS_TOO_LARGE", `Unix response trailers exceed the ${options.maxHeaderBytes} byte limit`, "response-received"));
2699
+ } else if (peerEnded) {
2700
+ fail(transportFailure(new Error("Unix chunked response ended inside trailers")));
2701
+ } else {
2702
+ resumeForData();
2703
+ }
2704
+ return;
2705
+ }
2706
+ takeWire(trailerEnd + 4);
2707
+ completeBody();
2708
+ return;
2709
+ }
2710
+ if (expectChunkCrlf) {
2711
+ if (wire.byteLength < 2) {
2712
+ if (peerEnded) {
2713
+ fail(transportFailure(new Error("Unix chunked response ended before chunk delimiter")));
2714
+ } else
2715
+ resumeForData();
2716
+ return;
2717
+ }
2718
+ if (wire[0] !== 13 || wire[1] !== 10) {
2719
+ fail(transportFailure(new Error("Unix chunked response has an invalid chunk delimiter")));
2720
+ return;
2721
+ }
2722
+ takeWire(2);
2723
+ expectChunkCrlf = false;
2724
+ }
2725
+ if (chunkRemaining === undefined) {
2726
+ const lineEnd = indexOfSequence(wire, [13, 10]);
2727
+ if (lineEnd < 0) {
2728
+ if (wire.byteLength > 1024) {
2729
+ fail(transportFailure(new Error("Unix chunk header is too large")));
2730
+ } else if (peerEnded) {
2731
+ fail(transportFailure(new Error("Unix chunked response ended inside chunk header")));
2732
+ } else
2733
+ resumeForData();
2734
+ return;
2735
+ }
2736
+ const line = new TextDecoder("ascii", { fatal: true }).decode(takeWire(lineEnd));
2737
+ takeWire(2);
2738
+ const sizeText = line.split(";", 1)[0]?.trim() ?? "";
2739
+ if (!/^[0-9a-f]+$/i.test(sizeText)) {
2740
+ fail(transportFailure(new Error("Unix chunked response has an invalid chunk size")));
2741
+ return;
2742
+ }
2743
+ chunkRemaining = Number.parseInt(sizeText, 16);
2744
+ if (!Number.isSafeInteger(chunkRemaining)) {
2745
+ fail(transportFailure(new Error("Unix chunk size is not a safe integer")));
2746
+ return;
2747
+ }
2748
+ if (chunkRemaining === 0) {
2749
+ chunkRemaining = undefined;
2750
+ trailers = true;
2751
+ continue;
2752
+ }
2753
+ }
2754
+ if (wire.byteLength === 0) {
2755
+ if (peerEnded) {
2756
+ fail(transportFailure(new Error("Unix chunked response ended inside a chunk")));
2757
+ } else
2758
+ resumeForData();
2759
+ return;
2760
+ }
2761
+ const bytes = Math.min(chunkRemaining, wire.byteLength, MAX_BODY_CHUNK_BYTES);
2762
+ enqueue(takeWire(bytes));
2763
+ if (settled)
2764
+ return;
2765
+ chunkRemaining -= bytes;
2766
+ if (chunkRemaining === 0) {
2767
+ chunkRemaining = undefined;
2768
+ expectChunkCrlf = true;
2769
+ }
2770
+ return;
2771
+ }
2772
+ };
2773
+ const pumpPlain = () => {
2774
+ if (!controller || settled || bodyComplete)
2775
+ return;
2776
+ if ((controller.desiredSize ?? 1) <= 0)
2777
+ return;
2778
+ if (wire.byteLength > 0) {
2779
+ const permitted = remainingLength ?? wire.byteLength;
2780
+ const bytes = Math.min(permitted, wire.byteLength, MAX_BODY_CHUNK_BYTES);
2781
+ enqueue(takeWire(bytes));
2782
+ if (settled)
2783
+ return;
2784
+ if (remainingLength !== undefined) {
2785
+ remainingLength -= bytes;
2786
+ if (remainingLength === 0) {
2787
+ completeBody();
2788
+ return;
2789
+ }
2790
+ }
2791
+ return;
2792
+ }
2793
+ if (peerEnded) {
2794
+ if (remainingLength !== undefined && remainingLength > 0) {
2795
+ fail(transportFailure(new Error("Unix response ended before Content-Length bytes arrived")));
2796
+ } else {
2797
+ completeBody();
2798
+ }
2799
+ return;
2800
+ }
2801
+ resumeForData();
2802
+ };
2803
+ const pump = () => {
2804
+ if (chunked)
2805
+ pumpChunked();
2806
+ else
2807
+ pumpPlain();
2808
+ };
2809
+ const parseHeaders = () => {
2810
+ const headerEnd = indexOfSequence(wire, [13, 10, 13, 10]);
2811
+ if (headerEnd < 0) {
2812
+ if (wire.byteLength > options.maxHeaderBytes) {
2813
+ fail(new UnixClientTransportError("UNIX_HEADERS_TOO_LARGE", `Unix response headers exceed the ${options.maxHeaderBytes} byte limit`, "response-received"));
2814
+ } else {
2815
+ socket?.resume();
2816
+ }
2817
+ return;
2818
+ }
2819
+ if (headerEnd > options.maxHeaderBytes) {
2820
+ fail(new UnixClientTransportError("UNIX_HEADERS_TOO_LARGE", `Unix response headers exceed the ${options.maxHeaderBytes} byte limit`, "response-received"));
2821
+ return;
2822
+ }
2823
+ headersReceived = true;
2824
+ let text;
2825
+ try {
2826
+ text = new TextDecoder("utf-8", { fatal: true }).decode(takeWire(headerEnd));
2827
+ } catch (error) {
2828
+ fail(transportFailure(error));
2829
+ return;
2830
+ }
2831
+ takeWire(4);
2832
+ const lines = text.split(`\r
2833
+ `);
2834
+ const statusLine = lines.shift() ?? "";
2835
+ const matched = /^HTTP\/1\.[01] (\d{3})(?: (.*))?$/.exec(statusLine);
2836
+ if (!matched) {
2837
+ fail(transportFailure(new Error("Unix response has an invalid HTTP status line")));
2838
+ return;
2839
+ }
2840
+ const status = Number(matched[1]);
2841
+ const statusText = matched[2] ?? "";
2842
+ const headers = new Headers;
2843
+ try {
2844
+ for (const line of lines) {
2845
+ const separator = line.indexOf(":");
2846
+ if (separator <= 0)
2847
+ throw new Error("Unix response has an invalid header line");
2848
+ headers.append(line.slice(0, separator).trim(), line.slice(separator + 1).trim());
2849
+ }
2850
+ remainingLength = parseContentLength(headers);
2851
+ } catch (error) {
2852
+ fail(transportFailure(error));
2853
+ return;
2854
+ }
2855
+ if (remainingLength !== undefined && remainingLength > options.maxResponseBytes) {
2856
+ fail(new UnixClientTransportError("UNIX_RESPONSE_TOO_LARGE", `Unix response body exceeds the ${options.maxResponseBytes} byte limit`, "response-received"));
2857
+ return;
2858
+ }
2859
+ chunked = headers.get("transfer-encoding")?.toLowerCase().includes("chunked") ?? false;
2860
+ const bodyless = options.request.method === "HEAD" || status === 204 || status === 205 || status === 304;
2861
+ clearTimeout(timer);
2862
+ responseResolved = true;
2863
+ if (bodyless || remainingLength === 0) {
2864
+ resolveResponse(new Response(null, { status, statusText, headers }));
2865
+ finish();
2866
+ return;
2867
+ }
2868
+ const body = new ReadableStream({
2869
+ start(value) {
2870
+ controller = value;
2871
+ pump();
2872
+ },
2873
+ pull() {
2874
+ pump();
2875
+ },
2876
+ cancel() {
2877
+ finish();
2878
+ }
2879
+ }, { highWaterMark: 1 });
2880
+ resolveResponse(new Response(body, { status, statusText, headers }));
2881
+ };
2882
+ const flushRequest = (active) => {
2883
+ while (writeOffset < outgoing.byteLength) {
2884
+ const written = active.write(outgoing, writeOffset, outgoing.byteLength - writeOffset);
2885
+ if (written < 0) {
2886
+ fail(transportFailure(new Error("Unix request socket closed while writing")));
2887
+ return;
2888
+ }
2889
+ if (written === 0)
2890
+ return;
2891
+ writeOffset += written;
2892
+ }
2893
+ };
2894
+ if (options.request.signal.aborted) {
2895
+ fail(options.request.signal.reason ?? new DOMException("Request aborted", "AbortError"));
2896
+ return response;
2897
+ }
2898
+ options.request.signal.addEventListener("abort", abortRequest, { once: true });
2899
+ bun.connect({
2900
+ unix: options.socketPath,
2901
+ socket: {
2902
+ binaryType: "uint8array",
2903
+ open(active) {
2904
+ socket = active;
2905
+ if (settled) {
2906
+ active.terminate();
2907
+ return;
2908
+ }
2909
+ flushRequest(active);
2910
+ },
2911
+ drain(active) {
2912
+ flushRequest(active);
2913
+ },
2914
+ data(active, data) {
2915
+ active.pause();
2916
+ wire = concatBytes2(wire, data);
2917
+ if (!responseResolved)
2918
+ parseHeaders();
2919
+ else
2920
+ pump();
2921
+ },
2922
+ end() {
2923
+ peerEnded = true;
2924
+ if (!responseResolved) {
2925
+ fail(transportFailure(new Error("Unix connection ended before response headers")));
2926
+ } else
2927
+ pump();
2928
+ },
2929
+ close(_active, error) {
2930
+ peerEnded = true;
2931
+ if (settled)
2932
+ return;
2933
+ if (error)
2934
+ fail(transportFailure(error));
2935
+ else if (!responseResolved) {
2936
+ fail(transportFailure(new Error("Unix connection closed before response headers")));
2937
+ } else
2938
+ pump();
2939
+ },
2940
+ error(_active, error) {
2941
+ fail(transportFailure(error));
2942
+ },
2943
+ connectError(_active, error) {
2944
+ fail(transportFailure(error));
2945
+ }
2946
+ }
2947
+ }).catch((error) => fail(transportFailure(error)));
2948
+ return response;
2949
+ }
2950
+
2951
+ // src/server/unix-client.ts
2952
+ var DEFAULT_MAX_BODY_BYTES = 16 * 1024 * 1024;
2953
+ var DEFAULT_HEADERS_TIMEOUT_MS = 30000;
2954
+ var DEFAULT_MAX_CONNECTIONS = 8;
2955
+ var DEFAULT_MAX_REDIRECTS = 5;
2956
+ var DEFAULT_MAX_HEADER_BYTES2 = 64 * 1024;
2957
+ function positiveInteger(value, fallback, name) {
2958
+ const resolved = value ?? fallback;
2959
+ if (!Number.isSafeInteger(resolved) || resolved <= 0) {
2960
+ throw new TypeError(`${name} must be a positive safe integer`);
2961
+ }
2962
+ return resolved;
2963
+ }
2964
+ function nonNegativeInteger(value, fallback, name) {
2965
+ const resolved = value ?? fallback;
2966
+ if (!Number.isSafeInteger(resolved) || resolved < 0) {
2967
+ throw new TypeError(`${name} must be a non-negative safe integer`);
2968
+ }
2969
+ return resolved;
2970
+ }
2971
+ function isRedirect(status) {
2972
+ return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
2973
+ }
2974
+ function requestBody(body) {
2975
+ if (body === undefined)
2976
+ return;
2977
+ const copy = new ArrayBuffer(body.byteLength);
2978
+ new Uint8Array(copy).set(body);
2979
+ return copy;
2980
+ }
2981
+ function responseHeaders2(response) {
2982
+ const headers = new Headers;
2983
+ for (let index = 0;index < response.rawHeaders.length; index += 2) {
2984
+ const name = response.rawHeaders[index];
2985
+ const value = response.rawHeaders[index + 1];
2986
+ if (name !== undefined && value !== undefined)
2987
+ headers.append(name, value);
2988
+ }
2989
+ return headers;
2990
+ }
2991
+ function redirectedRequest(request, location, status, body) {
2992
+ const url = new URL(location, request.url);
2993
+ const changesToGet = status === 303 || (status === 301 || status === 302) && request.method === "POST";
2994
+ const headers = new Headers(request.headers);
2995
+ if (changesToGet) {
2996
+ headers.delete("content-length");
2997
+ headers.delete("content-type");
2998
+ }
2999
+ return new Request(url, {
3000
+ method: changesToGet ? "GET" : request.method,
3001
+ headers,
3002
+ ...!changesToGet && body !== undefined && { body: requestBody(body) },
3003
+ redirect: request.redirect,
3004
+ signal: request.signal
3005
+ });
3006
+ }
3007
+ function createUnixClientTransport(config) {
3008
+ if (!config.socketPath.startsWith("/") || config.socketPath.includes("\x00")) {
3009
+ throw new TypeError("socketPath must be an absolute Unix socket path");
3010
+ }
3011
+ const maxRequestBytes = positiveInteger(config.maxRequestBytes, DEFAULT_MAX_BODY_BYTES, "maxRequestBytes");
3012
+ const maxResponseBytes = positiveInteger(config.maxResponseBytes, DEFAULT_MAX_BODY_BYTES, "maxResponseBytes");
3013
+ const headersTimeoutMs = positiveInteger(config.headersTimeoutMs, DEFAULT_HEADERS_TIMEOUT_MS, "headersTimeoutMs");
3014
+ const maxConnections = positiveInteger(config.maxConnections, DEFAULT_MAX_CONNECTIONS, "maxConnections");
3015
+ const maxHeaderBytes = positiveInteger(config.maxHeaderBytes, DEFAULT_MAX_HEADER_BYTES2, "maxHeaderBytes");
3016
+ const maxRedirects = nonNegativeInteger(config.maxRedirects, DEFAULT_MAX_REDIRECTS, "maxRedirects");
3017
+ const agent = new Agent({ keepAlive: true, maxSockets: maxConnections });
3018
+ const activeRequests = new Set;
3019
+ const activeResponses = new Set;
3020
+ const bunAborters = new Set;
3021
+ let closed = false;
3022
+ const dispatch = async (request, redirectCount) => {
3023
+ if (closed) {
3024
+ throw new UnixClientTransportError("UNIX_CLIENT_CLOSED", "Unix client transport is closed", "not-dispatched");
3025
+ }
3026
+ if (request.signal.aborted)
3027
+ throw request.signal.reason;
3028
+ const url = new URL(request.url);
3029
+ if (url.protocol !== "http:") {
3030
+ throw new TypeError("Unix client transport accepts only http: request URLs");
3031
+ }
3032
+ const body = await readBoundedRequestBody(request, maxRequestBytes);
3033
+ if (hasBunUnixRuntime()) {
3034
+ if (bunAborters.size >= maxConnections) {
3035
+ throw new UnixClientTransportError("UNIX_CONNECTION_LIMIT", `Unix client connection limit ${maxConnections} reached`, "not-dispatched");
3036
+ }
3037
+ const response = await bunUnixRequest({
3038
+ socketPath: config.socketPath,
3039
+ request,
3040
+ body,
3041
+ maxResponseBytes,
3042
+ maxHeaderBytes,
3043
+ headersTimeoutMs,
3044
+ registerAbort(abort) {
3045
+ bunAborters.add(abort);
3046
+ return () => void bunAborters.delete(abort);
3047
+ }
3048
+ });
3049
+ const location2 = response.headers.get("location");
3050
+ if (location2 && isRedirect(response.status)) {
3051
+ if (request.redirect === "error" || redirectCount >= maxRedirects) {
3052
+ await response.body?.cancel();
3053
+ throw new UnixClientTransportError("UNIX_REDIRECT_REFUSED", redirectCount >= maxRedirects ? `Unix redirect limit ${maxRedirects} exceeded` : "Unix request redirect mode is error", "response-received");
3054
+ }
3055
+ if (request.redirect === "follow") {
3056
+ await response.body?.cancel();
3057
+ return dispatch(redirectedRequest(request, location2, response.status, body), redirectCount + 1);
3058
+ }
3059
+ }
3060
+ return response;
3061
+ }
3062
+ const headers = Object.fromEntries(request.headers.entries());
3063
+ if (!Object.keys(headers).some((name) => name.toLowerCase() === "host")) {
3064
+ headers.host = url.host;
3065
+ }
3066
+ const incoming = await new Promise((resolve, reject) => {
3067
+ let settled = false;
3068
+ const resolveOnce = (response) => {
3069
+ if (settled)
3070
+ return;
3071
+ settled = true;
3072
+ resolve(response);
3073
+ };
3074
+ const rejectOnce = (error) => {
3075
+ if (settled)
3076
+ return;
3077
+ settled = true;
3078
+ reject(error);
3079
+ };
3080
+ const outgoing = httpRequest({
3081
+ socketPath: config.socketPath,
3082
+ path: `${url.pathname}${url.search}`,
3083
+ method: request.method,
3084
+ headers,
3085
+ agent,
3086
+ signal: request.signal
3087
+ }, resolveOnce);
3088
+ let connected = false;
3089
+ let requestStartBytes = 0;
3090
+ outgoing.once("socket", (socket) => {
3091
+ requestStartBytes = socket.bytesWritten;
3092
+ if (!socket.connecting && !socket.destroyed)
3093
+ connected = true;
3094
+ else {
3095
+ socket.once("connect", () => {
3096
+ connected = true;
3097
+ });
3098
+ }
3099
+ });
3100
+ activeRequests.add(outgoing);
3101
+ const timer = setTimeout(() => {
3102
+ const error = new UnixClientTransportError("UNIX_HEADERS_TIMEOUT", `Unix response headers did not arrive within ${headersTimeoutMs}ms`);
3103
+ rejectOnce(error);
3104
+ outgoing.destroy(error);
3105
+ }, headersTimeoutMs);
3106
+ timer.unref();
3107
+ outgoing.once("response", () => clearTimeout(timer));
3108
+ outgoing.once("error", (error) => {
3109
+ const dispatched = connected && (outgoing.socket?.bytesWritten ?? requestStartBytes) > requestStartBytes;
3110
+ rejectOnce(new UnixClientTransportError(dispatched ? "UNIX_DELIVERY_UNCERTAIN" : "UNIX_CONNECT_FAILED", dispatched ? "Unix request transport failed after dispatch may have begun" : "Unix socket connection failed before request dispatch", dispatched ? "possibly-dispatched" : "not-dispatched", { cause: error }));
3111
+ });
3112
+ outgoing.once("close", () => {
3113
+ clearTimeout(timer);
3114
+ activeRequests.delete(outgoing);
3115
+ });
3116
+ outgoing.end(body);
3117
+ });
3118
+ activeResponses.add(incoming);
3119
+ const releaseResponse = () => void activeResponses.delete(incoming);
3120
+ const status = incoming.statusCode ?? 0;
3121
+ const responseHeadersValue = responseHeaders2(incoming);
3122
+ const declaredResponseBytes = responseHeadersValue.get("content-length");
3123
+ if (declaredResponseBytes !== null && /^\d+$/.test(declaredResponseBytes) && Number(declaredResponseBytes) > maxResponseBytes) {
3124
+ incoming.destroy();
3125
+ releaseResponse();
3126
+ throw new UnixClientTransportError("UNIX_RESPONSE_TOO_LARGE", `Unix response body exceeds the ${maxResponseBytes} byte limit`, "response-received");
3127
+ }
3128
+ const location = responseHeadersValue.get("location");
3129
+ if (location && isRedirect(status)) {
3130
+ if (request.redirect === "error" || redirectCount >= maxRedirects) {
3131
+ incoming.destroy();
3132
+ releaseResponse();
3133
+ throw new UnixClientTransportError("UNIX_REDIRECT_REFUSED", redirectCount >= maxRedirects ? `Unix redirect limit ${maxRedirects} exceeded` : "Unix request redirect mode is error", "response-received");
3134
+ }
3135
+ if (request.redirect === "follow") {
3136
+ incoming.destroy();
3137
+ releaseResponse();
3138
+ return dispatch(redirectedRequest(request, location, status, body), redirectCount + 1);
3139
+ }
3140
+ }
3141
+ const bodyless = request.method === "HEAD" || status === 204 || status === 205 || status === 304;
3142
+ if (bodyless) {
3143
+ incoming.resume();
3144
+ incoming.once("end", releaseResponse);
3145
+ return new Response(null, {
3146
+ status,
3147
+ statusText: incoming.statusMessage,
3148
+ headers: responseHeadersValue
3149
+ });
3150
+ }
3151
+ return new Response(boundedIncomingBody(incoming, maxResponseBytes, releaseResponse), {
3152
+ status,
3153
+ statusText: incoming.statusMessage,
3154
+ headers: responseHeadersValue
3155
+ });
3156
+ };
3157
+ return {
3158
+ fetch(input, init) {
3159
+ return dispatch(new Request(input, init), 0);
3160
+ },
3161
+ get closed() {
3162
+ return closed;
3163
+ },
3164
+ async close() {
3165
+ if (closed)
3166
+ return;
3167
+ closed = true;
3168
+ for (const request of activeRequests)
3169
+ request.destroy();
3170
+ for (const response of activeResponses)
3171
+ response.destroy();
3172
+ for (const abort of [...bunAborters])
3173
+ abort();
3174
+ activeRequests.clear();
3175
+ activeResponses.clear();
3176
+ bunAborters.clear();
3177
+ agent.destroy();
3178
+ }
3179
+ };
3180
+ }
3181
+
3182
+ export { parseMultipart, DEFAULT_STREAM_HEARTBEAT_MS, streamingRoute, ndjsonRoute, sseRoute, createHandler, bindProcessSignals, bindRealtimeServer, webSocketLane, composeWebSocketHandlers, createSocketIOServer, socketIoLane, UnixClientTransportError, createUnixClientTransport };