brass-runtime 1.15.0 → 1.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +673 -136
  3. package/dist/agent/cli/main.cjs +40 -35
  4. package/dist/agent/cli/main.js +9 -4
  5. package/dist/agent/cli/main.mjs +9 -4
  6. package/dist/agent/index.cjs +8 -4
  7. package/dist/agent/index.d.ts +1 -1
  8. package/dist/agent/index.js +7 -3
  9. package/dist/agent/index.mjs +7 -3
  10. package/dist/chunk-2HQTDLHF.mjs +683 -0
  11. package/dist/chunk-36I3M4UC.mjs +370 -0
  12. package/dist/chunk-3AYM6WPJ.js +1629 -0
  13. package/dist/chunk-3LOYJFRR.cjs +300 -0
  14. package/dist/chunk-3RG5ZIWI.js +10 -0
  15. package/dist/chunk-3Y2RIUMM.js +300 -0
  16. package/dist/{chunk-VEZNF5GZ.cjs → chunk-4ROBZFL6.cjs} +130 -126
  17. package/dist/{chunk-3QMOKAS5.js → chunk-52OB2ROS.js} +9 -5
  18. package/dist/chunk-52PPNNI4.cjs +416 -0
  19. package/dist/chunk-5EC274J5.cjs +2874 -0
  20. package/dist/chunk-5QC7LRZ3.js +229 -0
  21. package/dist/chunk-5VRJNBLZ.mjs +2874 -0
  22. package/dist/chunk-62AZW6UT.cjs +313 -0
  23. package/dist/chunk-6IXXWIUM.js +683 -0
  24. package/dist/chunk-74ZTY6CP.js +2871 -0
  25. package/dist/chunk-76YMRMH2.cjs +777 -0
  26. package/dist/chunk-7CMJS3QE.mjs +2871 -0
  27. package/dist/{chunk-4NHES7VK.mjs → chunk-7JIJOVCT.js} +27 -13
  28. package/dist/chunk-A2OM6NEH.mjs +194 -0
  29. package/dist/chunk-AGR5B2BC.cjs +683 -0
  30. package/dist/chunk-AVNQLJ5V.js +777 -0
  31. package/dist/chunk-B33ICAKP.js +313 -0
  32. package/dist/{chunk-ELOOF35R.mjs → chunk-B5JD23U7.mjs} +1 -1
  33. package/dist/chunk-BABBZK4Y.js +2024 -0
  34. package/dist/chunk-C3MDXTRZ.js +354 -0
  35. package/dist/chunk-CIZFIMK5.js +2193 -0
  36. package/dist/chunk-CZIVE6NT.cjs +354 -0
  37. package/dist/chunk-DNFJLJMW.mjs +354 -0
  38. package/dist/chunk-DNFO2EIZ.mjs +777 -0
  39. package/dist/chunk-EJ6BPYVR.mjs +416 -0
  40. package/dist/chunk-ENKODRU3.cjs +2193 -0
  41. package/dist/chunk-EOC4UHBS.mjs +229 -0
  42. package/dist/{chunk-BMH5AV44.js → chunk-FH2X7BVP.js} +756 -440
  43. package/dist/{chunk-PPUXIH5R.js → chunk-FHQGHPMO.mjs} +27 -13
  44. package/dist/{chunk-TGIFUAK4.cjs → chunk-GLE2WY7Z.cjs} +951 -635
  45. package/dist/{chunk-BDF4AMWX.mjs → chunk-GYM3LLGS.mjs} +756 -440
  46. package/dist/chunk-HLWLMW2F.mjs +2024 -0
  47. package/dist/chunk-JF5WGYJJ.cjs +194 -0
  48. package/dist/chunk-KH4SYAOS.mjs +1629 -0
  49. package/dist/chunk-KN32XNTH.mjs +313 -0
  50. package/dist/chunk-KQLYONSE.cjs +2871 -0
  51. package/dist/{chunk-STVLQ3XD.cjs → chunk-KZJQ723N.cjs} +92 -78
  52. package/dist/chunk-L2SYFEBS.js +194 -0
  53. package/dist/chunk-L6VB5N7Q.cjs +104 -0
  54. package/dist/{chunk-K6M7MDZ4.mjs → chunk-MBEJI5HF.mjs} +9 -5
  55. package/dist/chunk-MIIYDLGM.js +2874 -0
  56. package/dist/chunk-MOO4L7F4.mjs +104 -0
  57. package/dist/chunk-MT3OWDPC.mjs +2193 -0
  58. package/dist/chunk-MVGUEJ5Z.cjs +370 -0
  59. package/dist/chunk-OBGZSXTJ.cjs +10 -0
  60. package/dist/chunk-PD4EJTQC.cjs +229 -0
  61. package/dist/chunk-PWC3RBQE.mjs +300 -0
  62. package/dist/chunk-Q2I37RP3.cjs +1629 -0
  63. package/dist/chunk-RKGKFN2A.js +416 -0
  64. package/dist/{chunk-R3R2FVLG.cjs → chunk-SA6HUJVI.cjs} +5 -5
  65. package/dist/chunk-TRM4JUZQ.js +104 -0
  66. package/dist/chunk-UB4B6OFY.js +370 -0
  67. package/dist/{chunk-TO7IKXYT.js → chunk-UCUBNWM2.js} +1 -1
  68. package/dist/chunk-VN44DYYT.cjs +2024 -0
  69. package/dist/chunk-Y6FXYEAI.mjs +10 -0
  70. package/dist/client-CZHU674n.d.ts +820 -0
  71. package/dist/core/index.cjs +198 -4
  72. package/dist/core/index.d.ts +311 -212
  73. package/dist/core/index.js +237 -43
  74. package/dist/core/index.mjs +237 -43
  75. package/dist/{effect-CMOQKX8y.d.ts → effect-DIUHZ9IN.d.ts} +195 -1
  76. package/dist/effectRunner-CFLC32IK.cjs +8 -0
  77. package/dist/effectRunner-L4S7IPT3.js +8 -0
  78. package/dist/effectRunner-NNGG75QA.mjs +8 -0
  79. package/dist/http/index.cjs +1227 -2971
  80. package/dist/http/index.d.ts +826 -280
  81. package/dist/http/index.js +1089 -2833
  82. package/dist/http/index.mjs +1089 -2833
  83. package/dist/http/testing.cjs +161 -0
  84. package/dist/http/testing.d.ts +43 -0
  85. package/dist/http/testing.js +161 -0
  86. package/dist/http/testing.mjs +161 -0
  87. package/dist/index.cjs +486 -250
  88. package/dist/index.d.ts +87 -95
  89. package/dist/index.js +391 -155
  90. package/dist/index.mjs +391 -155
  91. package/dist/observability/index.cjs +162 -0
  92. package/dist/observability/index.d.ts +152 -0
  93. package/dist/observability/index.js +162 -0
  94. package/dist/observability/index.mjs +162 -0
  95. package/dist/perf/cli.cjs +401 -0
  96. package/dist/perf/cli.d.ts +1 -0
  97. package/dist/perf/cli.js +401 -0
  98. package/dist/perf/cli.mjs +401 -0
  99. package/dist/perf/index.cjs +141 -0
  100. package/dist/perf/index.d.ts +483 -0
  101. package/dist/perf/index.js +141 -0
  102. package/dist/perf/index.mjs +141 -0
  103. package/dist/schedule-CK3Ml_7p.d.ts +259 -0
  104. package/dist/schema/index.cjs +29 -0
  105. package/dist/schema/index.d.ts +179 -0
  106. package/dist/schema/index.js +29 -0
  107. package/dist/schema/index.mjs +29 -0
  108. package/dist/server-GJPg8ZSG.d.ts +675 -0
  109. package/dist/{stream-FQm9h4Mg.d.ts → stream-B4oK9JFP.d.ts} +1 -1
  110. package/dist/tracer-Hwt1cl7h.d.ts +189 -0
  111. package/dist/tracing-DqbTKGcf.d.ts +148 -0
  112. package/docs/ARCHITECTURE.md +292 -0
  113. package/docs/README.md +63 -0
  114. package/docs/adr/0001-ai-context-pack.md +32 -0
  115. package/docs/agent-apply-mode.md +104 -0
  116. package/docs/agent-approvals.md +110 -0
  117. package/docs/agent-batch.md +185 -0
  118. package/docs/agent-boundaries.md +112 -0
  119. package/docs/agent-chat-sessions.md +160 -0
  120. package/docs/agent-ci.md +17 -0
  121. package/docs/agent-cli.md +405 -0
  122. package/docs/agent-config.md +480 -0
  123. package/docs/agent-context-discovery.md +159 -0
  124. package/docs/agent-copilot-like-dx.md +126 -0
  125. package/docs/agent-declarative-optimized-planning.md +138 -0
  126. package/docs/agent-dx.md +224 -0
  127. package/docs/agent-env-files.md +126 -0
  128. package/docs/agent-follow-up-context.md +43 -0
  129. package/docs/agent-global-usage.md +180 -0
  130. package/docs/agent-init.md +109 -0
  131. package/docs/agent-install-and-configure.md +516 -0
  132. package/docs/agent-language-workspace-ux.md +99 -0
  133. package/docs/agent-llm-adapters.md +123 -0
  134. package/docs/agent-local-install.md +190 -0
  135. package/docs/agent-local-tests.md +51 -0
  136. package/docs/agent-observability.md +155 -0
  137. package/docs/agent-patch-quality-loop.md +162 -0
  138. package/docs/agent-presets.md +22 -0
  139. package/docs/agent-project-commands.md +237 -0
  140. package/docs/agent-project-intelligence.md +156 -0
  141. package/docs/agent-redaction.md +18 -0
  142. package/docs/agent-release-readiness.md +76 -0
  143. package/docs/agent-rollback-safety.md +162 -0
  144. package/docs/agent-rollback.md +23 -0
  145. package/docs/agent-run-artifacts.md +16 -0
  146. package/docs/agent-vscode-auto-discovery.md +137 -0
  147. package/docs/agent-vscode-batch-runner.md +100 -0
  148. package/docs/agent-vscode-chat-layout.md +90 -0
  149. package/docs/agent-vscode-clean-install.md +147 -0
  150. package/docs/agent-vscode-code-actions.md +70 -0
  151. package/docs/agent-vscode-diff-preview.md +45 -0
  152. package/docs/agent-vscode-inline-assist.md +56 -0
  153. package/docs/agent-vscode-install.md +186 -0
  154. package/docs/agent-vscode-model-setup.md +97 -0
  155. package/docs/agent-vscode-patch-preview.md +92 -0
  156. package/docs/agent-vscode-problems.md +79 -0
  157. package/docs/agent-vscode-project-dashboard.md +106 -0
  158. package/docs/agent-vscode-run-history.md +92 -0
  159. package/docs/agent-vscode-ux.md +73 -0
  160. package/docs/ai/INVARIANTS.md +84 -0
  161. package/docs/ai/PROJECT_MAP.md +338 -0
  162. package/docs/ai/PUBLIC_API.md +336 -0
  163. package/docs/ai/VALIDATION_MATRIX.md +67 -0
  164. package/docs/api-polish.md +37 -0
  165. package/docs/cancellation.md +162 -0
  166. package/docs/coverage.md +46 -0
  167. package/docs/getting-started.md +159 -0
  168. package/docs/guides/README.md +40 -0
  169. package/docs/guides/circuit-breaker.md +89 -0
  170. package/docs/guides/error-handling.md +91 -0
  171. package/docs/guides/getting-started.md +107 -0
  172. package/docs/guides/layers.md +189 -0
  173. package/docs/guides/metrics.md +101 -0
  174. package/docs/guides/resource-management.md +141 -0
  175. package/docs/guides/retry.md +215 -0
  176. package/docs/guides/semaphore.md +66 -0
  177. package/docs/guides/streams.md +117 -0
  178. package/docs/guides/supervisors.md +98 -0
  179. package/docs/guides/testing.md +162 -0
  180. package/docs/guides/tracing.md +71 -0
  181. package/docs/http-recipes.md +399 -0
  182. package/docs/http.md +749 -0
  183. package/docs/modules.md +285 -0
  184. package/docs/observability-collector-smoke.md +31 -0
  185. package/docs/observability-framework-examples.md +98 -0
  186. package/docs/observability.md +542 -0
  187. package/docs/otel-collector-smoke.yaml +27 -0
  188. package/docs/performance-profiler.md +199 -0
  189. package/docs/production-readiness.md +73 -0
  190. package/docs/recipes/README.md +12 -0
  191. package/docs/recipes/http-server.md +45 -0
  192. package/docs/recipes/layers.md +44 -0
  193. package/docs/recipes/performance.md +47 -0
  194. package/docs/recipes/runtime.md +41 -0
  195. package/docs/recipes/testing.md +41 -0
  196. package/docs/release.md +53 -0
  197. package/docs/wasm-bounded-queues.md +44 -0
  198. package/docs/wasm-engine-observability-benchmarks.md +85 -0
  199. package/docs/wasm-fiber-engine.md +117 -0
  200. package/docs/wasm-scheduler-state-machine.md +122 -0
  201. package/docs/wasm-stream-chunks.md +54 -0
  202. package/package.json +48 -2
  203. package/dist/chunk-AR22SXML.js +0 -1043
  204. package/dist/chunk-BDYEENHT.js +0 -224
  205. package/dist/chunk-JFPU5GQI.mjs +0 -1043
  206. package/dist/chunk-MS34J5LY.cjs +0 -224
  207. package/dist/chunk-UMAZLXAB.mjs +0 -224
  208. package/dist/chunk-XPZNXSVN.cjs +0 -1043
  209. package/dist/tracing-DNT9jEbr.d.ts +0 -106
@@ -1,937 +1,151 @@
1
- import {
2
- streamFromReadableStream
3
- } from "../chunk-PPUXIH5R.js";
4
1
  import {
5
2
  makeCircuitBreaker,
6
- sleep
7
- } from "../chunk-BDYEENHT.js";
8
- import "../chunk-TO7IKXYT.js";
3
+ resource
4
+ } from "../chunk-B33ICAKP.js";
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-74ZTY6CP.js";
41
+ import {
42
+ AdaptiveLimiter,
43
+ EmaComputer,
44
+ HttpConcurrencyPool,
45
+ LatencyWindow,
46
+ abortErrorForSignal,
47
+ adaptiveLimiterPresets,
48
+ backoffDelayMs,
49
+ computeGradient,
50
+ computeNewLimit,
51
+ decorate,
52
+ defaultRetryOnError,
53
+ defaultRetryOnStatus,
54
+ defaultRetryableMethods,
55
+ headersOf,
56
+ isAbortError,
57
+ isTaggedHttpError,
58
+ linkAbortSignals,
59
+ makeAdaptiveLimiterConfig,
60
+ makeFetchStreamTransport,
61
+ makeFetchTransport,
62
+ makeHttp,
63
+ makeHttpStream,
64
+ makePromiseHttpTransport,
65
+ normalizeHeadersInit,
66
+ normalizeHttpError,
67
+ normalizeHttpHeaders,
68
+ normalizeRetryBudget,
69
+ promiseHttpTransport,
70
+ resolveConfig,
71
+ resolveHttpPoolKey,
72
+ retryAfterMs,
73
+ setHeaderIfMissing,
74
+ validateConfig,
75
+ withMiddleware,
76
+ withRetry,
77
+ withRetryStream
78
+ } from "../chunk-MIIYDLGM.js";
79
+ import {
80
+ registerHttpEffect
81
+ } from "../chunk-TRM4JUZQ.js";
82
+ import "../chunk-7JIJOVCT.js";
83
+ import "../chunk-UCUBNWM2.js";
84
+ import {
85
+ fixed,
86
+ makeScheduleDriver,
87
+ take
88
+ } from "../chunk-6IXXWIUM.js";
89
+ import "../chunk-L2SYFEBS.js";
90
+ import {
91
+ healthToHttpResponse,
92
+ makeRuntimeHealth,
93
+ runObservedHttpServerEffect
94
+ } from "../chunk-CIZFIMK5.js";
95
+ import "../chunk-RKGKFN2A.js";
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-3Y2RIUMM.js";
119
+ import {
120
+ Runtime,
121
+ fromPromiseAbortable,
122
+ toPromise
123
+ } from "../chunk-FH2X7BVP.js";
9
124
  import {
10
125
  Cause,
11
- __require,
12
126
  asyncEffect,
13
127
  asyncFail,
14
128
  asyncFlatMap,
15
129
  asyncFold,
16
130
  asyncSucceed,
17
- fromPromiseAbortable,
18
131
  mapTryAsync,
19
- resolveWasmModule,
20
- toPromise,
21
132
  withAsyncPromise
22
- } from "../chunk-BMH5AV44.js";
23
-
24
- // src/http/optics/lens.ts
25
- var Lens = {
26
- make(get, set) {
27
- return { get, set };
28
- },
29
- over(ln, f) {
30
- return (s) => ln.set(f(ln.get(s)))(s);
31
- },
32
- compose(ab, sa) {
33
- return Lens.make(
34
- (s) => ab.get(sa.get(s)),
35
- (b) => (s) => sa.set(ab.set(b)(sa.get(s)))(s)
36
- );
37
- }
38
- };
39
-
40
- // src/http/optics/request.ts
41
- var Request = {
42
- headers: Lens.make(
43
- (req) => req.headers ?? {},
44
- (headers) => (req) => ({ ...req, headers })
45
- )
46
- };
47
- var mergeHeaders = (extra) => (req) => Lens.over(Request.headers, (h) => ({ ...h, ...extra }))(req);
48
- var mergeHeadersUnder = (under) => (req) => Lens.over(Request.headers, (h) => ({ ...under, ...h }))(req);
49
- var setHeaderIfMissing = (k, v) => (req) => Lens.over(Request.headers, (h) => h[k] ? h : { ...h, [k]: v })(req);
50
-
51
- // src/http/retry/wasmRetryPlanner.ts
52
- var WasmRetryPlannerBridge = class {
53
- planner;
54
- constructor(Ctor) {
55
- this.planner = new Ctor();
56
- }
57
- start(options) {
58
- return this.planner.start(
59
- options.nowMs,
60
- options.maxRetries,
61
- options.baseDelayMs,
62
- options.maxDelayMs,
63
- options.maxElapsedMs ?? -1,
64
- BigInt(this.seed())
65
- );
66
- }
67
- nextDelayMs(retryId, options) {
68
- const delay = this.planner.next_delay_ms(retryId, options.nowMs, options.retryable, options.retryAfterMs ?? -1);
69
- return delay < 0 ? void 0 : delay;
70
- }
71
- drop(retryId) {
72
- this.planner.drop_state(retryId);
73
- }
74
- stats() {
75
- return {
76
- live: this.planner.metric_u64(0),
77
- planned: this.planner.metric_u64(1),
78
- exhausted: this.planner.metric_u64(2),
79
- dropped: this.planner.metric_u64(3)
80
- };
81
- }
82
- seed() {
83
- return Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
84
- }
85
- };
86
- function makeWasmRetryPlanner() {
87
- const mod = resolveWasmModule();
88
- const Ctor = mod?.BrassWasmRetryPlanner;
89
- if (!Ctor) throw new Error("brass-runtime wasm retry planner is not available. Run npm run build:wasm first.");
90
- return new WasmRetryPlannerBridge(Ctor);
91
- }
92
-
93
- // src/http/retry/retry.ts
94
- var defaultRetryableMethods = ["GET", "HEAD", "OPTIONS"];
95
- var defaultRetryOnStatus = (s) => s === 408 || s === 429 || s === 500 || s === 502 || s === 503 || s === 504;
96
- var defaultRetryOnError = (e) => e._tag === "FetchError" || e._tag === "Timeout" || e._tag === "PoolTimeout";
97
- var clamp = (n, min, max) => Math.max(min, Math.min(max, n));
98
- var backoffDelayMs = (attempt, base, cap) => {
99
- const b = Math.max(0, base);
100
- const c = Math.max(0, cap);
101
- const exp = b * Math.pow(2, attempt);
102
- const lim = clamp(exp, 0, c);
103
- return Math.floor(Math.random() * lim);
104
- };
105
- var headerCI = (h, name) => {
106
- const k = Object.keys(h).find((x) => x.toLowerCase() === name.toLowerCase());
107
- return k ? h[k] : void 0;
108
- };
109
- var retryAfterMs = (headers) => {
110
- const v = headerCI(headers, "retry-after")?.trim();
111
- if (!v) return void 0;
112
- const secs = Number(v);
113
- if (Number.isFinite(secs)) return Math.max(0, Math.floor(secs * 1e3));
114
- const t = Date.parse(v);
115
- if (Number.isFinite(t)) return Math.max(0, t - Date.now());
116
- return void 0;
117
- };
118
- var normalizeRetryBudget = (ms) => {
119
- if (ms === void 0 || !Number.isFinite(ms)) return void 0;
120
- return Math.max(0, Math.floor(ms));
121
- };
122
- var resolveEffectivePolicy = (req, basePolicy) => {
123
- const override = req.retry;
124
- if (override === false) return null;
125
- if (override === void 0) return basePolicy;
126
- return {
127
- ...basePolicy,
128
- ...override.maxRetries !== void 0 && { maxRetries: override.maxRetries },
129
- ...override.baseDelayMs !== void 0 && { baseDelayMs: override.baseDelayMs },
130
- ...override.maxDelayMs !== void 0 && { maxDelayMs: override.maxDelayMs },
131
- ...override.retryOnStatus !== void 0 && { retryOnStatus: override.retryOnStatus }
132
- };
133
- };
134
- var resolveRetryEngine = (p) => {
135
- if (p.engine !== void 0) {
136
- if (p.engine === "ts" || p.engine === "wasm") return p.engine;
137
- throw new Error(`brass-runtime retry engine must be 'ts' or 'wasm'; received '${String(p.engine)}'`);
138
- }
139
- if (p.wasm === true) return "wasm";
140
- if (p.wasm === false) return "ts";
141
- return "ts";
142
- };
143
- var withRetry = (p) => (next) => {
144
- const retryOnMethods = p.retryOnMethods ?? defaultRetryableMethods;
145
- const retryEngine = resolveRetryEngine(p);
146
- const wasmPlanner = retryEngine === "wasm" ? makeWasmRetryPlanner() : void 0;
147
- const isMethodRetryable = (req) => retryOnMethods.includes(req.method);
148
- const nextDelay = (ep, epMaxElapsedMs, retryId, attempt, startedAt, retryable, retryAfter) => {
149
- if (!retryable) return void 0;
150
- if (wasmPlanner && retryId !== void 0) {
151
- return wasmPlanner.nextDelayMs(retryId, {
152
- nowMs: performance.now(),
153
- retryable,
154
- retryAfterMs: retryAfter
155
- });
156
- }
157
- const remainingBudget = epMaxElapsedMs === void 0 ? Number.POSITIVE_INFINITY : epMaxElapsedMs - (performance.now() - startedAt);
158
- if (remainingBudget <= 0) return void 0;
159
- const rawDelay = retryAfter === void 0 ? backoffDelayMs(attempt, ep.baseDelayMs, ep.maxDelayMs) : Math.min(retryAfter, ep.maxDelayMs);
160
- return Math.max(0, Math.min(rawDelay, remainingBudget));
161
- };
162
- const sleepWithCleanup = (ms, onCancel) => {
163
- return asyncEffect((_env, cb) => {
164
- const delay = Math.max(0, Math.floor(ms));
165
- const id = setTimeout(() => cb({ _tag: "Success", value: void 0 }), delay);
166
- return () => {
167
- clearTimeout(id);
168
- onCancel();
169
- };
170
- });
171
- };
172
- const loop = (req, attempt, startedAt, retryId, ep, epMaxElapsedMs, epRetryOnStatus, epRetryOnError, originalPriority, safeDrop) => {
173
- if (!isMethodRetryable(req)) return next(req);
174
- const effectiveReq = attempt > 0 ? (() => {
175
- const boostedReq = { ...req };
176
- boostedReq.priority = Math.max(0, originalPriority - 1);
177
- return boostedReq;
178
- })() : req;
179
- const remainingBudget = () => epMaxElapsedMs === void 0 ? Number.POSITIVE_INFINITY : epMaxElapsedMs - (performance.now() - startedAt);
180
- return asyncFold(
181
- next(effectiveReq),
182
- (e) => {
183
- if (e._tag === "Abort" || e._tag === "BadUrl" || e._tag === "PoolRejected" || e._tag === "CircuitBreakerOpen") {
184
- safeDrop(retryId);
185
- return asyncFail(e);
186
- }
187
- const retryable = attempt < ep.maxRetries && epRetryOnError(e) && remainingBudget() > 0;
188
- const d = nextDelay(ep, epMaxElapsedMs, retryId, attempt, startedAt, retryable);
189
- if (d === void 0 || d <= 0 && epMaxElapsedMs !== void 0) {
190
- safeDrop(retryId);
191
- return asyncFail(e);
192
- }
193
- if (ep.onRetry) {
194
- ep.onRetry({
195
- attempt,
196
- delayMs: d,
197
- error: e,
198
- status: void 0,
199
- url: req.url,
200
- method: req.method,
201
- timestamp: Date.now()
202
- });
203
- }
204
- return asyncFlatMap(sleepWithCleanup(d, () => safeDrop(retryId)), () => loop(req, attempt + 1, startedAt, retryId, ep, epMaxElapsedMs, epRetryOnStatus, epRetryOnError, originalPriority, safeDrop));
205
- },
206
- (w) => {
207
- const retryable = attempt < ep.maxRetries && epRetryOnStatus(w.status) && remainingBudget() > 0;
208
- const ra = ep.respectRetryAfter === false ? void 0 : retryAfterMs(w.headers);
209
- const d = nextDelay(ep, epMaxElapsedMs, retryId, attempt, startedAt, retryable, ra);
210
- if (d === void 0 || d <= 0 && epMaxElapsedMs !== void 0) {
211
- safeDrop(retryId);
212
- return asyncSucceed(w);
213
- }
214
- if (ep.onRetry) {
215
- ep.onRetry({
216
- attempt,
217
- delayMs: d,
218
- error: void 0,
219
- status: w.status,
220
- url: req.url,
221
- method: req.method,
222
- timestamp: Date.now()
223
- });
224
- }
225
- return asyncFlatMap(sleepWithCleanup(d, () => safeDrop(retryId)), () => loop(req, attempt + 1, startedAt, retryId, ep, epMaxElapsedMs, epRetryOnStatus, epRetryOnError, originalPriority, safeDrop));
226
- }
227
- );
228
- };
229
- return (req) => {
230
- const effectivePolicy = resolveEffectivePolicy(req, p);
231
- if (effectivePolicy === null) return next(req);
232
- if (!isMethodRetryable(req)) return next(req);
233
- const epRetryOnStatus = effectivePolicy.retryOnStatus ?? defaultRetryOnStatus;
234
- const epRetryOnError = effectivePolicy.retryOnError ?? defaultRetryOnError;
235
- const epMaxElapsedMs = normalizeRetryBudget(effectivePolicy.maxElapsedMs);
236
- const originalPriority = req.priority ?? 5;
237
- const startedAt = performance.now();
238
- const retryId = wasmPlanner?.start({
239
- nowMs: startedAt,
240
- maxRetries: effectivePolicy.maxRetries,
241
- baseDelayMs: effectivePolicy.baseDelayMs,
242
- maxDelayMs: effectivePolicy.maxDelayMs,
243
- maxElapsedMs: epMaxElapsedMs
244
- });
245
- let plannerDropped = false;
246
- const safeDrop = (id) => {
247
- if (id !== void 0 && !plannerDropped) {
248
- plannerDropped = true;
249
- wasmPlanner?.drop(id);
250
- }
251
- };
252
- return loop(req, 0, startedAt, retryId, effectivePolicy, epMaxElapsedMs, epRetryOnStatus, epRetryOnError, originalPriority, safeDrop);
253
- };
254
- };
255
-
256
- // src/http/wasmPermitPool.ts
257
- var DECISION_RUN_NOW = 0;
258
- var DECISION_QUEUED = 1;
259
- var WasmHttpPermitPoolBridge = class {
260
- pool;
261
- keyCache = /* @__PURE__ */ new Map();
262
- constructor(Ctor, options) {
263
- this.pool = new Ctor(options.concurrency, options.maxQueue, toU64(options.queueTimeoutMs));
264
- }
265
- acquire(key, subjectId, nowMs = Date.now()) {
266
- const keyId = this.internKey(key);
267
- const decision = this.pool.acquire(subjectId, keyId, toU64(nowMs));
268
- const permitId = this.pool.last_permit_id();
269
- if (decision === DECISION_RUN_NOW) return { kind: "run", keyId, permitId };
270
- if (decision === DECISION_QUEUED) return { kind: "queued", keyId, permitId };
271
- return { kind: "rejected", keyId, permitId };
272
- }
273
- release(keyId, nowMs = Date.now()) {
274
- const ptr = this.pool.release(keyId, toU64(nowMs));
275
- return this.readEvents(ptr, this.pool.permit_events_len());
276
- }
277
- cancel(permitId) {
278
- this.pool.cancel(permitId);
279
- }
280
- advanceTime(nowMs = Date.now()) {
281
- const ptr = this.pool.advance_time(toU64(nowMs));
282
- return this.readEvents(ptr, this.pool.permit_events_len());
283
- }
284
- nextDeadlineMs() {
285
- return this.pool.next_deadline_ms();
286
- }
287
- stats() {
288
- return {
289
- running: this.pool.metric_u64(0),
290
- queued: this.pool.metric_u64(1),
291
- acquired: this.pool.metric_u64(2),
292
- released: this.pool.metric_u64(3),
293
- rejected: this.pool.metric_u64(4),
294
- queueTimeouts: this.pool.metric_u64(5),
295
- keys: this.pool.metric_u64(6)
296
- };
297
- }
298
- internKey(key) {
299
- const normalized = key.trim().slice(0, 160) || "global";
300
- let id = this.keyCache.get(normalized);
301
- if (id === void 0) {
302
- id = this.pool.intern_key(normalized);
303
- this.keyCache.set(normalized, id);
304
- }
305
- return id;
306
- }
307
- readEvents(ptr, len) {
308
- if (ptr === 0 || len <= 1) return [];
309
- const words = new Uint32Array(this.pool.memory().buffer, ptr, len);
310
- const count = words[0] >>> 0;
311
- const out = [];
312
- for (let i = 0; i < count; i++) {
313
- const base = 1 + i * 3;
314
- if (base + 2 >= words.length) break;
315
- out.push({
316
- subjectId: words[base] >>> 0,
317
- permitId: words[base + 1] >>> 0,
318
- keyId: words[base + 2] >>> 0
319
- });
320
- }
321
- return out;
322
- }
323
- };
324
- function makeWasmHttpPermitPool(options) {
325
- const mod = resolveWasmModule();
326
- const Ctor = mod?.BrassWasmHttpPermitPool;
327
- if (!Ctor) throw new Error("brass-runtime wasm HTTP permit pool is not available. Run npm run build:wasm first.");
328
- return new WasmHttpPermitPoolBridge(Ctor, options);
329
- }
330
- function toU64(value) {
331
- return BigInt(Math.max(0, Math.floor(value)));
332
- }
333
-
334
- // src/http/pool.ts
335
- var DEFAULT_CONCURRENCY = 64;
336
- var DEFAULT_MAX_QUEUE = 256;
337
- var clampInt = (n, fallback, min) => {
338
- if (n === void 0 || !Number.isFinite(n)) return fallback;
339
- return Math.max(min, Math.floor(n));
340
- };
341
- var queueTimeoutError = (key, timeoutMs) => ({
342
- _tag: "PoolTimeout",
343
- key,
344
- timeoutMs,
345
- message: `HTTP pool '${key}' did not grant a slot within ${timeoutMs}ms`
346
- });
347
- var poolRejectedError = (key, maxQueue) => ({
348
- _tag: "PoolRejected",
349
- key,
350
- limit: maxQueue,
351
- message: `HTTP pool '${key}' queue is full`
352
- });
353
- var abortError = () => ({ _tag: "Abort" });
354
- function resolveHttpPoolEngine(config) {
355
- if (config.engine !== void 0) {
356
- if (config.engine === "ts" || config.engine === "wasm") return config.engine;
357
- throw new Error(`brass-runtime HTTP pool engine must be 'ts' or 'wasm'; received '${String(config.engine)}'`);
358
- }
359
- if (config.wasm === true) return "wasm";
360
- if (config.wasm === false) return "ts";
361
- return "ts";
362
- }
363
- function resolveHttpPoolKey(resolver, req, url) {
364
- const custom = req.poolKey?.trim();
365
- if (custom) return custom.slice(0, 160);
366
- const r = resolver ?? "origin";
367
- if (typeof r === "function") return r(req, url).trim().slice(0, 160) || "global";
368
- if (r === "global") return "global";
369
- if (r === "host") return url.host;
370
- return url.origin;
371
- }
372
- var HttpConcurrencyPool = class {
373
- states = /* @__PURE__ */ new Map();
374
- concurrency;
375
- maxQueue;
376
- queueTimeoutMs;
377
- keyResolver;
378
- wasm;
379
- wasmWaiters = /* @__PURE__ */ new Map();
380
- wasmTimer;
381
- nextSubjectId = 1;
382
- constructor(config = {}) {
383
- this.concurrency = clampInt(config.concurrency, DEFAULT_CONCURRENCY, 1);
384
- this.maxQueue = clampInt(config.maxQueue, DEFAULT_MAX_QUEUE, 0);
385
- this.queueTimeoutMs = config.queueTimeoutMs !== void 0 && Number.isFinite(config.queueTimeoutMs) ? Math.max(0, Math.floor(config.queueTimeoutMs)) : void 0;
386
- this.keyResolver = config.key;
387
- const engine = resolveHttpPoolEngine(config);
388
- this.wasm = engine === "wasm" ? makeWasmHttpPermitPool({
389
- concurrency: this.concurrency,
390
- maxQueue: this.maxQueue,
391
- queueTimeoutMs: this.queueTimeoutMs ?? 0
392
- }) : void 0;
393
- }
394
- acquire(key, signal) {
395
- return this.wasm ? this.acquireWasm(key, signal) : this.acquireJs(key, signal);
396
- }
397
- stats() {
398
- const keys = Array.from(this.states.values()).map((state) => ({
399
- key: state.key,
400
- running: state.running,
401
- queued: state.queue.length,
402
- concurrency: this.concurrency,
403
- maxQueue: this.maxQueue,
404
- acquired: state.acquired,
405
- released: state.released,
406
- rejected: state.rejected,
407
- queueTimeouts: state.queueTimeouts,
408
- abortedWhileQueued: state.abortedWhileQueued
409
- })).sort((a, b) => b.running + b.queued - (a.running + a.queued) || a.key.localeCompare(b.key));
410
- return keys.reduce((acc, key) => ({
411
- running: acc.running + key.running,
412
- queued: acc.queued + key.queued,
413
- acquired: acc.acquired + key.acquired,
414
- released: acc.released + key.released,
415
- rejected: acc.rejected + key.rejected,
416
- queueTimeouts: acc.queueTimeouts + key.queueTimeouts,
417
- abortedWhileQueued: acc.abortedWhileQueued + key.abortedWhileQueued,
418
- wasm: this.wasm?.stats(),
419
- keys: acc.keys.concat(key)
420
- }), {
421
- running: 0,
422
- queued: 0,
423
- acquired: 0,
424
- released: 0,
425
- rejected: 0,
426
- queueTimeouts: 0,
427
- abortedWhileQueued: 0,
428
- ...this.wasm ? { wasm: this.wasm.stats() } : {},
429
- keys: []
430
- });
431
- }
432
- acquireJs(key, signal) {
433
- const state = this.getState(key);
434
- if (signal.aborted) return Promise.reject(abortError());
435
- if (state.running < this.concurrency) {
436
- state.running++;
437
- state.acquired++;
438
- return Promise.resolve(this.makeLease(state));
439
- }
440
- if (state.queue.length >= this.maxQueue) {
441
- state.rejected++;
442
- return Promise.reject(poolRejectedError(key, this.maxQueue));
443
- }
444
- return new Promise((resolve, reject) => {
445
- const waiter = { signal, resolve, reject };
446
- const removeWaiter = () => this.removeWaiter(state, waiter);
447
- const cleanup = () => this.cleanupWaiter(waiter);
448
- waiter.abort = () => {
449
- cleanup();
450
- removeWaiter();
451
- state.abortedWhileQueued++;
452
- reject(abortError());
453
- };
454
- signal.addEventListener("abort", waiter.abort, { once: true });
455
- if (this.queueTimeoutMs !== void 0 && this.queueTimeoutMs > 0) {
456
- waiter.timer = setTimeout(() => {
457
- cleanup();
458
- removeWaiter();
459
- state.queueTimeouts++;
460
- reject(queueTimeoutError(key, this.queueTimeoutMs));
461
- }, this.queueTimeoutMs);
462
- }
463
- state.queue.push(waiter);
464
- });
465
- }
466
- acquireWasm(key, signal) {
467
- const wasm = this.wasm;
468
- const state = this.getState(key);
469
- if (signal.aborted) return Promise.reject(abortError());
470
- const subjectId = this.allocateSubjectId();
471
- const decision = wasm.acquire(key, subjectId);
472
- if (decision.kind === "run") {
473
- state.running++;
474
- state.acquired++;
475
- return Promise.resolve(this.makeLease(state, decision.keyId));
476
- }
477
- if (decision.kind === "rejected") {
478
- state.rejected++;
479
- return Promise.reject(poolRejectedError(key, this.maxQueue));
480
- }
481
- return new Promise((resolve, reject) => {
482
- const waiter = { signal, resolve, reject };
483
- const removeWaiter = () => this.removeWaiter(state, waiter);
484
- const cleanup = () => this.cleanupWaiter(waiter);
485
- waiter.abort = () => {
486
- cleanup();
487
- removeWaiter();
488
- wasm.cancel(decision.permitId);
489
- this.wasmWaiters.delete(decision.permitId);
490
- state.abortedWhileQueued++;
491
- reject(abortError());
492
- };
493
- signal.addEventListener("abort", waiter.abort, { once: true });
494
- state.queue.push(waiter);
495
- this.wasmWaiters.set(decision.permitId, { waiter, state, keyId: decision.keyId });
496
- this.scheduleWasmTimeoutPump();
497
- });
498
- }
499
- getState(key) {
500
- const k = key.trim().slice(0, 160) || "global";
501
- const existing = this.states.get(k);
502
- if (existing) return existing;
503
- const created = {
504
- key: k,
505
- running: 0,
506
- queue: [],
507
- acquired: 0,
508
- released: 0,
509
- rejected: 0,
510
- queueTimeouts: 0,
511
- abortedWhileQueued: 0
512
- };
513
- this.states.set(k, created);
514
- return created;
515
- }
516
- makeLease(state, wasmKeyId) {
517
- let released = false;
518
- return {
519
- key: state.key,
520
- release: () => {
521
- if (released) return;
522
- released = true;
523
- if (state.running > 0) state.running--;
524
- state.released++;
525
- if (this.wasm && wasmKeyId !== void 0) {
526
- this.handleWasmGrants(this.wasm.release(wasmKeyId));
527
- this.scheduleWasmTimeoutPump();
528
- return;
529
- }
530
- this.drain(state);
531
- }
532
- };
533
- }
534
- drain(state) {
535
- while (state.running < this.concurrency && state.queue.length > 0) {
536
- const waiter = state.queue.shift();
537
- this.cleanupWaiter(waiter);
538
- if (waiter.signal.aborted) {
539
- state.abortedWhileQueued++;
540
- waiter.reject(abortError());
541
- continue;
542
- }
543
- state.running++;
544
- state.acquired++;
545
- waiter.resolve(this.makeLease(state));
546
- }
547
- }
548
- handleWasmGrants(events) {
549
- for (const event of events) {
550
- const pending = this.wasmWaiters.get(event.permitId);
551
- if (!pending) continue;
552
- this.wasmWaiters.delete(event.permitId);
553
- this.cleanupWaiter(pending.waiter);
554
- this.removeWaiter(pending.state, pending.waiter);
555
- if (pending.waiter.signal.aborted) {
556
- pending.state.abortedWhileQueued++;
557
- pending.waiter.reject(abortError());
558
- continue;
559
- }
560
- pending.state.running++;
561
- pending.state.acquired++;
562
- pending.waiter.resolve(this.makeLease(pending.state, event.keyId));
563
- }
564
- }
565
- handleWasmTimeouts(events) {
566
- for (const event of events) {
567
- const pending = this.wasmWaiters.get(event.permitId);
568
- if (!pending) continue;
569
- this.wasmWaiters.delete(event.permitId);
570
- this.cleanupWaiter(pending.waiter);
571
- this.removeWaiter(pending.state, pending.waiter);
572
- pending.state.queueTimeouts++;
573
- pending.waiter.reject(queueTimeoutError(pending.state.key, this.queueTimeoutMs ?? 0));
574
- }
575
- }
576
- scheduleWasmTimeoutPump() {
577
- if (!this.wasm) return;
578
- if (this.wasmTimer !== void 0) clearTimeout(this.wasmTimer);
579
- this.wasmTimer = void 0;
580
- const next = this.wasm.nextDeadlineMs();
581
- if (!Number.isFinite(next) || next < 0) return;
582
- const delay = Math.max(0, Math.min(2 ** 31 - 1, Math.floor(next - Date.now())));
583
- this.wasmTimer = setTimeout(() => {
584
- this.wasmTimer = void 0;
585
- if (!this.wasm) return;
586
- this.handleWasmTimeouts(this.wasm.advanceTime());
587
- this.scheduleWasmTimeoutPump();
588
- }, delay);
589
- if (typeof this.wasmTimer.unref === "function") this.wasmTimer.unref();
590
- }
591
- cleanupWaiter(waiter) {
592
- if (waiter.timer !== void 0) {
593
- clearTimeout(waiter.timer);
594
- waiter.timer = void 0;
595
- }
596
- if (waiter.abort) {
597
- waiter.signal.removeEventListener("abort", waiter.abort);
598
- waiter.abort = void 0;
599
- }
600
- }
601
- removeWaiter(state, waiter) {
602
- const idx = state.queue.indexOf(waiter);
603
- if (idx >= 0) state.queue.splice(idx, 1);
604
- }
605
- allocateSubjectId() {
606
- const id = this.nextSubjectId >>> 0;
607
- this.nextSubjectId = this.nextSubjectId + 1 >>> 0;
608
- if (this.nextSubjectId === 0) this.nextSubjectId = 1;
609
- return id === 0 ? this.allocateSubjectId() : id;
610
- }
611
- };
612
-
613
- // src/http/client.ts
614
- var emptyStats = () => ({
615
- inFlight: 0,
616
- started: 0,
617
- succeeded: 0,
618
- failed: 0,
619
- aborted: 0,
620
- timedOut: 0,
621
- poolRejected: 0,
622
- poolTimeouts: 0
623
- });
624
- var decorate = (run, stats = emptyStats) => Object.assign(((req) => run(req)), {
625
- with: (mw) => decorate(mw(run), stats),
626
- stats
627
- });
628
- var withMiddleware = (mw) => (c) => decorate(mw(c), c.stats);
629
- var decorateStream = (run, stats = emptyStats) => Object.assign(((req) => run(req)), { stats });
630
- var isTaggedHttpError = (e) => {
631
- if (typeof e !== "object" || e === null || !("_tag" in e)) return false;
632
- const tag = e._tag;
633
- return tag === "Abort" || tag === "BadUrl" || tag === "FetchError" || tag === "Timeout" || tag === "PoolRejected" || tag === "PoolTimeout";
634
- };
635
- var isAbortError = (e) => typeof e === "object" && e !== null && "name" in e && e.name === "AbortError";
636
- var normalizeHttpError = (e) => {
637
- if (isTaggedHttpError(e)) return e;
638
- if (isAbortError(e)) return { _tag: "Abort" };
639
- return { _tag: "FetchError", message: String(e) };
640
- };
641
- var normalizeHeadersInit = (h) => {
642
- if (!h) return void 0;
643
- if (typeof Headers !== "undefined" && h instanceof Headers) {
644
- const out = {};
645
- h.forEach((v, k) => out[k] = v);
646
- return out;
647
- }
648
- if (Array.isArray(h)) return Object.fromEntries(h);
649
- if (typeof h === "object") return { ...h };
650
- return void 0;
651
- };
652
- var normalizeRequest = (defaultHeaders) => (req0) => {
653
- let req = Object.keys(defaultHeaders).length ? mergeHeadersUnder(defaultHeaders)(req0) : req0;
654
- const initHeaders = normalizeHeadersInit(req0.init?.headers);
655
- if (initHeaders && Object.keys(initHeaders).length) {
656
- req = mergeHeadersUnder(initHeaders)(req);
657
- }
658
- return req;
659
- };
660
- var resolvePositiveTimeout = (value) => {
661
- if (value === void 0 || !Number.isFinite(value)) return void 0;
662
- const n = Math.floor(value);
663
- return n > 0 ? n : void 0;
664
- };
665
- var makeHttpStats = (pool) => {
666
- const stats = {
667
- inFlight: 0,
668
- started: 0,
669
- succeeded: 0,
670
- failed: 0,
671
- aborted: 0,
672
- timedOut: 0,
673
- poolRejected: 0,
674
- poolTimeouts: 0
675
- };
676
- const onStart = () => {
677
- stats.inFlight++;
678
- stats.started++;
679
- };
680
- const onFinish = (finish) => {
681
- if (stats.inFlight > 0) stats.inFlight--;
682
- stats.lastDurationMs = finish.durationMs;
683
- if (finish.outcome === "success") {
684
- stats.succeeded++;
685
- return;
686
- }
687
- if (finish.outcome === "interrupt") {
688
- stats.aborted++;
689
- return;
690
- }
691
- if (finish.outcome === "timeout") {
692
- stats.timedOut++;
693
- return;
694
- }
695
- const err = normalizeHttpError(finish.error);
696
- switch (err._tag) {
697
- case "Abort":
698
- stats.aborted++;
699
- return;
700
- case "Timeout":
701
- stats.timedOut++;
702
- return;
703
- case "PoolRejected":
704
- stats.poolRejected++;
705
- stats.failed++;
706
- return;
707
- case "PoolTimeout":
708
- stats.poolTimeouts++;
709
- stats.failed++;
710
- return;
711
- default:
712
- stats.failed++;
713
- return;
714
- }
715
- };
716
- const snapshot = () => ({
717
- ...stats,
718
- ...pool ? { pool: pool.stats() } : {}
719
- });
720
- return { onStart, onFinish, snapshot };
721
- };
722
- var makePool = (cfg) => cfg.pool === void 0 || cfg.pool === false ? void 0 : new HttpConcurrencyPool(cfg.pool);
723
- var resolveRequestUrl = (req, baseUrl) => {
724
- try {
725
- return new URL(req.url, baseUrl);
726
- } catch {
727
- return { _tag: "BadUrl", message: `URL inv\xE1lida: ${req.url}` };
728
- }
729
- };
730
- var headersOf = (res) => {
731
- const headers = {};
732
- res.headers.forEach((v, k) => headers[k] = v);
733
- return headers;
734
- };
735
- var fetchLabel = (req, url) => `http:${req.method}:${url.origin}`;
736
- var timeoutReason = (req, url, timeoutMs) => ({
737
- _tag: "Timeout",
738
- timeoutMs,
739
- phase: "request",
740
- message: `HTTP ${req.method} ${url.origin} timed out after ${timeoutMs}ms`
741
- });
742
- var linkAbortSignals = (runtimeSignal, requestSignal) => {
743
- if (!requestSignal) return { signal: runtimeSignal, cleanup: () => void 0 };
744
- const controller = new AbortController();
745
- const abort = (source) => {
746
- try {
747
- controller.abort(source.reason);
748
- } catch {
749
- controller.abort();
750
- }
751
- };
752
- const abortFromRuntime = () => abort(runtimeSignal);
753
- const abortFromRequest = () => abort(requestSignal);
754
- if (runtimeSignal.aborted) abortFromRuntime();
755
- else runtimeSignal.addEventListener("abort", abortFromRuntime, { once: true });
756
- if (requestSignal.aborted) abortFromRequest();
757
- else requestSignal.addEventListener("abort", abortFromRequest, { once: true });
758
- return {
759
- signal: controller.signal,
760
- cleanup: () => {
761
- runtimeSignal.removeEventListener("abort", abortFromRuntime);
762
- requestSignal.removeEventListener("abort", abortFromRequest);
763
- }
764
- };
765
- };
766
- function makeHttpStream(cfg = {}) {
767
- const baseUrl = cfg.baseUrl ?? "";
768
- const defaultHeaders = cfg.headers ?? {};
769
- const normalize = normalizeRequest(defaultHeaders);
770
- const pool = makePool(cfg);
771
- const metrics = makeHttpStats(pool);
772
- const run = (req0) => {
773
- const req = normalize(req0);
774
- const url = resolveRequestUrl(req, baseUrl);
775
- if (!(url instanceof URL)) return asyncFail(url);
776
- const timeoutMs = resolvePositiveTimeout(req.timeoutMs ?? cfg.timeoutMs);
777
- return fromPromiseAbortable(
778
- async (signal) => {
779
- let lease;
780
- const linkedSignal = linkAbortSignals(signal, req.init?.signal);
781
- let cleanupTransferredToBody = false;
782
- try {
783
- if (pool) {
784
- const key = resolveHttpPoolKey(pool.keyResolver, req, url);
785
- lease = await pool.acquire(key, linkedSignal.signal);
786
- }
787
- const started = performance.now();
788
- const res = await fetch(url, {
789
- ...req.init ?? {},
790
- method: req.method,
791
- headers: Request.headers.get(req),
792
- body: req.body,
793
- signal: linkedSignal.signal
794
- });
795
- const headers = headersOf(res);
796
- const body = streamFromReadableStream(res.body, normalizeHttpError, {
797
- signal: linkedSignal.signal,
798
- onRelease: linkedSignal.cleanup
799
- });
800
- cleanupTransferredToBody = res.body !== null;
801
- lease?.release();
802
- lease = void 0;
803
- return {
804
- status: res.status,
805
- statusText: res.statusText,
806
- headers,
807
- body,
808
- ms: Math.round(performance.now() - started)
809
- };
810
- } finally {
811
- if (!cleanupTransferredToBody) {
812
- linkedSignal.cleanup();
813
- }
814
- lease?.release();
815
- }
816
- },
817
- normalizeHttpError,
818
- {
819
- label: fetchLabel(req, url),
820
- timeoutMs,
821
- timeoutReason: timeoutMs ? () => timeoutReason(req, url, timeoutMs) : void 0,
822
- onStart: metrics.onStart,
823
- onFinish: metrics.onFinish
824
- }
825
- );
826
- };
827
- return decorateStream(run, metrics.snapshot);
828
- }
829
- function makeHttp(cfg = {}) {
830
- const baseUrl = cfg.baseUrl ?? "";
831
- const defaultHeaders = cfg.headers ?? {};
832
- const normalize = normalizeRequest(defaultHeaders);
833
- const pool = makePool(cfg);
834
- const metrics = makeHttpStats(pool);
835
- const run = (req0) => {
836
- const req = normalize(req0);
837
- const url = resolveRequestUrl(req, baseUrl);
838
- if (!(url instanceof URL)) return asyncFail(url);
839
- const timeoutMs = resolvePositiveTimeout(req.timeoutMs ?? cfg.timeoutMs);
840
- return fromPromiseAbortable(
841
- async (signal) => {
842
- let lease;
843
- const linkedSignal = linkAbortSignals(signal, req.init?.signal);
844
- try {
845
- if (pool) {
846
- const key = resolveHttpPoolKey(pool.keyResolver, req, url);
847
- lease = await pool.acquire(key, linkedSignal.signal);
848
- }
849
- const started = performance.now();
850
- const res = await fetch(url, {
851
- ...req.init ?? {},
852
- method: req.method,
853
- headers: Request.headers.get(req),
854
- body: req.body,
855
- signal: linkedSignal.signal
856
- });
857
- const bodyText = await res.text();
858
- const headers = headersOf(res);
859
- return {
860
- status: res.status,
861
- statusText: res.statusText,
862
- headers,
863
- bodyText,
864
- ms: Math.round(performance.now() - started)
865
- };
866
- } finally {
867
- linkedSignal.cleanup();
868
- lease?.release();
869
- }
870
- },
871
- normalizeHttpError,
872
- {
873
- label: fetchLabel(req, url),
874
- timeoutMs,
875
- timeoutReason: timeoutMs ? () => timeoutReason(req, url, timeoutMs) : void 0,
876
- onStart: metrics.onStart,
877
- onFinish: metrics.onFinish
878
- }
879
- );
880
- };
881
- return decorate(run, metrics.snapshot);
882
- }
883
- var withRetryStream = (p) => (next) => {
884
- const retryOnStatus = p.retryOnStatus ?? defaultRetryOnStatus;
885
- const retryOnError = p.retryOnError ?? defaultRetryOnError;
886
- const retryOnMethods = p.retryOnMethods ?? defaultRetryableMethods;
887
- const maxElapsedMs = normalizeRetryBudget(p.maxElapsedMs);
888
- const run = (req) => {
889
- if (!retryOnMethods.includes(req.method)) return next(req);
890
- const startedAt = performance.now();
891
- const remainingBudget = () => maxElapsedMs === void 0 ? Number.POSITIVE_INFINITY : maxElapsedMs - (performance.now() - startedAt);
892
- const delayWithinBudget = (delayMs) => Math.max(0, Math.min(delayMs, remainingBudget()));
893
- const loop = (attempt) => asyncFold(
894
- next(req),
895
- (e) => {
896
- if (e._tag === "Abort" || e._tag === "BadUrl" || e._tag === "PoolRejected") return asyncFail(e);
897
- const canRetry = attempt < p.maxRetries && retryOnError(e) && remainingBudget() > 0;
898
- if (!canRetry) return asyncFail(e);
899
- const d = delayWithinBudget(backoffDelayMs(attempt, p.baseDelayMs, p.maxDelayMs));
900
- if (d <= 0 && maxElapsedMs !== void 0) return asyncFail(e);
901
- p.onRetry?.({
902
- attempt,
903
- delayMs: d,
904
- error: e,
905
- status: void 0,
906
- url: req.url,
907
- method: req.method,
908
- timestamp: Date.now()
909
- });
910
- return asyncFlatMap(sleep(d), () => loop(attempt + 1));
911
- },
912
- (w) => {
913
- const canRetry = attempt < p.maxRetries && retryOnStatus(w.status) && remainingBudget() > 0;
914
- if (!canRetry) return asyncSucceed(w);
915
- const ra = p.respectRetryAfter === false ? void 0 : retryAfterMs(w.headers);
916
- const rawDelay = ra === void 0 ? backoffDelayMs(attempt, p.baseDelayMs, p.maxDelayMs) : Math.min(ra, p.maxDelayMs);
917
- const d = delayWithinBudget(rawDelay);
918
- if (d <= 0 && maxElapsedMs !== void 0) return asyncSucceed(w);
919
- p.onRetry?.({
920
- attempt,
921
- delayMs: d,
922
- error: void 0,
923
- status: w.status,
924
- url: req.url,
925
- method: req.method,
926
- timestamp: Date.now()
927
- });
928
- return asyncFlatMap(sleep(d), () => loop(attempt + 1));
929
- }
930
- );
931
- return loop(0);
932
- };
933
- return decorateStream(run, next.stats);
934
- };
133
+ } from "../chunk-UB4B6OFY.js";
134
+ import {
135
+ ConfigValidationError,
136
+ Schema,
137
+ SchemaValidationException,
138
+ formatConfigError,
139
+ formatIssues,
140
+ isConfigValidationError,
141
+ isSchema,
142
+ makeSchemaIssue,
143
+ parseConfig,
144
+ s,
145
+ schema,
146
+ validateValue
147
+ } from "../chunk-C3MDXTRZ.js";
148
+ import "../chunk-3RG5ZIWI.js";
935
149
 
936
150
  // src/http/httpClient.ts
937
151
  var resolveFinalUrl = (baseUrl, url) => {
@@ -945,43 +159,24 @@ var createHttpCore = (cfg = {}) => {
945
159
  const wire = makeHttp(cfg);
946
160
  const withPromise = (eff) => withAsyncPromise((e, env) => toPromise(e, env))(eff);
947
161
  const requestRaw = (req) => wire(req);
948
- const splitInit = (init) => {
949
- const { headers, timeoutMs, poolKey, ...rest } = init ?? {};
950
- return {
951
- headers: normalizeHeadersInit(headers),
952
- timeoutMs: typeof timeoutMs === "number" ? timeoutMs : void 0,
953
- poolKey: typeof poolKey === "string" ? poolKey : void 0,
954
- init: rest
955
- };
956
- };
957
- const applyInitHeaders = (headers) => (req) => headers ? mergeHeaders(headers)(req) : req;
958
- const buildReq = (method, url, init, body) => {
959
- const s = splitInit(init);
960
- const req = {
961
- method,
962
- url,
963
- ...body && body.length > 0 ? { body } : {},
964
- ...s.timeoutMs !== void 0 ? { timeoutMs: s.timeoutMs } : {},
965
- ...s.poolKey !== void 0 ? { poolKey: s.poolKey } : {},
966
- init: s.init
967
- };
968
- return applyInitHeaders(s.headers)(req);
969
- };
970
162
  const toResponse = (w, body) => ({
971
163
  status: w.status,
972
164
  statusText: w.statusText,
973
165
  headers: w.headers,
974
166
  body
975
167
  });
168
+ const decodeResponse = (w, validator, schemaName) => asyncFlatMap(
169
+ decodeJsonBodyEffect(w.bodyText, validator, { schemaName }),
170
+ (body) => asyncSucceed(toResponse(w, body))
171
+ );
976
172
  return {
977
173
  cfg,
978
174
  wire,
979
175
  withPromise,
980
176
  requestRaw,
981
- splitInit,
982
- applyInitHeaders,
983
- buildReq,
984
- toResponse
177
+ buildReq: buildHttpRequest,
178
+ toResponse,
179
+ decodeResponse
985
180
  };
986
181
  };
987
182
  function httpClient(cfg = {}) {
@@ -995,20 +190,25 @@ function httpClient(cfg = {}) {
995
190
  const req = core.buildReq("GET", url, init);
996
191
  return core.withPromise(mapTryAsync(requestRaw(req), (w) => core.toResponse(w, w.bodyText)));
997
192
  };
998
- const getJson = (url, init) => {
193
+ const getJson = ((url, init) => {
999
194
  const base = core.buildReq("GET", url, init);
1000
195
  const req = setHeaderIfMissing("accept", "application/json")(base);
1001
- return core.withPromise(mapTryAsync(requestRaw(req), (w) => core.toResponse(w, JSON.parse(w.bodyText))));
1002
- };
1003
- const postJson = (url, bodyObj, init) => {
1004
- const base = core.buildReq("POST", url, init, JSON.stringify(bodyObj ?? {}));
1005
- const req = setHeaderIfMissing("content-type", "application/json")(
1006
- setHeaderIfMissing("accept", "application/json")(base)
1007
- );
196
+ return core.withPromise(asyncFlatMap(requestRaw(req), (w) => core.decodeResponse(w, init?.schema, init?.schemaName)));
197
+ });
198
+ const postJson = ((url, bodyObj, init) => {
1008
199
  return core.withPromise(
1009
- mapTryAsync(requestRaw(req), (w) => core.toResponse(w, JSON.parse(w.bodyText)))
200
+ asyncFlatMap(
201
+ encodeJsonBodyEffect(bodyObj, init?.bodySchema, { schemaName: init?.bodySchemaName }),
202
+ (bodyText) => {
203
+ const base = core.buildReq("POST", url, init, bodyText);
204
+ const req = setHeaderIfMissing("content-type", "application/json")(
205
+ setHeaderIfMissing("accept", "application/json")(base)
206
+ );
207
+ return asyncFlatMap(requestRaw(req), (w) => core.decodeResponse(w, init?.schema, init?.schemaName));
208
+ }
209
+ )
1010
210
  );
1011
- };
211
+ });
1012
212
  return {
1013
213
  request,
1014
214
  get,
@@ -1050,17 +250,28 @@ function httpClientWithMeta(cfg = {}) {
1050
250
  return request(req);
1051
251
  };
1052
252
  const postJson = (url, bodyObj, init) => {
1053
- const base = core.buildReq("POST", url, init, JSON.stringify(bodyObj ?? {}));
1054
- const req = setHeaderIfMissing("content-type", "application/json")(
1055
- setHeaderIfMissing("accept", "application/json")(base)
1056
- );
1057
253
  const startedAt = Date.now();
1058
254
  return core.withPromise(
1059
- mapTryAsync(core.requestRaw(req), (w) => ({
1060
- wire: w,
1061
- response: core.toResponse(w, JSON.parse(w.bodyText)),
1062
- meta: mkMeta(req, w, startedAt)
1063
- }))
255
+ asyncFlatMap(
256
+ encodeJsonBodyEffect(bodyObj, init?.bodySchema, { schemaName: init?.bodySchemaName }),
257
+ (bodyText) => {
258
+ const base = core.buildReq("POST", url, init, bodyText);
259
+ const req = setHeaderIfMissing("content-type", "application/json")(
260
+ setHeaderIfMissing("accept", "application/json")(base)
261
+ );
262
+ return asyncFlatMap(
263
+ core.requestRaw(req),
264
+ (w) => asyncFlatMap(
265
+ core.decodeResponse(w, init?.schema, init?.schemaName),
266
+ (response) => asyncSucceed({
267
+ wire: w,
268
+ response,
269
+ meta: mkMeta(req, w, startedAt)
270
+ })
271
+ )
272
+ );
273
+ }
274
+ )
1064
275
  );
1065
276
  };
1066
277
  const getText = (url, init) => {
@@ -1079,11 +290,17 @@ function httpClientWithMeta(cfg = {}) {
1079
290
  const req = setHeaderIfMissing("accept", "application/json")(base);
1080
291
  const startedAt = Date.now();
1081
292
  return core.withPromise(
1082
- mapTryAsync(core.requestRaw(req), (w) => ({
1083
- wire: w,
1084
- response: core.toResponse(w, JSON.parse(w.bodyText)),
1085
- meta: mkMeta(req, w, startedAt)
1086
- }))
293
+ asyncFlatMap(
294
+ core.requestRaw(req),
295
+ (w) => asyncFlatMap(
296
+ core.decodeResponse(w, init?.schema, init?.schemaName),
297
+ (response) => asyncSucceed({
298
+ wire: w,
299
+ response,
300
+ meta: mkMeta(req, w, startedAt)
301
+ })
302
+ )
303
+ )
1087
304
  );
1088
305
  };
1089
306
  return {
@@ -1124,137 +341,77 @@ function httpClientStream(cfg = {}) {
1124
341
  return make(wire);
1125
342
  }
1126
343
 
1127
- // src/http/effectRunner.ts
1128
- function registerHttpEffect(effect, env, cb) {
1129
- let done = false;
1130
- let currentCancel;
1131
- const finish = (exit) => {
1132
- if (done) return;
1133
- done = true;
1134
- currentCancel = void 0;
1135
- cb(exit);
1136
- };
1137
- const run = (current, cont) => {
1138
- if (done) return;
1139
- switch (current._tag) {
1140
- case "Succeed":
1141
- cont({ _tag: "Success", value: current.value });
1142
- return;
1143
- case "Fail":
1144
- cont({ _tag: "Failure", cause: Cause.fail(current.error) });
1145
- return;
1146
- case "Sync":
1147
- try {
1148
- cont({ _tag: "Success", value: current.thunk(env) });
1149
- } catch (e) {
1150
- cont({ _tag: "Failure", cause: Cause.die(e) });
1151
- }
1152
- return;
1153
- case "Async": {
1154
- const cancel = current.register(env, (exit) => {
1155
- currentCancel = void 0;
1156
- if (done) return;
1157
- cont(exit);
1158
- });
1159
- currentCancel = typeof cancel === "function" ? cancel : void 0;
1160
- return;
1161
- }
1162
- case "FlatMap":
1163
- run(current.first, (exit) => {
1164
- if (done) return;
1165
- if (exit._tag === "Failure") {
1166
- cont(exit);
1167
- return;
1168
- }
1169
- try {
1170
- run(current.andThen(exit.value), cont);
1171
- } catch (e) {
1172
- cont({ _tag: "Failure", cause: Cause.die(e) });
1173
- }
1174
- });
1175
- return;
1176
- case "Fold":
1177
- run(current.first, (exit) => {
1178
- if (done) return;
1179
- try {
1180
- if (exit._tag === "Success") {
1181
- run(current.onSuccess(exit.value), cont);
1182
- return;
1183
- }
1184
- if (exit.cause._tag === "Fail") {
1185
- run(current.onFailure(exit.cause.error), cont);
1186
- return;
1187
- }
1188
- cont(exit);
1189
- } catch (e) {
1190
- cont({ _tag: "Failure", cause: Cause.die(e) });
1191
- }
1192
- });
1193
- return;
1194
- case "Fork":
1195
- cont({ _tag: "Success", value: void 0 });
1196
- return;
1197
- }
1198
- };
1199
- run(effect, finish);
1200
- return () => {
1201
- if (done) return;
1202
- const cancel = currentCancel;
1203
- currentCancel = void 0;
1204
- done = true;
1205
- try {
1206
- cancel?.();
1207
- } finally {
1208
- cb({ _tag: "Failure", cause: Cause.interrupt() });
1209
- }
1210
- };
1211
- }
1212
-
1213
344
  // src/http/circuitBreaker.ts
1214
345
  function withCircuitBreaker(config = {}) {
1215
346
  if (config.perOrigin) {
1216
347
  const breakers = /* @__PURE__ */ new Map();
1217
- const getBreaker = (url) => {
348
+ const getBreaker = (url, onOpen) => {
1218
349
  try {
1219
350
  const origin = new URL(url).origin;
1220
351
  if (!breakers.has(origin)) {
1221
- breakers.set(origin, makeCircuitBreaker(config));
352
+ breakers.set(origin, makeCircuitBreaker({
353
+ ...config,
354
+ onStateChange: (from, to) => {
355
+ config.onStateChange?.(from, to);
356
+ if (to === "open") onOpen();
357
+ }
358
+ }));
1222
359
  }
1223
360
  return breakers.get(origin);
1224
361
  } catch {
1225
362
  if (!breakers.has("__global__")) {
1226
- breakers.set("__global__", makeCircuitBreaker(config));
363
+ breakers.set("__global__", makeCircuitBreaker({
364
+ ...config,
365
+ onStateChange: (from, to) => {
366
+ config.onStateChange?.(from, to);
367
+ if (to === "open") onOpen();
368
+ }
369
+ }));
1227
370
  }
1228
371
  return breakers.get("__global__");
1229
372
  }
1230
373
  };
1231
374
  return (next) => (req) => {
1232
- const breaker2 = getBreaker(req.url);
1233
- return protectLazy(breaker2, next, req);
375
+ const limiter = resolveAdaptiveLimiter(config, next);
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);
1234
380
  };
1235
381
  }
1236
382
  const breaker = makeCircuitBreaker({
1237
383
  ...config,
384
+ onStateChange: (from, to) => {
385
+ config.onStateChange?.(from, to);
386
+ },
1238
387
  isFailure: config.isFailure ?? ((e) => {
1239
388
  const err = e;
1240
389
  return err._tag !== "BadUrl" && err._tag !== "Abort";
1241
390
  })
1242
391
  });
1243
392
  return (next) => (req) => {
1244
- return protectLazy(breaker, 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);
1245
397
  };
1246
398
  }
1247
- function protectLazy(breaker, next, req) {
399
+ function protectLazy(breaker, next, req, onOpen) {
1248
400
  return {
1249
401
  _tag: "Async",
1250
402
  register: (env, cb) => {
1251
403
  let cancel;
1252
404
  try {
405
+ if (breaker.state() === "open") onOpen?.();
406
+ const finish = (exit) => {
407
+ if (breaker.state() === "open") onOpen?.();
408
+ cb(exit);
409
+ };
1253
410
  const deferred = {
1254
411
  _tag: "Async",
1255
412
  register: (innerEnv, innerCb) => registerHttpEffect(next(req), innerEnv, innerCb)
1256
413
  };
1257
- cancel = registerHttpEffect(breaker.protect(deferred), env, cb);
414
+ cancel = registerHttpEffect(breaker.protect(deferred), env, finish);
1258
415
  } catch (error) {
1259
416
  cb({
1260
417
  _tag: "Failure",
@@ -1268,6 +425,23 @@ function protectLazy(breaker, next, req) {
1268
425
  }
1269
426
  };
1270
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
+ }
1271
445
 
1272
446
  // src/http/tracing.ts
1273
447
  function withTracing(tracer) {
@@ -1284,1779 +458,72 @@ function withTracing(tracer) {
1284
458
  };
1285
459
  }
1286
460
 
1287
- // src/http/validation.ts
1288
- function validatedJson(client, validator) {
1289
- return (req) => asyncFold(
1290
- client(req),
1291
- (error) => asyncFail(error),
1292
- (response) => {
1293
- try {
1294
- const parsed = JSON.parse(response.bodyText);
1295
- const result = validator(parsed);
1296
- if (result.success) {
1297
- return asyncSucceed(result.data);
1298
- }
1299
- return asyncFail({
1300
- _tag: "ValidationError",
1301
- message: result.error,
1302
- body: response.bodyText
1303
- });
1304
- } catch (e) {
1305
- return asyncFail({
1306
- _tag: "ValidationError",
1307
- message: `JSON parse error: ${String(e)}`,
1308
- body: response.bodyText
1309
- });
1310
- }
1311
- }
1312
- );
1313
- }
1314
-
1315
- // src/http/body.ts
1316
- function httpBodyByteLength(body) {
1317
- if (body === void 0) return 0;
1318
- if (typeof body === "string") return Buffer.byteLength(body, "utf8");
1319
- if (body instanceof ArrayBuffer) return body.byteLength;
1320
- return body.byteLength;
1321
- }
1322
- function httpBodyToBuffer(body) {
1323
- if (typeof body === "string") return Buffer.from(body, "utf8");
1324
- if (body instanceof ArrayBuffer) return Buffer.from(body);
1325
- return Buffer.from(body);
1326
- }
1327
- function httpBodyKeyPart(body) {
1328
- if (body === void 0) return "";
1329
- if (typeof body === "string") return body;
1330
- return `base64:${httpBodyToBuffer(body).toString("base64")}`;
1331
- }
1332
-
1333
- // src/http/lifecycle/cacheKey.ts
1334
- var SEPARATOR = "\0";
1335
- var DEFAULT_CACHE_RELEVANT_HEADERS = ["accept", "authorization", "content-type"];
1336
- function computeCacheKey(req, baseUrl, extraHeaders = []) {
1337
- const method = req.method.toUpperCase();
1338
- const resolvedUrl = new URL(req.url, baseUrl || void 0).toString();
1339
- const relevantSet = /* @__PURE__ */ new Set([
1340
- ...DEFAULT_CACHE_RELEVANT_HEADERS,
1341
- ...extraHeaders.map((h) => h.toLowerCase())
1342
- ]);
1343
- const headers = req.headers ?? {};
1344
- const sortedHeaders = Object.keys(headers).filter((k) => relevantSet.has(k.toLowerCase())).sort().map((k) => `${k.toLowerCase()}:${headers[k]}`).join(",");
1345
- const body = httpBodyKeyPart(req.body);
1346
- return `${method}${SEPARATOR}${resolvedUrl}${SEPARATOR}${sortedHeaders}${SEPARATOR}${body}`;
1347
- }
1348
- function parseCacheKey(key) {
1349
- const [method, resolvedUrl, headersStr, ...bodyParts] = key.split(SEPARATOR);
1350
- const body = bodyParts.join(SEPARATOR);
1351
- const headers = {};
1352
- if (headersStr) {
1353
- for (const entry of headersStr.split(",")) {
1354
- const colonIdx = entry.indexOf(":");
1355
- if (colonIdx > 0) {
1356
- headers[entry.slice(0, colonIdx)] = entry.slice(colonIdx + 1);
1357
- }
1358
- }
1359
- }
1360
- return { method, resolvedUrl, headers, body };
1361
- }
1362
-
1363
- // src/http/lifecycle/dedupKey.ts
1364
- var HOP_BY_HOP = /* @__PURE__ */ new Set([
1365
- "connection",
1366
- "keep-alive",
1367
- "proxy-authenticate",
1368
- "proxy-authorization",
1369
- "te",
1370
- "trailer",
1371
- "transfer-encoding",
1372
- "upgrade"
1373
- ]);
1374
- var SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
1375
- function computeDedupKey(req, baseUrl) {
1376
- const method = req.method.toUpperCase();
1377
- const resolvedUrl = new URL(req.url, baseUrl || void 0).toString();
1378
- const headers = req.headers ?? {};
1379
- const sortedHeaders = Object.keys(headers).filter((k) => {
1380
- const lower = k.toLowerCase();
1381
- return !HOP_BY_HOP.has(lower) && lower !== "authorization";
1382
- }).sort().map((k) => `${k.toLowerCase()}:${headers[k]}`).join(",");
1383
- const body = httpBodyKeyPart(req.body);
1384
- return `${method}${SEPARATOR}${resolvedUrl}${SEPARATOR}${sortedHeaders}${SEPARATOR}${body}`;
1385
- }
1386
-
1387
- // src/http/lifecycle/dedup.ts
1388
- function safeEmit(onEvent, event) {
1389
- if (!onEvent) return;
1390
- try {
1391
- onEvent(event);
1392
- } catch {
1393
- }
1394
- }
1395
- function withDedup(config) {
1396
- const inFlight = /* @__PURE__ */ new Map();
1397
- const customKeyFn = config?.dedupKey;
1398
- const onEvent = config?.onEvent;
1399
- return (next) => {
1400
- return (req) => {
1401
- if (!SAFE_METHODS.has(req.method.toUpperCase())) {
1402
- return next(req);
1403
- }
1404
- let key;
1405
- if (customKeyFn) {
1406
- try {
1407
- key = customKeyFn(req);
1408
- } catch {
1409
- return next(req);
1410
- }
1411
- if (!key) {
1412
- return next(req);
1413
- }
1414
- } else {
1415
- key = computeDedupKey(req, "");
1416
- }
1417
- return {
1418
- _tag: "Async",
1419
- register: (_env, cb) => {
1420
- const existing = inFlight.get(key);
1421
- let callerDone = false;
1422
- const finishCaller = (exit) => {
1423
- if (callerDone) return;
1424
- callerDone = true;
1425
- cb(exit);
1426
- };
1427
- if (existing) {
1428
- safeEmit(onEvent, { type: "dedup-hit", cacheKey: key });
1429
- existing.refCount++;
1430
- const waiter = {
1431
- resolve: (res) => {
1432
- finishCaller({ _tag: "Success", value: res });
1433
- },
1434
- reject: (err) => {
1435
- finishCaller({ _tag: "Failure", cause: Cause.fail(err) });
1436
- }
1437
- };
1438
- existing.waiters.push(waiter);
1439
- return () => {
1440
- if (callerDone) return;
1441
- existing.refCount--;
1442
- const idx = existing.waiters.indexOf(waiter);
1443
- if (idx >= 0) {
1444
- existing.waiters.splice(idx, 1);
1445
- }
1446
- if (existing.refCount <= 0) {
1447
- inFlight.delete(key);
1448
- safeEmit(onEvent, { type: "dedup-active", active: inFlight.size });
1449
- existing.controller.abort();
1450
- }
1451
- finishCaller({ _tag: "Failure", cause: Cause.interrupt() });
1452
- };
1453
- }
1454
- safeEmit(onEvent, { type: "dedup-miss", cacheKey: key });
1455
- const controller = new AbortController();
1456
- const entry = {
1457
- key,
1458
- controller,
1459
- refCount: 1,
1460
- waiters: []
1461
- };
1462
- inFlight.set(key, entry);
1463
- safeEmit(onEvent, { type: "dedup-active", active: inFlight.size });
1464
- const dedupReq = {
1465
- ...req,
1466
- init: {
1467
- ...req.init ?? {},
1468
- signal: controller.signal
1469
- }
1470
- };
1471
- const innerEffect = next(dedupReq);
1472
- const innerCancel = registerHttpEffect(innerEffect, _env, (exit) => {
1473
- inFlight.delete(key);
1474
- safeEmit(onEvent, { type: "dedup-active", active: inFlight.size });
1475
- if (exit._tag === "Success") {
1476
- resolveAll(entry, exit.value);
1477
- finishCaller(exit);
1478
- return;
1479
- }
1480
- if (exit.cause._tag === "Interrupt") {
1481
- rejectAll(entry, { _tag: "Abort" });
1482
- finishCaller({ _tag: "Failure", cause: Cause.interrupt() });
1483
- return;
1484
- }
1485
- if (exit.cause._tag === "Fail") {
1486
- rejectAll(entry, exit.cause.error);
1487
- finishCaller(exit);
1488
- return;
1489
- }
1490
- const err = { _tag: "FetchError", message: String(exit.cause.defect ?? "unknown") };
1491
- rejectAll(entry, err);
1492
- finishCaller({ _tag: "Failure", cause: Cause.fail(err) });
1493
- });
1494
- return () => {
1495
- if (callerDone) return;
1496
- entry.refCount--;
1497
- if (entry.refCount <= 0) {
1498
- inFlight.delete(key);
1499
- safeEmit(onEvent, { type: "dedup-active", active: inFlight.size });
1500
- controller.abort();
1501
- if (innerCancel) {
1502
- innerCancel();
1503
- }
1504
- }
1505
- finishCaller({ _tag: "Failure", cause: Cause.interrupt() });
1506
- };
1507
- }
1508
- };
1509
- };
1510
- };
1511
- }
1512
- function resolveAll(entry, res) {
1513
- const waiters = entry.waiters.slice();
1514
- for (const w of waiters) {
1515
- w.resolve(res);
1516
- }
1517
- }
1518
- function rejectAll(entry, err) {
1519
- const waiters = entry.waiters.slice();
1520
- for (const w of waiters) {
1521
- w.reject(err);
1522
- }
1523
- }
1524
-
1525
- // src/http/lifecycle/timing.ts
1526
- var now = typeof performance !== "undefined" && typeof performance.now === "function" ? () => performance.now() : () => Date.now();
1527
-
1528
- // src/http/lifecycle/lruCache.ts
1529
- function isExpired(node) {
1530
- return now() - node.storedAt >= node.ttlMs;
1531
- }
1532
- var LRUCache = class {
1533
- map = /* @__PURE__ */ new Map();
1534
- head = null;
1535
- tail = null;
1536
- maxEntries;
1537
- onEvict;
1538
- /**
1539
- * Creates a new LRU cache instance.
1540
- *
1541
- * @param config - Cache configuration options.
1542
- * @param config.maxEntries - Maximum number of entries. Must be >= 1. Default: 1024.
1543
- * @param config.onEvict - Optional eviction callback.
1544
- *
1545
- * @example
1546
- * ```typescript
1547
- * import { LRUCache } from "./lruCache";
1548
- *
1549
- * const cache = new LRUCache<number>({ maxEntries: 50 });
1550
- * ```
1551
- */
1552
- constructor(config = {}) {
1553
- const max = config.maxEntries ?? 1024;
1554
- this.maxEntries = Math.max(1, Math.floor(max));
1555
- this.onEvict = config.onEvict;
1556
- }
1557
- /**
1558
- * Returns the number of entries currently in the cache.
1559
- *
1560
- * @returns The current entry count.
1561
- *
1562
- * @example
1563
- * ```typescript
1564
- * import { LRUCache } from "./lruCache";
1565
- *
1566
- * const cache = new LRUCache<string>();
1567
- * cache.set("a", "1", 10_000);
1568
- * console.log(cache.size); // 1
1569
- * ```
1570
- */
1571
- get size() {
1572
- return this.map.size;
1573
- }
1574
- /**
1575
- * Retrieves a value by key.
1576
- *
1577
- * Returns `undefined` if the key is not found or the entry has expired.
1578
- * On a hit (non-expired), the entry is moved to the head (most recently used).
1579
- * Expired entries are lazily removed on access.
1580
- *
1581
- * @param key - The cache key to look up.
1582
- * @returns The cached value, or `undefined` if not found or expired.
1583
- *
1584
- * @example
1585
- * ```typescript
1586
- * import { LRUCache } from "./lruCache";
1587
- *
1588
- * const cache = new LRUCache<string>();
1589
- * cache.set("greeting", "hello", 30_000);
1590
- * const val = cache.get("greeting"); // "hello"
1591
- * const miss = cache.get("unknown"); // undefined
1592
- * ```
1593
- */
1594
- get(key) {
1595
- const node = this.map.get(key);
1596
- if (!node) return void 0;
1597
- if (isExpired(node)) {
1598
- this.removeNode(node);
1599
- this.map.delete(key);
1600
- return void 0;
1601
- }
1602
- this.moveToHead(node);
1603
- return node.value;
1604
- }
1605
- /**
1606
- * Inserts or updates an entry in the cache.
1607
- *
1608
- * If the key already exists, the value and TTL are updated and the entry is
1609
- * moved to the head. If inserting a new entry causes the cache to exceed
1610
- * `maxEntries` (must be >= 1), the least recently used entry is evicted.
1611
- *
1612
- * @param key - The cache key.
1613
- * @param value - The value to store.
1614
- * @param ttlMs - Time-to-live in milliseconds. The entry expires after this duration.
1615
- *
1616
- * @example
1617
- * ```typescript
1618
- * import { LRUCache } from "./lruCache";
1619
- *
1620
- * const cache = new LRUCache<string>({ maxEntries: 2 });
1621
- * cache.set("a", "alpha", 60_000);
1622
- * cache.set("b", "beta", 60_000);
1623
- * cache.set("c", "gamma", 60_000); // evicts "a" (LRU)
1624
- * ```
1625
- */
1626
- set(key, value, ttlMs) {
1627
- const existing = this.map.get(key);
1628
- if (existing) {
1629
- existing.value = value;
1630
- existing.storedAt = now();
1631
- existing.ttlMs = ttlMs;
1632
- this.moveToHead(existing);
1633
- return;
1634
- }
1635
- const node = {
1636
- key,
1637
- value,
1638
- storedAt: now(),
1639
- ttlMs,
1640
- prev: null,
1641
- next: null
1642
- };
1643
- this.map.set(key, node);
1644
- this.addToHead(node);
1645
- if (this.map.size > this.maxEntries) {
1646
- this.evictTail();
1647
- }
1648
- }
1649
- /**
1650
- * Removes an entry by key.
1651
- *
1652
- * @param key - The cache key to remove.
1653
- * @returns `true` if the entry was found and removed, `false` otherwise.
1654
- *
1655
- * @example
1656
- * ```typescript
1657
- * import { LRUCache } from "./lruCache";
1658
- *
1659
- * const cache = new LRUCache<string>();
1660
- * cache.set("x", "value", 10_000);
1661
- * cache.delete("x"); // true
1662
- * cache.delete("x"); // false (already removed)
1663
- * ```
1664
- */
1665
- delete(key) {
1666
- const node = this.map.get(key);
1667
- if (!node) return false;
1668
- this.removeNode(node);
1669
- this.map.delete(key);
1670
- return true;
1671
- }
1672
- /**
1673
- * Removes all entries from the cache, resetting it to an empty state.
1674
- *
1675
- * @example
1676
- * ```typescript
1677
- * import { LRUCache } from "./lruCache";
1678
- *
1679
- * const cache = new LRUCache<string>();
1680
- * cache.set("a", "1", 10_000);
1681
- * cache.clear();
1682
- * console.log(cache.size); // 0
1683
- * ```
1684
- */
1685
- clear() {
1686
- this.map.clear();
1687
- this.head = null;
1688
- this.tail = null;
1689
- }
1690
- // --- Doubly-linked list operations ---
1691
- /** Adds a node to the head of the list (most recently used position). */
1692
- addToHead(node) {
1693
- node.prev = null;
1694
- node.next = this.head;
1695
- if (this.head) {
1696
- this.head.prev = node;
1697
- }
1698
- this.head = node;
1699
- if (!this.tail) {
1700
- this.tail = node;
1701
- }
1702
- }
1703
- /** Removes a node from its current position in the list. */
1704
- removeNode(node) {
1705
- if (node.prev) {
1706
- node.prev.next = node.next;
1707
- } else {
1708
- this.head = node.next;
1709
- }
1710
- if (node.next) {
1711
- node.next.prev = node.prev;
1712
- } else {
1713
- this.tail = node.prev;
1714
- }
1715
- node.prev = null;
1716
- node.next = null;
1717
- }
1718
- /** Moves an existing node to the head of the list. */
1719
- moveToHead(node) {
1720
- if (this.head === node) return;
1721
- this.removeNode(node);
1722
- this.addToHead(node);
1723
- }
1724
- /** Evicts the tail node (least recently used) and notifies via callback. */
1725
- evictTail() {
1726
- if (!this.tail) return;
1727
- const evicted = this.tail;
1728
- this.removeNode(evicted);
1729
- this.map.delete(evicted.key);
1730
- if (this.onEvict) {
1731
- this.onEvict(1);
1732
- }
1733
- }
1734
- };
1735
-
1736
- // src/http/lifecycle/responseCache.ts
1737
- function clamp2(n, min, max) {
1738
- return Math.max(min, Math.min(max, n));
1739
- }
1740
- function safeEmit2(onEvent, event) {
1741
- if (!onEvent) return;
1742
- try {
1743
- onEvent(event);
1744
- } catch {
1745
- }
1746
- }
1747
- function withCache(config) {
1748
- const ttlSeconds = clamp2(config?.ttlSeconds ?? 60, 1, 86400);
1749
- const ttlMs = ttlSeconds * 1e3;
1750
- const maxEntries = Math.max(1, Math.floor(config?.maxEntries ?? 1024));
1751
- const staleWhileRevalidate = config?.staleWhileRevalidate ?? false;
1752
- const cachePolicy = config?.cachePolicy;
1753
- const cacheRelevantHeaders = config?.cacheRelevantHeaders ?? [];
1754
- const baseUrl = config?.baseUrl ?? "";
1755
- const onEvent = config?.onEvent;
1756
- const onLifecycleEvent = config?.onLifecycleEvent;
1757
- const cache = new LRUCache({
1758
- maxEntries,
1759
- onEvict: (count) => onLifecycleEvent?.({ type: "cache-eviction", count })
1760
- });
1761
- const revalidating = /* @__PURE__ */ new Set();
1762
- const invalidate = (key) => {
1763
- cache.delete(key);
1764
- };
1765
- const clear = () => {
1766
- cache.clear();
1767
- };
1768
- const middleware = (next) => {
1769
- return (req) => {
1770
- const method = req.method.toUpperCase();
1771
- if (!SAFE_METHODS.has(method) && !cachePolicy) {
1772
- return next(req);
1773
- }
1774
- const key = computeCacheKey(req, baseUrl, cacheRelevantHeaders);
1775
- return {
1776
- _tag: "Async",
1777
- register: (env, cb) => {
1778
- const cached = cache.get(key);
1779
- if (cached !== void 0) {
1780
- onLifecycleEvent?.({ type: "cache-hit", cacheKey: key });
1781
- cb({ _tag: "Success", value: cached });
1782
- return;
1783
- }
1784
- onLifecycleEvent?.({ type: "cache-miss", cacheKey: key });
1785
- const innerEffect = next(req);
1786
- return registerHttpEffect(innerEffect, env, (exit) => {
1787
- if (exit._tag === "Success") {
1788
- storeIfCacheable(req, exit.value, key);
1789
- }
1790
- cb(exit);
1791
- });
1792
- }
1793
- };
1794
- };
1795
- };
1796
- function storeIfCacheable(req, res, key) {
1797
- const method = req.method.toUpperCase();
1798
- if (cachePolicy) {
1799
- const result = cachePolicy(req, res);
1800
- if (!result.cacheable) return;
1801
- const entryTtlMs = result.ttlSeconds !== void 0 ? clamp2(result.ttlSeconds, 1, 86400) * 1e3 : ttlMs;
1802
- cache.set(key, res, entryTtlMs);
1803
- return;
1804
- }
1805
- if (!SAFE_METHODS.has(method)) return;
1806
- cache.set(key, res, ttlMs);
1807
- }
1808
- function triggerRevalidation(next, req, key) {
1809
- if (revalidating.has(key)) return;
1810
- revalidating.add(key);
1811
- const innerEffect = next(req);
1812
- const handleExit = (exit) => {
1813
- revalidating.delete(key);
1814
- if (exit._tag === "Success") {
1815
- storeIfCacheable(req, exit.value, key);
1816
- } else {
1817
- safeEmit2(onEvent, {
1818
- type: "revalidation-failure",
1819
- cacheKey: key,
1820
- error: exit.cause._tag === "Fail" ? exit.cause.error : void 0
1821
- });
1822
- }
1823
- };
1824
- registerHttpEffect(innerEffect, void 0, handleExit);
1825
- }
1826
- const expirationMap = /* @__PURE__ */ new Map();
1827
- const swrMiddleware = (next) => {
1828
- return (req) => {
1829
- const method = req.method.toUpperCase();
1830
- if (!SAFE_METHODS.has(method) && !cachePolicy) {
1831
- return next(req);
1832
- }
1833
- const key = computeCacheKey(req, baseUrl, cacheRelevantHeaders);
1834
- return {
1835
- _tag: "Async",
1836
- register: (env, cb) => {
1837
- const cached = cache.get(key);
1838
- if (cached !== void 0) {
1839
- const expiresAt = expirationMap.get(key);
1840
- if (expiresAt !== void 0 && now() < expiresAt) {
1841
- onLifecycleEvent?.({ type: "cache-hit", cacheKey: key });
1842
- cb({ _tag: "Success", value: cached });
1843
- return;
1844
- }
1845
- onLifecycleEvent?.({ type: "cache-hit", cacheKey: key });
1846
- cb({ _tag: "Success", value: cached });
1847
- triggerRevalidation(next, req, key);
1848
- return;
1849
- }
1850
- onLifecycleEvent?.({ type: "cache-miss", cacheKey: key });
1851
- const innerEffect = next(req);
1852
- const handleSuccess = (res) => {
1853
- swrStoreIfCacheable(req, res, key);
1854
- };
1855
- return registerHttpEffect(innerEffect, env, (exit) => {
1856
- if (exit._tag === "Success") {
1857
- handleSuccess(exit.value);
1858
- }
1859
- cb(exit);
1860
- });
1861
- }
1862
- };
1863
- };
1864
- };
1865
- function swrStoreIfCacheable(req, res, key) {
1866
- const method = req.method.toUpperCase();
1867
- let entryTtlMs = ttlMs;
1868
- if (cachePolicy) {
1869
- const result = cachePolicy(req, res);
1870
- if (!result.cacheable) return;
1871
- entryTtlMs = result.ttlSeconds !== void 0 ? clamp2(result.ttlSeconds, 1, 86400) * 1e3 : ttlMs;
1872
- } else if (!SAFE_METHODS.has(method)) {
1873
- return;
1874
- }
1875
- const lruTtl = Number.MAX_SAFE_INTEGER;
1876
- cache.set(key, res, lruTtl);
1877
- expirationMap.set(key, now() + entryTtlMs);
1878
- }
1879
- const swrInvalidate = (key) => {
1880
- cache.delete(key);
1881
- expirationMap.delete(key);
1882
- };
1883
- const swrClear = () => {
1884
- cache.clear();
1885
- expirationMap.clear();
1886
- };
1887
- if (staleWhileRevalidate) {
1888
- return {
1889
- middleware: swrMiddleware,
1890
- invalidate: swrInvalidate,
1891
- clear: swrClear
1892
- };
1893
- }
1894
- return {
1895
- middleware,
1896
- invalidate,
1897
- clear
1898
- };
1899
- }
1900
-
1901
- // src/http/lifecycle/priorityQueue.ts
1902
- function clampPriority(value) {
1903
- if (value === void 0 || !Number.isFinite(value)) return 5;
1904
- return Math.max(0, Math.min(9, Math.trunc(value)));
1905
- }
1906
- function comparePriority(a, b) {
1907
- if (a.priority !== b.priority) return a.priority - b.priority;
1908
- return a.arrivalOrder - b.arrivalOrder;
1909
- }
1910
- var PriorityQueue = class {
1911
- heap = [];
1912
- counter = 0;
1913
- /**
1914
- * Returns the number of entries in the queue (including cancelled entries).
1915
- *
1916
- * @returns The total number of entries in the internal heap.
1917
- *
1918
- * @example
1919
- * ```typescript
1920
- * import { PriorityQueue } from "./priorityQueue";
1921
- *
1922
- * const queue = new PriorityQueue<string>();
1923
- * queue.enqueue("task", 5);
1924
- * console.log(queue.size); // 1
1925
- * ```
1926
- */
1927
- get size() {
1928
- return this.heap.length;
1929
- }
1930
- /** Returns the number of entries that have not been cancelled. */
1931
- get activeSize() {
1932
- return this.heap.reduce((n, entry) => n + (entry.cancelled ? 0 : 1), 0);
1933
- }
1934
- /**
1935
- * Adds a value to the queue with the given priority.
1936
- *
1937
- * Priority is clamped to the valid range [0, 9] via `clampPriority`.
1938
- * Returns the created entry, which can be used for later cancellation
1939
- * by setting `entry.cancelled = true`.
1940
- *
1941
- * @param value - The value to enqueue.
1942
- * @param priority - Priority level, integer from 0 (highest) to 9 (lowest).
1943
- * Clamped to [0, 9]. Defaults to 5 if undefined.
1944
- * @returns The created queue entry.
1945
- *
1946
- * @example
1947
- * ```typescript
1948
- * import { PriorityQueue } from "./priorityQueue";
1949
- *
1950
- * const queue = new PriorityQueue<string>();
1951
- * const entry = queue.enqueue("urgent-task", 0);
1952
- * entry.cancelled = true; // cancel later if needed
1953
- * ```
1954
- */
1955
- enqueue(value, priority) {
1956
- const entry = {
1957
- priority: clampPriority(priority),
1958
- arrivalOrder: this.counter++,
1959
- value,
1960
- cancelled: false
1961
- };
1962
- this.heap.push(entry);
1963
- this.bubbleUp(this.heap.length - 1);
1964
- return entry;
1965
- }
1966
- /**
1967
- * Removes and returns the highest-priority non-cancelled entry.
1968
- *
1969
- * Skips (and discards) any cancelled entries at the top of the heap.
1970
- * Returns `undefined` if the queue is empty or all entries are cancelled.
1971
- *
1972
- * @returns The highest-priority non-cancelled entry, or `undefined` if none available.
1973
- *
1974
- * @example
1975
- * ```typescript
1976
- * import { PriorityQueue } from "./priorityQueue";
1977
- *
1978
- * const queue = new PriorityQueue<string>();
1979
- * queue.enqueue("first", 1);
1980
- * queue.enqueue("second", 2);
1981
- * const entry = queue.dequeue(); // { value: "first", priority: 1, ... }
1982
- * ```
1983
- */
1984
- dequeue() {
1985
- while (this.heap.length > 0) {
1986
- const top = this.heap[0];
1987
- if (top.cancelled) {
1988
- this.removeTop();
1989
- continue;
1990
- }
1991
- this.removeTop();
1992
- return top;
1993
- }
1994
- return void 0;
1995
- }
1996
- /**
1997
- * Returns the highest-priority non-cancelled entry without removing it.
1998
- *
1999
- * Discards cancelled entries at the top of the heap as a side effect.
2000
- * Returns `undefined` if the queue is empty or all entries are cancelled.
2001
- *
2002
- * @returns The highest-priority non-cancelled entry, or `undefined` if none available.
2003
- *
2004
- * @example
2005
- * ```typescript
2006
- * import { PriorityQueue } from "./priorityQueue";
2007
- *
2008
- * const queue = new PriorityQueue<string>();
2009
- * queue.enqueue("task", 3);
2010
- * const top = queue.peek(); // { value: "task", priority: 3, ... }
2011
- * console.log(queue.size); // 1 (not removed)
2012
- * ```
2013
- */
2014
- peek() {
2015
- while (this.heap.length > 0) {
2016
- const top = this.heap[0];
2017
- if (top.cancelled) {
2018
- this.removeTop();
2019
- continue;
2020
- }
2021
- return top;
2022
- }
2023
- return void 0;
2024
- }
2025
- /**
2026
- * Marks all entries matching the predicate as cancelled (lazy removal).
2027
- *
2028
- * Cancelled entries are skipped on subsequent dequeue/peek calls.
2029
- * This does not immediately remove entries from the heap; they are
2030
- * discarded lazily when encountered at the top during dequeue or peek.
2031
- *
2032
- * @param predicate - A function that returns `true` for entries to cancel.
2033
- * @returns The number of entries marked as cancelled.
2034
- *
2035
- * @example
2036
- * ```typescript
2037
- * import { PriorityQueue } from "./priorityQueue";
2038
- *
2039
- * const queue = new PriorityQueue<string>();
2040
- * queue.enqueue("a", 1);
2041
- * queue.enqueue("b", 2);
2042
- * const removed = queue.remove((e) => e.value === "a"); // 1
2043
- * ```
2044
- */
2045
- remove(predicate) {
2046
- let count = 0;
2047
- for (const entry of this.heap) {
2048
- if (!entry.cancelled && predicate(entry)) {
2049
- entry.cancelled = true;
2050
- count++;
2051
- }
2052
- }
2053
- return count;
2054
- }
2055
- // --- Binary heap operations ---
2056
- /** Removes the top element from the heap and restores heap property. */
2057
- removeTop() {
2058
- const last = this.heap.pop();
2059
- if (this.heap.length > 0 && last !== void 0) {
2060
- this.heap[0] = last;
2061
- this.sinkDown(0);
2062
- }
2063
- }
2064
- /** Moves an element up the heap until the heap property is restored. */
2065
- bubbleUp(index) {
2066
- while (index > 0) {
2067
- const parentIndex = index - 1 >>> 1;
2068
- const current = this.heap[index];
2069
- const parent = this.heap[parentIndex];
2070
- if (comparePriority(current, parent) >= 0) break;
2071
- this.heap[index] = parent;
2072
- this.heap[parentIndex] = current;
2073
- index = parentIndex;
2074
- }
2075
- }
2076
- /** Moves an element down the heap until the heap property is restored. */
2077
- sinkDown(index) {
2078
- const length = this.heap.length;
2079
- while (true) {
2080
- const leftIndex = 2 * index + 1;
2081
- const rightIndex = 2 * index + 2;
2082
- let smallest = index;
2083
- if (leftIndex < length && comparePriority(this.heap[leftIndex], this.heap[smallest]) < 0) {
2084
- smallest = leftIndex;
2085
- }
2086
- if (rightIndex < length && comparePriority(this.heap[rightIndex], this.heap[smallest]) < 0) {
2087
- smallest = rightIndex;
2088
- }
2089
- if (smallest === index) break;
2090
- const temp = this.heap[index];
2091
- this.heap[index] = this.heap[smallest];
2092
- this.heap[smallest] = temp;
2093
- index = smallest;
2094
- }
2095
- }
2096
- };
2097
-
2098
- // src/http/lifecycle/priorityScheduler.ts
2099
- var DEFAULT_CONCURRENCY2 = 32;
2100
- function extractPriority(req) {
2101
- const fromReq = req.priority;
2102
- if (fromReq !== void 0) return clampPriority(fromReq);
2103
- const fromInit = req.init?.priority;
2104
- if (fromInit !== void 0) return clampPriority(fromInit);
2105
- return 5;
2106
- }
2107
- function safeEmit3(onEvent, event) {
2108
- if (!onEvent) return;
2109
- try {
2110
- onEvent(event);
2111
- } catch {
2112
- }
2113
- }
2114
- function withPriority(config) {
2115
- const concurrency = resolveConcurrency(config?.concurrency);
2116
- const queueTimeoutMs = resolveQueueTimeout(config?.queueTimeoutMs);
2117
- const onEvent = config?.onEvent;
2118
- const queue = new PriorityQueue();
2119
- let inFlight = 0;
2120
- const queueDepth = () => {
2121
- return queue.activeSize;
2122
- };
2123
- const middleware = (next) => {
2124
- return (req) => {
2125
- const priority = extractPriority(req);
2126
- return {
2127
- _tag: "Async",
2128
- register: (env, cb) => {
2129
- if (inFlight < concurrency) {
2130
- return dispatchRequest(next, req, env, cb);
2131
- }
2132
- const queued = { req, env, cb, signal: getSignal(req) };
2133
- const entry = queue.enqueue(queued, priority);
2134
- safeEmit3(onEvent, { type: "queue-enqueue", priority });
2135
- if (queueTimeoutMs !== void 0) {
2136
- queued.timer = setTimeout(() => {
2137
- entry.cancelled = true;
2138
- queued.timer = void 0;
2139
- cb({
2140
- _tag: "Failure",
2141
- cause: Cause.fail({
2142
- _tag: "PoolTimeout",
2143
- key: "priority",
2144
- timeoutMs: queueTimeoutMs,
2145
- message: `Priority queue did not dispatch within ${queueTimeoutMs}ms`
2146
- })
2147
- });
2148
- }, queueTimeoutMs);
2149
- }
2150
- const signal = queued.signal;
2151
- let abortHandler;
2152
- if (signal && !signal.aborted) {
2153
- abortHandler = () => {
2154
- entry.cancelled = true;
2155
- if (queued.timer !== void 0) {
2156
- clearTimeout(queued.timer);
2157
- queued.timer = void 0;
2158
- }
2159
- cb({ _tag: "Failure", cause: Cause.fail({ _tag: "Abort" }) });
2160
- };
2161
- signal.addEventListener("abort", abortHandler, { once: true });
2162
- } else if (signal?.aborted) {
2163
- entry.cancelled = true;
2164
- cb({ _tag: "Failure", cause: Cause.fail({ _tag: "Abort" }) });
2165
- return;
2166
- }
2167
- return () => {
2168
- entry.cancelled = true;
2169
- if (queued.timer !== void 0) {
2170
- clearTimeout(queued.timer);
2171
- queued.timer = void 0;
2172
- }
2173
- if (abortHandler && signal) {
2174
- signal.removeEventListener("abort", abortHandler);
2175
- }
2176
- cb({ _tag: "Failure", cause: Cause.interrupt() });
2177
- };
2178
- }
2179
- };
2180
- };
2181
- function dispatchRequest(downstream, req, env, cb) {
2182
- inFlight++;
2183
- safeEmit3(onEvent, { type: "queue-dispatch", priority: extractPriority(req) });
2184
- const innerEffect = downstream(req);
2185
- let completed = false;
2186
- const onComplete = (exit) => {
2187
- if (completed) return;
2188
- completed = true;
2189
- inFlight--;
2190
- cb(exit);
2191
- drainNext(downstream);
2192
- };
2193
- const innerCancel = registerHttpEffect(innerEffect, env, onComplete);
2194
- return () => {
2195
- innerCancel();
2196
- };
2197
- }
2198
- function drainNext(downstream) {
2199
- while (inFlight < concurrency) {
2200
- const entry = queue.dequeue();
2201
- if (!entry) break;
2202
- if (entry.cancelled) continue;
2203
- const queued = entry.value;
2204
- if (queued.timer !== void 0) {
2205
- clearTimeout(queued.timer);
2206
- queued.timer = void 0;
2207
- }
2208
- if (queued.signal?.aborted) {
2209
- queued.cb({ _tag: "Failure", cause: Cause.fail({ _tag: "Abort" }) });
2210
- continue;
2211
- }
2212
- dispatchRequest(downstream, queued.req, queued.env, queued.cb);
2213
- }
2214
- }
2215
- };
2216
- return Object.assign(middleware, { queueDepth });
2217
- }
2218
- function resolveConcurrency(value) {
2219
- if (value === void 0 || !Number.isFinite(value)) return DEFAULT_CONCURRENCY2;
2220
- return Math.max(1, Math.floor(value));
2221
- }
2222
- function resolveQueueTimeout(value) {
2223
- if (value === void 0 || !Number.isFinite(value)) return void 0;
2224
- const n = Math.floor(value);
2225
- return n > 0 ? n : void 0;
2226
- }
2227
- function getSignal(req) {
2228
- return req.init?.signal;
2229
- }
2230
-
2231
- // src/http/lifecycle/stats.ts
2232
- var LifecycleStatsTracker = class {
2233
- _cacheHits = 0;
2234
- _cacheMisses = 0;
2235
- _cacheEvictions = 0;
2236
- _dedupHits = 0;
2237
- _dedupActive = 0;
2238
- _queueDepth = 0;
2239
- _requestsStarted = 0;
2240
- _requestsCompleted = 0;
2241
- _requestsFailed = 0;
2242
- _retries = 0;
2243
- _onEvent;
2244
- _wireStats;
2245
- /**
2246
- * Creates a new lifecycle stats tracker.
2247
- *
2248
- * @param opts - Configuration options for the tracker.
2249
- * @param opts.onEvent - Optional callback invoked on each lifecycle event.
2250
- * Errors thrown by this callback are silently discarded.
2251
- * @param opts.wireStats - A function returning the current wire-level HTTP client stats.
2252
- *
2253
- * @example
2254
- * ```typescript
2255
- * import { LifecycleStatsTracker } from "./stats";
2256
- *
2257
- * const tracker = new LifecycleStatsTracker({
2258
- * wireStats: () => ({ requestCount: 0, errorCount: 0 }),
2259
- * });
2260
- * ```
2261
- */
2262
- constructor(opts) {
2263
- this._onEvent = opts.onEvent;
2264
- this._wireStats = opts.wireStats;
2265
- }
2266
- // --- Increment methods ---
2267
- /**
2268
- * Records a cache hit. Increments the cache hit counter by 1.
2269
- *
2270
- * @example
2271
- * ```typescript
2272
- * import { LifecycleStatsTracker } from "./stats";
2273
- *
2274
- * const tracker = new LifecycleStatsTracker({ wireStats: () => ({ requestCount: 0, errorCount: 0 }) });
2275
- * tracker.cacheHit();
2276
- * ```
2277
- */
2278
- cacheHit() {
2279
- this._cacheHits++;
2280
- }
2281
- /**
2282
- * Records a cache miss. Increments the cache miss counter by 1.
2283
- *
2284
- * @example
2285
- * ```typescript
2286
- * import { LifecycleStatsTracker } from "./stats";
2287
- *
2288
- * const tracker = new LifecycleStatsTracker({ wireStats: () => ({ requestCount: 0, errorCount: 0 }) });
2289
- * tracker.cacheMiss();
2290
- * ```
2291
- */
2292
- cacheMiss() {
2293
- this._cacheMisses++;
2294
- }
2295
- /**
2296
- * Records a cache eviction. Increments the cache eviction counter by 1.
2297
- *
2298
- * @example
2299
- * ```typescript
2300
- * import { LifecycleStatsTracker } from "./stats";
2301
- *
2302
- * const tracker = new LifecycleStatsTracker({ wireStats: () => ({ requestCount: 0, errorCount: 0 }) });
2303
- * tracker.cacheEviction();
2304
- * ```
2305
- */
2306
- cacheEviction() {
2307
- this._cacheEvictions++;
2308
- }
2309
- /**
2310
- * Records a dedup hit (a request that joined an in-flight duplicate).
2311
- * Increments the dedup hit counter by 1.
2312
- *
2313
- * @example
2314
- * ```typescript
2315
- * import { LifecycleStatsTracker } from "./stats";
2316
- *
2317
- * const tracker = new LifecycleStatsTracker({ wireStats: () => ({ requestCount: 0, errorCount: 0 }) });
2318
- * tracker.dedupHit();
2319
- * ```
2320
- */
2321
- dedupHit() {
2322
- this._dedupHits++;
2323
- }
2324
- /**
2325
- * Sets the current number of active dedup groups.
2326
- *
2327
- * @param n - The current count of active dedup groups. Must be >= 0.
2328
- *
2329
- * @example
2330
- * ```typescript
2331
- * import { LifecycleStatsTracker } from "./stats";
2332
- *
2333
- * const tracker = new LifecycleStatsTracker({ wireStats: () => ({ requestCount: 0, errorCount: 0 }) });
2334
- * tracker.setDedupActive(3);
2335
- * ```
2336
- */
2337
- setDedupActive(n) {
2338
- this._dedupActive = n;
2339
- }
2340
- /**
2341
- * Sets the current priority queue depth.
2342
- *
2343
- * @param n - The current number of entries in the priority queue. Must be >= 0.
2344
- *
2345
- * @example
2346
- * ```typescript
2347
- * import { LifecycleStatsTracker } from "./stats";
2348
- *
2349
- * const tracker = new LifecycleStatsTracker({ wireStats: () => ({ requestCount: 0, errorCount: 0 }) });
2350
- * tracker.setQueueDepth(5);
2351
- * ```
2352
- */
2353
- setQueueDepth(n) {
2354
- this._queueDepth = n;
2355
- }
2356
- /**
2357
- * Records that a request has started. Increments the requests started counter by 1.
2358
- *
2359
- * @example
2360
- * ```typescript
2361
- * import { LifecycleStatsTracker } from "./stats";
2362
- *
2363
- * const tracker = new LifecycleStatsTracker({ wireStats: () => ({ requestCount: 0, errorCount: 0 }) });
2364
- * tracker.requestStarted();
2365
- * ```
2366
- */
2367
- requestStarted() {
2368
- this._requestsStarted++;
2369
- }
2370
- /**
2371
- * Records that a request has completed successfully.
2372
- * Increments the requests completed counter by 1.
2373
- *
2374
- * @example
2375
- * ```typescript
2376
- * import { LifecycleStatsTracker } from "./stats";
2377
- *
2378
- * const tracker = new LifecycleStatsTracker({ wireStats: () => ({ requestCount: 0, errorCount: 0 }) });
2379
- * tracker.requestCompleted();
2380
- * ```
2381
- */
2382
- requestCompleted() {
2383
- this._requestsCompleted++;
2384
- }
2385
- /**
2386
- * Records that a request has failed.
2387
- * Increments the requests failed counter by 1.
2388
- *
2389
- * @example
2390
- * ```typescript
2391
- * import { LifecycleStatsTracker } from "./stats";
2392
- *
2393
- * const tracker = new LifecycleStatsTracker({ wireStats: () => ({ requestCount: 0, errorCount: 0 }) });
2394
- * tracker.requestFailed();
2395
- * ```
2396
- */
2397
- requestFailed() {
2398
- this._requestsFailed++;
2399
- }
2400
- retry() {
2401
- this._retries++;
2402
- }
2403
- // --- Event emission ---
2404
- /**
2405
- * Emits a lifecycle event to the registered `onEvent` callback.
2406
- *
2407
- * The callback is wrapped in a try-catch so that any exception thrown by
2408
- * the callback is silently discarded and request processing continues
2409
- * unaffected. If no `onEvent` callback was provided, this is a no-op.
2410
- *
2411
- * @param type - The lifecycle event type to emit (e.g., `"cache-hit"`, `"request-start"`).
2412
- * @param extra - Optional additional event data.
2413
- *
2414
- * @example
2415
- * ```typescript
2416
- * import { LifecycleStatsTracker } from "./stats";
2417
- *
2418
- * const tracker = new LifecycleStatsTracker({
2419
- * onEvent: (event) => console.log(event.type, event.timestamp),
2420
- * wireStats: () => ({ requestCount: 0, errorCount: 0 }),
2421
- * });
2422
- * tracker.emit("cache-hit", { cacheKey: "GET|/api/users" });
2423
- * ```
2424
- */
2425
- emit(type, extra) {
2426
- if (!this._onEvent) return;
2427
- try {
2428
- const event = {
2429
- type,
2430
- timestamp: now(),
2431
- ...extra
2432
- };
2433
- this._onEvent(event);
2434
- } catch {
2435
- }
2436
- }
2437
- // --- Snapshot ---
2438
- /**
2439
- * Returns a frozen snapshot of all lifecycle statistics including wire stats.
2440
- *
2441
- * The returned object is frozen (immutable) and represents a point-in-time
2442
- * view of all counters and gauges.
2443
- *
2444
- * @returns A frozen `LifecycleStats` object containing all current statistics.
2445
- *
2446
- * @example
2447
- * ```typescript
2448
- * import { LifecycleStatsTracker } from "./stats";
2449
- *
2450
- * const tracker = new LifecycleStatsTracker({
2451
- * wireStats: () => ({ requestCount: 10, errorCount: 1 }),
2452
- * });
2453
- * tracker.cacheHit();
2454
- * tracker.cacheHit();
2455
- * const stats = tracker.snapshot();
2456
- * console.log(stats.cacheHits); // 2
2457
- * ```
2458
- */
2459
- snapshot() {
2460
- return Object.freeze({
2461
- cacheHits: this._cacheHits,
2462
- cacheMisses: this._cacheMisses,
2463
- cacheEvictions: this._cacheEvictions,
2464
- dedupHits: this._dedupHits,
2465
- dedupActive: this._dedupActive,
2466
- queueDepth: this._queueDepth,
2467
- requestsStarted: this._requestsStarted,
2468
- requestsCompleted: this._requestsCompleted,
2469
- requestsFailed: this._requestsFailed,
2470
- retries: this._retries,
2471
- wire: this._wireStats()
2472
- });
2473
- }
2474
- };
2475
-
2476
- // src/http/lifecycle/lifecycleClient.ts
2477
- function validateGlobals() {
2478
- if (typeof fetch === "undefined") {
2479
- throw new Error(
2480
- "makeLifecycleClient: global `fetch` is not available. Ensure you are running in an environment with fetch support (Node.js 18+ or modern browser)."
2481
- );
2482
- }
2483
- if (typeof AbortController === "undefined") {
2484
- throw new Error(
2485
- "makeLifecycleClient: global `AbortController` is not available. Ensure you are running in an environment with AbortController support (Node.js 15+ or modern browser)."
2486
- );
2487
- }
2488
- }
2489
- function extractWireConfig(config) {
2490
- const { dedup, cache, priority, retry, onEvent, ...wireConfig } = config;
2491
- return wireConfig;
2492
- }
2493
- function makeLifecycleClient(config = {}) {
2494
- validateGlobals();
2495
- const wireConfig = extractWireConfig(config);
2496
- const wireClient = makeHttp(wireConfig);
2497
- const activeControllers = /* @__PURE__ */ new Set();
2498
- const tracker = new LifecycleStatsTracker({
2499
- onEvent: config.onEvent,
2500
- wireStats: wireClient.stats
2501
- });
2502
- const hasDedup = config.dedup !== void 0 && config.dedup !== false;
2503
- const hasCache = config.cache !== void 0 && config.cache !== false;
2504
- const hasPriority = config.priority !== void 0 && config.priority !== false;
2505
- const hasRetry = config.retry !== void 0 && config.retry !== false;
2506
- if (!hasDedup && !hasCache && !hasPriority && !hasRetry) {
2507
- return buildLifecycleClient(wireClient, tracker, {
2508
- cacheInvalidate: noopInvalidate,
2509
- cacheClear: noopClear,
2510
- cancelAll: () => cancelControllers(activeControllers),
2511
- activeControllers
2512
- });
2513
- }
2514
- let priorityMiddleware;
2515
- if (hasPriority) {
2516
- const priorityConfig = config.priority;
2517
- priorityMiddleware = withPriority({
2518
- ...priorityConfig,
2519
- onEvent: (event) => {
2520
- tracker.setQueueDepth(priorityMiddleware?.queueDepth() ?? 0);
2521
- tracker.emit(event.type, { priority: event.priority });
2522
- }
2523
- });
2524
- }
2525
- let cacheLayer;
2526
- if (hasCache) {
2527
- const cacheConfig = config.cache;
2528
- cacheLayer = withCache({
2529
- ...cacheConfig,
2530
- baseUrl: wireConfig.baseUrl,
2531
- onLifecycleEvent: (event) => {
2532
- if (event.type === "cache-hit") tracker.cacheHit();
2533
- if (event.type === "cache-miss") tracker.cacheMiss();
2534
- if (event.type === "cache-eviction") tracker.cacheEviction();
2535
- if (event.type === "cache-hit" || event.type === "cache-miss") {
2536
- tracker.emit(event.type, { cacheKey: event.cacheKey });
2537
- }
2538
- }
2539
- });
2540
- }
2541
- let dedupMiddleware;
2542
- if (hasDedup) {
2543
- const dedupConfig = config.dedup;
2544
- const baseUrl = wireConfig.baseUrl ?? "";
2545
- const effectiveDedupConfig = dedupConfig.dedupKey || !baseUrl ? dedupConfig : { ...dedupConfig, dedupKey: (req) => computeDedupKey(req, baseUrl) };
2546
- dedupMiddleware = withDedup({
2547
- ...effectiveDedupConfig,
2548
- onEvent: (event) => {
2549
- if (event.type === "dedup-hit") tracker.dedupHit();
2550
- if (event.type === "dedup-active") {
2551
- tracker.setDedupActive(event.active ?? 0);
2552
- return;
2553
- }
2554
- tracker.emit(event.type, { cacheKey: event.cacheKey });
2555
- }
2556
- });
2557
- }
2558
- let composedFn = wireClient;
2559
- if (priorityMiddleware) {
2560
- composedFn = priorityMiddleware(composedFn);
2561
- }
2562
- if (hasRetry) {
2563
- const retryConfig = config.retry;
2564
- composedFn = withRetry({
2565
- ...retryConfig,
2566
- onRetry: (event) => {
2567
- tracker.retry();
2568
- tracker.emit("retry", {
2569
- attempt: event.attempt,
2570
- delayMs: event.delayMs,
2571
- status: event.status,
2572
- errorTag: event.error?._tag
2573
- });
2574
- retryConfig.onRetry?.(event);
2575
- }
2576
- })(composedFn);
2577
- }
2578
- if (cacheLayer) {
2579
- composedFn = cacheLayer.middleware(composedFn);
2580
- }
2581
- if (dedupMiddleware) {
2582
- composedFn = dedupMiddleware(composedFn);
2583
- }
2584
- return buildLifecycleClient(composedFn, tracker, {
2585
- cacheInvalidate: cacheLayer?.invalidate ?? noopInvalidate,
2586
- cacheClear: cacheLayer?.clear ?? noopClear,
2587
- cancelAll: () => cancelControllers(activeControllers),
2588
- activeControllers,
2589
- queueDepth: priorityMiddleware?.queueDepth
2590
- });
2591
- }
2592
- function makeHttpClient(config = {}) {
2593
- return makeLifecycleClient(config);
2594
- }
2595
- function noopInvalidate(_key) {
2596
- }
2597
- function noopClear() {
2598
- }
2599
- function buildLifecycleClient(fn, tracker, internals) {
2600
- const client = (req) => trackRequest(fn, req, tracker, internals);
2601
- const stats = () => {
2602
- tracker.setQueueDepth(internals.queueDepth?.() ?? 0);
2603
- return tracker.snapshot();
2604
- };
2605
- const withMw = (mw) => {
2606
- const wrappedFn = mw(fn);
2607
- return buildLifecycleClient(wrappedFn, tracker, internals);
2608
- };
2609
- const lifecycleClient = Object.assign(client, {
2610
- with: withMw,
2611
- stats,
2612
- cancelAll: internals.cancelAll,
2613
- cache: {
2614
- invalidate: internals.cacheInvalidate,
2615
- clear: internals.cacheClear
2616
- }
2617
- });
2618
- return lifecycleClient;
2619
- }
2620
- function cancelControllers(activeControllers) {
2621
- for (const controller of Array.from(activeControllers)) {
2622
- try {
2623
- controller.abort();
2624
- } catch {
2625
- }
2626
- }
2627
- return asyncSucceed(void 0);
2628
- }
2629
- function trackRequest(fn, req, tracker, internals) {
2630
- return {
2631
- _tag: "Async",
2632
- register: (env, cb) => {
2633
- const controller = new AbortController();
2634
- const previousSignal = req.init?.signal;
2635
- let done = false;
2636
- let abortedByPreviousSignal = false;
2637
- let cancelInner;
2638
- const abortFromPrevious = () => {
2639
- abortedByPreviousSignal = true;
2640
- try {
2641
- controller.abort(previousSignal?.reason);
2642
- } catch {
2643
- controller.abort();
2644
- }
2645
- cancelInner?.();
2646
- };
2647
- if (previousSignal?.aborted) {
2648
- abortFromPrevious();
2649
- } else {
2650
- previousSignal?.addEventListener("abort", abortFromPrevious, { once: true });
2651
- }
2652
- internals.activeControllers.add(controller);
2653
- tracker.requestStarted();
2654
- tracker.emit("request-start");
2655
- const finish = (exit0) => {
2656
- if (done) return;
2657
- done = true;
2658
- const exit = abortedByPreviousSignal && exit0._tag === "Failure" && exit0.cause._tag === "Interrupt" ? { _tag: "Failure", cause: Cause.fail({ _tag: "Abort" }) } : exit0;
2659
- previousSignal?.removeEventListener("abort", abortFromPrevious);
2660
- internals.activeControllers.delete(controller);
2661
- if (exit._tag === "Success") {
2662
- tracker.requestCompleted();
2663
- } else {
2664
- tracker.requestFailed();
2665
- }
2666
- tracker.emit("request-end");
2667
- cb(exit);
2668
- };
2669
- const trackedReq = {
2670
- ...req,
2671
- init: {
2672
- ...req.init ?? {},
2673
- signal: controller.signal
2674
- }
2675
- };
2676
- try {
2677
- cancelInner = registerHttpEffect(fn(trackedReq), env, finish);
2678
- } catch (error) {
2679
- finish({
2680
- _tag: "Failure",
2681
- cause: Cause.fail({ _tag: "FetchError", message: String(error) })
2682
- });
2683
- }
2684
- return () => {
2685
- if (done) return;
2686
- try {
2687
- controller.abort();
2688
- } catch {
2689
- }
2690
- if (cancelInner) {
2691
- cancelInner();
2692
- } else {
2693
- finish({ _tag: "Failure", cause: Cause.interrupt() });
2694
- }
2695
- };
2696
- }
2697
- };
2698
- }
2699
-
2700
461
  // src/http/lifecycle/middleware.ts
2701
462
  function withAuth(tokenProvider) {
2702
463
  return (next) => {
2703
- return (req) => {
2704
- return asyncFlatMap(tokenProvider(), (token) => {
2705
- const modifiedReq = {
2706
- ...req,
2707
- headers: {
2708
- ...req.headers ?? {},
2709
- Authorization: `Bearer ${token}`
2710
- }
2711
- };
2712
- return next(modifiedReq);
2713
- });
2714
- };
2715
- };
2716
- }
2717
- function withLogging(logger) {
2718
- return (next) => {
2719
- return (req) => {
2720
- try {
2721
- logger({ phase: "request", req });
2722
- } catch {
2723
- }
2724
- const startedAt = now();
2725
- return asyncFold(
2726
- next(req),
2727
- (error) => {
2728
- const durationMs = Math.round(now() - startedAt);
2729
- try {
2730
- logger({ phase: "error", req, error, durationMs });
2731
- } catch {
2732
- }
2733
- return asyncFail(error);
2734
- },
2735
- (res) => {
2736
- const durationMs = Math.round(now() - startedAt);
2737
- try {
2738
- logger({ phase: "response", req, res, durationMs });
2739
- } catch {
2740
- }
2741
- return asyncSucceed(res);
2742
- }
2743
- );
2744
- };
2745
- };
2746
- }
2747
- function withResponseTransform(fn) {
2748
- return (next) => {
2749
- return (req) => {
2750
- return asyncFold(
2751
- next(req),
2752
- (error) => {
2753
- return asyncFail(error);
2754
- },
2755
- (res) => {
2756
- try {
2757
- const transformed = fn(res, req);
2758
- return asyncSucceed(transformed);
2759
- } catch (e) {
2760
- return asyncFail({ _tag: "FetchError", message: String(e) });
2761
- }
2762
- }
2763
- );
2764
- };
2765
- };
2766
- }
2767
-
2768
- // src/http/compression/decompressor.ts
2769
- import zlib from "zlib";
2770
-
2771
- // src/http/compression/environment.ts
2772
- function isNodeEnvironment() {
2773
- return typeof process !== "undefined" && process.versions != null && process.versions.node != null;
2774
- }
2775
-
2776
- // src/http/compression/decompressorNode.ts
2777
- function createNodeDecompressor(zlib2) {
2778
- return {
2779
- isPassthrough: false,
2780
- decompress(data, encoding) {
2781
- try {
2782
- const input = Buffer.isBuffer(data) ? data : Buffer.from(data);
2783
- let result;
2784
- switch (encoding) {
2785
- case "gzip":
2786
- result = zlib2.gunzipSync(input);
2787
- break;
2788
- case "br":
2789
- result = zlib2.brotliDecompressSync(input);
2790
- break;
2791
- case "deflate":
2792
- result = zlib2.inflateSync(input);
2793
- break;
2794
- default:
2795
- return { ok: false, error: `Unsupported encoding: ${encoding}` };
2796
- }
2797
- return { ok: true, data: result };
2798
- } catch (err) {
2799
- const message = err instanceof Error ? err.message : String(err);
2800
- return { ok: false, error: message };
2801
- }
2802
- }
2803
- };
2804
- }
2805
-
2806
- // src/http/compression/decompressor.noop.ts
2807
- function createNoopDecompressor() {
2808
- return {
2809
- isPassthrough: true,
2810
- decompress(data, _encoding) {
2811
- const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
2812
- return { ok: true, data: buf };
2813
- }
2814
- };
2815
- }
2816
-
2817
- // src/http/compression/decompressor.ts
2818
- function createDecompressor() {
2819
- if (isNodeEnvironment()) {
2820
- return createNodeDecompressor(zlib);
2821
- }
2822
- return createNoopDecompressor();
2823
- }
2824
-
2825
- // src/http/compression/types.ts
2826
- var SUPPORTED_ENCODINGS = ["br", "gzip", "deflate"];
2827
- function emptyStats2() {
2828
- return {
2829
- decompressed: { gzip: 0, br: 0, deflate: 0 },
2830
- compressedBytes: 0,
2831
- decompressedBytes: 0,
2832
- passthroughCount: 0,
2833
- errorCount: 0,
2834
- unsupportedEncodingCount: 0
2835
- };
2836
- }
2837
- function emptyRequestCompressionStats() {
2838
- return {
2839
- compressedCount: 0,
2840
- skippedCount: 0,
2841
- errorCount: 0,
2842
- originalBytes: 0,
2843
- compressedBytes: 0
2844
- };
2845
- }
2846
-
2847
- // src/http/compression/middleware.ts
2848
- function injectAcceptEncoding(req, encodings) {
2849
- const headers = req.headers ?? {};
2850
- const hasAcceptEncoding = Object.keys(headers).some(
2851
- (k) => k.toLowerCase() === "accept-encoding"
2852
- );
2853
- if (hasAcceptEncoding) return req;
2854
- return {
2855
- ...req,
2856
- headers: {
2857
- ...headers,
2858
- "Accept-Encoding": encodings.join(", ")
2859
- }
2860
- };
2861
- }
2862
- function isSupportedEncoding(enc) {
2863
- return SUPPORTED_ENCODINGS.includes(enc);
2864
- }
2865
- function processResponse(res, decompressor, enabledEncodings, stats) {
2866
- const contentEncodingKey = Object.keys(res.headers).find(
2867
- (k) => k.toLowerCase() === "content-encoding"
2868
- );
2869
- const contentEncodingValue = contentEncodingKey ? res.headers[contentEncodingKey]?.trim() : void 0;
2870
- if (!contentEncodingValue || contentEncodingValue.toLowerCase() === "identity") {
2871
- stats.passthroughCount++;
2872
- return res;
2873
- }
2874
- const encodings = contentEncodingValue.split(",").map((e) => e.trim().toLowerCase());
2875
- const reversedEncodings = [...encodings].reverse();
2876
- let currentData = Buffer.from(res.bodyText, "latin1");
2877
- const originalData = currentData;
2878
- let decompressedCount = 0;
2879
- for (let i = 0; i < reversedEncodings.length; i++) {
2880
- const enc = reversedEncodings[i];
2881
- if (!isSupportedEncoding(enc)) {
2882
- stats.unsupportedEncodingCount++;
2883
- if (decompressedCount === 0) {
2884
- stats.passthroughCount++;
2885
- return res;
2886
- }
2887
- const remainingEncodings = reversedEncodings.slice(i).reverse();
2888
- const newHeaders2 = { ...res.headers };
2889
- if (contentEncodingKey) {
2890
- newHeaders2[contentEncodingKey] = remainingEncodings.join(", ");
2891
- }
2892
- newHeaders2["Content-Length"] = String(currentData.byteLength);
2893
- return {
2894
- ...res,
2895
- headers: newHeaders2,
2896
- bodyText: currentData.toString("latin1")
2897
- };
2898
- }
2899
- if (!enabledEncodings.includes(enc)) {
2900
- stats.passthroughCount++;
2901
- if (decompressedCount === 0) {
2902
- return res;
2903
- }
2904
- const remainingEncodings = reversedEncodings.slice(i).reverse();
2905
- const newHeaders2 = { ...res.headers };
2906
- if (contentEncodingKey) {
2907
- newHeaders2[contentEncodingKey] = remainingEncodings.join(", ");
2908
- }
2909
- newHeaders2["Content-Length"] = String(currentData.byteLength);
2910
- return {
2911
- ...res,
2912
- headers: newHeaders2,
2913
- bodyText: currentData.toString("latin1")
2914
- };
2915
- }
2916
- const result = decompressor.decompress(currentData, enc);
2917
- if (!result.ok) {
2918
- stats.errorCount++;
2919
- return res;
2920
- }
2921
- stats.compressedBytes += currentData.byteLength;
2922
- stats.decompressedBytes += result.data.byteLength;
2923
- stats.decompressed[enc]++;
2924
- decompressedCount++;
2925
- currentData = result.data;
2926
- }
2927
- const newHeaders = { ...res.headers };
2928
- if (contentEncodingKey) {
2929
- delete newHeaders[contentEncodingKey];
2930
- }
2931
- const lowerKey = Object.keys(newHeaders).find(
2932
- (k) => k.toLowerCase() === "content-encoding"
2933
- );
2934
- if (lowerKey) {
2935
- delete newHeaders[lowerKey];
2936
- }
2937
- newHeaders["Content-Length"] = String(currentData.byteLength);
2938
- return {
2939
- ...res,
2940
- headers: newHeaders,
2941
- bodyText: currentData.toString("utf-8")
464
+ return (req) => {
465
+ return asyncFlatMap(tokenProvider(), (token) => {
466
+ const modifiedReq = {
467
+ ...req,
468
+ headers: {
469
+ ...req.headers ?? {},
470
+ Authorization: `Bearer ${token}`
471
+ }
472
+ };
473
+ return next(modifiedReq);
474
+ });
475
+ };
2942
476
  };
2943
477
  }
2944
- function makeCompressionMiddleware(config) {
2945
- const enabledEncodings = config?.encodings ?? [...SUPPORTED_ENCODINGS];
2946
- const decompressor = createDecompressor();
2947
- const mutableStats = emptyStats2();
2948
- const middleware = (next) => {
478
+ function withLogging(logger) {
479
+ return (next) => {
2949
480
  return (req) => {
2950
- const modifiedReq = injectAcceptEncoding(req, enabledEncodings);
481
+ try {
482
+ logger({ phase: "request", req });
483
+ } catch {
484
+ }
485
+ const startedAt = now();
2951
486
  return asyncFold(
2952
- next(modifiedReq),
2953
- // Pass HttpErrors through unchanged
2954
- (error) => asyncFail(error),
2955
- // Process successful responses
487
+ next(req),
488
+ (error) => {
489
+ const durationMs = Math.round(now() - startedAt);
490
+ try {
491
+ logger({ phase: "error", req, error, durationMs });
492
+ } catch {
493
+ }
494
+ return asyncFail(error);
495
+ },
2956
496
  (res) => {
2957
- if (decompressor.isPassthrough) {
2958
- mutableStats.passthroughCount++;
2959
- return asyncSucceed(res);
497
+ const durationMs = Math.round(now() - startedAt);
498
+ try {
499
+ logger({ phase: "response", req, res, durationMs });
500
+ } catch {
2960
501
  }
2961
- const processed = processResponse(
2962
- res,
2963
- decompressor,
2964
- enabledEncodings,
2965
- mutableStats
2966
- );
2967
- return asyncSucceed(processed);
502
+ return asyncSucceed(res);
2968
503
  }
2969
504
  );
2970
505
  };
2971
506
  };
2972
- const stats = () => Object.freeze({
2973
- decompressed: Object.freeze({ ...mutableStats.decompressed }),
2974
- compressedBytes: mutableStats.compressedBytes,
2975
- decompressedBytes: mutableStats.decompressedBytes,
2976
- passthroughCount: mutableStats.passthroughCount,
2977
- errorCount: mutableStats.errorCount,
2978
- unsupportedEncodingCount: mutableStats.unsupportedEncodingCount
2979
- });
2980
- return { middleware, stats };
2981
- }
2982
- var makeResponseCompressionMiddleware = makeCompressionMiddleware;
2983
- var DEFAULT_REQUEST_COMPRESS_METHODS = ["POST", "PUT", "PATCH"];
2984
- function makeRequestCompressionMiddleware(config) {
2985
- const encoding = config?.encoding ?? "gzip";
2986
- const minBytes = Math.max(0, Math.floor(config?.minBytes ?? 1024));
2987
- const methods = new Set((config?.methods ?? DEFAULT_REQUEST_COMPRESS_METHODS).map((m) => m.toUpperCase()));
2988
- const mutableStats = emptyRequestCompressionStats();
2989
- const middleware = (next) => {
507
+ }
508
+ function withResponseTransform(fn) {
509
+ return (next) => {
2990
510
  return (req) => {
2991
- const compressed = compressRequest(req, encoding, minBytes, methods, mutableStats);
2992
- return next(compressed);
511
+ return asyncFold(
512
+ next(req),
513
+ (error) => {
514
+ return asyncFail(error);
515
+ },
516
+ (res) => {
517
+ try {
518
+ const transformed = fn(res, req);
519
+ return asyncSucceed(transformed);
520
+ } catch (e) {
521
+ return asyncFail({ _tag: "FetchError", message: String(e) });
522
+ }
523
+ }
524
+ );
2993
525
  };
2994
526
  };
2995
- const stats = () => Object.freeze({
2996
- compressedCount: mutableStats.compressedCount,
2997
- skippedCount: mutableStats.skippedCount,
2998
- errorCount: mutableStats.errorCount,
2999
- originalBytes: mutableStats.originalBytes,
3000
- compressedBytes: mutableStats.compressedBytes
3001
- });
3002
- return { middleware, stats };
3003
- }
3004
- function compressRequest(req, encoding, minBytes, methods, stats) {
3005
- if (!methods.has(req.method.toUpperCase())) {
3006
- stats.skippedCount++;
3007
- return req;
3008
- }
3009
- if (req.body === void 0 || hasHeader(req.headers, "content-encoding")) {
3010
- stats.skippedCount++;
3011
- return req;
3012
- }
3013
- const originalBytes = httpBodyByteLength(req.body);
3014
- if (originalBytes < minBytes) {
3015
- stats.skippedCount++;
3016
- return req;
3017
- }
3018
- try {
3019
- const compressed = compressBuffer(httpBodyToBuffer(req.body), encoding);
3020
- stats.compressedCount++;
3021
- stats.originalBytes += originalBytes;
3022
- stats.compressedBytes += compressed.byteLength;
3023
- return {
3024
- ...req,
3025
- body: compressed,
3026
- headers: setHeaders(req.headers, {
3027
- "Content-Encoding": encoding,
3028
- "Content-Length": String(compressed.byteLength)
3029
- })
3030
- };
3031
- } catch {
3032
- stats.errorCount++;
3033
- return req;
3034
- }
3035
- }
3036
- function compressBuffer(input, encoding) {
3037
- const zlib2 = __require("zlib");
3038
- switch (encoding) {
3039
- case "gzip":
3040
- return zlib2.gzipSync(input);
3041
- case "br":
3042
- return zlib2.brotliCompressSync(input);
3043
- case "deflate":
3044
- return zlib2.deflateSync(input);
3045
- }
3046
- }
3047
- function hasHeader(headers, name) {
3048
- if (!headers) return false;
3049
- const lower = name.toLowerCase();
3050
- return Object.keys(headers).some((key) => key.toLowerCase() === lower);
3051
- }
3052
- function setHeaders(headers, values) {
3053
- const out = { ...headers ?? {} };
3054
- for (const [key, value] of Object.entries(values)) {
3055
- const existing = Object.keys(out).find((h) => h.toLowerCase() === key.toLowerCase());
3056
- if (existing) out[existing] = value;
3057
- else out[key] = value;
3058
- }
3059
- return out;
3060
527
  }
3061
528
 
3062
529
  // src/http/batching.ts
@@ -3141,7 +608,7 @@ function withRequestBatching(config) {
3141
608
  const effect = downstream(batchReq);
3142
609
  group.cancel = runEffect(effect, entries[0].env, (exit) => {
3143
610
  if (exit._tag === "Failure") {
3144
- 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);
3145
612
  failEntries(config, key, entries, err);
3146
613
  return;
3147
614
  }
@@ -3174,10 +641,16 @@ function failEntries(config, key, entries, error) {
3174
641
  }
3175
642
  }
3176
643
  function toFetchError(error) {
3177
- if (isHttpError(error)) return error;
644
+ if (isHttpError2(error)) return error;
3178
645
  return { _tag: "FetchError", message: error instanceof Error ? error.message : String(error) };
3179
646
  }
3180
- function isHttpError(error) {
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
+ }
653
+ function isHttpError2(error) {
3181
654
  if (typeof error !== "object" || error === null || !("_tag" in error)) return false;
3182
655
  const tag = error._tag;
3183
656
  return tag === "Abort" || tag === "BadUrl" || tag === "FetchError" || tag === "Timeout" || tag === "PoolRejected" || tag === "PoolTimeout";
@@ -3238,10 +711,13 @@ function runEffect(effect, env, cb) {
3238
711
  try {
3239
712
  if (exit._tag === "Success") {
3240
713
  run(eff.onSuccess(exit.value), k);
3241
- } else if (exit.cause._tag === "Fail") {
3242
- run(eff.onFailure(exit.cause.error), k);
3243
714
  } else {
3244
- 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
+ }
3245
721
  }
3246
722
  } catch (e) {
3247
723
  k({ _tag: "Failure", cause: Cause.die(e) });
@@ -3251,6 +727,14 @@ function runEffect(effect, env, cb) {
3251
727
  case "Fork":
3252
728
  k({ _tag: "Success", value: void 0 });
3253
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;
3254
738
  }
3255
739
  };
3256
740
  run(effect, finish);
@@ -3261,7 +745,7 @@ function runEffect(effect, env, cb) {
3261
745
  };
3262
746
  }
3263
747
 
3264
- // src/http/prewarm.ts
748
+ // src/http/prewarm/legacy.ts
3265
749
  function prewarmConnections(config = {}) {
3266
750
  const fetchImpl = config.fetchImpl ?? globalThis.fetch;
3267
751
  const method = config.method ?? "HEAD";
@@ -3376,13 +860,13 @@ function resolveUrl(value, baseUrl) {
3376
860
  }
3377
861
  }
3378
862
  function normalizePrewarmError(error) {
3379
- if (isHttpError2(error)) return error;
863
+ if (isHttpError3(error)) return error;
3380
864
  if (typeof error === "object" && error !== null && error.name === "AbortError") {
3381
865
  return { _tag: "Abort" };
3382
866
  }
3383
867
  return { _tag: "FetchError", message: error instanceof Error ? error.message : String(error) };
3384
868
  }
3385
- function isHttpError2(error) {
869
+ function isHttpError3(error) {
3386
870
  if (typeof error !== "object" || error === null || !("_tag" in error)) return false;
3387
871
  const tag = error._tag;
3388
872
  return tag === "Abort" || tag === "BadUrl" || tag === "FetchError" || tag === "Timeout" || tag === "PoolRejected" || tag === "PoolTimeout";
@@ -3394,48 +878,820 @@ function emit2(config, event) {
3394
878
  } catch {
3395
879
  }
3396
880
  }
881
+
882
+ // src/http/builder.ts
883
+ var DEFAULT_BUILDER_RETRY = {
884
+ maxRetries: 2,
885
+ baseDelayMs: 100,
886
+ maxDelayMs: 1e3
887
+ };
888
+ var cloneConfig = (config) => ({
889
+ ...config,
890
+ headers: config.headers ? { ...config.headers } : void 0,
891
+ middleware: config.middleware ? [...config.middleware] : void 0
892
+ });
893
+ var freezeConfig = (config) => ({
894
+ ...config,
895
+ headers: config.headers ? { ...config.headers } : void 0,
896
+ middleware: config.middleware ? [...config.middleware] : void 0
897
+ });
898
+ var mergeConfig = (current, next) => ({
899
+ ...current,
900
+ ...next,
901
+ headers: {
902
+ ...current.headers ?? {},
903
+ ...next.headers ?? {}
904
+ },
905
+ middleware: [
906
+ ...current.middleware ?? [],
907
+ ...next.middleware ?? []
908
+ ]
909
+ });
910
+ function makeBuilder(config) {
911
+ const replace = (patch) => makeBuilder({
912
+ ...config,
913
+ ...patch,
914
+ headers: patch.headers ? { ...patch.headers } : config.headers,
915
+ middleware: patch.middleware ? [...patch.middleware] : config.middleware
916
+ });
917
+ const setLayer = (key, value) => replace({ [key]: value });
918
+ const middleware = (mw) => makeBuilder({
919
+ ...config,
920
+ middleware: [...config.middleware ?? [], mw]
921
+ });
922
+ return Object.freeze({
923
+ config: () => freezeConfig(config),
924
+ baseUrl: (baseUrl) => replace({ baseUrl }),
925
+ header: (name, value) => makeBuilder({
926
+ ...config,
927
+ headers: {
928
+ ...config.headers ?? {},
929
+ [name]: value
930
+ }
931
+ }),
932
+ headers: (headers) => makeBuilder({
933
+ ...config,
934
+ headers: {
935
+ ...config.headers ?? {},
936
+ ...headers
937
+ }
938
+ }),
939
+ timeoutMs: (timeoutMs) => replace({ timeoutMs }),
940
+ timeout: (timeoutMs) => replace({ timeoutMs }),
941
+ transport: (transport) => replace({ transport }),
942
+ preset: (preset) => replace({ preset }),
943
+ minimal: () => replace({ preset: "minimal" }),
944
+ balanced: () => replace({ preset: "balanced" }),
945
+ defaultPreset: () => replace({ preset: "default" }),
946
+ production: () => replace({ preset: "production" }),
947
+ dedup: (layer = {}) => setLayer("dedup", layer),
948
+ noDedup: () => setLayer("dedup", false),
949
+ batch: (layer) => setLayer("batch", layer),
950
+ noBatch: () => setLayer("batch", false),
951
+ cache: (layer = {}) => setLayer("cache", layer),
952
+ noCache: () => setLayer("cache", false),
953
+ priority: (layer = {}) => setLayer("priority", layer),
954
+ noPriority: () => setLayer("priority", false),
955
+ retry: (layer = DEFAULT_BUILDER_RETRY) => setLayer("retry", layer),
956
+ noRetry: () => setLayer("retry", false),
957
+ prewarm: (layer = {}) => setLayer("prewarm", layer),
958
+ noPrewarm: () => setLayer("prewarm", false),
959
+ adaptiveLimiter: (layer = {}) => setLayer("adaptiveLimiter", layer),
960
+ adaptiveLimiterPreset: (preset, overrides = {}) => setLayer("adaptiveLimiter", makeAdaptiveLimiterConfig(preset, overrides)),
961
+ conservativeLimiter: (overrides = {}) => setLayer("adaptiveLimiter", makeAdaptiveLimiterConfig("conservative", overrides)),
962
+ balancedLimiter: (overrides = {}) => setLayer("adaptiveLimiter", makeAdaptiveLimiterConfig("balanced", overrides)),
963
+ aggressiveLimiter: (overrides = {}) => setLayer("adaptiveLimiter", makeAdaptiveLimiterConfig("aggressive", overrides)),
964
+ noAdaptiveLimiter: () => setLayer("adaptiveLimiter", false),
965
+ pool: (layer = {}) => setLayer("pool", layer),
966
+ noPool: () => setLayer("pool", false),
967
+ compression: (layer = {}) => setLayer("compression", layer),
968
+ noCompression: () => setLayer("compression", false),
969
+ middleware,
970
+ use: middleware,
971
+ configure: (next) => makeBuilder(mergeConfig(config, next)),
972
+ build: () => makeDefaultHttpClient(freezeConfig(config)),
973
+ buildWire: () => makeDefaultHttpClient(freezeConfig(config)).wire
974
+ });
975
+ }
976
+ function httpClientBuilder(config = {}) {
977
+ return makeBuilder(cloneConfig(config));
978
+ }
979
+ var makeHttpClientBuilder = httpClientBuilder;
980
+ var httpBuilder = httpClientBuilder;
981
+
982
+ // src/http/server.ts
983
+ import { createServer } from "http";
984
+ var DEFAULT_MAX_BODY_BYTES = 1024 * 1024;
985
+ function route(method, path, optionsOrHandler, maybeHandler) {
986
+ const options = typeof optionsOrHandler === "function" ? {} : optionsOrHandler;
987
+ const handler = typeof optionsOrHandler === "function" ? optionsOrHandler : maybeHandler;
988
+ if (!handler) throw new Error(`Missing handler for HTTP route ${method} ${path}`);
989
+ return {
990
+ method: normalizeRouteMethod(method),
991
+ path: normalizeRoutePath(path),
992
+ options,
993
+ handler,
994
+ match: compileRoutePath(path)
995
+ };
996
+ }
997
+ var httpRoute = route;
998
+ function makeHttpRouter(routes, options = {}) {
999
+ const normalizedRoutes = routes.map((item) => ({
1000
+ ...item,
1001
+ method: normalizeRouteMethod(item.method),
1002
+ path: normalizeRoutePath(item.path)
1003
+ }));
1004
+ const router = {
1005
+ routes: normalizedRoutes,
1006
+ match: (method, path) => matchRoute(normalizedRoutes, method, path),
1007
+ handle: (request, existingMatch) => {
1008
+ const matched = existingMatch ?? router.match(request.method, request.path);
1009
+ return handleRouteMatch(request, matched, options);
1010
+ },
1011
+ listen: (serverOptions = {}) => nodeHttpServerResource({
1012
+ ...serverOptions,
1013
+ router
1014
+ })
1015
+ };
1016
+ return router;
1017
+ }
1018
+ function json(body, init = {}) {
1019
+ return {
1020
+ ...init,
1021
+ headers: setHeaderIfMissing2(init.headers, "content-type", "application/json"),
1022
+ body
1023
+ };
1024
+ }
1025
+ function text(body, init = {}) {
1026
+ return {
1027
+ ...init,
1028
+ headers: setHeaderIfMissing2(init.headers, "content-type", "text/plain; charset=utf-8"),
1029
+ body
1030
+ };
1031
+ }
1032
+ function empty(status = 204, headers) {
1033
+ return { status, headers };
1034
+ }
1035
+ function makeRuntimeHealthRoute(options = {}) {
1036
+ const { path = "/health", ...healthOptions } = options;
1037
+ return route(
1038
+ "GET",
1039
+ path,
1040
+ () => asyncFlatMap(
1041
+ makeRuntimeHealth(healthOptions),
1042
+ (report) => asyncSucceed(healthReportToServerResponse(report))
1043
+ )
1044
+ );
1045
+ }
1046
+ function makeRuntimeReadinessRoute(options = {}) {
1047
+ return makeRuntimeHealthRoute({
1048
+ ...options,
1049
+ path: options.path ?? "/ready"
1050
+ });
1051
+ }
1052
+ var runtimeHealthRoute = makeRuntimeHealthRoute;
1053
+ var runtimeReadinessRoute = makeRuntimeReadinessRoute;
1054
+ function withResponseHeader(name, value) {
1055
+ return (next) => (ctx) => asyncFlatMap(
1056
+ next(ctx),
1057
+ (response) => asyncSucceed({
1058
+ ...response,
1059
+ headers: setHeaderIfMissing2(response.headers, name, value)
1060
+ })
1061
+ );
1062
+ }
1063
+ function healthReportToServerResponse(report) {
1064
+ return healthHttpResponseToServerResponse(healthToHttpResponse(report));
1065
+ }
1066
+ function healthHttpResponseToServerResponse(response) {
1067
+ return {
1068
+ status: response.status,
1069
+ headers: response.headers,
1070
+ body: response.body
1071
+ };
1072
+ }
1073
+ function makeNodeHttpServer(options) {
1074
+ return asyncEffect((_env, cb) => {
1075
+ const router = resolveRouter(options.router);
1076
+ const server = createServer((req, res) => {
1077
+ void handleNodeRequest(req, res, router, options);
1078
+ });
1079
+ let settled = false;
1080
+ let closed = false;
1081
+ const close = () => closeNodeServer(server, options);
1082
+ const fail = (error) => {
1083
+ if (settled) {
1084
+ options.onError?.(error);
1085
+ return;
1086
+ }
1087
+ settled = true;
1088
+ cb({
1089
+ _tag: "Failure",
1090
+ cause: {
1091
+ _tag: "Fail",
1092
+ error: {
1093
+ _tag: "ListenError",
1094
+ error,
1095
+ message: error instanceof Error ? error.message : String(error)
1096
+ }
1097
+ }
1098
+ });
1099
+ };
1100
+ server.once("error", fail);
1101
+ server.once("close", () => {
1102
+ closed = true;
1103
+ });
1104
+ server.listen(options.port ?? 0, options.host, () => {
1105
+ if (settled) return;
1106
+ settled = true;
1107
+ server.off("error", fail);
1108
+ server.on("error", (error) => options.onError?.(error));
1109
+ cb({
1110
+ _tag: "Success",
1111
+ value: {
1112
+ server,
1113
+ router,
1114
+ address: () => server.address(),
1115
+ url: () => serverUrl(server),
1116
+ close
1117
+ }
1118
+ });
1119
+ });
1120
+ return () => {
1121
+ if (closed) return;
1122
+ void close().catch((error) => options.onError?.(error));
1123
+ };
1124
+ });
1125
+ }
1126
+ function nodeHttpServerResource(options) {
1127
+ return resource(
1128
+ makeNodeHttpServer(options),
1129
+ (handle) => asyncEffect((_env, cb) => {
1130
+ handle.close().then(() => cb({ _tag: "Success", value: void 0 })).catch(() => cb({ _tag: "Success", value: void 0 }));
1131
+ })
1132
+ );
1133
+ }
1134
+ var makeNodeHttpServerResource = nodeHttpServerResource;
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
+ });
1151
+ function handleRouteMatch(request, match, options) {
1152
+ if (match._tag === "NotFound") {
1153
+ return asyncSucceed(json({ error: "Not Found" }, { status: 404 }));
1154
+ }
1155
+ if (match._tag === "MethodNotAllowed") {
1156
+ return asyncSucceed(json(
1157
+ { error: "Method Not Allowed", allowed: match.allowed.filter((method) => method !== "ALL") },
1158
+ { status: 405, headers: { allow: match.allowed.filter((method) => method !== "ALL").join(", ") } }
1159
+ ));
1160
+ }
1161
+ const routeWithParams = match.route;
1162
+ const prepared = prepareRouteContext(request, match.params, routeWithParams);
1163
+ if (!prepared.success) return asyncSucceed(validationErrorResponse(prepared.error));
1164
+ const handler = composeMiddleware(
1165
+ [...options.middleware ?? [], ...routeWithParams.options.middleware ?? []],
1166
+ routeWithParams.handler
1167
+ );
1168
+ let handled;
1169
+ try {
1170
+ handled = handler(prepared.ctx);
1171
+ } catch (error) {
1172
+ return asyncSucceed(handlerErrorResponse(error, options));
1173
+ }
1174
+ return asyncFold(
1175
+ handled,
1176
+ (error) => asyncSucceed(handlerErrorResponse(error, options)),
1177
+ (response) => {
1178
+ const normalized = normalizeServerResponse(response);
1179
+ const validation = validateResponseBody(normalized, routeWithParams);
1180
+ return asyncSucceed(validation.success ? normalized : validationErrorResponse(validation.error));
1181
+ }
1182
+ );
1183
+ }
1184
+ function prepareRouteContext(request, params, routeWithSchemas) {
1185
+ const paramsResult = validatePart(params, routeWithSchemas.options.params, {
1186
+ phase: "request",
1187
+ schemaName: routeWithSchemas.options.paramsSchemaName ?? "params",
1188
+ body: JSON.stringify(params)
1189
+ });
1190
+ if (!paramsResult.success) return paramsResult;
1191
+ const queryResult = validatePart(request.query, routeWithSchemas.options.query, {
1192
+ phase: "request",
1193
+ schemaName: routeWithSchemas.options.querySchemaName ?? "query",
1194
+ body: JSON.stringify(request.query)
1195
+ });
1196
+ if (!queryResult.success) return queryResult;
1197
+ const bodyInput = routeWithSchemas.options.body ? parseJsonRequestBody(request.bodyText) : { success: true, data: request.bodyText.length > 0 ? request.bodyText : void 0 };
1198
+ if (!bodyInput.success) {
1199
+ return {
1200
+ success: false,
1201
+ error: makeValidationError({
1202
+ message: bodyInput.message,
1203
+ body: request.bodyText,
1204
+ phase: "request",
1205
+ schema: routeWithSchemas.options.bodySchemaName ?? "body",
1206
+ issues: [makeSchemaIssue([], "valid JSON", request.bodyText, bodyInput.message)]
1207
+ })
1208
+ };
1209
+ }
1210
+ const bodyResult = validatePart(bodyInput.data, routeWithSchemas.options.body, {
1211
+ phase: "request",
1212
+ schemaName: routeWithSchemas.options.bodySchemaName ?? "body",
1213
+ body: request.bodyText
1214
+ });
1215
+ if (!bodyResult.success) return bodyResult;
1216
+ return {
1217
+ success: true,
1218
+ ctx: {
1219
+ ...request,
1220
+ route: routeWithSchemas.path,
1221
+ params: paramsResult.data,
1222
+ query: queryResult.data,
1223
+ body: bodyResult.data
1224
+ }
1225
+ };
1226
+ }
1227
+ function validateResponseBody(response, routeWithSchemas) {
1228
+ const schema2 = routeWithSchemas.options.response;
1229
+ if (!schema2) return { success: true };
1230
+ const result = validateValue(response.body, schema2);
1231
+ if (result.success) return { success: true };
1232
+ return {
1233
+ success: false,
1234
+ error: makeValidationError({
1235
+ message: `HTTP response failed validation: ${formatIssues(result.issues)}`,
1236
+ body: previewJson(response.body),
1237
+ phase: "response",
1238
+ schema: routeWithSchemas.options.responseSchemaName ?? "response",
1239
+ issues: result.issues
1240
+ })
1241
+ };
1242
+ }
1243
+ function validatePart(input, schema2, options) {
1244
+ if (!schema2) return { success: true, data: input };
1245
+ const result = validateValue(input, schema2);
1246
+ if (result.success) return { success: true, data: result.data };
1247
+ return {
1248
+ success: false,
1249
+ error: makeValidationError({
1250
+ message: `HTTP ${options.schemaName} failed validation: ${formatIssues(result.issues)}`,
1251
+ body: options.body,
1252
+ phase: options.phase,
1253
+ schema: options.schemaName,
1254
+ issues: result.issues
1255
+ })
1256
+ };
1257
+ }
1258
+ function resolveRouter(input) {
1259
+ return isHttpRouter(input) ? input : makeHttpRouter(input);
1260
+ }
1261
+ function isHttpRouter(value) {
1262
+ return !Array.isArray(value) && typeof value === "object" && value !== null && typeof value.match === "function" && typeof value.handle === "function";
1263
+ }
1264
+ function makeValidationError(input) {
1265
+ return {
1266
+ _tag: "ValidationError",
1267
+ message: input.message,
1268
+ body: input.body,
1269
+ phase: input.phase,
1270
+ schema: input.schema,
1271
+ issues: input.issues
1272
+ };
1273
+ }
1274
+ function validationErrorResponse(error) {
1275
+ return json(
1276
+ {
1277
+ error: error.phase === "response" ? "Response validation failed" : "Request validation failed",
1278
+ message: error.message,
1279
+ phase: error.phase,
1280
+ schema: error.schema,
1281
+ issues: error.issues
1282
+ },
1283
+ { status: error.phase === "response" ? 500 : 400 }
1284
+ );
1285
+ }
1286
+ function handlerErrorResponse(error, options) {
1287
+ return json(
1288
+ {
1289
+ error: "Internal Server Error",
1290
+ ...options.includeErrorDetails ? { message: error instanceof Error ? error.message : String(error) } : {}
1291
+ },
1292
+ { status: 500 }
1293
+ );
1294
+ }
1295
+ async function handleNodeRequest(req, res, router, options) {
1296
+ try {
1297
+ const bodyText = await readNodeRequestBody(req, options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES);
1298
+ const serverRequest = nodeRequestToServerRequest(req, bodyText);
1299
+ const match = router.match(serverRequest.method, serverRequest.path);
1300
+ const effect = router.handle(serverRequest, match);
1301
+ const routeLabel = match._tag === "Match" || match._tag === "MethodNotAllowed" ? match.route.path : void 0;
1302
+ const observedInput = {
1303
+ method: serverRequest.method,
1304
+ route: routeLabel,
1305
+ target: serverRequest.target,
1306
+ headers: serverRequest.headers
1307
+ };
1308
+ const response = options.observability ? (await runObservedHttpServerEffect(
1309
+ options.observability,
1310
+ observedInput,
1311
+ effect,
1312
+ {
1313
+ statusCode: (value) => value.status ?? 200,
1314
+ ...options.observabilityOptions ?? {}
1315
+ },
1316
+ options.env,
1317
+ options.runtimeOptions
1318
+ )).value : await (options.runtime ?? new Runtime({
1319
+ env: options.env ?? {},
1320
+ ...options.runtimeOptions ?? {}
1321
+ })).toPromise(effect);
1322
+ writeNodeResponse(res, response);
1323
+ } catch (error) {
1324
+ options.onError?.(error);
1325
+ writeNodeResponse(res, json({ error: "Internal Server Error" }, { status: 500 }));
1326
+ }
1327
+ }
1328
+ function nodeRequestToServerRequest(req, bodyText) {
1329
+ const headers = normalizeNodeHeaders(req.headers);
1330
+ const target = req.url ?? "/";
1331
+ const parsed = parseRequestUrl(target, headers.host);
1332
+ return {
1333
+ method: (req.method ?? "GET").toUpperCase(),
1334
+ url: parsed.toString(),
1335
+ path: parsed.pathname,
1336
+ target,
1337
+ headers,
1338
+ query: queryToObject(parsed.searchParams),
1339
+ params: {},
1340
+ bodyText,
1341
+ raw: req
1342
+ };
1343
+ }
1344
+ function writeNodeResponse(res, response) {
1345
+ const normalized = normalizeServerResponse(response);
1346
+ const encoded = encodeResponseBody(normalized);
1347
+ res.statusCode = normalized.status ?? 200;
1348
+ for (const [name, value] of Object.entries(encoded.headers)) {
1349
+ res.setHeader(name, value);
1350
+ }
1351
+ if (encoded.body === void 0) {
1352
+ res.end();
1353
+ return;
1354
+ }
1355
+ res.end(encoded.body);
1356
+ }
1357
+ function normalizeServerResponse(response) {
1358
+ if (isServerResponse(response)) {
1359
+ return { status: 200, ...response };
1360
+ }
1361
+ return { status: 200, body: response };
1362
+ }
1363
+ function isServerResponse(value) {
1364
+ return typeof value === "object" && value !== null && ("status" in value || "headers" in value || "body" in value);
1365
+ }
1366
+ function encodeResponseBody(response) {
1367
+ const headers = { ...response.headers ?? {} };
1368
+ const body = response.body;
1369
+ if (body === void 0 || response.status === 204 || response.status === 304) return { headers };
1370
+ if (typeof body === "string") {
1371
+ return {
1372
+ headers: setHeaderIfMissing2(headers, "content-type", "text/plain; charset=utf-8"),
1373
+ body
1374
+ };
1375
+ }
1376
+ if (body instanceof Uint8Array) return { headers, body };
1377
+ if (body instanceof ArrayBuffer) return { headers, body: new Uint8Array(body) };
1378
+ return {
1379
+ headers: setHeaderIfMissing2(headers, "content-type", "application/json"),
1380
+ body: JSON.stringify(body)
1381
+ };
1382
+ }
1383
+ function readNodeRequestBody(req, maxBytes) {
1384
+ return new Promise((resolve, reject) => {
1385
+ const chunks = [];
1386
+ let total = 0;
1387
+ req.on("data", (chunk) => {
1388
+ total += chunk.byteLength;
1389
+ if (total > maxBytes) {
1390
+ reject(new Error(`HTTP request body exceeded ${maxBytes} bytes`));
1391
+ req.destroy();
1392
+ return;
1393
+ }
1394
+ chunks.push(chunk);
1395
+ });
1396
+ req.on("error", reject);
1397
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
1398
+ });
1399
+ }
1400
+ function closeNodeServer(server, options) {
1401
+ return new Promise((resolve, reject) => {
1402
+ const startedAt = Date.now();
1403
+ const schedule = options.shutdownPollSchedule ?? defaultShutdownPollSchedule(options.gracefulShutdownMs ?? 5e3);
1404
+ const driver = makeScheduleDriver(schedule, { name: schedule.name ?? "http.server.shutdown", startedAtMs: startedAt });
1405
+ let finished = false;
1406
+ const done = (error) => {
1407
+ if (finished) return;
1408
+ finished = true;
1409
+ if (timer) clearTimeout(timer);
1410
+ if (error && error.message !== "Server is not running.") reject(error);
1411
+ else resolve();
1412
+ };
1413
+ let timer;
1414
+ const poll = () => {
1415
+ if (finished) return;
1416
+ if (!server.listening) {
1417
+ done();
1418
+ return;
1419
+ }
1420
+ const decision = driver.next({
1421
+ listening: server.listening,
1422
+ elapsedMs: Date.now() - startedAt
1423
+ });
1424
+ if (!decision.continue) {
1425
+ server.closeAllConnections?.();
1426
+ done();
1427
+ return;
1428
+ }
1429
+ timer = setTimeout(poll, decision.delayMs);
1430
+ timer.unref?.();
1431
+ };
1432
+ server.close(done);
1433
+ poll();
1434
+ });
1435
+ }
1436
+ function defaultShutdownPollSchedule(maxMs) {
1437
+ const intervalMs = 25;
1438
+ return take(fixed(intervalMs), Math.max(1, Math.ceil(Math.max(0, maxMs) / intervalMs)));
1439
+ }
1440
+ function matchRoute(routes, method, path) {
1441
+ const requestedMethod = method.toUpperCase();
1442
+ const allowed = [];
1443
+ let methodMismatch;
1444
+ for (const candidate of routes) {
1445
+ const params = candidate.match(path);
1446
+ if (!params) continue;
1447
+ if (candidate.method === "ALL" || candidate.method === requestedMethod) {
1448
+ return { _tag: "Match", route: candidate, params };
1449
+ }
1450
+ methodMismatch ??= candidate;
1451
+ allowed.push(candidate.method);
1452
+ }
1453
+ if (methodMismatch) {
1454
+ return { _tag: "MethodNotAllowed", route: methodMismatch, allowed };
1455
+ }
1456
+ return { _tag: "NotFound" };
1457
+ }
1458
+ function composeMiddleware(middleware, handler) {
1459
+ return middleware.reduceRight((next, current) => current(next), handler);
1460
+ }
1461
+ function parseJsonRequestBody(bodyText) {
1462
+ if (bodyText.length === 0) return { success: true, data: void 0 };
1463
+ try {
1464
+ return { success: true, data: JSON.parse(bodyText) };
1465
+ } catch (error) {
1466
+ return {
1467
+ success: false,
1468
+ message: `JSON parse error: ${error instanceof Error ? error.message : String(error)}`
1469
+ };
1470
+ }
1471
+ }
1472
+ function compileRoutePath(path) {
1473
+ const keys = [];
1474
+ const normalized = normalizeRoutePath(path);
1475
+ if (normalized === "/") {
1476
+ return (input) => input === "/" ? {} : void 0;
1477
+ }
1478
+ const source = normalized.split("/").filter(Boolean).map((segment) => {
1479
+ if (segment === "*") {
1480
+ keys.push("*");
1481
+ return "(.*)";
1482
+ }
1483
+ if (segment.startsWith(":")) {
1484
+ keys.push(segment.slice(1));
1485
+ return "([^/]+)";
1486
+ }
1487
+ return escapeRegExp(segment);
1488
+ }).join("/");
1489
+ const regex = new RegExp(`^/${source}/?$`);
1490
+ return (input) => {
1491
+ const match = regex.exec(input);
1492
+ if (!match) return void 0;
1493
+ const params = {};
1494
+ keys.forEach((key, index) => {
1495
+ params[key] = decodePathPart(match[index + 1] ?? "");
1496
+ });
1497
+ return params;
1498
+ };
1499
+ }
1500
+ function normalizeRouteMethod(method) {
1501
+ const upper = method.toUpperCase();
1502
+ return upper === "ALL" ? "ALL" : upper;
1503
+ }
1504
+ function normalizeRoutePath(path) {
1505
+ if (!path || path === "*") return "/";
1506
+ const withSlash = path.startsWith("/") ? path : `/${path}`;
1507
+ return withSlash.length > 1 ? withSlash.replace(/\/+$/, "") : withSlash;
1508
+ }
1509
+ function parseRequestUrl(target, host) {
1510
+ try {
1511
+ return new URL(target, `http://${host ?? "localhost"}`);
1512
+ } catch {
1513
+ return new URL("/", `http://${host ?? "localhost"}`);
1514
+ }
1515
+ }
1516
+ function queryToObject(searchParams) {
1517
+ const out = {};
1518
+ for (const [key, value] of searchParams.entries()) {
1519
+ const current = out[key];
1520
+ if (current === void 0) {
1521
+ out[key] = value;
1522
+ } else if (Array.isArray(current)) {
1523
+ current.push(value);
1524
+ } else {
1525
+ out[key] = [current, value];
1526
+ }
1527
+ }
1528
+ return out;
1529
+ }
1530
+ function normalizeNodeHeaders(headers) {
1531
+ const out = {};
1532
+ for (const [key, value] of Object.entries(headers)) {
1533
+ if (value === void 0) continue;
1534
+ out[key.toLowerCase()] = Array.isArray(value) ? value.join(",") : value;
1535
+ }
1536
+ return out;
1537
+ }
1538
+ function setHeaderIfMissing2(headers, name, value) {
1539
+ const out = { ...headers ?? {} };
1540
+ const existing = Object.keys(out).find((key) => key.toLowerCase() === name.toLowerCase());
1541
+ if (!existing) out[name] = value;
1542
+ return out;
1543
+ }
1544
+ function serverUrl(server) {
1545
+ const address = server.address();
1546
+ if (!address || typeof address === "string") return void 0;
1547
+ const host = address.address === "::" || address.address === "0.0.0.0" ? "127.0.0.1" : address.address;
1548
+ return `http://${host}:${address.port}`;
1549
+ }
1550
+ function previewJson(value) {
1551
+ try {
1552
+ return JSON.stringify(value);
1553
+ } catch {
1554
+ return "[unserializable]";
1555
+ }
1556
+ }
1557
+ function decodePathPart(value) {
1558
+ try {
1559
+ return decodeURIComponent(value);
1560
+ } catch {
1561
+ return value;
1562
+ }
1563
+ }
1564
+ function escapeRegExp(value) {
1565
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1566
+ }
3397
1567
  export {
1568
+ AdaptiveLimiter,
1569
+ ConfigValidationError,
3398
1570
  DEFAULT_CACHE_RELEVANT_HEADERS,
1571
+ EmaComputer,
3399
1572
  HttpConcurrencyPool,
1573
+ HttpServer,
3400
1574
  LRUCache,
1575
+ LatencyWindow,
3401
1576
  LifecycleStatsTracker,
3402
1577
  PriorityQueue,
3403
1578
  SEPARATOR,
3404
1579
  SUPPORTED_ENCODINGS,
1580
+ Schema,
1581
+ SchemaValidationException,
1582
+ abortErrorForSignal,
1583
+ adaptiveLimiterPresets,
3405
1584
  backoffDelayMs,
3406
1585
  clampPriority,
3407
1586
  computeCacheKey,
1587
+ computeGradient,
1588
+ computeNewLimit,
1589
+ decodeJsonBody,
1590
+ decodeJsonBodyEffect,
3408
1591
  decorate,
1592
+ defaultHttpClientPreset,
3409
1593
  defaultRetryOnError,
3410
1594
  defaultRetryOnStatus,
3411
1595
  defaultRetryableMethods,
1596
+ defineHttpPolicyPresets,
1597
+ detectPlatform,
1598
+ empty,
1599
+ encodeJsonBodyEffect,
1600
+ executeProbe,
1601
+ formatConfigError,
1602
+ formatHttpError,
1603
+ formatIssues,
1604
+ getHttpRequestPolicy,
1605
+ headersOf,
1606
+ httpBuilder,
3412
1607
  httpClient,
1608
+ httpClientBuilder,
3413
1609
  httpClientStream,
3414
1610
  httpClientWithMeta,
1611
+ httpErrorStatus,
1612
+ httpPolicy,
1613
+ httpRoute,
1614
+ isAbortError,
1615
+ isAbortHttpError,
1616
+ isCircuitBreakerOpen,
1617
+ isConfigValidationError,
1618
+ isExternalAbortError,
1619
+ isExternalTimeoutError,
1620
+ isFetchHttpError,
1621
+ isHttpError,
1622
+ isKnownHttpError,
1623
+ isRetryableHttpError,
1624
+ isRetryableHttpStatus,
1625
+ isSchema,
1626
+ isTaggedHttpError,
1627
+ isTimeoutHttpError,
1628
+ isValidationError,
1629
+ json,
1630
+ linkAbortSignals,
1631
+ makeAdaptiveLimiterConfig,
1632
+ makeBudgetSemaphore,
3415
1633
  makeCompressionMiddleware,
1634
+ makeConnectionStateMap,
1635
+ makeDefaultHttpClient,
1636
+ makeFetchStreamTransport,
1637
+ makeFetchTransport,
3416
1638
  makeHttp,
3417
1639
  makeHttpClient,
1640
+ makeHttpClientBuilder,
1641
+ makeHttpRouter,
1642
+ makeHttpServerResource,
3418
1643
  makeHttpStream,
3419
1644
  makeLifecycleClient,
1645
+ makeNodeHttpServer,
1646
+ makeNodeHttpServerResource,
1647
+ makePrewarmManager,
1648
+ makePromiseHttpTransport,
3420
1649
  makeRequestCompressionMiddleware,
3421
1650
  makeResponseCompressionMiddleware,
1651
+ makeRuntimeHealthRoute,
1652
+ makeRuntimeReadinessRoute,
1653
+ makeSchemaIssue,
1654
+ matchHttpError,
1655
+ nodeHttpServerResource,
3422
1656
  normalizeHeadersInit,
1657
+ normalizeHttpError,
1658
+ normalizeHttpHeaders,
3423
1659
  normalizeRetryBudget,
3424
1660
  parseCacheKey,
1661
+ parseConfig,
3425
1662
  prewarmConnections,
3426
1663
  prewarmHttpConnections,
1664
+ promiseHttpTransport,
1665
+ resolveConfig,
3427
1666
  resolveHttpPoolKey,
1667
+ resolveHttpRequestPolicyPresets,
3428
1668
  retryAfterMs,
1669
+ route,
1670
+ runtimeHealthRoute,
1671
+ runtimeReadinessRoute,
1672
+ s,
1673
+ schema,
1674
+ text,
1675
+ toHttpError,
1676
+ validateConfig,
1677
+ validateFetchAvailable,
1678
+ validateOrigin,
1679
+ validateValue,
3429
1680
  validatedJson,
1681
+ validatedJsonResponse,
3430
1682
  withAuth,
1683
+ withBatch,
3431
1684
  withCache,
3432
1685
  withCircuitBreaker,
3433
1686
  withConnectionPrewarming,
3434
1687
  withDedup,
1688
+ withHttpPolicyPresets,
1689
+ withHttpRequestPolicy,
3435
1690
  withLogging,
3436
1691
  withMiddleware,
3437
1692
  withPriority,
3438
1693
  withRequestBatching,
1694
+ withResponseHeader,
3439
1695
  withResponseTransform,
3440
1696
  withRetry,
3441
1697
  withRetryStream,