brass-runtime 1.16.0 → 1.17.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 (219) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +287 -23
  3. package/dist/agent/cli/main.cjs +38 -38
  4. package/dist/agent/cli/main.js +6 -6
  5. package/dist/agent/cli/main.mjs +6 -6
  6. package/dist/agent/index.cjs +7 -7
  7. package/dist/agent/index.d.ts +1 -1
  8. package/dist/agent/index.js +6 -6
  9. package/dist/agent/index.mjs +6 -6
  10. package/dist/chunk-2HQTDLHF.mjs +683 -0
  11. package/dist/chunk-36I3M4UC.mjs +370 -0
  12. package/dist/{chunk-QY5FKYEQ.js → chunk-3AYM6WPJ.js} +570 -51
  13. package/dist/chunk-3LOYJFRR.cjs +300 -0
  14. package/dist/chunk-3Y2RIUMM.js +300 -0
  15. package/dist/{chunk-7XOPAB5Q.js → chunk-4P2HHGAX.mjs} +83 -5
  16. package/dist/{chunk-N6VHMOWB.cjs → chunk-4ROBZFL6.cjs} +128 -128
  17. package/dist/{chunk-NC5SDRYE.js → chunk-52OB2ROS.js} +4 -4
  18. package/dist/{chunk-JX3LZQJH.cjs → chunk-52PPNNI4.cjs} +82 -20
  19. package/dist/{chunk-5YOQOXEQ.cjs → chunk-5EC274J5.cjs} +676 -293
  20. package/dist/chunk-5QC7LRZ3.js +229 -0
  21. package/dist/{chunk-7TL2LHQJ.js → chunk-5VRJNBLZ.mjs} +524 -141
  22. package/dist/chunk-62AZW6UT.cjs +313 -0
  23. package/dist/chunk-6IXXWIUM.js +683 -0
  24. package/dist/chunk-6RY2FFN4.mjs +2024 -0
  25. package/dist/chunk-74ZTY6CP.js +2871 -0
  26. package/dist/chunk-7CMJS3QE.mjs +2871 -0
  27. package/dist/{chunk-2WC63LJK.mjs → chunk-7JIJOVCT.js} +20 -10
  28. package/dist/chunk-7X3K5RMS.js +2024 -0
  29. package/dist/chunk-7ZPEZ57L.cjs +2024 -0
  30. package/dist/{chunk-FM4W4QPL.js → chunk-A2OM6NEH.mjs} +5 -4
  31. package/dist/chunk-AGR5B2BC.cjs +683 -0
  32. package/dist/chunk-B33ICAKP.js +313 -0
  33. package/dist/{chunk-J3H54ZRV.mjs → chunk-B5JD23U7.mjs} +1 -1
  34. package/dist/{chunk-F5EUMJL7.mjs → chunk-BKK77SBA.js} +83 -5
  35. package/dist/{chunk-U5KWK3PX.mjs → chunk-C3MDXTRZ.js} +11 -0
  36. package/dist/{chunk-SPUEME2B.cjs → chunk-CZIVE6NT.cjs} +12 -1
  37. package/dist/{chunk-TDVMADDN.js → chunk-DNFJLJMW.mjs} +11 -0
  38. package/dist/{chunk-XDZOO4L5.js → chunk-EJ6BPYVR.mjs} +79 -17
  39. package/dist/chunk-EOC4UHBS.mjs +229 -0
  40. package/dist/chunk-F6XWZQY4.cjs +777 -0
  41. package/dist/{chunk-7LVI2GIN.js → chunk-FH2X7BVP.js} +507 -72
  42. package/dist/{chunk-OOGJ73B6.js → chunk-FHQGHPMO.mjs} +20 -10
  43. package/dist/{chunk-WQ5QNU5R.cjs → chunk-GLE2WY7Z.cjs} +652 -217
  44. package/dist/{chunk-G6IQOE4P.mjs → chunk-GYM3LLGS.mjs} +507 -72
  45. package/dist/{chunk-TVN5I4U6.cjs → chunk-JF5WGYJJ.cjs} +25 -24
  46. package/dist/{chunk-CY33PGEX.mjs → chunk-KH4SYAOS.mjs} +570 -51
  47. package/dist/chunk-KN32XNTH.mjs +313 -0
  48. package/dist/chunk-KQLYONSE.cjs +2871 -0
  49. package/dist/{chunk-7HUOJA4W.cjs → chunk-KZJQ723N.cjs} +90 -80
  50. package/dist/{chunk-CCKHV5BT.mjs → chunk-L2SYFEBS.js} +5 -4
  51. package/dist/{chunk-IJT6RRQ5.cjs → chunk-L6VB5N7Q.cjs} +20 -9
  52. package/dist/{chunk-ZGLD4TVZ.mjs → chunk-MBEJI5HF.mjs} +4 -4
  53. package/dist/{chunk-PRWCB3QL.mjs → chunk-MIIYDLGM.js} +524 -141
  54. package/dist/{chunk-H55LI6WY.js → chunk-MOO4L7F4.mjs} +15 -4
  55. package/dist/chunk-MVGUEJ5Z.cjs +370 -0
  56. package/dist/chunk-PD4EJTQC.cjs +229 -0
  57. package/dist/chunk-PWC3RBQE.mjs +300 -0
  58. package/dist/{chunk-MWXMNYJS.cjs → chunk-Q2I37RP3.cjs} +643 -124
  59. package/dist/{chunk-VFIUZG7J.mjs → chunk-RKGKFN2A.js} +79 -17
  60. package/dist/{chunk-NYL4D7SK.cjs → chunk-SA6HUJVI.cjs} +5 -5
  61. package/dist/chunk-SK7UZRNI.mjs +777 -0
  62. package/dist/{chunk-K2T3DV26.mjs → chunk-TRM4JUZQ.js} +15 -4
  63. package/dist/chunk-UB4B6OFY.js +370 -0
  64. package/dist/{chunk-G3XGCZDQ.js → chunk-UCUBNWM2.js} +1 -1
  65. package/dist/chunk-VWIPB6I5.js +777 -0
  66. package/dist/{chunk-JNFRRJYH.cjs → chunk-WBGRHGBP.cjs} +270 -192
  67. package/dist/{client-CtFmoDvM.d.ts → client-CZHU674n.d.ts} +211 -36
  68. package/dist/core/index.cjs +135 -9
  69. package/dist/core/index.d.ts +238 -33
  70. package/dist/core/index.js +155 -29
  71. package/dist/core/index.mjs +155 -29
  72. package/dist/{effect-CGNl5Rqp.d.ts → effect-DIUHZ9IN.d.ts} +89 -1
  73. package/dist/effectRunner-CFLC32IK.cjs +8 -0
  74. package/dist/{effectRunner-A4CHJXJI.js → effectRunner-L4S7IPT3.js} +2 -2
  75. package/dist/{effectRunner-OPUF6QRN.mjs → effectRunner-NNGG75QA.mjs} +2 -2
  76. package/dist/http/index.cjs +324 -2986
  77. package/dist/http/index.d.ts +54 -68
  78. package/dist/http/index.js +238 -2900
  79. package/dist/http/index.mjs +238 -2900
  80. package/dist/http/testing.cjs +14 -12
  81. package/dist/http/testing.d.ts +5 -4
  82. package/dist/http/testing.js +10 -8
  83. package/dist/http/testing.mjs +10 -8
  84. package/dist/index.cjs +423 -255
  85. package/dist/index.d.ts +87 -69
  86. package/dist/index.js +301 -133
  87. package/dist/index.mjs +301 -133
  88. package/dist/observability/index.cjs +18 -531
  89. package/dist/observability/index.d.ts +81 -8
  90. package/dist/observability/index.js +25 -538
  91. package/dist/observability/index.mjs +25 -538
  92. package/dist/perf/cli.cjs +401 -0
  93. package/dist/perf/cli.d.ts +1 -0
  94. package/dist/perf/cli.js +401 -0
  95. package/dist/perf/cli.mjs +401 -0
  96. package/dist/perf/index.cjs +141 -0
  97. package/dist/perf/index.d.ts +483 -0
  98. package/dist/perf/index.js +141 -0
  99. package/dist/perf/index.mjs +141 -0
  100. package/dist/schedule-CK3Ml_7p.d.ts +259 -0
  101. package/dist/schema/index.cjs +6 -2
  102. package/dist/schema/index.d.ts +3 -1
  103. package/dist/schema/index.js +5 -1
  104. package/dist/schema/index.mjs +5 -1
  105. package/dist/{server-C8hDXA74.d.ts → server-D6JZ15_e.d.ts} +16 -4
  106. package/dist/{stream-dvSs0QS5.d.ts → stream-B4oK9JFP.d.ts} +1 -1
  107. package/dist/{tracer-B5tRH9H7.d.ts → tracer-Hwt1cl7h.d.ts} +13 -54
  108. package/dist/{tracing-Dt9S_6V8.d.ts → tracing-DqbTKGcf.d.ts} +1 -1
  109. package/docs/ARCHITECTURE.md +292 -0
  110. package/docs/README.md +65 -0
  111. package/docs/adr/0001-ai-context-pack.md +32 -0
  112. package/docs/agent-apply-mode.md +104 -0
  113. package/docs/agent-approvals.md +110 -0
  114. package/docs/agent-batch.md +185 -0
  115. package/docs/agent-boundaries.md +112 -0
  116. package/docs/agent-chat-sessions.md +160 -0
  117. package/docs/agent-ci.md +17 -0
  118. package/docs/agent-cli.md +405 -0
  119. package/docs/agent-config.md +480 -0
  120. package/docs/agent-context-discovery.md +159 -0
  121. package/docs/agent-copilot-like-dx.md +126 -0
  122. package/docs/agent-declarative-optimized-planning.md +138 -0
  123. package/docs/agent-dx.md +224 -0
  124. package/docs/agent-env-files.md +126 -0
  125. package/docs/agent-follow-up-context.md +43 -0
  126. package/docs/agent-global-usage.md +180 -0
  127. package/docs/agent-init.md +109 -0
  128. package/docs/agent-install-and-configure.md +516 -0
  129. package/docs/agent-language-workspace-ux.md +99 -0
  130. package/docs/agent-llm-adapters.md +123 -0
  131. package/docs/agent-local-install.md +190 -0
  132. package/docs/agent-local-tests.md +51 -0
  133. package/docs/agent-observability.md +155 -0
  134. package/docs/agent-patch-quality-loop.md +162 -0
  135. package/docs/agent-presets.md +22 -0
  136. package/docs/agent-project-commands.md +237 -0
  137. package/docs/agent-project-intelligence.md +156 -0
  138. package/docs/agent-redaction.md +18 -0
  139. package/docs/agent-release-readiness.md +76 -0
  140. package/docs/agent-rollback-safety.md +162 -0
  141. package/docs/agent-rollback.md +23 -0
  142. package/docs/agent-run-artifacts.md +16 -0
  143. package/docs/agent-vscode-auto-discovery.md +137 -0
  144. package/docs/agent-vscode-batch-runner.md +100 -0
  145. package/docs/agent-vscode-chat-layout.md +90 -0
  146. package/docs/agent-vscode-clean-install.md +147 -0
  147. package/docs/agent-vscode-code-actions.md +70 -0
  148. package/docs/agent-vscode-diff-preview.md +45 -0
  149. package/docs/agent-vscode-inline-assist.md +56 -0
  150. package/docs/agent-vscode-install.md +186 -0
  151. package/docs/agent-vscode-model-setup.md +97 -0
  152. package/docs/agent-vscode-patch-preview.md +92 -0
  153. package/docs/agent-vscode-problems.md +79 -0
  154. package/docs/agent-vscode-project-dashboard.md +106 -0
  155. package/docs/agent-vscode-run-history.md +92 -0
  156. package/docs/agent-vscode-ux.md +73 -0
  157. package/docs/ai/INVARIANTS.md +84 -0
  158. package/docs/ai/PROJECT_MAP.md +338 -0
  159. package/docs/ai/PUBLIC_API.md +339 -0
  160. package/docs/ai/VALIDATION_MATRIX.md +67 -0
  161. package/docs/api-polish.md +37 -0
  162. package/docs/cancellation.md +162 -0
  163. package/docs/coverage.md +46 -0
  164. package/docs/framework-integrations.md +38 -0
  165. package/docs/frameworks/angular.md +153 -0
  166. package/docs/frameworks/express.md +125 -0
  167. package/docs/frameworks/fastify.md +124 -0
  168. package/docs/frameworks/nestjs.md +282 -0
  169. package/docs/frameworks/nextjs.md +147 -0
  170. package/docs/frameworks/react.md +139 -0
  171. package/docs/frameworks/vanilla.md +224 -0
  172. package/docs/getting-started.md +159 -0
  173. package/docs/guides/README.md +40 -0
  174. package/docs/guides/circuit-breaker.md +89 -0
  175. package/docs/guides/error-handling.md +91 -0
  176. package/docs/guides/getting-started.md +107 -0
  177. package/docs/guides/layers.md +189 -0
  178. package/docs/guides/metrics.md +101 -0
  179. package/docs/guides/resource-management.md +141 -0
  180. package/docs/guides/retry.md +215 -0
  181. package/docs/guides/semaphore.md +66 -0
  182. package/docs/guides/streams.md +117 -0
  183. package/docs/guides/supervisors.md +98 -0
  184. package/docs/guides/testing.md +162 -0
  185. package/docs/guides/tracing.md +71 -0
  186. package/docs/http-recipes.md +399 -0
  187. package/docs/http.md +749 -0
  188. package/docs/modules.md +285 -0
  189. package/docs/nestjs.md +6 -0
  190. package/docs/observability-collector-smoke.md +31 -0
  191. package/docs/observability-framework-examples.md +110 -0
  192. package/docs/observability.md +649 -0
  193. package/docs/otel-collector-smoke.yaml +27 -0
  194. package/docs/performance-profiler.md +199 -0
  195. package/docs/production-readiness.md +73 -0
  196. package/docs/recipes/README.md +12 -0
  197. package/docs/recipes/http-server.md +45 -0
  198. package/docs/recipes/layers.md +44 -0
  199. package/docs/recipes/performance.md +47 -0
  200. package/docs/recipes/runtime.md +41 -0
  201. package/docs/recipes/testing.md +41 -0
  202. package/docs/release.md +53 -0
  203. package/docs/wasm-bounded-queues.md +44 -0
  204. package/docs/wasm-engine-observability-benchmarks.md +85 -0
  205. package/docs/wasm-fiber-engine.md +117 -0
  206. package/docs/wasm-scheduler-state-machine.md +122 -0
  207. package/docs/wasm-stream-chunks.md +54 -0
  208. package/package.json +22 -2
  209. package/dist/chunk-45F7OKGT.cjs +0 -104
  210. package/dist/chunk-7V4KY4RL.mjs +0 -104
  211. package/dist/chunk-DJQ7OMMB.cjs +0 -144
  212. package/dist/chunk-GOV47PPB.mjs +0 -552
  213. package/dist/chunk-JF4XXPZ5.cjs +0 -552
  214. package/dist/chunk-KCPT2D6G.js +0 -552
  215. package/dist/chunk-NOYZIMUJ.mjs +0 -144
  216. package/dist/chunk-PNVFW245.js +0 -144
  217. package/dist/chunk-ROJC3NBJ.js +0 -104
  218. package/dist/effectRunner-3ZHAD3LE.cjs +0 -8
  219. package/dist/schedule-Fque9Abz.d.ts +0 -70
@@ -1,17 +1,49 @@
1
1
  import {
2
- registerHttpEffect
3
- } from "../chunk-K2T3DV26.mjs";
4
- import {
5
- fixed,
6
2
  makeCircuitBreaker,
7
- resource,
8
- take
9
- } from "../chunk-GOV47PPB.mjs";
3
+ resource
4
+ } from "../chunk-KN32XNTH.mjs";
5
+ import {
6
+ DEFAULT_CACHE_RELEVANT_HEADERS,
7
+ LRUCache,
8
+ LifecycleStatsTracker,
9
+ PriorityQueue,
10
+ SEPARATOR,
11
+ SUPPORTED_ENCODINGS,
12
+ buildHttpRequest,
13
+ clampPriority,
14
+ computeCacheKey,
15
+ decodeJsonBody,
16
+ decodeJsonBodyEffect,
17
+ defaultHttpClientPreset,
18
+ detectPlatform,
19
+ encodeJsonBodyEffect,
20
+ executeProbe,
21
+ makeBudgetSemaphore,
22
+ makeCompressionMiddleware,
23
+ makeConnectionStateMap,
24
+ makeDefaultHttpClient,
25
+ makeHttpClient,
26
+ makeLifecycleClient,
27
+ makePrewarmManager,
28
+ makeRequestCompressionMiddleware,
29
+ makeResponseCompressionMiddleware,
30
+ now,
31
+ parseCacheKey,
32
+ validateFetchAvailable,
33
+ validateOrigin,
34
+ validatedJson,
35
+ validatedJsonResponse,
36
+ withBatch,
37
+ withCache,
38
+ withDedup,
39
+ withPriority
40
+ } from "../chunk-7CMJS3QE.mjs";
10
41
  import {
11
42
  AdaptiveLimiter,
12
43
  EmaComputer,
13
44
  HttpConcurrencyPool,
14
45
  LatencyWindow,
46
+ abortErrorForSignal,
15
47
  adaptiveLimiterPresets,
16
48
  backoffDelayMs,
17
49
  computeGradient,
@@ -20,38 +52,75 @@ import {
20
52
  defaultRetryOnError,
21
53
  defaultRetryOnStatus,
22
54
  defaultRetryableMethods,
55
+ headersOf,
56
+ isAbortError,
57
+ isTaggedHttpError,
58
+ linkAbortSignals,
23
59
  makeAdaptiveLimiterConfig,
60
+ makeFetchStreamTransport,
61
+ makeFetchTransport,
24
62
  makeHttp,
25
63
  makeHttpStream,
26
- mergeHeaders,
64
+ makePromiseHttpTransport,
27
65
  normalizeHeadersInit,
66
+ normalizeHttpError,
67
+ normalizeHttpHeaders,
28
68
  normalizeRetryBudget,
69
+ promiseHttpTransport,
29
70
  resolveConfig,
30
71
  resolveHttpPoolKey,
31
72
  retryAfterMs,
32
73
  setHeaderIfMissing,
33
74
  validateConfig,
34
- validateDefaultHttpClientConfig,
35
- validateLifecycleClientConfig,
36
75
  withMiddleware,
37
76
  withRetry,
38
77
  withRetryStream
39
- } from "../chunk-PRWCB3QL.mjs";
40
- import "../chunk-2WC63LJK.mjs";
41
- import "../chunk-7V4KY4RL.mjs";
78
+ } from "../chunk-5VRJNBLZ.mjs";
79
+ import {
80
+ registerHttpEffect
81
+ } from "../chunk-MOO4L7F4.mjs";
82
+ import "../chunk-FHQGHPMO.mjs";
83
+ import "../chunk-B5JD23U7.mjs";
84
+ import {
85
+ fixed,
86
+ makeScheduleDriver,
87
+ take
88
+ } from "../chunk-2HQTDLHF.mjs";
89
+ import "../chunk-A2OM6NEH.mjs";
42
90
  import {
43
91
  healthToHttpResponse,
44
92
  makeRuntimeHealth,
45
93
  runObservedHttpServerEffect
46
- } from "../chunk-F5EUMJL7.mjs";
47
- import "../chunk-VFIUZG7J.mjs";
48
- import "../chunk-J3H54ZRV.mjs";
49
- import "../chunk-CCKHV5BT.mjs";
94
+ } from "../chunk-4P2HHGAX.mjs";
95
+ import "../chunk-EJ6BPYVR.mjs";
96
+ import {
97
+ defineHttpPolicyPresets,
98
+ formatHttpError,
99
+ getHttpRequestPolicy,
100
+ httpErrorStatus,
101
+ httpPolicy,
102
+ isAbortHttpError,
103
+ isCircuitBreakerOpen,
104
+ isExternalAbortError,
105
+ isExternalTimeoutError,
106
+ isFetchHttpError,
107
+ isHttpError,
108
+ isKnownHttpError,
109
+ isRetryableHttpError,
110
+ isRetryableHttpStatus,
111
+ isTimeoutHttpError,
112
+ isValidationError,
113
+ matchHttpError,
114
+ resolveHttpRequestPolicyPresets,
115
+ toHttpError,
116
+ withHttpPolicyPresets,
117
+ withHttpRequestPolicy
118
+ } from "../chunk-PWC3RBQE.mjs";
50
119
  import {
51
120
  Runtime,
52
121
  fromPromiseAbortable,
53
122
  toPromise
54
- } from "../chunk-G6IQOE4P.mjs";
123
+ } from "../chunk-GYM3LLGS.mjs";
55
124
  import {
56
125
  Cause,
57
126
  asyncEffect,
@@ -61,122 +130,22 @@ import {
61
130
  asyncSucceed,
62
131
  mapTryAsync,
63
132
  withAsyncPromise
64
- } from "../chunk-NOYZIMUJ.mjs";
133
+ } from "../chunk-36I3M4UC.mjs";
65
134
  import {
66
135
  ConfigValidationError,
67
136
  Schema,
68
137
  SchemaValidationException,
138
+ formatConfigError,
69
139
  formatIssues,
140
+ isConfigValidationError,
70
141
  isSchema,
71
142
  makeSchemaIssue,
72
143
  parseConfig,
73
144
  s,
74
145
  schema,
75
146
  validateValue
76
- } from "../chunk-U5KWK3PX.mjs";
77
- import {
78
- __require
79
- } from "../chunk-Y6FXYEAI.mjs";
80
-
81
- // src/http/validation.ts
82
- function decodeJsonBody(bodyText, validator, options = {}) {
83
- let parsed;
84
- try {
85
- parsed = JSON.parse(bodyText);
86
- } catch (error) {
87
- return {
88
- success: false,
89
- error: {
90
- _tag: "ValidationError",
91
- message: `JSON parse error: ${error instanceof Error ? error.message : String(error)}`,
92
- body: bodyText,
93
- phase: "response",
94
- schema: options.schemaName,
95
- issues: [makeSchemaIssue([], "valid JSON", bodyText, "Response body is not valid JSON")]
96
- }
97
- };
98
- }
99
- if (!validator) return { success: true, data: parsed };
100
- let legacyMessage;
101
- const validation = isSchema(validator) ? validator.safeParse(parsed) : (() => {
102
- const result = validator(parsed);
103
- if (result.success) return { success: true, data: result.data };
104
- legacyMessage = result.error;
105
- return {
106
- success: false,
107
- issues: result.issues ?? [makeSchemaIssue([], "valid JSON shape", parsed, result.error)]
108
- };
109
- })();
110
- if (validation.success) return { success: true, data: validation.data };
111
- return {
112
- success: false,
113
- error: {
114
- _tag: "ValidationError",
115
- message: legacyMessage ?? `JSON response failed validation: ${formatIssues(validation.issues)}`,
116
- body: bodyText,
117
- phase: "response",
118
- schema: options.schemaName,
119
- issues: validation.issues
120
- }
121
- };
122
- }
123
- function encodeJsonBodyEffect(bodyObj, validator, options = {}) {
124
- if (validator) {
125
- const validation = validateValue(bodyObj, validator);
126
- if (!validation.success) {
127
- return asyncFail({
128
- _tag: "ValidationError",
129
- message: `JSON request body failed validation: ${formatIssues(validation.issues)}`,
130
- body: previewJson(bodyObj),
131
- phase: "request",
132
- schema: options.schemaName,
133
- issues: validation.issues
134
- });
135
- }
136
- }
137
- try {
138
- return asyncSucceed(JSON.stringify(bodyObj ?? {}));
139
- } catch (error) {
140
- return asyncFail({
141
- _tag: "ValidationError",
142
- message: `JSON request body could not be serialized: ${error instanceof Error ? error.message : String(error)}`,
143
- body: "",
144
- phase: "request",
145
- schema: options.schemaName,
146
- issues: [
147
- makeSchemaIssue([], "JSON-serializable value", bodyObj, "Request body could not be serialized to JSON")
148
- ]
149
- });
150
- }
151
- }
152
- function decodeJsonBodyEffect(bodyText, validator, options) {
153
- const result = decodeJsonBody(bodyText, validator, options);
154
- return result.success ? asyncSucceed(result.data) : asyncFail(result.error);
155
- }
156
- function validatedJson(client, validator) {
157
- return (req) => asyncFold(
158
- client(req),
159
- (error) => asyncFail(error),
160
- (response) => decodeJsonBodyEffect(response.bodyText, validator)
161
- );
162
- }
163
- function validatedJsonResponse(client, validator) {
164
- return (req) => asyncFold(
165
- client(req),
166
- (error) => asyncFail(error),
167
- (response) => asyncFlatMap(
168
- decodeJsonBodyEffect(response.bodyText, validator),
169
- (body) => asyncSucceed({ ...response, body })
170
- )
171
- );
172
- }
173
- function previewJson(value) {
174
- try {
175
- return JSON.stringify(value);
176
- } catch {
177
- return "[unserializable]";
178
- }
179
- }
147
+ } from "../chunk-DNFJLJMW.mjs";
148
+ import "../chunk-Y6FXYEAI.mjs";
180
149
 
181
150
  // src/http/httpClient.ts
182
151
  var resolveFinalUrl = (baseUrl, url) => {
@@ -190,48 +159,24 @@ var createHttpCore = (cfg = {}) => {
190
159
  const wire = makeHttp(cfg);
191
160
  const withPromise = (eff) => withAsyncPromise((e, env) => toPromise(e, env))(eff);
192
161
  const requestRaw = (req) => wire(req);
193
- const splitInit = (init) => {
194
- const { headers, timeoutMs, poolKey, schema: schema2, schemaName, bodySchema, bodySchemaName, ...rest } = init ?? {};
195
- return {
196
- headers: normalizeHeadersInit(headers),
197
- timeoutMs: typeof timeoutMs === "number" ? timeoutMs : void 0,
198
- poolKey: typeof poolKey === "string" ? poolKey : void 0,
199
- init: rest
200
- };
201
- };
202
- const applyInitHeaders = (headers) => (req) => headers ? mergeHeaders(headers)(req) : req;
203
- const buildReq2 = (method, url, init, body) => {
204
- const s2 = splitInit(init);
205
- const req = {
206
- method,
207
- url,
208
- ...body && body.length > 0 ? { body } : {},
209
- ...s2.timeoutMs !== void 0 ? { timeoutMs: s2.timeoutMs } : {},
210
- ...s2.poolKey !== void 0 ? { poolKey: s2.poolKey } : {},
211
- init: s2.init
212
- };
213
- return applyInitHeaders(s2.headers)(req);
214
- };
215
- const toResponse2 = (w, body) => ({
162
+ const toResponse = (w, body) => ({
216
163
  status: w.status,
217
164
  statusText: w.statusText,
218
165
  headers: w.headers,
219
166
  body
220
167
  });
221
- const decodeResponse2 = (w, validator, schemaName) => asyncFlatMap(
168
+ const decodeResponse = (w, validator, schemaName) => asyncFlatMap(
222
169
  decodeJsonBodyEffect(w.bodyText, validator, { schemaName }),
223
- (body) => asyncSucceed(toResponse2(w, body))
170
+ (body) => asyncSucceed(toResponse(w, body))
224
171
  );
225
172
  return {
226
173
  cfg,
227
174
  wire,
228
175
  withPromise,
229
176
  requestRaw,
230
- splitInit,
231
- applyInitHeaders,
232
- buildReq: buildReq2,
233
- toResponse: toResponse2,
234
- decodeResponse: decodeResponse2
177
+ buildReq: buildHttpRequest,
178
+ toResponse,
179
+ decodeResponse
235
180
  };
236
181
  };
237
182
  function httpClient(cfg = {}) {
@@ -428,2252 +373,90 @@ function withCircuitBreaker(config = {}) {
428
373
  };
429
374
  return (next) => (req) => {
430
375
  const limiter = resolveAdaptiveLimiter(config, next);
431
- const limiterKey = resolveAdaptiveLimiterKey(config, limiter, req);
432
- const onOpen = () => limiterKey !== void 0 && limiter?.markCircuitOpen(limiterKey);
433
- const breaker2 = getBreaker(req.url, onOpen);
434
- return protectLazy(breaker2, next, req, onOpen);
435
- };
436
- }
437
- const breaker = makeCircuitBreaker({
438
- ...config,
439
- onStateChange: (from, to) => {
440
- config.onStateChange?.(from, to);
441
- },
442
- isFailure: config.isFailure ?? ((e) => {
443
- const err = e;
444
- return err._tag !== "BadUrl" && err._tag !== "Abort";
445
- })
446
- });
447
- return (next) => (req) => {
448
- const limiter = resolveAdaptiveLimiter(config, next);
449
- const limiterKey = resolveAdaptiveLimiterKey(config, limiter, req);
450
- const onOpen = () => limiterKey !== void 0 && limiter?.markCircuitOpen(limiterKey);
451
- return protectLazy(breaker, next, req, onOpen);
452
- };
453
- }
454
- function protectLazy(breaker, next, req, onOpen) {
455
- return {
456
- _tag: "Async",
457
- register: (env, cb) => {
458
- let cancel;
459
- try {
460
- if (breaker.state() === "open") onOpen?.();
461
- const finish = (exit) => {
462
- if (breaker.state() === "open") onOpen?.();
463
- cb(exit);
464
- };
465
- const deferred = {
466
- _tag: "Async",
467
- register: (innerEnv, innerCb) => registerHttpEffect(next(req), innerEnv, innerCb)
468
- };
469
- cancel = registerHttpEffect(breaker.protect(deferred), env, finish);
470
- } catch (error) {
471
- cb({
472
- _tag: "Failure",
473
- cause: {
474
- _tag: "Fail",
475
- error: { _tag: "FetchError", message: String(error) }
476
- }
477
- });
478
- }
479
- return () => cancel?.();
480
- }
481
- };
482
- }
483
- function resolveAdaptiveLimiter(config, next) {
484
- return config.adaptiveLimiter ?? next.adaptiveLimiter;
485
- }
486
- function resolveAdaptiveLimiterKey(config, limiter, req) {
487
- if (!limiter) return void 0;
488
- if (config.adaptiveLimiterKey) return config.adaptiveLimiterKey(req);
489
- if (req.poolKey) return req.poolKey;
490
- if (limiter.keyResolver === "global") return "global";
491
- try {
492
- const url = new URL(req.url);
493
- if (limiter.keyResolver === "host") return url.host;
494
- return url.origin;
495
- } catch {
496
- return "global";
497
- }
498
- }
499
-
500
- // src/http/tracing.ts
501
- function withTracing(tracer) {
502
- return (next) => (req) => {
503
- return tracer.span(
504
- `HTTP ${req.method} ${req.url}`,
505
- next(req),
506
- {
507
- "http.method": req.method,
508
- "http.url": req.url,
509
- ...req.headers?.["content-type"] ? { "http.content_type": req.headers["content-type"] } : {}
510
- }
511
- );
512
- };
513
- }
514
-
515
- // src/http/errors.ts
516
- var HTTP_ERROR_TAGS = /* @__PURE__ */ new Set([
517
- "Abort",
518
- "BadUrl",
519
- "FetchError",
520
- "Timeout",
521
- "PoolRejected",
522
- "PoolTimeout",
523
- "PoolClosed",
524
- "BatchSplitError"
525
- ]);
526
- function isHttpError(error) {
527
- return hasTag(error) && HTTP_ERROR_TAGS.has(error._tag);
528
- }
529
- function isValidationError(error) {
530
- return hasTag(error) && error._tag === "ValidationError";
531
- }
532
- function isCircuitBreakerOpen(error) {
533
- return hasTag(error) && error._tag === "CircuitBreakerOpen";
534
- }
535
- function isKnownHttpError(error) {
536
- return isHttpError(error) || isValidationError(error) || isCircuitBreakerOpen(error);
537
- }
538
- function matchHttpError(error, handlers) {
539
- if (isKnownHttpError(error)) {
540
- const handler = handlers[error._tag];
541
- if (handler) return handler(error);
542
- }
543
- return handlers.default?.(error);
544
- }
545
- function formatHttpError(error) {
546
- if (isValidationError(error)) {
547
- const phase = error.phase ? `${error.phase} ` : "";
548
- return `${phase}validation failed: ${error.message}`;
549
- }
550
- if (isCircuitBreakerOpen(error)) {
551
- return `Circuit breaker is open after ${error.failures} failure(s)`;
552
- }
553
- if (!isHttpError(error)) {
554
- return error instanceof Error ? error.message : String(error);
555
- }
556
- switch (error._tag) {
557
- case "Abort":
558
- return "HTTP request aborted";
559
- case "BadUrl":
560
- case "FetchError":
561
- case "PoolRejected":
562
- case "PoolTimeout":
563
- case "PoolClosed":
564
- case "BatchSplitError":
565
- return error.message;
566
- case "Timeout":
567
- return error.message;
568
- }
569
- }
570
- function hasTag(error) {
571
- return typeof error === "object" && error !== null && typeof error._tag === "string";
572
- }
573
-
574
- // src/http/body.ts
575
- function httpBodyByteLength(body) {
576
- if (body === void 0) return 0;
577
- if (typeof body === "string") return Buffer.byteLength(body, "utf8");
578
- if (body instanceof ArrayBuffer) return body.byteLength;
579
- return body.byteLength;
580
- }
581
- function httpBodyToBuffer(body) {
582
- if (typeof body === "string") return Buffer.from(body, "utf8");
583
- if (body instanceof ArrayBuffer) return Buffer.from(body);
584
- return Buffer.from(body);
585
- }
586
- function httpBodyKeyPart(body) {
587
- if (body === void 0) return "";
588
- if (typeof body === "string") return body;
589
- return `base64:${httpBodyToBuffer(body).toString("base64")}`;
590
- }
591
-
592
- // src/http/lifecycle/cacheKey.ts
593
- var SEPARATOR = "\0";
594
- var DEFAULT_CACHE_RELEVANT_HEADERS = ["accept", "authorization", "content-type"];
595
- function computeCacheKey(req, baseUrl, extraHeaders = []) {
596
- const method = req.method.toUpperCase();
597
- const resolvedUrl = new URL(req.url, baseUrl || void 0).toString();
598
- const relevantSet = /* @__PURE__ */ new Set([
599
- ...DEFAULT_CACHE_RELEVANT_HEADERS,
600
- ...extraHeaders.map((h) => h.toLowerCase())
601
- ]);
602
- const headers = req.headers ?? {};
603
- const sortedHeaders = Object.keys(headers).filter((k) => relevantSet.has(k.toLowerCase())).sort().map((k) => `${k.toLowerCase()}:${headers[k]}`).join(",");
604
- const body = httpBodyKeyPart(req.body);
605
- return `${method}${SEPARATOR}${resolvedUrl}${SEPARATOR}${sortedHeaders}${SEPARATOR}${body}`;
606
- }
607
- function parseCacheKey(key) {
608
- const [method, resolvedUrl, headersStr, ...bodyParts] = key.split(SEPARATOR);
609
- const body = bodyParts.join(SEPARATOR);
610
- const headers = {};
611
- if (headersStr) {
612
- for (const entry of headersStr.split(",")) {
613
- const colonIdx = entry.indexOf(":");
614
- if (colonIdx > 0) {
615
- headers[entry.slice(0, colonIdx)] = entry.slice(colonIdx + 1);
616
- }
617
- }
618
- }
619
- return { method, resolvedUrl, headers, body };
620
- }
621
-
622
- // src/http/lifecycle/dedupKey.ts
623
- var HOP_BY_HOP = /* @__PURE__ */ new Set([
624
- "connection",
625
- "keep-alive",
626
- "proxy-authenticate",
627
- "proxy-authorization",
628
- "te",
629
- "trailer",
630
- "transfer-encoding",
631
- "upgrade"
632
- ]);
633
- var SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
634
- function computeDedupKey(req, baseUrl) {
635
- const method = req.method.toUpperCase();
636
- const resolvedUrl = new URL(req.url, baseUrl || void 0).toString();
637
- const headers = req.headers ?? {};
638
- const sortedHeaders = Object.keys(headers).filter((k) => {
639
- const lower = k.toLowerCase();
640
- return !HOP_BY_HOP.has(lower) && lower !== "authorization";
641
- }).sort().map((k) => `${k.toLowerCase()}:${headers[k]}`).join(",");
642
- const body = httpBodyKeyPart(req.body);
643
- return `${method}${SEPARATOR}${resolvedUrl}${SEPARATOR}${sortedHeaders}${SEPARATOR}${body}`;
644
- }
645
-
646
- // src/http/lifecycle/dedup.ts
647
- function safeEmit(onEvent, event) {
648
- if (!onEvent) return;
649
- try {
650
- onEvent(event);
651
- } catch {
652
- }
653
- }
654
- function withDedup(config) {
655
- const inFlight = /* @__PURE__ */ new Map();
656
- const customKeyFn = config?.dedupKey;
657
- const onEvent = config?.onEvent;
658
- return (next) => {
659
- return (req) => {
660
- if (!SAFE_METHODS.has(req.method.toUpperCase())) {
661
- return next(req);
662
- }
663
- let key;
664
- if (customKeyFn) {
665
- try {
666
- key = customKeyFn(req);
667
- } catch {
668
- return next(req);
669
- }
670
- if (!key) {
671
- return next(req);
672
- }
673
- } else {
674
- key = computeDedupKey(req, "");
675
- }
676
- return {
677
- _tag: "Async",
678
- register: (_env, cb) => {
679
- const existing = inFlight.get(key);
680
- let callerDone = false;
681
- const finishCaller = (exit) => {
682
- if (callerDone) return;
683
- callerDone = true;
684
- cb(exit);
685
- };
686
- if (existing) {
687
- safeEmit(onEvent, { type: "dedup-hit", cacheKey: key });
688
- existing.refCount++;
689
- const waiter = {
690
- resolve: (res) => {
691
- finishCaller({ _tag: "Success", value: res });
692
- },
693
- reject: (err) => {
694
- finishCaller({ _tag: "Failure", cause: Cause.fail(err) });
695
- }
696
- };
697
- existing.waiters.push(waiter);
698
- return () => {
699
- if (callerDone) return;
700
- existing.refCount--;
701
- const idx = existing.waiters.indexOf(waiter);
702
- if (idx >= 0) {
703
- existing.waiters.splice(idx, 1);
704
- }
705
- if (existing.refCount <= 0) {
706
- inFlight.delete(key);
707
- safeEmit(onEvent, { type: "dedup-active", active: inFlight.size });
708
- existing.controller.abort();
709
- }
710
- finishCaller({ _tag: "Failure", cause: Cause.interrupt() });
711
- };
712
- }
713
- safeEmit(onEvent, { type: "dedup-miss", cacheKey: key });
714
- const controller = new AbortController();
715
- const entry = {
716
- key,
717
- controller,
718
- refCount: 1,
719
- waiters: []
720
- };
721
- inFlight.set(key, entry);
722
- safeEmit(onEvent, { type: "dedup-active", active: inFlight.size });
723
- const dedupReq = {
724
- ...req,
725
- init: {
726
- ...req.init ?? {},
727
- signal: controller.signal
728
- }
729
- };
730
- const innerEffect = next(dedupReq);
731
- const innerCancel = registerHttpEffect(innerEffect, _env, (exit) => {
732
- inFlight.delete(key);
733
- safeEmit(onEvent, { type: "dedup-active", active: inFlight.size });
734
- if (exit._tag === "Success") {
735
- resolveAll(entry, exit.value);
736
- finishCaller(exit);
737
- return;
738
- }
739
- if (exit.cause._tag === "Interrupt") {
740
- rejectAll(entry, { _tag: "Abort" });
741
- finishCaller({ _tag: "Failure", cause: Cause.interrupt() });
742
- return;
743
- }
744
- if (exit.cause._tag === "Fail") {
745
- rejectAll(entry, exit.cause.error);
746
- finishCaller(exit);
747
- return;
748
- }
749
- const err = { _tag: "FetchError", message: String(exit.cause.defect ?? "unknown") };
750
- rejectAll(entry, err);
751
- finishCaller({ _tag: "Failure", cause: Cause.fail(err) });
752
- });
753
- return () => {
754
- if (callerDone) return;
755
- entry.refCount--;
756
- if (entry.refCount <= 0) {
757
- inFlight.delete(key);
758
- safeEmit(onEvent, { type: "dedup-active", active: inFlight.size });
759
- controller.abort();
760
- if (innerCancel) {
761
- innerCancel();
762
- }
763
- }
764
- finishCaller({ _tag: "Failure", cause: Cause.interrupt() });
765
- };
766
- }
767
- };
768
- };
769
- };
770
- }
771
- function resolveAll(entry, res) {
772
- const waiters = entry.waiters.slice();
773
- for (const w of waiters) {
774
- w.resolve(res);
775
- }
776
- }
777
- function rejectAll(entry, err) {
778
- const waiters = entry.waiters.slice();
779
- for (const w of waiters) {
780
- w.reject(err);
781
- }
782
- }
783
-
784
- // src/http/lifecycle/batch.ts
785
- function clamp(value, min, max) {
786
- if (!Number.isFinite(value)) return min;
787
- return Math.max(min, Math.min(max, Math.floor(value)));
788
- }
789
- function safeEmitBatch(onEvent, event) {
790
- if (!onEvent) return;
791
- try {
792
- onEvent(event);
793
- } catch {
794
- }
795
- }
796
- function withBatch(config, onEvent) {
797
- const windowMs = clamp(config.windowMs, 1, 5e3);
798
- const maxBatchSize = clamp(config.maxBatchSize, 2, 1e4);
799
- const { batch, batchKey } = config;
800
- const groups = /* @__PURE__ */ new Map();
801
- return (next) => {
802
- function dispatch(group) {
803
- group.dispatched = true;
804
- if (group.timer !== null) {
805
- clearTimeout(group.timer);
806
- group.timer = null;
807
- }
808
- groups.delete(group.key);
809
- const requests = group.waiters.map((w) => w.request);
810
- const waiterCount = group.waiters.length;
811
- let coalescedReq;
812
- try {
813
- coalescedReq = batch.coalesce(requests);
814
- } catch (err) {
815
- const error = { _tag: "FetchError", message: String(err) };
816
- rejectAllWaiters(group, error);
817
- return;
818
- }
819
- coalescedReq = {
820
- ...coalescedReq,
821
- init: {
822
- ...coalescedReq.init ?? {},
823
- signal: group.controller.signal
824
- }
825
- };
826
- safeEmitBatch(onEvent, { type: "batch-dispatch", batchKey: group.key, batchSize: waiterCount });
827
- const innerEffect = next(coalescedReq);
828
- registerHttpEffect(innerEffect, void 0, (exit) => {
829
- if (exit._tag === "Success") {
830
- let responses;
831
- try {
832
- responses = batch.split(exit.value, requests);
833
- } catch (err2) {
834
- const error = { _tag: "FetchError", message: String(err2) };
835
- rejectAllWaiters(group, error);
836
- return;
837
- }
838
- if (responses.length !== waiterCount) {
839
- const error = {
840
- _tag: "BatchSplitError",
841
- expected: waiterCount,
842
- actual: responses.length,
843
- message: `split returned ${responses.length} responses but expected ${waiterCount}`
844
- };
845
- rejectAllWaiters(group, error);
846
- return;
847
- }
848
- const waiters = group.waiters.slice();
849
- for (let i = 0; i < waiters.length; i++) {
850
- waiters[i].resolve(responses[i]);
851
- }
852
- return;
853
- }
854
- if (exit.cause._tag === "Fail") {
855
- rejectAllWaiters(group, exit.cause.error);
856
- return;
857
- }
858
- if (exit.cause._tag === "Interrupt") {
859
- rejectAllWaiters(group, { _tag: "Abort" });
860
- return;
861
- }
862
- const err = { _tag: "FetchError", message: String(exit.cause.defect ?? "unknown") };
863
- rejectAllWaiters(group, err);
864
- });
865
- }
866
- return (req) => {
867
- let key;
868
- try {
869
- key = batchKey(req);
870
- } catch {
871
- return next(req);
872
- }
873
- if (!key) {
874
- return next(req);
875
- }
876
- return {
877
- _tag: "Async",
878
- register: (_env, cb) => {
879
- let callerDone = false;
880
- const finishCaller = (exit) => {
881
- if (callerDone) return;
882
- callerDone = true;
883
- cb(exit);
884
- };
885
- const waiter = {
886
- request: req,
887
- resolve: (res) => {
888
- finishCaller({ _tag: "Success", value: res });
889
- },
890
- reject: (err) => {
891
- finishCaller({ _tag: "Failure", cause: Cause.fail(err) });
892
- }
893
- };
894
- const existing = groups.get(key);
895
- if (existing && !existing.dispatched) {
896
- existing.waiters.push(waiter);
897
- safeEmitBatch(onEvent, { type: "batch-hit", batchKey: key });
898
- if (existing.waiters.length >= maxBatchSize) {
899
- dispatch(existing);
900
- }
901
- return () => {
902
- if (callerDone) return;
903
- const idx = existing.waiters.indexOf(waiter);
904
- if (idx >= 0) {
905
- existing.waiters.splice(idx, 1);
906
- }
907
- if (existing.waiters.length === 0 && !existing.dispatched) {
908
- if (existing.timer !== null) {
909
- clearTimeout(existing.timer);
910
- existing.timer = null;
911
- }
912
- groups.delete(key);
913
- }
914
- if (existing.waiters.length === 0 && existing.dispatched) {
915
- existing.controller.abort();
916
- }
917
- finishCaller({ _tag: "Failure", cause: Cause.interrupt() });
918
- };
919
- }
920
- const controller = new AbortController();
921
- const group = {
922
- key,
923
- controller,
924
- waiters: [waiter],
925
- timer: null,
926
- dispatched: false
927
- };
928
- group.timer = setTimeout(() => {
929
- if (!group.dispatched && group.waiters.length > 0) {
930
- dispatch(group);
931
- }
932
- }, windowMs);
933
- groups.set(key, group);
934
- return () => {
935
- if (callerDone) return;
936
- const idx = group.waiters.indexOf(waiter);
937
- if (idx >= 0) {
938
- group.waiters.splice(idx, 1);
939
- }
940
- if (group.waiters.length === 0 && !group.dispatched) {
941
- if (group.timer !== null) {
942
- clearTimeout(group.timer);
943
- group.timer = null;
944
- }
945
- groups.delete(key);
946
- }
947
- if (group.waiters.length === 0 && group.dispatched) {
948
- group.controller.abort();
949
- }
950
- finishCaller({ _tag: "Failure", cause: Cause.interrupt() });
951
- };
952
- }
953
- };
954
- };
955
- };
956
- }
957
- function rejectAllWaiters(group, err) {
958
- const waiters = group.waiters.slice();
959
- for (const w of waiters) {
960
- w.reject(err);
961
- }
962
- }
963
-
964
- // src/http/lifecycle/timing.ts
965
- var now = typeof performance !== "undefined" && typeof performance.now === "function" ? () => performance.now() : () => Date.now();
966
-
967
- // src/http/lifecycle/lruCache.ts
968
- function isExpired(node) {
969
- return now() - node.storedAt >= node.ttlMs;
970
- }
971
- var LRUCache = class {
972
- map = /* @__PURE__ */ new Map();
973
- head = null;
974
- tail = null;
975
- maxEntries;
976
- onEvict;
977
- /**
978
- * Creates a new LRU cache instance.
979
- *
980
- * @param config - Cache configuration options.
981
- * @param config.maxEntries - Maximum number of entries. Must be >= 1. Default: 1024.
982
- * @param config.onEvict - Optional eviction callback.
983
- *
984
- * @example
985
- * ```typescript
986
- * import { LRUCache } from "./lruCache";
987
- *
988
- * const cache = new LRUCache<number>({ maxEntries: 50 });
989
- * ```
990
- */
991
- constructor(config = {}) {
992
- const max = config.maxEntries ?? 1024;
993
- this.maxEntries = Math.max(1, Math.floor(max));
994
- this.onEvict = config.onEvict;
995
- }
996
- /**
997
- * Returns the number of entries currently in the cache.
998
- *
999
- * @returns The current entry count.
1000
- *
1001
- * @example
1002
- * ```typescript
1003
- * import { LRUCache } from "./lruCache";
1004
- *
1005
- * const cache = new LRUCache<string>();
1006
- * cache.set("a", "1", 10_000);
1007
- * console.log(cache.size); // 1
1008
- * ```
1009
- */
1010
- get size() {
1011
- return this.map.size;
1012
- }
1013
- /**
1014
- * Retrieves a value by key.
1015
- *
1016
- * Returns `undefined` if the key is not found or the entry has expired.
1017
- * On a hit (non-expired), the entry is moved to the head (most recently used).
1018
- * Expired entries are lazily removed on access.
1019
- *
1020
- * @param key - The cache key to look up.
1021
- * @returns The cached value, or `undefined` if not found or expired.
1022
- *
1023
- * @example
1024
- * ```typescript
1025
- * import { LRUCache } from "./lruCache";
1026
- *
1027
- * const cache = new LRUCache<string>();
1028
- * cache.set("greeting", "hello", 30_000);
1029
- * const val = cache.get("greeting"); // "hello"
1030
- * const miss = cache.get("unknown"); // undefined
1031
- * ```
1032
- */
1033
- get(key) {
1034
- const node = this.map.get(key);
1035
- if (!node) return void 0;
1036
- if (isExpired(node)) {
1037
- this.removeNode(node);
1038
- this.map.delete(key);
1039
- return void 0;
1040
- }
1041
- this.moveToHead(node);
1042
- return node.value;
1043
- }
1044
- /**
1045
- * Inserts or updates an entry in the cache.
1046
- *
1047
- * If the key already exists, the value and TTL are updated and the entry is
1048
- * moved to the head. If inserting a new entry causes the cache to exceed
1049
- * `maxEntries` (must be >= 1), the least recently used entry is evicted.
1050
- *
1051
- * @param key - The cache key.
1052
- * @param value - The value to store.
1053
- * @param ttlMs - Time-to-live in milliseconds. The entry expires after this duration.
1054
- *
1055
- * @example
1056
- * ```typescript
1057
- * import { LRUCache } from "./lruCache";
1058
- *
1059
- * const cache = new LRUCache<string>({ maxEntries: 2 });
1060
- * cache.set("a", "alpha", 60_000);
1061
- * cache.set("b", "beta", 60_000);
1062
- * cache.set("c", "gamma", 60_000); // evicts "a" (LRU)
1063
- * ```
1064
- */
1065
- set(key, value, ttlMs) {
1066
- const existing = this.map.get(key);
1067
- if (existing) {
1068
- existing.value = value;
1069
- existing.storedAt = now();
1070
- existing.ttlMs = ttlMs;
1071
- this.moveToHead(existing);
1072
- return;
1073
- }
1074
- const node = {
1075
- key,
1076
- value,
1077
- storedAt: now(),
1078
- ttlMs,
1079
- prev: null,
1080
- next: null
1081
- };
1082
- this.map.set(key, node);
1083
- this.addToHead(node);
1084
- if (this.map.size > this.maxEntries) {
1085
- this.evictTail();
1086
- }
1087
- }
1088
- /**
1089
- * Removes an entry by key.
1090
- *
1091
- * @param key - The cache key to remove.
1092
- * @returns `true` if the entry was found and removed, `false` otherwise.
1093
- *
1094
- * @example
1095
- * ```typescript
1096
- * import { LRUCache } from "./lruCache";
1097
- *
1098
- * const cache = new LRUCache<string>();
1099
- * cache.set("x", "value", 10_000);
1100
- * cache.delete("x"); // true
1101
- * cache.delete("x"); // false (already removed)
1102
- * ```
1103
- */
1104
- delete(key) {
1105
- const node = this.map.get(key);
1106
- if (!node) return false;
1107
- this.removeNode(node);
1108
- this.map.delete(key);
1109
- return true;
1110
- }
1111
- /**
1112
- * Removes all entries from the cache, resetting it to an empty state.
1113
- *
1114
- * @example
1115
- * ```typescript
1116
- * import { LRUCache } from "./lruCache";
1117
- *
1118
- * const cache = new LRUCache<string>();
1119
- * cache.set("a", "1", 10_000);
1120
- * cache.clear();
1121
- * console.log(cache.size); // 0
1122
- * ```
1123
- */
1124
- clear() {
1125
- this.map.clear();
1126
- this.head = null;
1127
- this.tail = null;
1128
- }
1129
- // --- Doubly-linked list operations ---
1130
- /** Adds a node to the head of the list (most recently used position). */
1131
- addToHead(node) {
1132
- node.prev = null;
1133
- node.next = this.head;
1134
- if (this.head) {
1135
- this.head.prev = node;
1136
- }
1137
- this.head = node;
1138
- if (!this.tail) {
1139
- this.tail = node;
1140
- }
1141
- }
1142
- /** Removes a node from its current position in the list. */
1143
- removeNode(node) {
1144
- if (node.prev) {
1145
- node.prev.next = node.next;
1146
- } else {
1147
- this.head = node.next;
1148
- }
1149
- if (node.next) {
1150
- node.next.prev = node.prev;
1151
- } else {
1152
- this.tail = node.prev;
1153
- }
1154
- node.prev = null;
1155
- node.next = null;
1156
- }
1157
- /** Moves an existing node to the head of the list. */
1158
- moveToHead(node) {
1159
- if (this.head === node) return;
1160
- this.removeNode(node);
1161
- this.addToHead(node);
1162
- }
1163
- /** Evicts the tail node (least recently used) and notifies via callback. */
1164
- evictTail() {
1165
- if (!this.tail) return;
1166
- const evicted = this.tail;
1167
- this.removeNode(evicted);
1168
- this.map.delete(evicted.key);
1169
- if (this.onEvict) {
1170
- this.onEvict(1);
1171
- }
1172
- }
1173
- };
1174
-
1175
- // src/http/lifecycle/responseCache.ts
1176
- function clamp2(n, min, max) {
1177
- return Math.max(min, Math.min(max, n));
1178
- }
1179
- function safeEmit2(onEvent, event) {
1180
- if (!onEvent) return;
1181
- try {
1182
- onEvent(event);
1183
- } catch {
1184
- }
1185
- }
1186
- function withCache(config) {
1187
- const ttlSeconds = clamp2(config?.ttlSeconds ?? 60, 1, 86400);
1188
- const ttlMs = ttlSeconds * 1e3;
1189
- const maxEntries = Math.max(1, Math.floor(config?.maxEntries ?? 1024));
1190
- const staleWhileRevalidate = config?.staleWhileRevalidate ?? false;
1191
- const cachePolicy = config?.cachePolicy;
1192
- const cacheRelevantHeaders = config?.cacheRelevantHeaders ?? [];
1193
- const baseUrl = config?.baseUrl ?? "";
1194
- const onEvent = config?.onEvent;
1195
- const onLifecycleEvent = config?.onLifecycleEvent;
1196
- const cache = new LRUCache({
1197
- maxEntries,
1198
- onEvict: (count) => onLifecycleEvent?.({ type: "cache-eviction", count })
1199
- });
1200
- const revalidating = /* @__PURE__ */ new Set();
1201
- const invalidate = (key) => {
1202
- cache.delete(key);
1203
- };
1204
- const clear = () => {
1205
- cache.clear();
1206
- };
1207
- const middleware = (next) => {
1208
- return (req) => {
1209
- const method = req.method.toUpperCase();
1210
- if (!SAFE_METHODS.has(method) && !cachePolicy) {
1211
- return next(req);
1212
- }
1213
- const key = computeCacheKey(req, baseUrl, cacheRelevantHeaders);
1214
- return {
1215
- _tag: "Async",
1216
- register: (env, cb) => {
1217
- const cached = cache.get(key);
1218
- if (cached !== void 0) {
1219
- onLifecycleEvent?.({ type: "cache-hit", cacheKey: key });
1220
- cb({ _tag: "Success", value: cached });
1221
- return;
1222
- }
1223
- onLifecycleEvent?.({ type: "cache-miss", cacheKey: key });
1224
- const innerEffect = next(req);
1225
- return registerHttpEffect(innerEffect, env, (exit) => {
1226
- if (exit._tag === "Success") {
1227
- storeIfCacheable(req, exit.value, key);
1228
- }
1229
- cb(exit);
1230
- });
1231
- }
1232
- };
1233
- };
1234
- };
1235
- function storeIfCacheable(req, res, key) {
1236
- const method = req.method.toUpperCase();
1237
- if (cachePolicy) {
1238
- const result = cachePolicy(req, res);
1239
- if (!result.cacheable) return;
1240
- const entryTtlMs = result.ttlSeconds !== void 0 ? clamp2(result.ttlSeconds, 1, 86400) * 1e3 : ttlMs;
1241
- cache.set(key, res, entryTtlMs);
1242
- return;
1243
- }
1244
- if (!SAFE_METHODS.has(method)) return;
1245
- cache.set(key, res, ttlMs);
1246
- }
1247
- function triggerRevalidation(next, req, key) {
1248
- if (revalidating.has(key)) return;
1249
- revalidating.add(key);
1250
- const innerEffect = next(req);
1251
- const handleExit = (exit) => {
1252
- revalidating.delete(key);
1253
- if (exit._tag === "Success") {
1254
- storeIfCacheable(req, exit.value, key);
1255
- } else {
1256
- safeEmit2(onEvent, {
1257
- type: "revalidation-failure",
1258
- cacheKey: key,
1259
- error: exit.cause._tag === "Fail" ? exit.cause.error : void 0
1260
- });
1261
- }
1262
- };
1263
- registerHttpEffect(innerEffect, void 0, handleExit);
1264
- }
1265
- const expirationMap = /* @__PURE__ */ new Map();
1266
- const swrMiddleware = (next) => {
1267
- return (req) => {
1268
- const method = req.method.toUpperCase();
1269
- if (!SAFE_METHODS.has(method) && !cachePolicy) {
1270
- return next(req);
1271
- }
1272
- const key = computeCacheKey(req, baseUrl, cacheRelevantHeaders);
1273
- return {
1274
- _tag: "Async",
1275
- register: (env, cb) => {
1276
- const cached = cache.get(key);
1277
- if (cached !== void 0) {
1278
- const expiresAt = expirationMap.get(key);
1279
- if (expiresAt !== void 0 && now() < expiresAt) {
1280
- onLifecycleEvent?.({ type: "cache-hit", cacheKey: key });
1281
- cb({ _tag: "Success", value: cached });
1282
- return;
1283
- }
1284
- onLifecycleEvent?.({ type: "cache-hit", cacheKey: key });
1285
- cb({ _tag: "Success", value: cached });
1286
- triggerRevalidation(next, req, key);
1287
- return;
1288
- }
1289
- onLifecycleEvent?.({ type: "cache-miss", cacheKey: key });
1290
- const innerEffect = next(req);
1291
- const handleSuccess = (res) => {
1292
- swrStoreIfCacheable(req, res, key);
1293
- };
1294
- return registerHttpEffect(innerEffect, env, (exit) => {
1295
- if (exit._tag === "Success") {
1296
- handleSuccess(exit.value);
1297
- }
1298
- cb(exit);
1299
- });
1300
- }
1301
- };
1302
- };
1303
- };
1304
- function swrStoreIfCacheable(req, res, key) {
1305
- const method = req.method.toUpperCase();
1306
- let entryTtlMs = ttlMs;
1307
- if (cachePolicy) {
1308
- const result = cachePolicy(req, res);
1309
- if (!result.cacheable) return;
1310
- entryTtlMs = result.ttlSeconds !== void 0 ? clamp2(result.ttlSeconds, 1, 86400) * 1e3 : ttlMs;
1311
- } else if (!SAFE_METHODS.has(method)) {
1312
- return;
1313
- }
1314
- const lruTtl = Number.MAX_SAFE_INTEGER;
1315
- cache.set(key, res, lruTtl);
1316
- expirationMap.set(key, now() + entryTtlMs);
1317
- }
1318
- const swrInvalidate = (key) => {
1319
- cache.delete(key);
1320
- expirationMap.delete(key);
1321
- };
1322
- const swrClear = () => {
1323
- cache.clear();
1324
- expirationMap.clear();
1325
- };
1326
- if (staleWhileRevalidate) {
1327
- return {
1328
- middleware: swrMiddleware,
1329
- invalidate: swrInvalidate,
1330
- clear: swrClear
1331
- };
1332
- }
1333
- return {
1334
- middleware,
1335
- invalidate,
1336
- clear
1337
- };
1338
- }
1339
-
1340
- // src/http/lifecycle/priorityQueue.ts
1341
- function clampPriority(value) {
1342
- if (value === void 0 || !Number.isFinite(value)) return 5;
1343
- return Math.max(0, Math.min(9, Math.trunc(value)));
1344
- }
1345
- function comparePriority(a, b) {
1346
- if (a.priority !== b.priority) return a.priority - b.priority;
1347
- return a.arrivalOrder - b.arrivalOrder;
1348
- }
1349
- var PriorityQueue = class {
1350
- heap = [];
1351
- counter = 0;
1352
- /**
1353
- * Returns the number of entries in the queue (including cancelled entries).
1354
- *
1355
- * @returns The total number of entries in the internal heap.
1356
- *
1357
- * @example
1358
- * ```typescript
1359
- * import { PriorityQueue } from "./priorityQueue";
1360
- *
1361
- * const queue = new PriorityQueue<string>();
1362
- * queue.enqueue("task", 5);
1363
- * console.log(queue.size); // 1
1364
- * ```
1365
- */
1366
- get size() {
1367
- return this.heap.length;
1368
- }
1369
- /** Returns the number of entries that have not been cancelled. */
1370
- get activeSize() {
1371
- return this.heap.reduce((n, entry) => n + (entry.cancelled ? 0 : 1), 0);
1372
- }
1373
- /**
1374
- * Adds a value to the queue with the given priority.
1375
- *
1376
- * Priority is clamped to the valid range [0, 9] via `clampPriority`.
1377
- * Returns the created entry, which can be used for later cancellation
1378
- * by setting `entry.cancelled = true`.
1379
- *
1380
- * @param value - The value to enqueue.
1381
- * @param priority - Priority level, integer from 0 (highest) to 9 (lowest).
1382
- * Clamped to [0, 9]. Defaults to 5 if undefined.
1383
- * @returns The created queue entry.
1384
- *
1385
- * @example
1386
- * ```typescript
1387
- * import { PriorityQueue } from "./priorityQueue";
1388
- *
1389
- * const queue = new PriorityQueue<string>();
1390
- * const entry = queue.enqueue("urgent-task", 0);
1391
- * entry.cancelled = true; // cancel later if needed
1392
- * ```
1393
- */
1394
- enqueue(value, priority) {
1395
- const entry = {
1396
- priority: clampPriority(priority),
1397
- arrivalOrder: this.counter++,
1398
- value,
1399
- cancelled: false
1400
- };
1401
- this.heap.push(entry);
1402
- this.bubbleUp(this.heap.length - 1);
1403
- return entry;
1404
- }
1405
- /**
1406
- * Removes and returns the highest-priority non-cancelled entry.
1407
- *
1408
- * Skips (and discards) any cancelled entries at the top of the heap.
1409
- * Returns `undefined` if the queue is empty or all entries are cancelled.
1410
- *
1411
- * @returns The highest-priority non-cancelled entry, or `undefined` if none available.
1412
- *
1413
- * @example
1414
- * ```typescript
1415
- * import { PriorityQueue } from "./priorityQueue";
1416
- *
1417
- * const queue = new PriorityQueue<string>();
1418
- * queue.enqueue("first", 1);
1419
- * queue.enqueue("second", 2);
1420
- * const entry = queue.dequeue(); // { value: "first", priority: 1, ... }
1421
- * ```
1422
- */
1423
- dequeue() {
1424
- while (this.heap.length > 0) {
1425
- const top = this.heap[0];
1426
- if (top.cancelled) {
1427
- this.removeTop();
1428
- continue;
1429
- }
1430
- this.removeTop();
1431
- return top;
1432
- }
1433
- return void 0;
1434
- }
1435
- /**
1436
- * Returns the highest-priority non-cancelled entry without removing it.
1437
- *
1438
- * Discards cancelled entries at the top of the heap as a side effect.
1439
- * Returns `undefined` if the queue is empty or all entries are cancelled.
1440
- *
1441
- * @returns The highest-priority non-cancelled entry, or `undefined` if none available.
1442
- *
1443
- * @example
1444
- * ```typescript
1445
- * import { PriorityQueue } from "./priorityQueue";
1446
- *
1447
- * const queue = new PriorityQueue<string>();
1448
- * queue.enqueue("task", 3);
1449
- * const top = queue.peek(); // { value: "task", priority: 3, ... }
1450
- * console.log(queue.size); // 1 (not removed)
1451
- * ```
1452
- */
1453
- peek() {
1454
- while (this.heap.length > 0) {
1455
- const top = this.heap[0];
1456
- if (top.cancelled) {
1457
- this.removeTop();
1458
- continue;
1459
- }
1460
- return top;
1461
- }
1462
- return void 0;
1463
- }
1464
- /**
1465
- * Marks all entries matching the predicate as cancelled (lazy removal).
1466
- *
1467
- * Cancelled entries are skipped on subsequent dequeue/peek calls.
1468
- * This does not immediately remove entries from the heap; they are
1469
- * discarded lazily when encountered at the top during dequeue or peek.
1470
- *
1471
- * @param predicate - A function that returns `true` for entries to cancel.
1472
- * @returns The number of entries marked as cancelled.
1473
- *
1474
- * @example
1475
- * ```typescript
1476
- * import { PriorityQueue } from "./priorityQueue";
1477
- *
1478
- * const queue = new PriorityQueue<string>();
1479
- * queue.enqueue("a", 1);
1480
- * queue.enqueue("b", 2);
1481
- * const removed = queue.remove((e) => e.value === "a"); // 1
1482
- * ```
1483
- */
1484
- remove(predicate) {
1485
- let count = 0;
1486
- for (const entry of this.heap) {
1487
- if (!entry.cancelled && predicate(entry)) {
1488
- entry.cancelled = true;
1489
- count++;
1490
- }
1491
- }
1492
- return count;
1493
- }
1494
- // --- Binary heap operations ---
1495
- /** Removes the top element from the heap and restores heap property. */
1496
- removeTop() {
1497
- const last = this.heap.pop();
1498
- if (this.heap.length > 0 && last !== void 0) {
1499
- this.heap[0] = last;
1500
- this.sinkDown(0);
1501
- }
1502
- }
1503
- /** Moves an element up the heap until the heap property is restored. */
1504
- bubbleUp(index) {
1505
- while (index > 0) {
1506
- const parentIndex = index - 1 >>> 1;
1507
- const current = this.heap[index];
1508
- const parent = this.heap[parentIndex];
1509
- if (comparePriority(current, parent) >= 0) break;
1510
- this.heap[index] = parent;
1511
- this.heap[parentIndex] = current;
1512
- index = parentIndex;
1513
- }
1514
- }
1515
- /** Moves an element down the heap until the heap property is restored. */
1516
- sinkDown(index) {
1517
- const length = this.heap.length;
1518
- while (true) {
1519
- const leftIndex = 2 * index + 1;
1520
- const rightIndex = 2 * index + 2;
1521
- let smallest = index;
1522
- if (leftIndex < length && comparePriority(this.heap[leftIndex], this.heap[smallest]) < 0) {
1523
- smallest = leftIndex;
1524
- }
1525
- if (rightIndex < length && comparePriority(this.heap[rightIndex], this.heap[smallest]) < 0) {
1526
- smallest = rightIndex;
1527
- }
1528
- if (smallest === index) break;
1529
- const temp = this.heap[index];
1530
- this.heap[index] = this.heap[smallest];
1531
- this.heap[smallest] = temp;
1532
- index = smallest;
1533
- }
1534
- }
1535
- };
1536
-
1537
- // src/http/lifecycle/priorityScheduler.ts
1538
- var DEFAULT_CONCURRENCY = 32;
1539
- function extractPriority(req) {
1540
- const fromReq = req.priority;
1541
- if (fromReq !== void 0) return clampPriority(fromReq);
1542
- const fromInit = req.init?.priority;
1543
- if (fromInit !== void 0) return clampPriority(fromInit);
1544
- return 5;
1545
- }
1546
- function safeEmit3(onEvent, event) {
1547
- if (!onEvent) return;
1548
- try {
1549
- onEvent(event);
1550
- } catch {
1551
- }
1552
- }
1553
- function withPriority(config) {
1554
- const concurrency = resolveConcurrency(config?.concurrency);
1555
- const queueTimeoutMs = resolveQueueTimeout(config?.queueTimeoutMs);
1556
- const onEvent = config?.onEvent;
1557
- const queue = new PriorityQueue();
1558
- let inFlight = 0;
1559
- const queueDepth = () => {
1560
- return queue.activeSize;
1561
- };
1562
- const middleware = (next) => {
1563
- return (req) => {
1564
- const priority = extractPriority(req);
1565
- return {
1566
- _tag: "Async",
1567
- register: (env, cb) => {
1568
- if (inFlight < concurrency) {
1569
- return dispatchRequest(next, req, env, cb);
1570
- }
1571
- const queued = { req, env, cb, signal: getSignal(req) };
1572
- const entry = queue.enqueue(queued, priority);
1573
- safeEmit3(onEvent, { type: "queue-enqueue", priority });
1574
- if (queueTimeoutMs !== void 0) {
1575
- queued.timer = setTimeout(() => {
1576
- entry.cancelled = true;
1577
- queued.timer = void 0;
1578
- cb({
1579
- _tag: "Failure",
1580
- cause: Cause.fail({
1581
- _tag: "PoolTimeout",
1582
- key: "priority",
1583
- timeoutMs: queueTimeoutMs,
1584
- message: `Priority queue did not dispatch within ${queueTimeoutMs}ms`
1585
- })
1586
- });
1587
- }, queueTimeoutMs);
1588
- }
1589
- const signal = queued.signal;
1590
- let abortHandler;
1591
- if (signal && !signal.aborted) {
1592
- abortHandler = () => {
1593
- entry.cancelled = true;
1594
- if (queued.timer !== void 0) {
1595
- clearTimeout(queued.timer);
1596
- queued.timer = void 0;
1597
- }
1598
- cb({ _tag: "Failure", cause: Cause.fail({ _tag: "Abort" }) });
1599
- };
1600
- signal.addEventListener("abort", abortHandler, { once: true });
1601
- } else if (signal?.aborted) {
1602
- entry.cancelled = true;
1603
- cb({ _tag: "Failure", cause: Cause.fail({ _tag: "Abort" }) });
1604
- return;
1605
- }
1606
- return () => {
1607
- entry.cancelled = true;
1608
- if (queued.timer !== void 0) {
1609
- clearTimeout(queued.timer);
1610
- queued.timer = void 0;
1611
- }
1612
- if (abortHandler && signal) {
1613
- signal.removeEventListener("abort", abortHandler);
1614
- }
1615
- cb({ _tag: "Failure", cause: Cause.interrupt() });
1616
- };
1617
- }
1618
- };
1619
- };
1620
- function dispatchRequest(downstream, req, env, cb) {
1621
- inFlight++;
1622
- safeEmit3(onEvent, { type: "queue-dispatch", priority: extractPriority(req) });
1623
- const innerEffect = downstream(req);
1624
- let completed = false;
1625
- const onComplete = (exit) => {
1626
- if (completed) return;
1627
- completed = true;
1628
- inFlight--;
1629
- cb(exit);
1630
- drainNext(downstream);
1631
- };
1632
- const innerCancel = registerHttpEffect(innerEffect, env, onComplete);
1633
- return () => {
1634
- innerCancel();
1635
- };
1636
- }
1637
- function drainNext(downstream) {
1638
- while (inFlight < concurrency) {
1639
- const entry = queue.dequeue();
1640
- if (!entry) break;
1641
- if (entry.cancelled) continue;
1642
- const queued = entry.value;
1643
- if (queued.timer !== void 0) {
1644
- clearTimeout(queued.timer);
1645
- queued.timer = void 0;
1646
- }
1647
- if (queued.signal?.aborted) {
1648
- queued.cb({ _tag: "Failure", cause: Cause.fail({ _tag: "Abort" }) });
1649
- continue;
1650
- }
1651
- dispatchRequest(downstream, queued.req, queued.env, queued.cb);
1652
- }
1653
- }
1654
- };
1655
- return Object.assign(middleware, { queueDepth });
1656
- }
1657
- function resolveConcurrency(value) {
1658
- if (value === void 0 || !Number.isFinite(value)) return DEFAULT_CONCURRENCY;
1659
- return Math.max(1, Math.floor(value));
1660
- }
1661
- function resolveQueueTimeout(value) {
1662
- if (value === void 0 || !Number.isFinite(value)) return void 0;
1663
- const n = Math.floor(value);
1664
- return n > 0 ? n : void 0;
1665
- }
1666
- function getSignal(req) {
1667
- return req.init?.signal;
1668
- }
1669
-
1670
- // src/http/lifecycle/stats.ts
1671
- var LifecycleStatsTracker = class {
1672
- _cacheHits = 0;
1673
- _cacheMisses = 0;
1674
- _cacheEvictions = 0;
1675
- _dedupHits = 0;
1676
- _dedupActive = 0;
1677
- _queueDepth = 0;
1678
- _requestsStarted = 0;
1679
- _requestsCompleted = 0;
1680
- _requestsFailed = 0;
1681
- _retries = 0;
1682
- _batchDispatches = 0;
1683
- _batchedRequests = 0;
1684
- _onEvent;
1685
- _wireStats;
1686
- /**
1687
- * Creates a new lifecycle stats tracker.
1688
- *
1689
- * @param opts - Configuration options for the tracker.
1690
- * @param opts.onEvent - Optional callback invoked on each lifecycle event.
1691
- * Errors thrown by this callback are silently discarded.
1692
- * @param opts.wireStats - A function returning the current wire-level HTTP client stats.
1693
- *
1694
- * @example
1695
- * ```typescript
1696
- * import { LifecycleStatsTracker } from "./stats";
1697
- *
1698
- * const tracker = new LifecycleStatsTracker({
1699
- * wireStats: () => ({ requestCount: 0, errorCount: 0 }),
1700
- * });
1701
- * ```
1702
- */
1703
- constructor(opts) {
1704
- this._onEvent = opts.onEvent;
1705
- this._wireStats = opts.wireStats;
1706
- }
1707
- // --- Increment methods ---
1708
- /**
1709
- * Records a cache hit. Increments the cache hit counter by 1.
1710
- *
1711
- * @example
1712
- * ```typescript
1713
- * import { LifecycleStatsTracker } from "./stats";
1714
- *
1715
- * const tracker = new LifecycleStatsTracker({ wireStats: () => ({ requestCount: 0, errorCount: 0 }) });
1716
- * tracker.cacheHit();
1717
- * ```
1718
- */
1719
- cacheHit() {
1720
- this._cacheHits++;
1721
- }
1722
- /**
1723
- * Records a cache miss. Increments the cache miss counter by 1.
1724
- *
1725
- * @example
1726
- * ```typescript
1727
- * import { LifecycleStatsTracker } from "./stats";
1728
- *
1729
- * const tracker = new LifecycleStatsTracker({ wireStats: () => ({ requestCount: 0, errorCount: 0 }) });
1730
- * tracker.cacheMiss();
1731
- * ```
1732
- */
1733
- cacheMiss() {
1734
- this._cacheMisses++;
1735
- }
1736
- /**
1737
- * Records a cache eviction. Increments the cache eviction counter by 1.
1738
- *
1739
- * @example
1740
- * ```typescript
1741
- * import { LifecycleStatsTracker } from "./stats";
1742
- *
1743
- * const tracker = new LifecycleStatsTracker({ wireStats: () => ({ requestCount: 0, errorCount: 0 }) });
1744
- * tracker.cacheEviction();
1745
- * ```
1746
- */
1747
- cacheEviction() {
1748
- this._cacheEvictions++;
1749
- }
1750
- /**
1751
- * Records a dedup hit (a request that joined an in-flight duplicate).
1752
- * Increments the dedup hit counter by 1.
1753
- *
1754
- * @example
1755
- * ```typescript
1756
- * import { LifecycleStatsTracker } from "./stats";
1757
- *
1758
- * const tracker = new LifecycleStatsTracker({ wireStats: () => ({ requestCount: 0, errorCount: 0 }) });
1759
- * tracker.dedupHit();
1760
- * ```
1761
- */
1762
- dedupHit() {
1763
- this._dedupHits++;
1764
- }
1765
- /**
1766
- * Sets the current number of active dedup groups.
1767
- *
1768
- * @param n - The current count of active dedup groups. Must be >= 0.
1769
- *
1770
- * @example
1771
- * ```typescript
1772
- * import { LifecycleStatsTracker } from "./stats";
1773
- *
1774
- * const tracker = new LifecycleStatsTracker({ wireStats: () => ({ requestCount: 0, errorCount: 0 }) });
1775
- * tracker.setDedupActive(3);
1776
- * ```
1777
- */
1778
- setDedupActive(n) {
1779
- this._dedupActive = n;
1780
- }
1781
- /**
1782
- * Sets the current priority queue depth.
1783
- *
1784
- * @param n - The current number of entries in the priority queue. Must be >= 0.
1785
- *
1786
- * @example
1787
- * ```typescript
1788
- * import { LifecycleStatsTracker } from "./stats";
1789
- *
1790
- * const tracker = new LifecycleStatsTracker({ wireStats: () => ({ requestCount: 0, errorCount: 0 }) });
1791
- * tracker.setQueueDepth(5);
1792
- * ```
1793
- */
1794
- setQueueDepth(n) {
1795
- this._queueDepth = n;
1796
- }
1797
- /**
1798
- * Records that a request has started. Increments the requests started counter by 1.
1799
- *
1800
- * @example
1801
- * ```typescript
1802
- * import { LifecycleStatsTracker } from "./stats";
1803
- *
1804
- * const tracker = new LifecycleStatsTracker({ wireStats: () => ({ requestCount: 0, errorCount: 0 }) });
1805
- * tracker.requestStarted();
1806
- * ```
1807
- */
1808
- requestStarted() {
1809
- this._requestsStarted++;
1810
- }
1811
- /**
1812
- * Records that a request has completed successfully.
1813
- * Increments the requests completed counter by 1.
1814
- *
1815
- * @example
1816
- * ```typescript
1817
- * import { LifecycleStatsTracker } from "./stats";
1818
- *
1819
- * const tracker = new LifecycleStatsTracker({ wireStats: () => ({ requestCount: 0, errorCount: 0 }) });
1820
- * tracker.requestCompleted();
1821
- * ```
1822
- */
1823
- requestCompleted() {
1824
- this._requestsCompleted++;
1825
- }
1826
- /**
1827
- * Records that a request has failed.
1828
- * Increments the requests failed counter by 1.
1829
- *
1830
- * @example
1831
- * ```typescript
1832
- * import { LifecycleStatsTracker } from "./stats";
1833
- *
1834
- * const tracker = new LifecycleStatsTracker({ wireStats: () => ({ requestCount: 0, errorCount: 0 }) });
1835
- * tracker.requestFailed();
1836
- * ```
1837
- */
1838
- requestFailed() {
1839
- this._requestsFailed++;
1840
- }
1841
- retry() {
1842
- this._retries++;
1843
- }
1844
- /**
1845
- * Records a batch dispatch. Increments the batch dispatches counter by 1.
1846
- */
1847
- batchDispatch() {
1848
- this._batchDispatches++;
1849
- }
1850
- /**
1851
- * Records requests that were coalesced into a batch.
1852
- * @param count - The number of individual requests in the batch.
1853
- */
1854
- batchedRequests(count) {
1855
- this._batchedRequests += count;
1856
- }
1857
- // --- Event emission ---
1858
- /**
1859
- * Emits a lifecycle event to the registered `onEvent` callback.
1860
- *
1861
- * The callback is wrapped in a try-catch so that any exception thrown by
1862
- * the callback is silently discarded and request processing continues
1863
- * unaffected. If no `onEvent` callback was provided, this is a no-op.
1864
- *
1865
- * @param type - The lifecycle event type to emit (e.g., `"cache-hit"`, `"request-start"`).
1866
- * @param extra - Optional additional event data.
1867
- *
1868
- * @example
1869
- * ```typescript
1870
- * import { LifecycleStatsTracker } from "./stats";
1871
- *
1872
- * const tracker = new LifecycleStatsTracker({
1873
- * onEvent: (event) => console.log(event.type, event.timestamp),
1874
- * wireStats: () => ({ requestCount: 0, errorCount: 0 }),
1875
- * });
1876
- * tracker.emit("cache-hit", { cacheKey: "GET|/api/users" });
1877
- * ```
1878
- */
1879
- emit(type, extra) {
1880
- if (!this._onEvent) return;
1881
- try {
1882
- const event = {
1883
- type,
1884
- timestamp: now(),
1885
- ...extra
1886
- };
1887
- this._onEvent(event);
1888
- } catch {
1889
- }
1890
- }
1891
- // --- Snapshot ---
1892
- /**
1893
- * Returns a frozen snapshot of all lifecycle statistics including wire stats.
1894
- *
1895
- * The returned object is frozen (immutable) and represents a point-in-time
1896
- * view of all counters and gauges.
1897
- *
1898
- * @returns A frozen `LifecycleStats` object containing all current statistics.
1899
- *
1900
- * @example
1901
- * ```typescript
1902
- * import { LifecycleStatsTracker } from "./stats";
1903
- *
1904
- * const tracker = new LifecycleStatsTracker({
1905
- * wireStats: () => ({ requestCount: 10, errorCount: 1 }),
1906
- * });
1907
- * tracker.cacheHit();
1908
- * tracker.cacheHit();
1909
- * const stats = tracker.snapshot();
1910
- * console.log(stats.cacheHits); // 2
1911
- * ```
1912
- */
1913
- snapshot() {
1914
- return Object.freeze({
1915
- cacheHits: this._cacheHits,
1916
- cacheMisses: this._cacheMisses,
1917
- cacheEvictions: this._cacheEvictions,
1918
- dedupHits: this._dedupHits,
1919
- dedupActive: this._dedupActive,
1920
- queueDepth: this._queueDepth,
1921
- requestsStarted: this._requestsStarted,
1922
- requestsCompleted: this._requestsCompleted,
1923
- requestsFailed: this._requestsFailed,
1924
- retries: this._retries,
1925
- batchDispatches: this._batchDispatches,
1926
- batchedRequests: this._batchedRequests,
1927
- wire: this._wireStats()
1928
- });
1929
- }
1930
- };
1931
-
1932
- // src/http/prewarm/validation.ts
1933
- function validateOrigin(origin) {
1934
- if (!origin || typeof origin !== "string") {
1935
- throw new Error(`validateOrigin: origin must be a non-empty string, got "${origin}"`);
1936
- }
1937
- const trimmed = origin.trim();
1938
- if (!trimmed) {
1939
- throw new Error(`validateOrigin: origin must be a non-empty string, got "${origin}"`);
1940
- }
1941
- const stripped = stripTrailingSlashes(trimmed);
1942
- const lower = stripped.toLowerCase();
1943
- if (!lower.startsWith("http://") && !lower.startsWith("https://")) {
1944
- throw new Error(
1945
- `validateOrigin: invalid origin "${origin}" \u2014 must start with http:// or https://`
1946
- );
1947
- }
1948
- let parsed;
1949
- try {
1950
- parsed = new URL(stripped);
1951
- } catch {
1952
- throw new Error(
1953
- `validateOrigin: invalid origin "${origin}" \u2014 must be a valid URL origin (scheme + host + optional port)`
1954
- );
1955
- }
1956
- if (parsed.pathname !== "/" && parsed.pathname !== "") {
1957
- throw new Error(
1958
- `validateOrigin: invalid origin "${origin}" \u2014 must not contain a path`
1959
- );
1960
- }
1961
- if (parsed.search) {
1962
- throw new Error(
1963
- `validateOrigin: invalid origin "${origin}" \u2014 must not contain query parameters`
1964
- );
1965
- }
1966
- if (parsed.hash) {
1967
- throw new Error(
1968
- `validateOrigin: invalid origin "${origin}" \u2014 must not contain a fragment`
1969
- );
1970
- }
1971
- return parsed.origin;
1972
- }
1973
- function stripTrailingSlashes(value) {
1974
- let end = value.length;
1975
- while (end > 0 && value.charCodeAt(end - 1) === 47) {
1976
- end--;
1977
- }
1978
- return end === value.length ? value : value.slice(0, end);
1979
- }
1980
-
1981
- // src/http/prewarm/platform.ts
1982
- function detectPlatform() {
1983
- return typeof window !== "undefined" && typeof window.document !== "undefined" ? "browser" : "node";
1984
- }
1985
- function validateFetchAvailable() {
1986
- if (typeof globalThis.fetch !== "function") {
1987
- throw new Error(
1988
- "makePrewarmManager: global fetch is not available. Requires Node.js 18+ or modern browser."
1989
- );
1990
- }
1991
- if (typeof globalThis.AbortController !== "function") {
1992
- throw new Error(
1993
- "makePrewarmManager: global AbortController is not available."
1994
- );
1995
- }
1996
- }
1997
-
1998
- // src/http/prewarm/probe.ts
1999
- async function executeProbe(origin, options) {
2000
- const { timeoutMs, signal, platform } = options;
2001
- const url = `${origin}/`;
2002
- const start = Date.now();
2003
- const timeoutController = new AbortController();
2004
- const timeoutId = setTimeout(() => timeoutController.abort(), timeoutMs);
2005
- const onExternalAbort = () => timeoutController.abort();
2006
- signal.addEventListener("abort", onExternalAbort);
2007
- try {
2008
- if (options.client) {
2009
- const result = await executeProbeViaClient(options.client, url, timeoutController.signal);
2010
- const durationMs2 = Date.now() - start;
2011
- if (result.ok) {
2012
- return { ok: true, durationMs: durationMs2 };
2013
- }
2014
- return { ok: false, durationMs: durationMs2, error: result.error };
2015
- }
2016
- const fetchOptions = {
2017
- method: "HEAD",
2018
- signal: timeoutController.signal,
2019
- // Prevent caching of probe requests
2020
- cache: "no-store"
2021
- };
2022
- if (platform === "browser") {
2023
- fetchOptions.mode = "no-cors";
2024
- }
2025
- await globalThis.fetch(url, fetchOptions);
2026
- const durationMs = Date.now() - start;
2027
- return { ok: true, durationMs };
2028
- } catch (err) {
2029
- const durationMs = Date.now() - start;
2030
- if (signal.aborted) {
2031
- return { ok: false, durationMs, error: "cancelled" };
2032
- }
2033
- if (timeoutController.signal.aborted && !signal.aborted) {
2034
- return { ok: false, durationMs, error: `Probe timed out after ${timeoutMs}ms` };
2035
- }
2036
- const message = err instanceof Error ? err.message : String(err);
2037
- return { ok: false, durationMs, error: message };
2038
- } finally {
2039
- clearTimeout(timeoutId);
2040
- signal.removeEventListener("abort", onExternalAbort);
2041
- }
2042
- }
2043
- async function executeProbeViaClient(client, url, signal) {
2044
- const { registerHttpEffect: registerHttpEffect2 } = await import("../effectRunner-OPUF6QRN.mjs");
2045
- return new Promise((resolve) => {
2046
- const effect = client({
2047
- method: "HEAD",
2048
- url,
2049
- init: { signal }
2050
- });
2051
- const cancel = registerHttpEffect2(effect, {}, (exit) => {
2052
- if (exit._tag === "Success") {
2053
- resolve({ ok: true });
2054
- } else {
2055
- const cause = exit.cause;
2056
- if (cause?._tag === "Fail") {
2057
- const err = cause.error;
2058
- resolve({ ok: false, error: err?.message ?? err?._tag ?? "Unknown error" });
2059
- } else {
2060
- resolve({ ok: false, error: "cancelled" });
2061
- }
2062
- }
2063
- });
2064
- if (signal.aborted) {
2065
- cancel();
2066
- } else {
2067
- signal.addEventListener("abort", () => {
2068
- cancel();
2069
- }, { once: true });
2070
- }
2071
- });
2072
- }
2073
-
2074
- // src/http/prewarm/connectionState.ts
2075
- function makeConnectionStateMap(origins, keepAliveDurationMs) {
2076
- const entries = /* @__PURE__ */ new Map();
2077
- for (const origin of origins) {
2078
- entries.set(origin, {
2079
- origin,
2080
- status: "idle",
2081
- lastProbeAt: void 0,
2082
- warmUntil: void 0
2083
- });
2084
- }
2085
- function markWarm(origin, now2) {
2086
- const entry = entries.get(origin);
2087
- if (!entry) return;
2088
- const timestamp = now2 ?? Date.now();
2089
- entry.status = "warm";
2090
- entry.lastProbeAt = timestamp;
2091
- entry.warmUntil = timestamp + keepAliveDurationMs;
2092
- }
2093
- function markExpired(origin) {
2094
- const entry = entries.get(origin);
2095
- if (!entry) return;
2096
- entry.status = "expired";
2097
- }
2098
- function markIdle(origin) {
2099
- const entry = entries.get(origin);
2100
- if (!entry) return;
2101
- entry.status = "idle";
2102
- entry.lastProbeAt = void 0;
2103
- entry.warmUntil = void 0;
2104
- }
2105
- function markProbing(origin) {
2106
- const entry = entries.get(origin);
2107
- if (!entry) return;
2108
- entry.status = "probing";
2109
- }
2110
- function isWarm(origin, now2) {
2111
- const entry = entries.get(origin);
2112
- if (!entry) return false;
2113
- if (entry.status !== "warm") return false;
2114
- if (entry.lastProbeAt === void 0 || entry.warmUntil === void 0) return false;
2115
- const currentTime = now2 ?? Date.now();
2116
- if (currentTime >= entry.warmUntil) {
2117
- entry.status = "expired";
2118
- return false;
2119
- }
2120
- return true;
2121
- }
2122
- function getState(origin) {
2123
- const entry = entries.get(origin);
2124
- if (!entry) return void 0;
2125
- return {
2126
- origin: entry.origin,
2127
- status: entry.status,
2128
- lastProbeAt: entry.lastProbeAt,
2129
- warmUntil: entry.warmUntil
2130
- };
2131
- }
2132
- function snapshot() {
2133
- const result = [];
2134
- for (const entry of entries.values()) {
2135
- result.push({
2136
- origin: entry.origin,
2137
- status: entry.status,
2138
- lastProbeAt: entry.lastProbeAt,
2139
- warmUntil: entry.warmUntil
2140
- });
2141
- }
2142
- return { origins: result };
2143
- }
2144
- return { markWarm, markExpired, markIdle, markProbing, isWarm, getState, snapshot };
2145
- }
2146
-
2147
- // src/http/prewarm/budgetSemaphore.ts
2148
- function makeBudgetSemaphore(capacity) {
2149
- if (capacity < 1 || !Number.isFinite(capacity)) {
2150
- throw new Error(`makeBudgetSemaphore: capacity must be >= 1, got ${capacity}`);
2151
- }
2152
- capacity = Math.floor(capacity);
2153
- let active = 0;
2154
- const waiters = [];
2155
- function release() {
2156
- active--;
2157
- if (waiters.length > 0) {
2158
- const next = waiters.shift();
2159
- active++;
2160
- next({ release });
2161
- }
2162
- }
2163
- function acquire() {
2164
- if (active < capacity) {
2165
- active++;
2166
- return Promise.resolve({ release });
2167
- }
2168
- return new Promise((resolve) => {
2169
- waiters.push(resolve);
2170
- });
2171
- }
2172
- function tryAcquire() {
2173
- if (active < capacity) {
2174
- active++;
2175
- return { release };
2176
- }
2177
- return void 0;
2178
- }
2179
- function available() {
2180
- return capacity - active;
2181
- }
2182
- function queued() {
2183
- return waiters.length;
2184
- }
2185
- return { acquire, tryAcquire, available, queued };
2186
- }
2187
-
2188
- // src/http/prewarm/prewarmManager.ts
2189
- var DEFAULTS = {
2190
- keepAliveDurationMs: 55e3,
2191
- budget: 4,
2192
- probeTimeoutMs: 5e3,
2193
- autoRefresh: false,
2194
- useClientPool: false
2195
- };
2196
- function makePrewarmManager(config) {
2197
- validateFetchAvailable();
2198
- const keepAliveDurationMs = config.keepAliveDurationMs ?? DEFAULTS.keepAliveDurationMs;
2199
- const budget = config.budget ?? DEFAULTS.budget;
2200
- const probeTimeoutMs = config.probeTimeoutMs ?? DEFAULTS.probeTimeoutMs;
2201
- const autoRefresh = config.autoRefresh ?? DEFAULTS.autoRefresh;
2202
- const useClientPool = config.useClientPool ?? DEFAULTS.useClientPool;
2203
- const onEvent = config.onEvent;
2204
- const client = config.client;
2205
- const origins = config.origins.map((o) => validateOrigin(o));
2206
- const platform = detectPlatform();
2207
- const stateMap = makeConnectionStateMap(origins, keepAliveDurationMs);
2208
- const semaphore = makeBudgetSemaphore(budget);
2209
- const abortControllers = /* @__PURE__ */ new Map();
2210
- const refreshTimers = /* @__PURE__ */ new Map();
2211
- let disposed = false;
2212
- function emit3(event) {
2213
- if (onEvent) {
2214
- try {
2215
- onEvent(event);
2216
- } catch {
2217
- }
2218
- }
2219
- }
2220
- function scheduleAutoRefresh(origin) {
2221
- if (!autoRefresh || disposed) return;
2222
- clearRefreshTimer(origin);
2223
- const delay = Math.floor(0.8 * keepAliveDurationMs);
2224
- const timer = setTimeout(() => {
2225
- refreshTimers.delete(origin);
2226
- if (disposed) return;
2227
- if (stateMap.isWarm(origin)) {
2228
- stateMap.markExpired(origin);
2229
- emit3({
2230
- type: "connection-expired",
2231
- origin,
2232
- timestamp: Date.now()
2233
- });
2234
- }
2235
- warm(origin).catch(() => {
2236
- });
2237
- }, delay);
2238
- if (typeof timer === "object" && "unref" in timer) {
2239
- timer.unref();
2240
- }
2241
- refreshTimers.set(origin, timer);
2242
- }
2243
- function clearRefreshTimer(origin) {
2244
- const timer = refreshTimers.get(origin);
2245
- if (timer !== void 0) {
2246
- clearTimeout(timer);
2247
- refreshTimers.delete(origin);
2248
- }
2249
- }
2250
- function clearAllRefreshTimers() {
2251
- for (const [, timer] of refreshTimers) {
2252
- clearTimeout(timer);
2253
- }
2254
- refreshTimers.clear();
2255
- }
2256
- async function warm(origin) {
2257
- if (disposed) {
2258
- return { origin, status: "cancelled", durationMs: 0 };
2259
- }
2260
- if (stateMap.isWarm(origin)) {
2261
- return { origin, status: "already-warm", durationMs: 0 };
2262
- }
2263
- const { release } = await semaphore.acquire();
2264
- if (disposed) {
2265
- release();
2266
- return { origin, status: "cancelled", durationMs: 0 };
2267
- }
2268
- const controller = new AbortController();
2269
- abortControllers.set(origin, controller);
2270
- stateMap.markProbing(origin);
2271
- try {
2272
- const probeOptions = {
2273
- timeoutMs: probeTimeoutMs,
2274
- signal: controller.signal,
2275
- platform,
2276
- client: useClientPool ? client : void 0
2277
- };
2278
- const outcome = await executeProbe(origin, probeOptions);
2279
- if (controller.signal.aborted) {
2280
- stateMap.markIdle(origin);
2281
- emit3({
2282
- type: "connection-cancelled",
2283
- origin,
2284
- timestamp: Date.now()
2285
- });
2286
- return { origin, status: "cancelled", durationMs: outcome.durationMs };
2287
- }
2288
- if (outcome.ok) {
2289
- stateMap.markWarm(origin);
2290
- emit3({
2291
- type: "connection-warmed",
2292
- origin,
2293
- timestamp: Date.now(),
2294
- durationMs: outcome.durationMs
2295
- });
2296
- scheduleAutoRefresh(origin);
2297
- return { origin, status: "warmed", durationMs: outcome.durationMs };
2298
- } else {
2299
- stateMap.markIdle(origin);
2300
- emit3({
2301
- type: "connection-failed",
2302
- origin,
2303
- timestamp: Date.now(),
2304
- error: outcome.error
2305
- });
2306
- return { origin, status: "failed", durationMs: outcome.durationMs, error: outcome.error };
2307
- }
2308
- } catch (err) {
2309
- stateMap.markIdle(origin);
2310
- const error = err instanceof Error ? err.message : String(err);
2311
- emit3({
2312
- type: "connection-failed",
2313
- origin,
2314
- timestamp: Date.now(),
2315
- error
2316
- });
2317
- return { origin, status: "failed", durationMs: 0, error };
2318
- } finally {
2319
- abortControllers.delete(origin);
2320
- release();
2321
- }
2322
- }
2323
- async function warmAll() {
2324
- const results = await Promise.all(origins.map((o) => warm(o)));
2325
- return results;
2326
- }
2327
- function cancel(origin) {
2328
- const controller = abortControllers.get(origin);
2329
- if (controller) {
2330
- controller.abort();
2331
- }
2332
- clearRefreshTimer(origin);
2333
- }
2334
- function cancelAll() {
2335
- for (const [, controller] of abortControllers) {
2336
- controller.abort();
2337
- }
2338
- clearAllRefreshTimers();
2339
- }
2340
- function isWarm(origin) {
2341
- if (disposed) return false;
2342
- return stateMap.isWarm(origin);
2343
- }
2344
- function status() {
2345
- return stateMap.snapshot();
2346
- }
2347
- function dispose() {
2348
- if (disposed) return;
2349
- disposed = true;
2350
- cancelAll();
2351
- for (const origin of origins) {
2352
- stateMap.markIdle(origin);
2353
- }
2354
- }
2355
- return {
2356
- warm,
2357
- warmAll,
2358
- isWarm,
2359
- cancel,
2360
- cancelAll,
2361
- status,
2362
- dispose
2363
- };
2364
- }
2365
-
2366
- // src/http/lifecycle/lifecycleClient.ts
2367
- function validateGlobals() {
2368
- if (typeof fetch === "undefined") {
2369
- throw new Error(
2370
- "makeLifecycleClient: global `fetch` is not available. Ensure you are running in an environment with fetch support (Node.js 18+ or modern browser)."
2371
- );
2372
- }
2373
- if (typeof AbortController === "undefined") {
2374
- throw new Error(
2375
- "makeLifecycleClient: global `AbortController` is not available. Ensure you are running in an environment with AbortController support (Node.js 15+ or modern browser)."
2376
- );
2377
- }
2378
- }
2379
- function extractWireConfig(config) {
2380
- const { dedup, batch, cache, priority, retry, prewarm, onEvent, ...wireConfig } = config;
2381
- return wireConfig;
2382
- }
2383
- function makeLifecycleClient(config = {}) {
2384
- validateLifecycleClientConfig(config);
2385
- validateGlobals();
2386
- const wireConfig = extractWireConfig(config);
2387
- const wireClient = makeHttp(wireConfig);
2388
- const activeControllers = /* @__PURE__ */ new Set();
2389
- const tracker = new LifecycleStatsTracker({
2390
- onEvent: config.onEvent,
2391
- wireStats: wireClient.stats
2392
- });
2393
- const hasDedup = config.dedup !== void 0 && config.dedup !== false;
2394
- const hasBatch = config.batch !== void 0 && config.batch !== false;
2395
- const hasCache = config.cache !== void 0 && config.cache !== false;
2396
- const hasPriority = config.priority !== void 0 && config.priority !== false;
2397
- const hasRetry = config.retry !== void 0 && config.retry !== false;
2398
- if (!hasDedup && !hasBatch && !hasCache && !hasPriority && !hasRetry) {
2399
- let prewarmMgr2;
2400
- const hasPrewarm2 = config.prewarm !== void 0 && config.prewarm !== false;
2401
- if (hasPrewarm2) {
2402
- const prewarmConfig = config.prewarm;
2403
- const prewarmOrigins = prewarmConfig.origins ?? [];
2404
- if (prewarmOrigins.length > 0) {
2405
- prewarmMgr2 = makePrewarmManager({
2406
- origins: prewarmOrigins,
2407
- keepAliveDurationMs: prewarmConfig.keepAliveDurationMs,
2408
- budget: prewarmConfig.budget,
2409
- probeTimeoutMs: prewarmConfig.probeTimeoutMs,
2410
- autoRefresh: prewarmConfig.autoRefresh,
2411
- useClientPool: prewarmConfig.useClientPool,
2412
- client: prewarmConfig.useClientPool ? wireClient : void 0,
2413
- onEvent: prewarmConfig.onEvent
2414
- });
2415
- }
2416
- }
2417
- return buildLifecycleClient(wireClient, tracker, {
2418
- cacheInvalidate: noopInvalidate,
2419
- cacheClear: noopClear,
2420
- cancelAll: () => cancelControllers(activeControllers, prewarmMgr2),
2421
- shutdown: () => shutdownClient(activeControllers, prewarmMgr2, wireClient.shutdown),
2422
- activeControllers,
2423
- adaptiveLimiter: wireClient.adaptiveLimiter,
2424
- prewarmManager: prewarmMgr2,
2425
- afterResponse: hasPrewarm2 ? config.prewarm.afterResponse : void 0
2426
- });
2427
- }
2428
- let priorityMiddleware;
2429
- if (hasPriority) {
2430
- const priorityConfig = config.priority;
2431
- priorityMiddleware = withPriority({
2432
- ...priorityConfig,
2433
- onEvent: (event) => {
2434
- tracker.setQueueDepth(priorityMiddleware?.queueDepth() ?? 0);
2435
- tracker.emit(event.type, { priority: event.priority });
2436
- }
2437
- });
2438
- }
2439
- let cacheLayer;
2440
- if (hasCache) {
2441
- const cacheConfig = config.cache;
2442
- cacheLayer = withCache({
2443
- ...cacheConfig,
2444
- baseUrl: wireConfig.baseUrl,
2445
- onLifecycleEvent: (event) => {
2446
- if (event.type === "cache-hit") tracker.cacheHit();
2447
- if (event.type === "cache-miss") tracker.cacheMiss();
2448
- if (event.type === "cache-eviction") tracker.cacheEviction();
2449
- if (event.type === "cache-hit" || event.type === "cache-miss") {
2450
- tracker.emit(event.type, { cacheKey: event.cacheKey });
2451
- }
2452
- }
2453
- });
2454
- }
2455
- let dedupMiddleware;
2456
- if (hasDedup) {
2457
- const dedupConfig = config.dedup;
2458
- const baseUrl = wireConfig.baseUrl ?? "";
2459
- const effectiveDedupConfig = dedupConfig.dedupKey || !baseUrl ? dedupConfig : { ...dedupConfig, dedupKey: (req) => computeDedupKey(req, baseUrl) };
2460
- dedupMiddleware = withDedup({
2461
- ...effectiveDedupConfig,
2462
- onEvent: (event) => {
2463
- if (event.type === "dedup-hit") tracker.dedupHit();
2464
- if (event.type === "dedup-active") {
2465
- tracker.setDedupActive(event.active ?? 0);
2466
- return;
2467
- }
2468
- tracker.emit(event.type, { cacheKey: event.cacheKey });
2469
- }
2470
- });
2471
- }
2472
- let batchMiddleware;
2473
- if (hasBatch) {
2474
- const batchConfig = config.batch;
2475
- batchMiddleware = withBatch(batchConfig, (event) => {
2476
- if (event.type === "batch-dispatch") {
2477
- tracker.batchDispatch();
2478
- tracker.batchedRequests(event.batchSize ?? 0);
2479
- }
2480
- tracker.emit(event.type, { batchKey: event.batchKey, batchSize: event.batchSize });
2481
- });
2482
- }
2483
- let composedFn = wireClient;
2484
- if (priorityMiddleware) {
2485
- composedFn = priorityMiddleware(composedFn);
2486
- }
2487
- if (hasRetry) {
2488
- const retryConfig = config.retry;
2489
- composedFn = withRetry({
2490
- ...retryConfig,
2491
- onRetry: (event) => {
2492
- tracker.retry();
2493
- tracker.emit("retry", {
2494
- attempt: event.attempt,
2495
- delayMs: event.delayMs,
2496
- status: event.status,
2497
- errorTag: event.error?._tag
2498
- });
2499
- retryConfig.onRetry?.(event);
2500
- }
2501
- })(composedFn);
2502
- }
2503
- if (cacheLayer) {
2504
- composedFn = cacheLayer.middleware(composedFn);
2505
- }
2506
- if (batchMiddleware) {
2507
- composedFn = batchMiddleware(composedFn);
2508
- }
2509
- if (dedupMiddleware) {
2510
- composedFn = dedupMiddleware(composedFn);
2511
- }
2512
- let prewarmMgr;
2513
- const hasPrewarm = config.prewarm !== void 0 && config.prewarm !== false;
2514
- if (hasPrewarm) {
2515
- const prewarmConfig = config.prewarm;
2516
- const prewarmOrigins = prewarmConfig.origins ?? [];
2517
- if (prewarmOrigins.length > 0) {
2518
- prewarmMgr = makePrewarmManager({
2519
- origins: prewarmOrigins,
2520
- keepAliveDurationMs: prewarmConfig.keepAliveDurationMs,
2521
- budget: prewarmConfig.budget,
2522
- probeTimeoutMs: prewarmConfig.probeTimeoutMs,
2523
- autoRefresh: prewarmConfig.autoRefresh,
2524
- useClientPool: prewarmConfig.useClientPool,
2525
- client: prewarmConfig.useClientPool ? wireClient : void 0,
2526
- onEvent: prewarmConfig.onEvent
2527
- });
2528
- }
376
+ const limiterKey = resolveAdaptiveLimiterKey(config, limiter, req);
377
+ const onOpen = () => limiterKey !== void 0 && limiter?.markCircuitOpen(limiterKey);
378
+ const breaker2 = getBreaker(req.url, onOpen);
379
+ return protectLazy(breaker2, next, req, onOpen);
380
+ };
2529
381
  }
2530
- return buildLifecycleClient(composedFn, tracker, {
2531
- cacheInvalidate: cacheLayer?.invalidate ?? noopInvalidate,
2532
- cacheClear: cacheLayer?.clear ?? noopClear,
2533
- cancelAll: () => cancelControllers(activeControllers, prewarmMgr),
2534
- shutdown: () => shutdownClient(activeControllers, prewarmMgr, wireClient.shutdown),
2535
- activeControllers,
2536
- adaptiveLimiter: wireClient.adaptiveLimiter,
2537
- queueDepth: priorityMiddleware?.queueDepth,
2538
- prewarmManager: prewarmMgr,
2539
- afterResponse: hasPrewarm ? config.prewarm.afterResponse : void 0
382
+ const breaker = makeCircuitBreaker({
383
+ ...config,
384
+ onStateChange: (from, to) => {
385
+ config.onStateChange?.(from, to);
386
+ },
387
+ isFailure: config.isFailure ?? ((e) => {
388
+ const err = e;
389
+ return err._tag !== "BadUrl" && err._tag !== "Abort";
390
+ })
2540
391
  });
2541
- }
2542
- function makeHttpClient(config = {}) {
2543
- return makeLifecycleClient(config);
2544
- }
2545
- function noopInvalidate(_key) {
2546
- }
2547
- function noopClear() {
2548
- }
2549
- function buildLifecycleClient(fn, tracker, internals) {
2550
- const client = (req) => trackRequest(fn, req, tracker, internals);
2551
- const stats = () => {
2552
- tracker.setQueueDepth(internals.queueDepth?.() ?? 0);
2553
- return tracker.snapshot();
2554
- };
2555
- const withMw = (mw) => {
2556
- const wrappedFn = mw(withLifecycleMetadata(fn, internals));
2557
- return buildLifecycleClient(wrappedFn, tracker, internals);
392
+ return (next) => (req) => {
393
+ const limiter = resolveAdaptiveLimiter(config, next);
394
+ const limiterKey = resolveAdaptiveLimiterKey(config, limiter, req);
395
+ const onOpen = () => limiterKey !== void 0 && limiter?.markCircuitOpen(limiterKey);
396
+ return protectLazy(breaker, next, req, onOpen);
2558
397
  };
2559
- const lifecycleClient = Object.assign(client, {
2560
- with: withMw,
2561
- stats,
2562
- cancelAll: internals.cancelAll,
2563
- shutdown: internals.shutdown,
2564
- adaptiveLimiter: internals.adaptiveLimiter,
2565
- cache: {
2566
- invalidate: internals.cacheInvalidate,
2567
- clear: internals.cacheClear
2568
- }
2569
- });
2570
- return lifecycleClient;
2571
- }
2572
- function withLifecycleMetadata(fn, internals) {
2573
- if (!internals.adaptiveLimiter) return fn;
2574
- return Object.assign(((req) => fn(req)), {
2575
- adaptiveLimiter: internals.adaptiveLimiter
2576
- });
2577
- }
2578
- function cancelControllers(activeControllers, prewarmMgr) {
2579
- for (const controller of Array.from(activeControllers)) {
2580
- try {
2581
- controller.abort();
2582
- } catch {
2583
- }
2584
- }
2585
- if (prewarmMgr) {
2586
- prewarmMgr.cancelAll();
2587
- }
2588
- return asyncSucceed(void 0);
2589
398
  }
2590
- function shutdownClient(activeControllers, prewarmMgr, wireShutdown) {
2591
- cancelControllers(activeControllers, prewarmMgr);
2592
- wireShutdown?.();
2593
- return asyncSucceed(void 0);
2594
- }
2595
- function trackRequest(fn, req, tracker, internals) {
399
+ function protectLazy(breaker, next, req, onOpen) {
2596
400
  return {
2597
401
  _tag: "Async",
2598
402
  register: (env, cb) => {
2599
- const controller = new AbortController();
2600
- const previousSignal = req.init?.signal;
2601
- let done = false;
2602
- let abortedByPreviousSignal = false;
2603
- let cancelInner;
2604
- const abortFromPrevious = () => {
2605
- abortedByPreviousSignal = true;
2606
- try {
2607
- controller.abort(previousSignal?.reason);
2608
- } catch {
2609
- controller.abort();
2610
- }
2611
- cancelInner?.();
2612
- };
2613
- if (previousSignal?.aborted) {
2614
- abortFromPrevious();
2615
- } else {
2616
- previousSignal?.addEventListener("abort", abortFromPrevious, { once: true });
2617
- }
2618
- internals.activeControllers.add(controller);
2619
- tracker.requestStarted();
2620
- tracker.emit("request-start");
2621
- const finish = (exit0) => {
2622
- if (done) return;
2623
- done = true;
2624
- const exit = abortedByPreviousSignal && exit0._tag === "Failure" && exit0.cause._tag === "Interrupt" ? { _tag: "Failure", cause: Cause.fail({ _tag: "Abort" }) } : exit0;
2625
- previousSignal?.removeEventListener("abort", abortFromPrevious);
2626
- internals.activeControllers.delete(controller);
2627
- if (exit._tag === "Success") {
2628
- tracker.requestCompleted();
2629
- if (internals.afterResponse && internals.prewarmManager) {
2630
- try {
2631
- const originsToWarm = internals.afterResponse(exit.value, req);
2632
- if (originsToWarm && originsToWarm.length > 0) {
2633
- for (const origin of originsToWarm) {
2634
- internals.prewarmManager.warm(origin).catch(() => {
2635
- });
2636
- }
2637
- }
2638
- } catch {
2639
- }
2640
- }
2641
- } else {
2642
- tracker.requestFailed();
2643
- }
2644
- tracker.emit("request-end");
2645
- cb(exit);
2646
- };
2647
- const trackedReq = {
2648
- ...req,
2649
- init: {
2650
- ...req.init ?? {},
2651
- signal: controller.signal
2652
- }
2653
- };
403
+ let cancel;
2654
404
  try {
2655
- cancelInner = registerHttpEffect(fn(trackedReq), env, finish);
405
+ if (breaker.state() === "open") onOpen?.();
406
+ const finish = (exit) => {
407
+ if (breaker.state() === "open") onOpen?.();
408
+ cb(exit);
409
+ };
410
+ const deferred = {
411
+ _tag: "Async",
412
+ register: (innerEnv, innerCb) => registerHttpEffect(next(req), innerEnv, innerCb)
413
+ };
414
+ cancel = registerHttpEffect(breaker.protect(deferred), env, finish);
2656
415
  } catch (error) {
2657
- finish({
416
+ cb({
2658
417
  _tag: "Failure",
2659
- cause: Cause.fail({ _tag: "FetchError", message: String(error) })
418
+ cause: {
419
+ _tag: "Fail",
420
+ error: { _tag: "FetchError", message: String(error) }
421
+ }
2660
422
  });
2661
423
  }
2662
- return () => {
2663
- if (done) return;
2664
- try {
2665
- controller.abort();
2666
- } catch {
2667
- }
2668
- if (cancelInner) {
2669
- cancelInner();
2670
- } else {
2671
- finish({ _tag: "Failure", cause: Cause.interrupt() });
2672
- }
2673
- };
424
+ return () => cancel?.();
2674
425
  }
2675
426
  };
2676
427
  }
428
+ function resolveAdaptiveLimiter(config, next) {
429
+ return config.adaptiveLimiter ?? next.adaptiveLimiter;
430
+ }
431
+ function resolveAdaptiveLimiterKey(config, limiter, req) {
432
+ if (!limiter) return void 0;
433
+ if (config.adaptiveLimiterKey) return config.adaptiveLimiterKey(req);
434
+ const poolKey = getHttpRequestPolicy(req).poolKey;
435
+ if (poolKey) return poolKey;
436
+ if (limiter.keyResolver === "global") return "global";
437
+ try {
438
+ const url = new URL(req.url);
439
+ if (limiter.keyResolver === "host") return url.host;
440
+ return url.origin;
441
+ } catch {
442
+ return "global";
443
+ }
444
+ }
445
+
446
+ // src/http/tracing.ts
447
+ function withTracing(tracer) {
448
+ return (next) => (req) => {
449
+ return tracer.span(
450
+ `HTTP ${req.method} ${req.url}`,
451
+ next(req),
452
+ {
453
+ "http.method": req.method,
454
+ "http.url": req.url,
455
+ ...req.headers?.["content-type"] ? { "http.content_type": req.headers["content-type"] } : {}
456
+ }
457
+ );
458
+ };
459
+ }
2677
460
 
2678
461
  // src/http/lifecycle/middleware.ts
2679
462
  function withAuth(tokenProvider) {
@@ -2743,299 +526,6 @@ function withResponseTransform(fn) {
2743
526
  };
2744
527
  }
2745
528
 
2746
- // src/http/compression/decompressor.ts
2747
- import zlib from "zlib";
2748
-
2749
- // src/http/compression/environment.ts
2750
- function isNodeEnvironment() {
2751
- return typeof process !== "undefined" && process.versions != null && process.versions.node != null;
2752
- }
2753
-
2754
- // src/http/compression/decompressor.node.ts
2755
- function createNodeDecompressor(zlib2) {
2756
- return {
2757
- isPassthrough: false,
2758
- decompress(data, encoding) {
2759
- try {
2760
- const input = Buffer.isBuffer(data) ? data : Buffer.from(data);
2761
- let result;
2762
- switch (encoding) {
2763
- case "gzip":
2764
- result = zlib2.gunzipSync(input);
2765
- break;
2766
- case "br":
2767
- result = zlib2.brotliDecompressSync(input);
2768
- break;
2769
- case "deflate":
2770
- result = zlib2.inflateSync(input);
2771
- break;
2772
- default:
2773
- return { ok: false, error: `Unsupported encoding: ${encoding}` };
2774
- }
2775
- return { ok: true, data: result };
2776
- } catch (err) {
2777
- const message = err instanceof Error ? err.message : String(err);
2778
- return { ok: false, error: message };
2779
- }
2780
- }
2781
- };
2782
- }
2783
-
2784
- // src/http/compression/decompressor.noop.ts
2785
- function createNoopDecompressor() {
2786
- return {
2787
- isPassthrough: true,
2788
- decompress(data, _encoding) {
2789
- const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
2790
- return { ok: true, data: buf };
2791
- }
2792
- };
2793
- }
2794
-
2795
- // src/http/compression/decompressor.ts
2796
- function createDecompressor() {
2797
- if (isNodeEnvironment()) {
2798
- return createNodeDecompressor(zlib);
2799
- }
2800
- return createNoopDecompressor();
2801
- }
2802
-
2803
- // src/http/compression/types.ts
2804
- var SUPPORTED_ENCODINGS = ["br", "gzip", "deflate"];
2805
- function emptyStats() {
2806
- return {
2807
- decompressed: { gzip: 0, br: 0, deflate: 0 },
2808
- compressedBytes: 0,
2809
- decompressedBytes: 0,
2810
- passthroughCount: 0,
2811
- errorCount: 0,
2812
- unsupportedEncodingCount: 0
2813
- };
2814
- }
2815
- function emptyRequestCompressionStats() {
2816
- return {
2817
- compressedCount: 0,
2818
- skippedCount: 0,
2819
- errorCount: 0,
2820
- originalBytes: 0,
2821
- compressedBytes: 0
2822
- };
2823
- }
2824
-
2825
- // src/http/compression/middleware.ts
2826
- function injectAcceptEncoding(req, encodings) {
2827
- const headers = req.headers ?? {};
2828
- const hasAcceptEncoding = Object.keys(headers).some(
2829
- (k) => k.toLowerCase() === "accept-encoding"
2830
- );
2831
- if (hasAcceptEncoding) return req;
2832
- return {
2833
- ...req,
2834
- headers: {
2835
- ...headers,
2836
- "Accept-Encoding": encodings.join(", ")
2837
- }
2838
- };
2839
- }
2840
- function isSupportedEncoding(enc) {
2841
- return SUPPORTED_ENCODINGS.includes(enc);
2842
- }
2843
- function processResponse(res, decompressor, enabledEncodings, stats) {
2844
- const contentEncodingKey = Object.keys(res.headers).find(
2845
- (k) => k.toLowerCase() === "content-encoding"
2846
- );
2847
- const contentEncodingValue = contentEncodingKey ? res.headers[contentEncodingKey]?.trim() : void 0;
2848
- if (!contentEncodingValue || contentEncodingValue.toLowerCase() === "identity") {
2849
- stats.passthroughCount++;
2850
- return res;
2851
- }
2852
- const encodings = contentEncodingValue.split(",").map((e) => e.trim().toLowerCase());
2853
- const reversedEncodings = [...encodings].reverse();
2854
- let currentData = Buffer.from(res.bodyText, "latin1");
2855
- let decompressedCount = 0;
2856
- for (let i = 0; i < reversedEncodings.length; i++) {
2857
- const enc = reversedEncodings[i];
2858
- if (!isSupportedEncoding(enc)) {
2859
- stats.unsupportedEncodingCount++;
2860
- if (decompressedCount === 0) {
2861
- stats.passthroughCount++;
2862
- return res;
2863
- }
2864
- const remainingEncodings = reversedEncodings.slice(i).reverse();
2865
- const newHeaders2 = { ...res.headers };
2866
- if (contentEncodingKey) {
2867
- newHeaders2[contentEncodingKey] = remainingEncodings.join(", ");
2868
- }
2869
- newHeaders2["Content-Length"] = String(currentData.byteLength);
2870
- return {
2871
- ...res,
2872
- headers: newHeaders2,
2873
- bodyText: currentData.toString("latin1")
2874
- };
2875
- }
2876
- if (!enabledEncodings.includes(enc)) {
2877
- stats.passthroughCount++;
2878
- if (decompressedCount === 0) {
2879
- return res;
2880
- }
2881
- const remainingEncodings = reversedEncodings.slice(i).reverse();
2882
- const newHeaders2 = { ...res.headers };
2883
- if (contentEncodingKey) {
2884
- newHeaders2[contentEncodingKey] = remainingEncodings.join(", ");
2885
- }
2886
- newHeaders2["Content-Length"] = String(currentData.byteLength);
2887
- return {
2888
- ...res,
2889
- headers: newHeaders2,
2890
- bodyText: currentData.toString("latin1")
2891
- };
2892
- }
2893
- const result = decompressor.decompress(currentData, enc);
2894
- if (!result.ok) {
2895
- stats.errorCount++;
2896
- return res;
2897
- }
2898
- stats.compressedBytes += currentData.byteLength;
2899
- stats.decompressedBytes += result.data.byteLength;
2900
- stats.decompressed[enc]++;
2901
- decompressedCount++;
2902
- currentData = result.data;
2903
- }
2904
- const newHeaders = { ...res.headers };
2905
- if (contentEncodingKey) {
2906
- delete newHeaders[contentEncodingKey];
2907
- }
2908
- const lowerKey = Object.keys(newHeaders).find(
2909
- (k) => k.toLowerCase() === "content-encoding"
2910
- );
2911
- if (lowerKey) {
2912
- delete newHeaders[lowerKey];
2913
- }
2914
- newHeaders["Content-Length"] = String(currentData.byteLength);
2915
- return {
2916
- ...res,
2917
- headers: newHeaders,
2918
- bodyText: currentData.toString("utf-8")
2919
- };
2920
- }
2921
- function makeCompressionMiddleware(config) {
2922
- const enabledEncodings = config?.encodings ?? [...SUPPORTED_ENCODINGS];
2923
- const decompressor = createDecompressor();
2924
- const mutableStats = emptyStats();
2925
- const middleware = (next) => {
2926
- return (req) => {
2927
- const modifiedReq = injectAcceptEncoding(req, enabledEncodings);
2928
- return asyncFold(
2929
- next(modifiedReq),
2930
- // Pass HttpErrors through unchanged
2931
- (error) => asyncFail(error),
2932
- // Process successful responses
2933
- (res) => {
2934
- if (decompressor.isPassthrough) {
2935
- mutableStats.passthroughCount++;
2936
- return asyncSucceed(res);
2937
- }
2938
- const processed = processResponse(
2939
- res,
2940
- decompressor,
2941
- enabledEncodings,
2942
- mutableStats
2943
- );
2944
- return asyncSucceed(processed);
2945
- }
2946
- );
2947
- };
2948
- };
2949
- const stats = () => Object.freeze({
2950
- decompressed: Object.freeze({ ...mutableStats.decompressed }),
2951
- compressedBytes: mutableStats.compressedBytes,
2952
- decompressedBytes: mutableStats.decompressedBytes,
2953
- passthroughCount: mutableStats.passthroughCount,
2954
- errorCount: mutableStats.errorCount,
2955
- unsupportedEncodingCount: mutableStats.unsupportedEncodingCount
2956
- });
2957
- return { middleware, stats };
2958
- }
2959
- var makeResponseCompressionMiddleware = makeCompressionMiddleware;
2960
- var DEFAULT_REQUEST_COMPRESS_METHODS = ["POST", "PUT", "PATCH"];
2961
- function makeRequestCompressionMiddleware(config) {
2962
- const encoding = config?.encoding ?? "gzip";
2963
- const minBytes = Math.max(0, Math.floor(config?.minBytes ?? 1024));
2964
- const methods = new Set((config?.methods ?? DEFAULT_REQUEST_COMPRESS_METHODS).map((m) => m.toUpperCase()));
2965
- const mutableStats = emptyRequestCompressionStats();
2966
- const middleware = (next) => {
2967
- return (req) => {
2968
- const compressed = compressRequest(req, encoding, minBytes, methods, mutableStats);
2969
- return next(compressed);
2970
- };
2971
- };
2972
- const stats = () => Object.freeze({
2973
- compressedCount: mutableStats.compressedCount,
2974
- skippedCount: mutableStats.skippedCount,
2975
- errorCount: mutableStats.errorCount,
2976
- originalBytes: mutableStats.originalBytes,
2977
- compressedBytes: mutableStats.compressedBytes
2978
- });
2979
- return { middleware, stats };
2980
- }
2981
- function compressRequest(req, encoding, minBytes, methods, stats) {
2982
- if (!methods.has(req.method.toUpperCase())) {
2983
- stats.skippedCount++;
2984
- return req;
2985
- }
2986
- if (req.body === void 0 || hasHeader(req.headers, "content-encoding")) {
2987
- stats.skippedCount++;
2988
- return req;
2989
- }
2990
- const originalBytes = httpBodyByteLength(req.body);
2991
- if (originalBytes < minBytes) {
2992
- stats.skippedCount++;
2993
- return req;
2994
- }
2995
- try {
2996
- const compressed = compressBuffer(httpBodyToBuffer(req.body), encoding);
2997
- stats.compressedCount++;
2998
- stats.originalBytes += originalBytes;
2999
- stats.compressedBytes += compressed.byteLength;
3000
- return {
3001
- ...req,
3002
- body: compressed,
3003
- headers: setHeaders(req.headers, {
3004
- "Content-Encoding": encoding,
3005
- "Content-Length": String(compressed.byteLength)
3006
- })
3007
- };
3008
- } catch {
3009
- stats.errorCount++;
3010
- return req;
3011
- }
3012
- }
3013
- function compressBuffer(input, encoding) {
3014
- const zlib2 = __require("zlib");
3015
- switch (encoding) {
3016
- case "gzip":
3017
- return zlib2.gzipSync(input);
3018
- case "br":
3019
- return zlib2.brotliCompressSync(input);
3020
- case "deflate":
3021
- return zlib2.deflateSync(input);
3022
- }
3023
- }
3024
- function hasHeader(headers, name) {
3025
- if (!headers) return false;
3026
- const lower = name.toLowerCase();
3027
- return Object.keys(headers).some((key) => key.toLowerCase() === lower);
3028
- }
3029
- function setHeaders(headers, values) {
3030
- const out = { ...headers ?? {} };
3031
- for (const [key, value] of Object.entries(values)) {
3032
- const existing = Object.keys(out).find((h) => h.toLowerCase() === key.toLowerCase());
3033
- if (existing) out[existing] = value;
3034
- else out[key] = value;
3035
- }
3036
- return out;
3037
- }
3038
-
3039
529
  // src/http/batching.ts
3040
530
  var DEFAULT_MAX_BATCH_SIZE = 16;
3041
531
  var DEFAULT_MAX_WAIT_MS = 5;
@@ -3118,7 +608,7 @@ function withRequestBatching(config) {
3118
608
  const effect = downstream(batchReq);
3119
609
  group.cancel = runEffect(effect, entries[0].env, (exit) => {
3120
610
  if (exit._tag === "Failure") {
3121
- const err = exit.cause._tag === "Fail" ? exit.cause.error : exit.cause._tag === "Interrupt" ? { _tag: "Abort" } : toFetchError(exit.cause.defect);
611
+ const err = causeToHttpError(exit.cause);
3122
612
  failEntries(config, key, entries, err);
3123
613
  return;
3124
614
  }
@@ -3154,6 +644,12 @@ function toFetchError(error) {
3154
644
  if (isHttpError2(error)) return error;
3155
645
  return { _tag: "FetchError", message: error instanceof Error ? error.message : String(error) };
3156
646
  }
647
+ function causeToHttpError(cause) {
648
+ const failure = Cause.firstFailure(cause);
649
+ if (failure._tag === "Some") return failure.value;
650
+ if (Cause.isInterruptedOnly(cause)) return { _tag: "Abort" };
651
+ return toFetchError(Cause.toError(cause));
652
+ }
3157
653
  function isHttpError2(error) {
3158
654
  if (typeof error !== "object" || error === null || !("_tag" in error)) return false;
3159
655
  const tag = error._tag;
@@ -3215,10 +711,13 @@ function runEffect(effect, env, cb) {
3215
711
  try {
3216
712
  if (exit._tag === "Success") {
3217
713
  run(eff.onSuccess(exit.value), k);
3218
- } else if (exit.cause._tag === "Fail") {
3219
- run(eff.onFailure(exit.cause.error), k);
3220
714
  } else {
3221
- k(exit);
715
+ const failure = Cause.isFailureOnly(exit.cause) ? Cause.firstFailure(exit.cause) : void 0;
716
+ if (failure?._tag === "Some") {
717
+ run(eff.onFailure(failure.value), k);
718
+ } else {
719
+ k(exit);
720
+ }
3222
721
  }
3223
722
  } catch (e) {
3224
723
  k({ _tag: "Failure", cause: Cause.die(e) });
@@ -3228,6 +727,14 @@ function runEffect(effect, env, cb) {
3228
727
  case "Fork":
3229
728
  k({ _tag: "Success", value: void 0 });
3230
729
  return;
730
+ case "Interruptibility":
731
+ case "InterruptibilityRestore":
732
+ case "FiberRefLocally":
733
+ run(eff.effect, k);
734
+ return;
735
+ case "InterruptibilityMask":
736
+ run(eff.body((effect2) => effect2), k);
737
+ return;
3231
738
  }
3232
739
  };
3233
740
  run(effect, finish);
@@ -3372,220 +879,6 @@ function emit2(config, event) {
3372
879
  }
3373
880
  }
3374
881
 
3375
- // src/http/defaultClient.ts
3376
- var MINIMAL_PRESET_CONFIG = {
3377
- timeoutMs: 3e4
3378
- };
3379
- var BALANCED_PRESET_CONFIG = {
3380
- ...MINIMAL_PRESET_CONFIG,
3381
- dedup: {},
3382
- priority: {
3383
- concurrency: 32,
3384
- queueTimeoutMs: 3e4
3385
- },
3386
- retry: {
3387
- maxRetries: 2,
3388
- baseDelayMs: 100,
3389
- maxDelayMs: 1e3,
3390
- maxElapsedMs: 5e3,
3391
- respectRetryAfter: true
3392
- },
3393
- adaptiveLimiter: makeAdaptiveLimiterConfig("balanced")
3394
- };
3395
- var DEFAULT_CACHEABLE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
3396
- var DEFAULT_PRESET_CONFIG = {
3397
- ...BALANCED_PRESET_CONFIG,
3398
- cache: {
3399
- ttlSeconds: 60,
3400
- maxEntries: 1024,
3401
- staleWhileRevalidate: true,
3402
- cachePolicy: (req, res) => ({
3403
- cacheable: DEFAULT_CACHEABLE_METHODS.has(req.method) && res.status >= 200 && res.status < 400
3404
- })
3405
- },
3406
- priority: {
3407
- concurrency: 64,
3408
- queueTimeoutMs: 3e4
3409
- },
3410
- retry: {
3411
- maxRetries: 3,
3412
- baseDelayMs: 100,
3413
- maxDelayMs: 2e3,
3414
- maxElapsedMs: 1e4,
3415
- respectRetryAfter: true
3416
- },
3417
- adaptiveLimiter: makeAdaptiveLimiterConfig("aggressive")
3418
- };
3419
- var PRESET_CONFIGS = {
3420
- minimal: MINIMAL_PRESET_CONFIG,
3421
- balanced: BALANCED_PRESET_CONFIG,
3422
- default: DEFAULT_PRESET_CONFIG
3423
- };
3424
- var defaultHttpClientPreset = "default";
3425
- function makeDefaultHttpClient(config = {}) {
3426
- validateDefaultHttpClientConfig(config);
3427
- const {
3428
- preset = defaultHttpClientPreset,
3429
- compression,
3430
- middleware = [],
3431
- ...lifecycleOverrides
3432
- } = config;
3433
- const lifecycleConfig = mergeLifecycleConfig(PRESET_CONFIGS[preset], lifecycleOverrides);
3434
- let wire = makeLifecycleClient(lifecycleConfig);
3435
- const compressionResult = compression === false || compression === void 0 && preset === "minimal" ? void 0 : makeCompressionMiddleware(compression === void 0 ? void 0 : compression);
3436
- if (compressionResult) {
3437
- wire = wire.with(compressionResult.middleware);
3438
- }
3439
- for (const mw of middleware) {
3440
- wire = wire.with(mw);
3441
- }
3442
- const features = featureSnapshot(lifecycleConfig, compressionResult !== void 0, middleware.length);
3443
- return buildDefaultClient(wire, {
3444
- preset,
3445
- features,
3446
- compressionStats: compressionResult?.stats
3447
- });
3448
- }
3449
- function buildDefaultClient(wire, meta) {
3450
- const withPromise = (eff) => withAsyncPromise((e, env) => toPromise(e, env))(eff);
3451
- const requestRaw = (req) => wire(req);
3452
- const request = (req) => withPromise(requestRaw(req));
3453
- const get = (url, init) => request(buildReq("GET", url, init));
3454
- const post = (url, body, init) => request(buildReq("POST", url, init, body));
3455
- const getText = (url, init) => {
3456
- const req = buildReq("GET", url, init);
3457
- return withPromise(
3458
- mapTryAsync(requestRaw(req), (w) => toResponse(w, w.bodyText))
3459
- );
3460
- };
3461
- const getJson = ((url, init) => {
3462
- const req = setHeaderIfMissing("accept", "application/json")(
3463
- buildReq("GET", url, init)
3464
- );
3465
- return withPromise(
3466
- asyncFlatMap(requestRaw(req), (w) => decodeResponse(w, init?.schema, init?.schemaName))
3467
- );
3468
- });
3469
- const postJson = ((url, bodyObj, init) => {
3470
- return withPromise(
3471
- asyncFlatMap(
3472
- encodeJsonBodyEffect(bodyObj, init?.bodySchema, { schemaName: init?.bodySchemaName }),
3473
- (bodyText) => {
3474
- const req = setHeaderIfMissing("content-type", "application/json")(
3475
- setHeaderIfMissing("accept", "application/json")(
3476
- buildReq("POST", url, init, bodyText)
3477
- )
3478
- );
3479
- return asyncFlatMap(requestRaw(req), (w) => decodeResponse(w, init?.schema, init?.schemaName));
3480
- }
3481
- )
3482
- );
3483
- });
3484
- return {
3485
- request,
3486
- get,
3487
- post,
3488
- getText,
3489
- getJson,
3490
- postJson,
3491
- with: (mw) => buildDefaultClient(wire.with(mw), {
3492
- ...meta,
3493
- features: {
3494
- ...meta.features,
3495
- middleware: meta.features.middleware + 1
3496
- }
3497
- }),
3498
- wire,
3499
- stats: () => wire.stats(),
3500
- cache: wire.cache,
3501
- cancelAll: wire.cancelAll,
3502
- shutdown: wire.shutdown,
3503
- preset: meta.preset,
3504
- features: meta.features,
3505
- ...meta.compressionStats ? {
3506
- compression: {
3507
- stats: meta.compressionStats
3508
- }
3509
- } : {}
3510
- };
3511
- }
3512
- function buildReq(method, url, init, body) {
3513
- const { headers, timeoutMs, poolKey, schema: schema2, schemaName, bodySchema, bodySchemaName, ...rest } = init ?? {};
3514
- const normalizedHeaders = normalizeHeadersInit(headers);
3515
- const req = {
3516
- method,
3517
- url,
3518
- ...body && body.length > 0 ? { body } : {},
3519
- ...typeof timeoutMs === "number" ? { timeoutMs } : {},
3520
- ...typeof poolKey === "string" ? { poolKey } : {},
3521
- init: rest
3522
- };
3523
- return normalizedHeaders ? mergeHeaders(normalizedHeaders)(req) : req;
3524
- }
3525
- function toResponse(wire, body) {
3526
- return {
3527
- status: wire.status,
3528
- statusText: wire.statusText,
3529
- headers: wire.headers,
3530
- body
3531
- };
3532
- }
3533
- function decodeResponse(wire, schema2, schemaName) {
3534
- return asyncFlatMap(
3535
- decodeJsonBodyEffect(wire.bodyText, schema2, { schemaName }),
3536
- (body) => asyncSucceed(toResponse(wire, body))
3537
- );
3538
- }
3539
- function mergeLifecycleConfig(defaults, overrides) {
3540
- return {
3541
- ...defaults,
3542
- ...overrides,
3543
- headers: mergeRecord(defaults.headers, overrides.headers),
3544
- dedup: mergeLayer(defaults.dedup, overrides.dedup),
3545
- batch: mergeLayer(defaults.batch, overrides.batch),
3546
- cache: mergeLayer(defaults.cache, overrides.cache),
3547
- priority: mergeLayer(defaults.priority, overrides.priority),
3548
- retry: mergeLayer(defaults.retry, overrides.retry),
3549
- prewarm: mergeLayer(defaults.prewarm, overrides.prewarm),
3550
- adaptiveLimiter: mergeAdaptiveLimiterLayer(defaults.adaptiveLimiter, overrides.adaptiveLimiter),
3551
- pool: mergeLayer(defaults.pool, overrides.pool)
3552
- };
3553
- }
3554
- function mergeRecord(defaults, overrides) {
3555
- if (!defaults) return overrides;
3556
- if (!overrides) return defaults;
3557
- return { ...defaults, ...overrides };
3558
- }
3559
- function mergeLayer(defaults, overrides) {
3560
- if (overrides === void 0) return defaults;
3561
- if (overrides === false) return false;
3562
- if (defaults === void 0 || defaults === false) return overrides;
3563
- return { ...defaults, ...overrides };
3564
- }
3565
- function mergeAdaptiveLimiterLayer(defaults, overrides) {
3566
- if (overrides === void 0) return defaults;
3567
- if (overrides === false) return false;
3568
- if (defaults === void 0 || defaults === false) return overrides;
3569
- if (overrides.preset !== void 0) return overrides;
3570
- return { ...defaults, ...overrides };
3571
- }
3572
- function featureSnapshot(config, compression, middleware) {
3573
- return Object.freeze({
3574
- dedup: isEnabled(config.dedup),
3575
- batch: isEnabled(config.batch),
3576
- cache: isEnabled(config.cache),
3577
- priority: isEnabled(config.priority),
3578
- retry: isEnabled(config.retry),
3579
- prewarm: isEnabled(config.prewarm),
3580
- adaptiveLimiter: isEnabled(config.adaptiveLimiter),
3581
- compression,
3582
- middleware
3583
- });
3584
- }
3585
- function isEnabled(value) {
3586
- return value !== void 0 && value !== false;
3587
- }
3588
-
3589
882
  // src/http/builder.ts
3590
883
  var DEFAULT_BUILDER_RETRY = {
3591
884
  maxRetries: 2,
@@ -3645,10 +938,12 @@ function makeBuilder(config) {
3645
938
  }),
3646
939
  timeoutMs: (timeoutMs) => replace({ timeoutMs }),
3647
940
  timeout: (timeoutMs) => replace({ timeoutMs }),
941
+ transport: (transport) => replace({ transport }),
3648
942
  preset: (preset) => replace({ preset }),
3649
943
  minimal: () => replace({ preset: "minimal" }),
3650
944
  balanced: () => replace({ preset: "balanced" }),
3651
945
  defaultPreset: () => replace({ preset: "default" }),
946
+ production: () => replace({ preset: "production" }),
3652
947
  dedup: (layer = {}) => setLayer("dedup", layer),
3653
948
  noDedup: () => setLayer("dedup", false),
3654
949
  batch: (layer) => setLayer("batch", layer),
@@ -3838,6 +1133,21 @@ function nodeHttpServerResource(options) {
3838
1133
  }
3839
1134
  var makeNodeHttpServerResource = nodeHttpServerResource;
3840
1135
  var makeHttpServerResource = nodeHttpServerResource;
1136
+ var HttpServer = Object.freeze({
1137
+ route,
1138
+ httpRoute,
1139
+ router: makeHttpRouter,
1140
+ listen: makeNodeHttpServer,
1141
+ resource: nodeHttpServerResource,
1142
+ json,
1143
+ text,
1144
+ empty,
1145
+ healthRoute: makeRuntimeHealthRoute,
1146
+ readinessRoute: makeRuntimeReadinessRoute,
1147
+ middleware: Object.freeze({
1148
+ header: withResponseHeader
1149
+ })
1150
+ });
3841
1151
  function handleRouteMatch(request, match, options) {
3842
1152
  if (match._tag === "NotFound") {
3843
1153
  return asyncSucceed(json({ error: "Not Found" }, { status: 404 }));
@@ -3923,7 +1233,7 @@ function validateResponseBody(response, routeWithSchemas) {
3923
1233
  success: false,
3924
1234
  error: makeValidationError({
3925
1235
  message: `HTTP response failed validation: ${formatIssues(result.issues)}`,
3926
- body: previewJson2(response.body),
1236
+ body: previewJson(response.body),
3927
1237
  phase: "response",
3928
1238
  schema: routeWithSchemas.options.responseSchemaName ?? "response",
3929
1239
  issues: result.issues
@@ -4091,7 +1401,7 @@ function closeNodeServer(server, options) {
4091
1401
  return new Promise((resolve, reject) => {
4092
1402
  const startedAt = Date.now();
4093
1403
  const schedule = options.shutdownPollSchedule ?? defaultShutdownPollSchedule(options.gracefulShutdownMs ?? 5e3);
4094
- let state = schedule.initial();
1404
+ const driver = makeScheduleDriver(schedule, { name: schedule.name ?? "http.server.shutdown", startedAtMs: startedAt });
4095
1405
  let finished = false;
4096
1406
  const done = (error) => {
4097
1407
  if (finished) return;
@@ -4107,11 +1417,10 @@ function closeNodeServer(server, options) {
4107
1417
  done();
4108
1418
  return;
4109
1419
  }
4110
- const [decision, nextState] = schedule.step(state, {
1420
+ const decision = driver.next({
4111
1421
  listening: server.listening,
4112
1422
  elapsedMs: Date.now() - startedAt
4113
1423
  });
4114
- state = nextState;
4115
1424
  if (!decision.continue) {
4116
1425
  server.closeAllConnections?.();
4117
1426
  done();
@@ -4238,7 +1547,7 @@ function serverUrl(server) {
4238
1547
  const host = address.address === "::" || address.address === "0.0.0.0" ? "127.0.0.1" : address.address;
4239
1548
  return `http://${host}:${address.port}`;
4240
1549
  }
4241
- function previewJson2(value) {
1550
+ function previewJson(value) {
4242
1551
  try {
4243
1552
  return JSON.stringify(value);
4244
1553
  } catch {
@@ -4261,6 +1570,7 @@ export {
4261
1570
  DEFAULT_CACHE_RELEVANT_HEADERS,
4262
1571
  EmaComputer,
4263
1572
  HttpConcurrencyPool,
1573
+ HttpServer,
4264
1574
  LRUCache,
4265
1575
  LatencyWindow,
4266
1576
  LifecycleStatsTracker,
@@ -4269,6 +1579,7 @@ export {
4269
1579
  SUPPORTED_ENCODINGS,
4270
1580
  Schema,
4271
1581
  SchemaValidationException,
1582
+ abortErrorForSignal,
4272
1583
  adaptiveLimiterPresets,
4273
1584
  backoffDelayMs,
4274
1585
  clampPriority,
@@ -4282,29 +1593,48 @@ export {
4282
1593
  defaultRetryOnError,
4283
1594
  defaultRetryOnStatus,
4284
1595
  defaultRetryableMethods,
1596
+ defineHttpPolicyPresets,
4285
1597
  detectPlatform,
4286
1598
  empty,
4287
1599
  encodeJsonBodyEffect,
4288
1600
  executeProbe,
1601
+ formatConfigError,
4289
1602
  formatHttpError,
4290
1603
  formatIssues,
1604
+ getHttpRequestPolicy,
1605
+ headersOf,
4291
1606
  httpBuilder,
4292
1607
  httpClient,
4293
1608
  httpClientBuilder,
4294
1609
  httpClientStream,
4295
1610
  httpClientWithMeta,
1611
+ httpErrorStatus,
1612
+ httpPolicy,
4296
1613
  httpRoute,
1614
+ isAbortError,
1615
+ isAbortHttpError,
4297
1616
  isCircuitBreakerOpen,
1617
+ isConfigValidationError,
1618
+ isExternalAbortError,
1619
+ isExternalTimeoutError,
1620
+ isFetchHttpError,
4298
1621
  isHttpError,
4299
1622
  isKnownHttpError,
1623
+ isRetryableHttpError,
1624
+ isRetryableHttpStatus,
4300
1625
  isSchema,
1626
+ isTaggedHttpError,
1627
+ isTimeoutHttpError,
4301
1628
  isValidationError,
4302
1629
  json,
1630
+ linkAbortSignals,
4303
1631
  makeAdaptiveLimiterConfig,
4304
1632
  makeBudgetSemaphore,
4305
1633
  makeCompressionMiddleware,
4306
1634
  makeConnectionStateMap,
4307
1635
  makeDefaultHttpClient,
1636
+ makeFetchStreamTransport,
1637
+ makeFetchTransport,
4308
1638
  makeHttp,
4309
1639
  makeHttpClient,
4310
1640
  makeHttpClientBuilder,
@@ -4315,6 +1645,7 @@ export {
4315
1645
  makeNodeHttpServer,
4316
1646
  makeNodeHttpServerResource,
4317
1647
  makePrewarmManager,
1648
+ makePromiseHttpTransport,
4318
1649
  makeRequestCompressionMiddleware,
4319
1650
  makeResponseCompressionMiddleware,
4320
1651
  makeRuntimeHealthRoute,
@@ -4323,13 +1654,17 @@ export {
4323
1654
  matchHttpError,
4324
1655
  nodeHttpServerResource,
4325
1656
  normalizeHeadersInit,
1657
+ normalizeHttpError,
1658
+ normalizeHttpHeaders,
4326
1659
  normalizeRetryBudget,
4327
1660
  parseCacheKey,
4328
1661
  parseConfig,
4329
1662
  prewarmConnections,
4330
1663
  prewarmHttpConnections,
1664
+ promiseHttpTransport,
4331
1665
  resolveConfig,
4332
1666
  resolveHttpPoolKey,
1667
+ resolveHttpRequestPolicyPresets,
4333
1668
  retryAfterMs,
4334
1669
  route,
4335
1670
  runtimeHealthRoute,
@@ -4337,6 +1672,7 @@ export {
4337
1672
  s,
4338
1673
  schema,
4339
1674
  text,
1675
+ toHttpError,
4340
1676
  validateConfig,
4341
1677
  validateFetchAvailable,
4342
1678
  validateOrigin,
@@ -4349,6 +1685,8 @@ export {
4349
1685
  withCircuitBreaker,
4350
1686
  withConnectionPrewarming,
4351
1687
  withDedup,
1688
+ withHttpPolicyPresets,
1689
+ withHttpRequestPolicy,
4352
1690
  withLogging,
4353
1691
  withMiddleware,
4354
1692
  withPriority,