brass-runtime 1.15.0 → 1.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +409 -137
  2. package/dist/agent/cli/main.cjs +40 -35
  3. package/dist/agent/cli/main.js +9 -4
  4. package/dist/agent/cli/main.mjs +9 -4
  5. package/dist/agent/index.cjs +8 -4
  6. package/dist/agent/index.d.ts +1 -1
  7. package/dist/agent/index.js +7 -3
  8. package/dist/agent/index.mjs +7 -3
  9. package/dist/{chunk-PPUXIH5R.js → chunk-2WC63LJK.mjs} +11 -7
  10. package/dist/chunk-3RG5ZIWI.js +10 -0
  11. package/dist/chunk-45F7OKGT.cjs +104 -0
  12. package/dist/chunk-5YOQOXEQ.cjs +2491 -0
  13. package/dist/{chunk-STVLQ3XD.cjs → chunk-7HUOJA4W.cjs} +78 -74
  14. package/dist/{chunk-BMH5AV44.js → chunk-7LVI2GIN.js} +251 -370
  15. package/dist/chunk-7TL2LHQJ.js +2491 -0
  16. package/dist/chunk-7V4KY4RL.mjs +104 -0
  17. package/dist/chunk-7XOPAB5Q.js +2143 -0
  18. package/dist/chunk-CCKHV5BT.mjs +193 -0
  19. package/dist/{chunk-AR22SXML.js → chunk-CY33PGEX.mjs} +488 -421
  20. package/dist/chunk-DJQ7OMMB.cjs +144 -0
  21. package/dist/chunk-F5EUMJL7.mjs +2143 -0
  22. package/dist/chunk-FM4W4QPL.js +193 -0
  23. package/dist/{chunk-TO7IKXYT.js → chunk-G3XGCZDQ.js} +1 -1
  24. package/dist/{chunk-BDF4AMWX.mjs → chunk-G6IQOE4P.mjs} +251 -370
  25. package/dist/chunk-GOV47PPB.mjs +552 -0
  26. package/dist/chunk-H55LI6WY.js +93 -0
  27. package/dist/chunk-IJT6RRQ5.cjs +93 -0
  28. package/dist/{chunk-ELOOF35R.mjs → chunk-J3H54ZRV.mjs} +1 -1
  29. package/dist/chunk-JF4XXPZ5.cjs +552 -0
  30. package/dist/chunk-JNFRRJYH.cjs +2143 -0
  31. package/dist/chunk-JX3LZQJH.cjs +354 -0
  32. package/dist/chunk-K2T3DV26.mjs +93 -0
  33. package/dist/chunk-KCPT2D6G.js +552 -0
  34. package/dist/chunk-MWXMNYJS.cjs +1110 -0
  35. package/dist/{chunk-VEZNF5GZ.cjs → chunk-N6VHMOWB.cjs} +130 -126
  36. package/dist/{chunk-3QMOKAS5.js → chunk-NC5SDRYE.js} +9 -5
  37. package/dist/chunk-NOYZIMUJ.mjs +144 -0
  38. package/dist/{chunk-R3R2FVLG.cjs → chunk-NYL4D7SK.cjs} +5 -5
  39. package/dist/chunk-OBGZSXTJ.cjs +10 -0
  40. package/dist/{chunk-4NHES7VK.mjs → chunk-OOGJ73B6.js} +11 -7
  41. package/dist/chunk-PNVFW245.js +144 -0
  42. package/dist/chunk-PRWCB3QL.mjs +2491 -0
  43. package/dist/{chunk-JFPU5GQI.mjs → chunk-QY5FKYEQ.js} +488 -421
  44. package/dist/chunk-ROJC3NBJ.js +104 -0
  45. package/dist/chunk-SPUEME2B.cjs +343 -0
  46. package/dist/chunk-TDVMADDN.js +343 -0
  47. package/dist/chunk-TVN5I4U6.cjs +193 -0
  48. package/dist/chunk-U5KWK3PX.mjs +343 -0
  49. package/dist/chunk-VFIUZG7J.mjs +354 -0
  50. package/dist/{chunk-TGIFUAK4.cjs → chunk-WQ5QNU5R.cjs} +459 -578
  51. package/dist/chunk-XDZOO4L5.js +354 -0
  52. package/dist/chunk-Y6FXYEAI.mjs +10 -0
  53. package/dist/{chunk-K6M7MDZ4.mjs → chunk-ZGLD4TVZ.mjs} +9 -5
  54. package/dist/client-CtFmoDvM.d.ts +645 -0
  55. package/dist/core/index.cjs +72 -4
  56. package/dist/core/index.d.ts +92 -198
  57. package/dist/core/index.js +106 -38
  58. package/dist/core/index.mjs +106 -38
  59. package/dist/{effect-CMOQKX8y.d.ts → effect-CGNl5Rqp.d.ts} +107 -1
  60. package/dist/effectRunner-3ZHAD3LE.cjs +8 -0
  61. package/dist/effectRunner-A4CHJXJI.js +8 -0
  62. package/dist/effectRunner-OPUF6QRN.mjs +8 -0
  63. package/dist/http/index.cjs +2189 -1271
  64. package/dist/http/index.d.ts +830 -270
  65. package/dist/http/index.js +2008 -1090
  66. package/dist/http/index.mjs +2008 -1090
  67. package/dist/http/testing.cjs +159 -0
  68. package/dist/http/testing.d.ts +42 -0
  69. package/dist/http/testing.js +159 -0
  70. package/dist/http/testing.mjs +159 -0
  71. package/dist/index.cjs +246 -178
  72. package/dist/index.d.ts +9 -35
  73. package/dist/index.js +120 -52
  74. package/dist/index.mjs +120 -52
  75. package/dist/observability/index.cjs +677 -0
  76. package/dist/observability/index.d.ts +79 -0
  77. package/dist/observability/index.js +677 -0
  78. package/dist/observability/index.mjs +677 -0
  79. package/dist/schedule-Fque9Abz.d.ts +70 -0
  80. package/dist/schema/index.cjs +25 -0
  81. package/dist/schema/index.d.ts +177 -0
  82. package/dist/schema/index.js +25 -0
  83. package/dist/schema/index.mjs +25 -0
  84. package/dist/server-C8hDXA74.d.ts +674 -0
  85. package/dist/{stream-FQm9h4Mg.d.ts → stream-dvSs0QS5.d.ts} +1 -1
  86. package/dist/tracer-B5tRH9H7.d.ts +230 -0
  87. package/dist/tracing-Dt9S_6V8.d.ts +148 -0
  88. package/package.json +27 -1
  89. package/dist/chunk-BDYEENHT.js +0 -224
  90. package/dist/chunk-MS34J5LY.cjs +0 -224
  91. package/dist/chunk-UMAZLXAB.mjs +0 -224
  92. package/dist/chunk-XPZNXSVN.cjs +0 -1043
  93. package/dist/tracing-DNT9jEbr.d.ts +0 -106
@@ -1,937 +1,182 @@
1
1
  import {
2
- streamFromReadableStream
3
- } from "../chunk-PPUXIH5R.js";
2
+ registerHttpEffect
3
+ } from "../chunk-H55LI6WY.js";
4
4
  import {
5
+ fixed,
5
6
  makeCircuitBreaker,
6
- sleep
7
- } from "../chunk-BDYEENHT.js";
8
- import "../chunk-TO7IKXYT.js";
7
+ resource,
8
+ take
9
+ } from "../chunk-KCPT2D6G.js";
10
+ import {
11
+ AdaptiveLimiter,
12
+ EmaComputer,
13
+ HttpConcurrencyPool,
14
+ LatencyWindow,
15
+ adaptiveLimiterPresets,
16
+ backoffDelayMs,
17
+ computeGradient,
18
+ computeNewLimit,
19
+ decorate,
20
+ defaultRetryOnError,
21
+ defaultRetryOnStatus,
22
+ defaultRetryableMethods,
23
+ makeAdaptiveLimiterConfig,
24
+ makeHttp,
25
+ makeHttpStream,
26
+ mergeHeaders,
27
+ normalizeHeadersInit,
28
+ normalizeRetryBudget,
29
+ resolveConfig,
30
+ resolveHttpPoolKey,
31
+ retryAfterMs,
32
+ setHeaderIfMissing,
33
+ validateConfig,
34
+ validateDefaultHttpClientConfig,
35
+ validateLifecycleClientConfig,
36
+ withMiddleware,
37
+ withRetry,
38
+ withRetryStream
39
+ } from "../chunk-7TL2LHQJ.js";
40
+ import "../chunk-OOGJ73B6.js";
41
+ import "../chunk-ROJC3NBJ.js";
42
+ import {
43
+ healthToHttpResponse,
44
+ makeRuntimeHealth,
45
+ runObservedHttpServerEffect
46
+ } from "../chunk-7XOPAB5Q.js";
47
+ import "../chunk-XDZOO4L5.js";
48
+ import "../chunk-G3XGCZDQ.js";
49
+ import "../chunk-FM4W4QPL.js";
50
+ import {
51
+ Runtime,
52
+ fromPromiseAbortable,
53
+ toPromise
54
+ } from "../chunk-7LVI2GIN.js";
9
55
  import {
10
56
  Cause,
11
- __require,
12
57
  asyncEffect,
13
58
  asyncFail,
14
59
  asyncFlatMap,
15
60
  asyncFold,
16
61
  asyncSucceed,
17
- fromPromiseAbortable,
18
62
  mapTryAsync,
19
- resolveWasmModule,
20
- toPromise,
21
63
  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);
64
+ } from "../chunk-PNVFW245.js";
65
+ import {
66
+ ConfigValidationError,
67
+ Schema,
68
+ SchemaValidationException,
69
+ formatIssues,
70
+ isSchema,
71
+ makeSchemaIssue,
72
+ parseConfig,
73
+ s,
74
+ schema,
75
+ validateValue
76
+ } from "../chunk-TDVMADDN.js";
77
+ import {
78
+ __require
79
+ } from "../chunk-3RG5ZIWI.js";
50
80
 
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() {
81
+ // src/http/validation.ts
82
+ function decodeJsonBody(bodyText, validator, options = {}) {
83
+ let parsed;
84
+ try {
85
+ parsed = JSON.parse(bodyText);
86
+ } catch (error) {
75
87
  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)
88
+ success: false,
89
+ error: {
90
+ _tag: "ValidationError",
91
+ message: `JSON parse error: ${error instanceof Error ? error.message : String(error)}`,
92
+ body: bodyText,
93
+ phase: "response",
94
+ schema: options.schemaName,
95
+ issues: [makeSchemaIssue([], "valid JSON", bodyText, "Response body is not valid JSON")]
96
+ }
80
97
  };
81
98
  }
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;
99
+ if (!validator) return { success: true, data: parsed };
100
+ let legacyMessage;
101
+ const validation = isSchema(validator) ? validator.safeParse(parsed) : (() => {
102
+ const result = validator(parsed);
103
+ if (result.success) return { success: true, data: result.data };
104
+ legacyMessage = result.error;
105
+ return {
106
+ success: false,
107
+ issues: result.issues ?? [makeSchemaIssue([], "valid JSON shape", parsed, result.error)]
108
+ };
109
+ })();
110
+ if (validation.success) return { success: true, data: validation.data };
126
111
  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
- });
112
+ success: false,
113
+ error: {
114
+ _tag: "ValidationError",
115
+ message: legacyMessage ?? `JSON response failed validation: ${formatIssues(validation.issues)}`,
116
+ body: bodyText,
117
+ phase: "response",
118
+ schema: options.schemaName,
119
+ issues: validation.issues
156
120
  }
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
121
  };
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
122
+ }
123
+ function encodeJsonBodyEffect(bodyObj, validator, options = {}) {
124
+ if (validator) {
125
+ const validation = validateValue(bodyObj, validator);
126
+ if (!validation.success) {
127
+ return asyncFail({
128
+ _tag: "ValidationError",
129
+ message: `JSON request body failed validation: ${formatIssues(validation.issues)}`,
130
+ body: previewJson(bodyObj),
131
+ phase: "request",
132
+ schema: options.schemaName,
133
+ issues: validation.issues
319
134
  });
320
135
  }
321
- return out;
322
136
  }
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();
137
+ try {
138
+ return asyncSucceed(JSON.stringify(bodyObj ?? {}));
139
+ } catch (error) {
140
+ return asyncFail({
141
+ _tag: "ValidationError",
142
+ message: `JSON request body could not be serialized: ${error instanceof Error ? error.message : String(error)}`,
143
+ body: "",
144
+ phase: "request",
145
+ schema: options.schemaName,
146
+ issues: [
147
+ makeSchemaIssue([], "JSON-serializable value", bodyObj, "Request body could not be serialized to JSON")
148
+ ]
497
149
  });
498
150
  }
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) => {
151
+ }
152
+ function decodeJsonBodyEffect(bodyText, validator, options) {
153
+ const result = decodeJsonBody(bodyText, validator, options);
154
+ return result.success ? asyncSucceed(result.data) : asyncFail(result.error);
155
+ }
156
+ function validatedJson(client, validator) {
157
+ return (req) => asyncFold(
158
+ client(req),
159
+ (error) => asyncFail(error),
160
+ (response) => decodeJsonBodyEffect(response.bodyText, validator)
161
+ );
162
+ }
163
+ function validatedJsonResponse(client, validator) {
164
+ return (req) => asyncFold(
165
+ client(req),
166
+ (error) => asyncFail(error),
167
+ (response) => asyncFlatMap(
168
+ decodeJsonBodyEffect(response.bodyText, validator),
169
+ (body) => asyncSucceed({ ...response, body })
170
+ )
171
+ );
172
+ }
173
+ function previewJson(value) {
724
174
  try {
725
- return new URL(req.url, baseUrl);
175
+ return JSON.stringify(value);
726
176
  } catch {
727
- return { _tag: "BadUrl", message: `URL inv\xE1lida: ${req.url}` };
177
+ return "[unserializable]";
728
178
  }
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
- };
179
+ }
935
180
 
936
181
  // src/http/httpClient.ts
937
182
  var resolveFinalUrl = (baseUrl, url) => {
@@ -946,7 +191,7 @@ var createHttpCore = (cfg = {}) => {
946
191
  const withPromise = (eff) => withAsyncPromise((e, env) => toPromise(e, env))(eff);
947
192
  const requestRaw = (req) => wire(req);
948
193
  const splitInit = (init) => {
949
- const { headers, timeoutMs, poolKey, ...rest } = init ?? {};
194
+ const { headers, timeoutMs, poolKey, schema: schema2, schemaName, bodySchema, bodySchemaName, ...rest } = init ?? {};
950
195
  return {
951
196
  headers: normalizeHeadersInit(headers),
952
197
  timeoutMs: typeof timeoutMs === "number" ? timeoutMs : void 0,
@@ -955,24 +200,28 @@ var createHttpCore = (cfg = {}) => {
955
200
  };
956
201
  };
957
202
  const applyInitHeaders = (headers) => (req) => headers ? mergeHeaders(headers)(req) : req;
958
- const buildReq = (method, url, init, body) => {
959
- const s = splitInit(init);
203
+ const buildReq2 = (method, url, init, body) => {
204
+ const s2 = splitInit(init);
960
205
  const req = {
961
206
  method,
962
207
  url,
963
208
  ...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
209
+ ...s2.timeoutMs !== void 0 ? { timeoutMs: s2.timeoutMs } : {},
210
+ ...s2.poolKey !== void 0 ? { poolKey: s2.poolKey } : {},
211
+ init: s2.init
967
212
  };
968
- return applyInitHeaders(s.headers)(req);
213
+ return applyInitHeaders(s2.headers)(req);
969
214
  };
970
- const toResponse = (w, body) => ({
215
+ const toResponse2 = (w, body) => ({
971
216
  status: w.status,
972
217
  statusText: w.statusText,
973
218
  headers: w.headers,
974
219
  body
975
220
  });
221
+ const decodeResponse2 = (w, validator, schemaName) => asyncFlatMap(
222
+ decodeJsonBodyEffect(w.bodyText, validator, { schemaName }),
223
+ (body) => asyncSucceed(toResponse2(w, body))
224
+ );
976
225
  return {
977
226
  cfg,
978
227
  wire,
@@ -980,8 +229,9 @@ var createHttpCore = (cfg = {}) => {
980
229
  requestRaw,
981
230
  splitInit,
982
231
  applyInitHeaders,
983
- buildReq,
984
- toResponse
232
+ buildReq: buildReq2,
233
+ toResponse: toResponse2,
234
+ decodeResponse: decodeResponse2
985
235
  };
986
236
  };
987
237
  function httpClient(cfg = {}) {
@@ -995,20 +245,25 @@ function httpClient(cfg = {}) {
995
245
  const req = core.buildReq("GET", url, init);
996
246
  return core.withPromise(mapTryAsync(requestRaw(req), (w) => core.toResponse(w, w.bodyText)));
997
247
  };
998
- const getJson = (url, init) => {
248
+ const getJson = ((url, init) => {
999
249
  const base = core.buildReq("GET", url, init);
1000
250
  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
- );
251
+ return core.withPromise(asyncFlatMap(requestRaw(req), (w) => core.decodeResponse(w, init?.schema, init?.schemaName)));
252
+ });
253
+ const postJson = ((url, bodyObj, init) => {
1008
254
  return core.withPromise(
1009
- mapTryAsync(requestRaw(req), (w) => core.toResponse(w, JSON.parse(w.bodyText)))
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(requestRaw(req), (w) => core.decodeResponse(w, init?.schema, init?.schemaName));
263
+ }
264
+ )
1010
265
  );
1011
- };
266
+ });
1012
267
  return {
1013
268
  request,
1014
269
  get,
@@ -1050,17 +305,28 @@ function httpClientWithMeta(cfg = {}) {
1050
305
  return request(req);
1051
306
  };
1052
307
  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
308
  const startedAt = Date.now();
1058
309
  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
- }))
310
+ asyncFlatMap(
311
+ encodeJsonBodyEffect(bodyObj, init?.bodySchema, { schemaName: init?.bodySchemaName }),
312
+ (bodyText) => {
313
+ const base = core.buildReq("POST", url, init, bodyText);
314
+ const req = setHeaderIfMissing("content-type", "application/json")(
315
+ setHeaderIfMissing("accept", "application/json")(base)
316
+ );
317
+ return asyncFlatMap(
318
+ core.requestRaw(req),
319
+ (w) => asyncFlatMap(
320
+ core.decodeResponse(w, init?.schema, init?.schemaName),
321
+ (response) => asyncSucceed({
322
+ wire: w,
323
+ response,
324
+ meta: mkMeta(req, w, startedAt)
325
+ })
326
+ )
327
+ );
328
+ }
329
+ )
1064
330
  );
1065
331
  };
1066
332
  const getText = (url, init) => {
@@ -1079,11 +345,17 @@ function httpClientWithMeta(cfg = {}) {
1079
345
  const req = setHeaderIfMissing("accept", "application/json")(base);
1080
346
  const startedAt = Date.now();
1081
347
  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
- }))
348
+ asyncFlatMap(
349
+ core.requestRaw(req),
350
+ (w) => asyncFlatMap(
351
+ core.decodeResponse(w, init?.schema, init?.schemaName),
352
+ (response) => asyncSucceed({
353
+ wire: w,
354
+ response,
355
+ meta: mkMeta(req, w, startedAt)
356
+ })
357
+ )
358
+ )
1087
359
  );
1088
360
  };
1089
361
  return {
@@ -1124,137 +396,77 @@ function httpClientStream(cfg = {}) {
1124
396
  return make(wire);
1125
397
  }
1126
398
 
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
399
  // src/http/circuitBreaker.ts
1214
400
  function withCircuitBreaker(config = {}) {
1215
401
  if (config.perOrigin) {
1216
402
  const breakers = /* @__PURE__ */ new Map();
1217
- const getBreaker = (url) => {
403
+ const getBreaker = (url, onOpen) => {
1218
404
  try {
1219
405
  const origin = new URL(url).origin;
1220
406
  if (!breakers.has(origin)) {
1221
- breakers.set(origin, makeCircuitBreaker(config));
407
+ breakers.set(origin, makeCircuitBreaker({
408
+ ...config,
409
+ onStateChange: (from, to) => {
410
+ config.onStateChange?.(from, to);
411
+ if (to === "open") onOpen();
412
+ }
413
+ }));
1222
414
  }
1223
415
  return breakers.get(origin);
1224
416
  } catch {
1225
417
  if (!breakers.has("__global__")) {
1226
- breakers.set("__global__", makeCircuitBreaker(config));
418
+ breakers.set("__global__", makeCircuitBreaker({
419
+ ...config,
420
+ onStateChange: (from, to) => {
421
+ config.onStateChange?.(from, to);
422
+ if (to === "open") onOpen();
423
+ }
424
+ }));
1227
425
  }
1228
426
  return breakers.get("__global__");
1229
427
  }
1230
428
  };
1231
429
  return (next) => (req) => {
1232
- const breaker2 = getBreaker(req.url);
1233
- return protectLazy(breaker2, next, req);
430
+ const limiter = resolveAdaptiveLimiter(config, next);
431
+ const limiterKey = resolveAdaptiveLimiterKey(config, limiter, req);
432
+ const onOpen = () => limiterKey !== void 0 && limiter?.markCircuitOpen(limiterKey);
433
+ const breaker2 = getBreaker(req.url, onOpen);
434
+ return protectLazy(breaker2, next, req, onOpen);
1234
435
  };
1235
436
  }
1236
437
  const breaker = makeCircuitBreaker({
1237
438
  ...config,
439
+ onStateChange: (from, to) => {
440
+ config.onStateChange?.(from, to);
441
+ },
1238
442
  isFailure: config.isFailure ?? ((e) => {
1239
443
  const err = e;
1240
444
  return err._tag !== "BadUrl" && err._tag !== "Abort";
1241
445
  })
1242
446
  });
1243
447
  return (next) => (req) => {
1244
- return protectLazy(breaker, next, req);
448
+ const limiter = resolveAdaptiveLimiter(config, next);
449
+ const limiterKey = resolveAdaptiveLimiterKey(config, limiter, req);
450
+ const onOpen = () => limiterKey !== void 0 && limiter?.markCircuitOpen(limiterKey);
451
+ return protectLazy(breaker, next, req, onOpen);
1245
452
  };
1246
453
  }
1247
- function protectLazy(breaker, next, req) {
454
+ function protectLazy(breaker, next, req, onOpen) {
1248
455
  return {
1249
456
  _tag: "Async",
1250
457
  register: (env, cb) => {
1251
458
  let cancel;
1252
459
  try {
460
+ if (breaker.state() === "open") onOpen?.();
461
+ const finish = (exit) => {
462
+ if (breaker.state() === "open") onOpen?.();
463
+ cb(exit);
464
+ };
1253
465
  const deferred = {
1254
466
  _tag: "Async",
1255
467
  register: (innerEnv, innerCb) => registerHttpEffect(next(req), innerEnv, innerCb)
1256
468
  };
1257
- cancel = registerHttpEffect(breaker.protect(deferred), env, cb);
469
+ cancel = registerHttpEffect(breaker.protect(deferred), env, finish);
1258
470
  } catch (error) {
1259
471
  cb({
1260
472
  _tag: "Failure",
@@ -1268,48 +480,95 @@ function protectLazy(breaker, next, req) {
1268
480
  }
1269
481
  };
1270
482
  }
483
+ function resolveAdaptiveLimiter(config, next) {
484
+ return config.adaptiveLimiter ?? next.adaptiveLimiter;
485
+ }
486
+ function resolveAdaptiveLimiterKey(config, limiter, req) {
487
+ if (!limiter) return void 0;
488
+ if (config.adaptiveLimiterKey) return config.adaptiveLimiterKey(req);
489
+ if (req.poolKey) return req.poolKey;
490
+ if (limiter.keyResolver === "global") return "global";
491
+ try {
492
+ const url = new URL(req.url);
493
+ if (limiter.keyResolver === "host") return url.host;
494
+ return url.origin;
495
+ } catch {
496
+ return "global";
497
+ }
498
+ }
1271
499
 
1272
500
  // src/http/tracing.ts
1273
501
  function withTracing(tracer) {
1274
502
  return (next) => (req) => {
1275
503
  return tracer.span(
1276
- `HTTP ${req.method} ${req.url}`,
1277
- next(req),
1278
- {
1279
- "http.method": req.method,
1280
- "http.url": req.url,
1281
- ...req.headers?.["content-type"] ? { "http.content_type": req.headers["content-type"] } : {}
1282
- }
1283
- );
1284
- };
1285
- }
1286
-
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
- });
504
+ `HTTP ${req.method} ${req.url}`,
505
+ next(req),
506
+ {
507
+ "http.method": req.method,
508
+ "http.url": req.url,
509
+ ...req.headers?.["content-type"] ? { "http.content_type": req.headers["content-type"] } : {}
1310
510
  }
1311
- }
1312
- );
511
+ );
512
+ };
513
+ }
514
+
515
+ // src/http/errors.ts
516
+ var HTTP_ERROR_TAGS = /* @__PURE__ */ new Set([
517
+ "Abort",
518
+ "BadUrl",
519
+ "FetchError",
520
+ "Timeout",
521
+ "PoolRejected",
522
+ "PoolTimeout",
523
+ "PoolClosed",
524
+ "BatchSplitError"
525
+ ]);
526
+ function isHttpError(error) {
527
+ return hasTag(error) && HTTP_ERROR_TAGS.has(error._tag);
528
+ }
529
+ function isValidationError(error) {
530
+ return hasTag(error) && error._tag === "ValidationError";
531
+ }
532
+ function isCircuitBreakerOpen(error) {
533
+ return hasTag(error) && error._tag === "CircuitBreakerOpen";
534
+ }
535
+ function isKnownHttpError(error) {
536
+ return isHttpError(error) || isValidationError(error) || isCircuitBreakerOpen(error);
537
+ }
538
+ function matchHttpError(error, handlers) {
539
+ if (isKnownHttpError(error)) {
540
+ const handler = handlers[error._tag];
541
+ if (handler) return handler(error);
542
+ }
543
+ return handlers.default?.(error);
544
+ }
545
+ function formatHttpError(error) {
546
+ if (isValidationError(error)) {
547
+ const phase = error.phase ? `${error.phase} ` : "";
548
+ return `${phase}validation failed: ${error.message}`;
549
+ }
550
+ if (isCircuitBreakerOpen(error)) {
551
+ return `Circuit breaker is open after ${error.failures} failure(s)`;
552
+ }
553
+ if (!isHttpError(error)) {
554
+ return error instanceof Error ? error.message : String(error);
555
+ }
556
+ switch (error._tag) {
557
+ case "Abort":
558
+ return "HTTP request aborted";
559
+ case "BadUrl":
560
+ case "FetchError":
561
+ case "PoolRejected":
562
+ case "PoolTimeout":
563
+ case "PoolClosed":
564
+ case "BatchSplitError":
565
+ return error.message;
566
+ case "Timeout":
567
+ return error.message;
568
+ }
569
+ }
570
+ function hasTag(error) {
571
+ return typeof error === "object" && error !== null && typeof error._tag === "string";
1313
572
  }
1314
573
 
1315
574
  // src/http/body.ts
@@ -1522,6 +781,186 @@ function rejectAll(entry, err) {
1522
781
  }
1523
782
  }
1524
783
 
784
+ // src/http/lifecycle/batch.ts
785
+ function clamp(value, min, max) {
786
+ if (!Number.isFinite(value)) return min;
787
+ return Math.max(min, Math.min(max, Math.floor(value)));
788
+ }
789
+ function safeEmitBatch(onEvent, event) {
790
+ if (!onEvent) return;
791
+ try {
792
+ onEvent(event);
793
+ } catch {
794
+ }
795
+ }
796
+ function withBatch(config, onEvent) {
797
+ const windowMs = clamp(config.windowMs, 1, 5e3);
798
+ const maxBatchSize = clamp(config.maxBatchSize, 2, 1e4);
799
+ const { batch, batchKey } = config;
800
+ const groups = /* @__PURE__ */ new Map();
801
+ return (next) => {
802
+ function dispatch(group) {
803
+ group.dispatched = true;
804
+ if (group.timer !== null) {
805
+ clearTimeout(group.timer);
806
+ group.timer = null;
807
+ }
808
+ groups.delete(group.key);
809
+ const requests = group.waiters.map((w) => w.request);
810
+ const waiterCount = group.waiters.length;
811
+ let coalescedReq;
812
+ try {
813
+ coalescedReq = batch.coalesce(requests);
814
+ } catch (err) {
815
+ const error = { _tag: "FetchError", message: String(err) };
816
+ rejectAllWaiters(group, error);
817
+ return;
818
+ }
819
+ coalescedReq = {
820
+ ...coalescedReq,
821
+ init: {
822
+ ...coalescedReq.init ?? {},
823
+ signal: group.controller.signal
824
+ }
825
+ };
826
+ safeEmitBatch(onEvent, { type: "batch-dispatch", batchKey: group.key, batchSize: waiterCount });
827
+ const innerEffect = next(coalescedReq);
828
+ registerHttpEffect(innerEffect, void 0, (exit) => {
829
+ if (exit._tag === "Success") {
830
+ let responses;
831
+ try {
832
+ responses = batch.split(exit.value, requests);
833
+ } catch (err2) {
834
+ const error = { _tag: "FetchError", message: String(err2) };
835
+ rejectAllWaiters(group, error);
836
+ return;
837
+ }
838
+ if (responses.length !== waiterCount) {
839
+ const error = {
840
+ _tag: "BatchSplitError",
841
+ expected: waiterCount,
842
+ actual: responses.length,
843
+ message: `split returned ${responses.length} responses but expected ${waiterCount}`
844
+ };
845
+ rejectAllWaiters(group, error);
846
+ return;
847
+ }
848
+ const waiters = group.waiters.slice();
849
+ for (let i = 0; i < waiters.length; i++) {
850
+ waiters[i].resolve(responses[i]);
851
+ }
852
+ return;
853
+ }
854
+ if (exit.cause._tag === "Fail") {
855
+ rejectAllWaiters(group, exit.cause.error);
856
+ return;
857
+ }
858
+ if (exit.cause._tag === "Interrupt") {
859
+ rejectAllWaiters(group, { _tag: "Abort" });
860
+ return;
861
+ }
862
+ const err = { _tag: "FetchError", message: String(exit.cause.defect ?? "unknown") };
863
+ rejectAllWaiters(group, err);
864
+ });
865
+ }
866
+ return (req) => {
867
+ let key;
868
+ try {
869
+ key = batchKey(req);
870
+ } catch {
871
+ return next(req);
872
+ }
873
+ if (!key) {
874
+ return next(req);
875
+ }
876
+ return {
877
+ _tag: "Async",
878
+ register: (_env, cb) => {
879
+ let callerDone = false;
880
+ const finishCaller = (exit) => {
881
+ if (callerDone) return;
882
+ callerDone = true;
883
+ cb(exit);
884
+ };
885
+ const waiter = {
886
+ request: req,
887
+ resolve: (res) => {
888
+ finishCaller({ _tag: "Success", value: res });
889
+ },
890
+ reject: (err) => {
891
+ finishCaller({ _tag: "Failure", cause: Cause.fail(err) });
892
+ }
893
+ };
894
+ const existing = groups.get(key);
895
+ if (existing && !existing.dispatched) {
896
+ existing.waiters.push(waiter);
897
+ safeEmitBatch(onEvent, { type: "batch-hit", batchKey: key });
898
+ if (existing.waiters.length >= maxBatchSize) {
899
+ dispatch(existing);
900
+ }
901
+ return () => {
902
+ if (callerDone) return;
903
+ const idx = existing.waiters.indexOf(waiter);
904
+ if (idx >= 0) {
905
+ existing.waiters.splice(idx, 1);
906
+ }
907
+ if (existing.waiters.length === 0 && !existing.dispatched) {
908
+ if (existing.timer !== null) {
909
+ clearTimeout(existing.timer);
910
+ existing.timer = null;
911
+ }
912
+ groups.delete(key);
913
+ }
914
+ if (existing.waiters.length === 0 && existing.dispatched) {
915
+ existing.controller.abort();
916
+ }
917
+ finishCaller({ _tag: "Failure", cause: Cause.interrupt() });
918
+ };
919
+ }
920
+ const controller = new AbortController();
921
+ const group = {
922
+ key,
923
+ controller,
924
+ waiters: [waiter],
925
+ timer: null,
926
+ dispatched: false
927
+ };
928
+ group.timer = setTimeout(() => {
929
+ if (!group.dispatched && group.waiters.length > 0) {
930
+ dispatch(group);
931
+ }
932
+ }, windowMs);
933
+ groups.set(key, group);
934
+ return () => {
935
+ if (callerDone) return;
936
+ const idx = group.waiters.indexOf(waiter);
937
+ if (idx >= 0) {
938
+ group.waiters.splice(idx, 1);
939
+ }
940
+ if (group.waiters.length === 0 && !group.dispatched) {
941
+ if (group.timer !== null) {
942
+ clearTimeout(group.timer);
943
+ group.timer = null;
944
+ }
945
+ groups.delete(key);
946
+ }
947
+ if (group.waiters.length === 0 && group.dispatched) {
948
+ group.controller.abort();
949
+ }
950
+ finishCaller({ _tag: "Failure", cause: Cause.interrupt() });
951
+ };
952
+ }
953
+ };
954
+ };
955
+ };
956
+ }
957
+ function rejectAllWaiters(group, err) {
958
+ const waiters = group.waiters.slice();
959
+ for (const w of waiters) {
960
+ w.reject(err);
961
+ }
962
+ }
963
+
1525
964
  // src/http/lifecycle/timing.ts
1526
965
  var now = typeof performance !== "undefined" && typeof performance.now === "function" ? () => performance.now() : () => Date.now();
1527
966
 
@@ -2096,7 +1535,7 @@ var PriorityQueue = class {
2096
1535
  };
2097
1536
 
2098
1537
  // src/http/lifecycle/priorityScheduler.ts
2099
- var DEFAULT_CONCURRENCY2 = 32;
1538
+ var DEFAULT_CONCURRENCY = 32;
2100
1539
  function extractPriority(req) {
2101
1540
  const fromReq = req.priority;
2102
1541
  if (fromReq !== void 0) return clampPriority(fromReq);
@@ -2216,7 +1655,7 @@ function withPriority(config) {
2216
1655
  return Object.assign(middleware, { queueDepth });
2217
1656
  }
2218
1657
  function resolveConcurrency(value) {
2219
- if (value === void 0 || !Number.isFinite(value)) return DEFAULT_CONCURRENCY2;
1658
+ if (value === void 0 || !Number.isFinite(value)) return DEFAULT_CONCURRENCY;
2220
1659
  return Math.max(1, Math.floor(value));
2221
1660
  }
2222
1661
  function resolveQueueTimeout(value) {
@@ -2240,6 +1679,8 @@ var LifecycleStatsTracker = class {
2240
1679
  _requestsCompleted = 0;
2241
1680
  _requestsFailed = 0;
2242
1681
  _retries = 0;
1682
+ _batchDispatches = 0;
1683
+ _batchedRequests = 0;
2243
1684
  _onEvent;
2244
1685
  _wireStats;
2245
1686
  /**
@@ -2400,6 +1841,19 @@ var LifecycleStatsTracker = class {
2400
1841
  retry() {
2401
1842
  this._retries++;
2402
1843
  }
1844
+ /**
1845
+ * Records a batch dispatch. Increments the batch dispatches counter by 1.
1846
+ */
1847
+ batchDispatch() {
1848
+ this._batchDispatches++;
1849
+ }
1850
+ /**
1851
+ * Records requests that were coalesced into a batch.
1852
+ * @param count - The number of individual requests in the batch.
1853
+ */
1854
+ batchedRequests(count) {
1855
+ this._batchedRequests += count;
1856
+ }
2403
1857
  // --- Event emission ---
2404
1858
  /**
2405
1859
  * Emits a lifecycle event to the registered `onEvent` callback.
@@ -2468,11 +1922,447 @@ var LifecycleStatsTracker = class {
2468
1922
  requestsCompleted: this._requestsCompleted,
2469
1923
  requestsFailed: this._requestsFailed,
2470
1924
  retries: this._retries,
1925
+ batchDispatches: this._batchDispatches,
1926
+ batchedRequests: this._batchedRequests,
2471
1927
  wire: this._wireStats()
2472
1928
  });
2473
1929
  }
2474
1930
  };
2475
1931
 
1932
+ // src/http/prewarm/validation.ts
1933
+ function validateOrigin(origin) {
1934
+ if (!origin || typeof origin !== "string") {
1935
+ throw new Error(`validateOrigin: origin must be a non-empty string, got "${origin}"`);
1936
+ }
1937
+ const trimmed = origin.trim();
1938
+ if (!trimmed) {
1939
+ throw new Error(`validateOrigin: origin must be a non-empty string, got "${origin}"`);
1940
+ }
1941
+ const stripped = stripTrailingSlashes(trimmed);
1942
+ const lower = stripped.toLowerCase();
1943
+ if (!lower.startsWith("http://") && !lower.startsWith("https://")) {
1944
+ throw new Error(
1945
+ `validateOrigin: invalid origin "${origin}" \u2014 must start with http:// or https://`
1946
+ );
1947
+ }
1948
+ let parsed;
1949
+ try {
1950
+ parsed = new URL(stripped);
1951
+ } catch {
1952
+ throw new Error(
1953
+ `validateOrigin: invalid origin "${origin}" \u2014 must be a valid URL origin (scheme + host + optional port)`
1954
+ );
1955
+ }
1956
+ if (parsed.pathname !== "/" && parsed.pathname !== "") {
1957
+ throw new Error(
1958
+ `validateOrigin: invalid origin "${origin}" \u2014 must not contain a path`
1959
+ );
1960
+ }
1961
+ if (parsed.search) {
1962
+ throw new Error(
1963
+ `validateOrigin: invalid origin "${origin}" \u2014 must not contain query parameters`
1964
+ );
1965
+ }
1966
+ if (parsed.hash) {
1967
+ throw new Error(
1968
+ `validateOrigin: invalid origin "${origin}" \u2014 must not contain a fragment`
1969
+ );
1970
+ }
1971
+ return parsed.origin;
1972
+ }
1973
+ function stripTrailingSlashes(value) {
1974
+ let end = value.length;
1975
+ while (end > 0 && value.charCodeAt(end - 1) === 47) {
1976
+ end--;
1977
+ }
1978
+ return end === value.length ? value : value.slice(0, end);
1979
+ }
1980
+
1981
+ // src/http/prewarm/platform.ts
1982
+ function detectPlatform() {
1983
+ return typeof window !== "undefined" && typeof window.document !== "undefined" ? "browser" : "node";
1984
+ }
1985
+ function validateFetchAvailable() {
1986
+ if (typeof globalThis.fetch !== "function") {
1987
+ throw new Error(
1988
+ "makePrewarmManager: global fetch is not available. Requires Node.js 18+ or modern browser."
1989
+ );
1990
+ }
1991
+ if (typeof globalThis.AbortController !== "function") {
1992
+ throw new Error(
1993
+ "makePrewarmManager: global AbortController is not available."
1994
+ );
1995
+ }
1996
+ }
1997
+
1998
+ // src/http/prewarm/probe.ts
1999
+ async function executeProbe(origin, options) {
2000
+ const { timeoutMs, signal, platform } = options;
2001
+ const url = `${origin}/`;
2002
+ const start = Date.now();
2003
+ const timeoutController = new AbortController();
2004
+ const timeoutId = setTimeout(() => timeoutController.abort(), timeoutMs);
2005
+ const onExternalAbort = () => timeoutController.abort();
2006
+ signal.addEventListener("abort", onExternalAbort);
2007
+ try {
2008
+ if (options.client) {
2009
+ const result = await executeProbeViaClient(options.client, url, timeoutController.signal);
2010
+ const durationMs2 = Date.now() - start;
2011
+ if (result.ok) {
2012
+ return { ok: true, durationMs: durationMs2 };
2013
+ }
2014
+ return { ok: false, durationMs: durationMs2, error: result.error };
2015
+ }
2016
+ const fetchOptions = {
2017
+ method: "HEAD",
2018
+ signal: timeoutController.signal,
2019
+ // Prevent caching of probe requests
2020
+ cache: "no-store"
2021
+ };
2022
+ if (platform === "browser") {
2023
+ fetchOptions.mode = "no-cors";
2024
+ }
2025
+ await globalThis.fetch(url, fetchOptions);
2026
+ const durationMs = Date.now() - start;
2027
+ return { ok: true, durationMs };
2028
+ } catch (err) {
2029
+ const durationMs = Date.now() - start;
2030
+ if (signal.aborted) {
2031
+ return { ok: false, durationMs, error: "cancelled" };
2032
+ }
2033
+ if (timeoutController.signal.aborted && !signal.aborted) {
2034
+ return { ok: false, durationMs, error: `Probe timed out after ${timeoutMs}ms` };
2035
+ }
2036
+ const message = err instanceof Error ? err.message : String(err);
2037
+ return { ok: false, durationMs, error: message };
2038
+ } finally {
2039
+ clearTimeout(timeoutId);
2040
+ signal.removeEventListener("abort", onExternalAbort);
2041
+ }
2042
+ }
2043
+ async function executeProbeViaClient(client, url, signal) {
2044
+ const { registerHttpEffect: registerHttpEffect2 } = await import("../effectRunner-A4CHJXJI.js");
2045
+ return new Promise((resolve) => {
2046
+ const effect = client({
2047
+ method: "HEAD",
2048
+ url,
2049
+ init: { signal }
2050
+ });
2051
+ const cancel = registerHttpEffect2(effect, {}, (exit) => {
2052
+ if (exit._tag === "Success") {
2053
+ resolve({ ok: true });
2054
+ } else {
2055
+ const cause = exit.cause;
2056
+ if (cause?._tag === "Fail") {
2057
+ const err = cause.error;
2058
+ resolve({ ok: false, error: err?.message ?? err?._tag ?? "Unknown error" });
2059
+ } else {
2060
+ resolve({ ok: false, error: "cancelled" });
2061
+ }
2062
+ }
2063
+ });
2064
+ if (signal.aborted) {
2065
+ cancel();
2066
+ } else {
2067
+ signal.addEventListener("abort", () => {
2068
+ cancel();
2069
+ }, { once: true });
2070
+ }
2071
+ });
2072
+ }
2073
+
2074
+ // src/http/prewarm/connectionState.ts
2075
+ function makeConnectionStateMap(origins, keepAliveDurationMs) {
2076
+ const entries = /* @__PURE__ */ new Map();
2077
+ for (const origin of origins) {
2078
+ entries.set(origin, {
2079
+ origin,
2080
+ status: "idle",
2081
+ lastProbeAt: void 0,
2082
+ warmUntil: void 0
2083
+ });
2084
+ }
2085
+ function markWarm(origin, now2) {
2086
+ const entry = entries.get(origin);
2087
+ if (!entry) return;
2088
+ const timestamp = now2 ?? Date.now();
2089
+ entry.status = "warm";
2090
+ entry.lastProbeAt = timestamp;
2091
+ entry.warmUntil = timestamp + keepAliveDurationMs;
2092
+ }
2093
+ function markExpired(origin) {
2094
+ const entry = entries.get(origin);
2095
+ if (!entry) return;
2096
+ entry.status = "expired";
2097
+ }
2098
+ function markIdle(origin) {
2099
+ const entry = entries.get(origin);
2100
+ if (!entry) return;
2101
+ entry.status = "idle";
2102
+ entry.lastProbeAt = void 0;
2103
+ entry.warmUntil = void 0;
2104
+ }
2105
+ function markProbing(origin) {
2106
+ const entry = entries.get(origin);
2107
+ if (!entry) return;
2108
+ entry.status = "probing";
2109
+ }
2110
+ function isWarm(origin, now2) {
2111
+ const entry = entries.get(origin);
2112
+ if (!entry) return false;
2113
+ if (entry.status !== "warm") return false;
2114
+ if (entry.lastProbeAt === void 0 || entry.warmUntil === void 0) return false;
2115
+ const currentTime = now2 ?? Date.now();
2116
+ if (currentTime >= entry.warmUntil) {
2117
+ entry.status = "expired";
2118
+ return false;
2119
+ }
2120
+ return true;
2121
+ }
2122
+ function getState(origin) {
2123
+ const entry = entries.get(origin);
2124
+ if (!entry) return void 0;
2125
+ return {
2126
+ origin: entry.origin,
2127
+ status: entry.status,
2128
+ lastProbeAt: entry.lastProbeAt,
2129
+ warmUntil: entry.warmUntil
2130
+ };
2131
+ }
2132
+ function snapshot() {
2133
+ const result = [];
2134
+ for (const entry of entries.values()) {
2135
+ result.push({
2136
+ origin: entry.origin,
2137
+ status: entry.status,
2138
+ lastProbeAt: entry.lastProbeAt,
2139
+ warmUntil: entry.warmUntil
2140
+ });
2141
+ }
2142
+ return { origins: result };
2143
+ }
2144
+ return { markWarm, markExpired, markIdle, markProbing, isWarm, getState, snapshot };
2145
+ }
2146
+
2147
+ // src/http/prewarm/budgetSemaphore.ts
2148
+ function makeBudgetSemaphore(capacity) {
2149
+ if (capacity < 1 || !Number.isFinite(capacity)) {
2150
+ throw new Error(`makeBudgetSemaphore: capacity must be >= 1, got ${capacity}`);
2151
+ }
2152
+ capacity = Math.floor(capacity);
2153
+ let active = 0;
2154
+ const waiters = [];
2155
+ function release() {
2156
+ active--;
2157
+ if (waiters.length > 0) {
2158
+ const next = waiters.shift();
2159
+ active++;
2160
+ next({ release });
2161
+ }
2162
+ }
2163
+ function acquire() {
2164
+ if (active < capacity) {
2165
+ active++;
2166
+ return Promise.resolve({ release });
2167
+ }
2168
+ return new Promise((resolve) => {
2169
+ waiters.push(resolve);
2170
+ });
2171
+ }
2172
+ function tryAcquire() {
2173
+ if (active < capacity) {
2174
+ active++;
2175
+ return { release };
2176
+ }
2177
+ return void 0;
2178
+ }
2179
+ function available() {
2180
+ return capacity - active;
2181
+ }
2182
+ function queued() {
2183
+ return waiters.length;
2184
+ }
2185
+ return { acquire, tryAcquire, available, queued };
2186
+ }
2187
+
2188
+ // src/http/prewarm/prewarmManager.ts
2189
+ var DEFAULTS = {
2190
+ keepAliveDurationMs: 55e3,
2191
+ budget: 4,
2192
+ probeTimeoutMs: 5e3,
2193
+ autoRefresh: false,
2194
+ useClientPool: false
2195
+ };
2196
+ function makePrewarmManager(config) {
2197
+ validateFetchAvailable();
2198
+ const keepAliveDurationMs = config.keepAliveDurationMs ?? DEFAULTS.keepAliveDurationMs;
2199
+ const budget = config.budget ?? DEFAULTS.budget;
2200
+ const probeTimeoutMs = config.probeTimeoutMs ?? DEFAULTS.probeTimeoutMs;
2201
+ const autoRefresh = config.autoRefresh ?? DEFAULTS.autoRefresh;
2202
+ const useClientPool = config.useClientPool ?? DEFAULTS.useClientPool;
2203
+ const onEvent = config.onEvent;
2204
+ const client = config.client;
2205
+ const origins = config.origins.map((o) => validateOrigin(o));
2206
+ const platform = detectPlatform();
2207
+ const stateMap = makeConnectionStateMap(origins, keepAliveDurationMs);
2208
+ const semaphore = makeBudgetSemaphore(budget);
2209
+ const abortControllers = /* @__PURE__ */ new Map();
2210
+ const refreshTimers = /* @__PURE__ */ new Map();
2211
+ let disposed = false;
2212
+ function emit3(event) {
2213
+ if (onEvent) {
2214
+ try {
2215
+ onEvent(event);
2216
+ } catch {
2217
+ }
2218
+ }
2219
+ }
2220
+ function scheduleAutoRefresh(origin) {
2221
+ if (!autoRefresh || disposed) return;
2222
+ clearRefreshTimer(origin);
2223
+ const delay = Math.floor(0.8 * keepAliveDurationMs);
2224
+ const timer = setTimeout(() => {
2225
+ refreshTimers.delete(origin);
2226
+ if (disposed) return;
2227
+ if (stateMap.isWarm(origin)) {
2228
+ stateMap.markExpired(origin);
2229
+ emit3({
2230
+ type: "connection-expired",
2231
+ origin,
2232
+ timestamp: Date.now()
2233
+ });
2234
+ }
2235
+ warm(origin).catch(() => {
2236
+ });
2237
+ }, delay);
2238
+ if (typeof timer === "object" && "unref" in timer) {
2239
+ timer.unref();
2240
+ }
2241
+ refreshTimers.set(origin, timer);
2242
+ }
2243
+ function clearRefreshTimer(origin) {
2244
+ const timer = refreshTimers.get(origin);
2245
+ if (timer !== void 0) {
2246
+ clearTimeout(timer);
2247
+ refreshTimers.delete(origin);
2248
+ }
2249
+ }
2250
+ function clearAllRefreshTimers() {
2251
+ for (const [, timer] of refreshTimers) {
2252
+ clearTimeout(timer);
2253
+ }
2254
+ refreshTimers.clear();
2255
+ }
2256
+ async function warm(origin) {
2257
+ if (disposed) {
2258
+ return { origin, status: "cancelled", durationMs: 0 };
2259
+ }
2260
+ if (stateMap.isWarm(origin)) {
2261
+ return { origin, status: "already-warm", durationMs: 0 };
2262
+ }
2263
+ const { release } = await semaphore.acquire();
2264
+ if (disposed) {
2265
+ release();
2266
+ return { origin, status: "cancelled", durationMs: 0 };
2267
+ }
2268
+ const controller = new AbortController();
2269
+ abortControllers.set(origin, controller);
2270
+ stateMap.markProbing(origin);
2271
+ try {
2272
+ const probeOptions = {
2273
+ timeoutMs: probeTimeoutMs,
2274
+ signal: controller.signal,
2275
+ platform,
2276
+ client: useClientPool ? client : void 0
2277
+ };
2278
+ const outcome = await executeProbe(origin, probeOptions);
2279
+ if (controller.signal.aborted) {
2280
+ stateMap.markIdle(origin);
2281
+ emit3({
2282
+ type: "connection-cancelled",
2283
+ origin,
2284
+ timestamp: Date.now()
2285
+ });
2286
+ return { origin, status: "cancelled", durationMs: outcome.durationMs };
2287
+ }
2288
+ if (outcome.ok) {
2289
+ stateMap.markWarm(origin);
2290
+ emit3({
2291
+ type: "connection-warmed",
2292
+ origin,
2293
+ timestamp: Date.now(),
2294
+ durationMs: outcome.durationMs
2295
+ });
2296
+ scheduleAutoRefresh(origin);
2297
+ return { origin, status: "warmed", durationMs: outcome.durationMs };
2298
+ } else {
2299
+ stateMap.markIdle(origin);
2300
+ emit3({
2301
+ type: "connection-failed",
2302
+ origin,
2303
+ timestamp: Date.now(),
2304
+ error: outcome.error
2305
+ });
2306
+ return { origin, status: "failed", durationMs: outcome.durationMs, error: outcome.error };
2307
+ }
2308
+ } catch (err) {
2309
+ stateMap.markIdle(origin);
2310
+ const error = err instanceof Error ? err.message : String(err);
2311
+ emit3({
2312
+ type: "connection-failed",
2313
+ origin,
2314
+ timestamp: Date.now(),
2315
+ error
2316
+ });
2317
+ return { origin, status: "failed", durationMs: 0, error };
2318
+ } finally {
2319
+ abortControllers.delete(origin);
2320
+ release();
2321
+ }
2322
+ }
2323
+ async function warmAll() {
2324
+ const results = await Promise.all(origins.map((o) => warm(o)));
2325
+ return results;
2326
+ }
2327
+ function cancel(origin) {
2328
+ const controller = abortControllers.get(origin);
2329
+ if (controller) {
2330
+ controller.abort();
2331
+ }
2332
+ clearRefreshTimer(origin);
2333
+ }
2334
+ function cancelAll() {
2335
+ for (const [, controller] of abortControllers) {
2336
+ controller.abort();
2337
+ }
2338
+ clearAllRefreshTimers();
2339
+ }
2340
+ function isWarm(origin) {
2341
+ if (disposed) return false;
2342
+ return stateMap.isWarm(origin);
2343
+ }
2344
+ function status() {
2345
+ return stateMap.snapshot();
2346
+ }
2347
+ function dispose() {
2348
+ if (disposed) return;
2349
+ disposed = true;
2350
+ cancelAll();
2351
+ for (const origin of origins) {
2352
+ stateMap.markIdle(origin);
2353
+ }
2354
+ }
2355
+ return {
2356
+ warm,
2357
+ warmAll,
2358
+ isWarm,
2359
+ cancel,
2360
+ cancelAll,
2361
+ status,
2362
+ dispose
2363
+ };
2364
+ }
2365
+
2476
2366
  // src/http/lifecycle/lifecycleClient.ts
2477
2367
  function validateGlobals() {
2478
2368
  if (typeof fetch === "undefined") {
@@ -2487,10 +2377,11 @@ function validateGlobals() {
2487
2377
  }
2488
2378
  }
2489
2379
  function extractWireConfig(config) {
2490
- const { dedup, cache, priority, retry, onEvent, ...wireConfig } = config;
2380
+ const { dedup, batch, cache, priority, retry, prewarm, onEvent, ...wireConfig } = config;
2491
2381
  return wireConfig;
2492
2382
  }
2493
2383
  function makeLifecycleClient(config = {}) {
2384
+ validateLifecycleClientConfig(config);
2494
2385
  validateGlobals();
2495
2386
  const wireConfig = extractWireConfig(config);
2496
2387
  const wireClient = makeHttp(wireConfig);
@@ -2500,15 +2391,38 @@ function makeLifecycleClient(config = {}) {
2500
2391
  wireStats: wireClient.stats
2501
2392
  });
2502
2393
  const hasDedup = config.dedup !== void 0 && config.dedup !== false;
2394
+ const hasBatch = config.batch !== void 0 && config.batch !== false;
2503
2395
  const hasCache = config.cache !== void 0 && config.cache !== false;
2504
2396
  const hasPriority = config.priority !== void 0 && config.priority !== false;
2505
2397
  const hasRetry = config.retry !== void 0 && config.retry !== false;
2506
- if (!hasDedup && !hasCache && !hasPriority && !hasRetry) {
2398
+ if (!hasDedup && !hasBatch && !hasCache && !hasPriority && !hasRetry) {
2399
+ let prewarmMgr2;
2400
+ const hasPrewarm2 = config.prewarm !== void 0 && config.prewarm !== false;
2401
+ if (hasPrewarm2) {
2402
+ const prewarmConfig = config.prewarm;
2403
+ const prewarmOrigins = prewarmConfig.origins ?? [];
2404
+ if (prewarmOrigins.length > 0) {
2405
+ prewarmMgr2 = makePrewarmManager({
2406
+ origins: prewarmOrigins,
2407
+ keepAliveDurationMs: prewarmConfig.keepAliveDurationMs,
2408
+ budget: prewarmConfig.budget,
2409
+ probeTimeoutMs: prewarmConfig.probeTimeoutMs,
2410
+ autoRefresh: prewarmConfig.autoRefresh,
2411
+ useClientPool: prewarmConfig.useClientPool,
2412
+ client: prewarmConfig.useClientPool ? wireClient : void 0,
2413
+ onEvent: prewarmConfig.onEvent
2414
+ });
2415
+ }
2416
+ }
2507
2417
  return buildLifecycleClient(wireClient, tracker, {
2508
2418
  cacheInvalidate: noopInvalidate,
2509
2419
  cacheClear: noopClear,
2510
- cancelAll: () => cancelControllers(activeControllers),
2511
- activeControllers
2420
+ cancelAll: () => cancelControllers(activeControllers, prewarmMgr2),
2421
+ shutdown: () => shutdownClient(activeControllers, prewarmMgr2, wireClient.shutdown),
2422
+ activeControllers,
2423
+ adaptiveLimiter: wireClient.adaptiveLimiter,
2424
+ prewarmManager: prewarmMgr2,
2425
+ afterResponse: hasPrewarm2 ? config.prewarm.afterResponse : void 0
2512
2426
  });
2513
2427
  }
2514
2428
  let priorityMiddleware;
@@ -2555,6 +2469,17 @@ function makeLifecycleClient(config = {}) {
2555
2469
  }
2556
2470
  });
2557
2471
  }
2472
+ let batchMiddleware;
2473
+ if (hasBatch) {
2474
+ const batchConfig = config.batch;
2475
+ batchMiddleware = withBatch(batchConfig, (event) => {
2476
+ if (event.type === "batch-dispatch") {
2477
+ tracker.batchDispatch();
2478
+ tracker.batchedRequests(event.batchSize ?? 0);
2479
+ }
2480
+ tracker.emit(event.type, { batchKey: event.batchKey, batchSize: event.batchSize });
2481
+ });
2482
+ }
2558
2483
  let composedFn = wireClient;
2559
2484
  if (priorityMiddleware) {
2560
2485
  composedFn = priorityMiddleware(composedFn);
@@ -2578,15 +2503,40 @@ function makeLifecycleClient(config = {}) {
2578
2503
  if (cacheLayer) {
2579
2504
  composedFn = cacheLayer.middleware(composedFn);
2580
2505
  }
2506
+ if (batchMiddleware) {
2507
+ composedFn = batchMiddleware(composedFn);
2508
+ }
2581
2509
  if (dedupMiddleware) {
2582
2510
  composedFn = dedupMiddleware(composedFn);
2583
2511
  }
2512
+ let prewarmMgr;
2513
+ const hasPrewarm = config.prewarm !== void 0 && config.prewarm !== false;
2514
+ if (hasPrewarm) {
2515
+ const prewarmConfig = config.prewarm;
2516
+ const prewarmOrigins = prewarmConfig.origins ?? [];
2517
+ if (prewarmOrigins.length > 0) {
2518
+ prewarmMgr = makePrewarmManager({
2519
+ origins: prewarmOrigins,
2520
+ keepAliveDurationMs: prewarmConfig.keepAliveDurationMs,
2521
+ budget: prewarmConfig.budget,
2522
+ probeTimeoutMs: prewarmConfig.probeTimeoutMs,
2523
+ autoRefresh: prewarmConfig.autoRefresh,
2524
+ useClientPool: prewarmConfig.useClientPool,
2525
+ client: prewarmConfig.useClientPool ? wireClient : void 0,
2526
+ onEvent: prewarmConfig.onEvent
2527
+ });
2528
+ }
2529
+ }
2584
2530
  return buildLifecycleClient(composedFn, tracker, {
2585
2531
  cacheInvalidate: cacheLayer?.invalidate ?? noopInvalidate,
2586
2532
  cacheClear: cacheLayer?.clear ?? noopClear,
2587
- cancelAll: () => cancelControllers(activeControllers),
2533
+ cancelAll: () => cancelControllers(activeControllers, prewarmMgr),
2534
+ shutdown: () => shutdownClient(activeControllers, prewarmMgr, wireClient.shutdown),
2588
2535
  activeControllers,
2589
- queueDepth: priorityMiddleware?.queueDepth
2536
+ adaptiveLimiter: wireClient.adaptiveLimiter,
2537
+ queueDepth: priorityMiddleware?.queueDepth,
2538
+ prewarmManager: prewarmMgr,
2539
+ afterResponse: hasPrewarm ? config.prewarm.afterResponse : void 0
2590
2540
  });
2591
2541
  }
2592
2542
  function makeHttpClient(config = {}) {
@@ -2603,13 +2553,15 @@ function buildLifecycleClient(fn, tracker, internals) {
2603
2553
  return tracker.snapshot();
2604
2554
  };
2605
2555
  const withMw = (mw) => {
2606
- const wrappedFn = mw(fn);
2556
+ const wrappedFn = mw(withLifecycleMetadata(fn, internals));
2607
2557
  return buildLifecycleClient(wrappedFn, tracker, internals);
2608
2558
  };
2609
2559
  const lifecycleClient = Object.assign(client, {
2610
2560
  with: withMw,
2611
2561
  stats,
2612
2562
  cancelAll: internals.cancelAll,
2563
+ shutdown: internals.shutdown,
2564
+ adaptiveLimiter: internals.adaptiveLimiter,
2613
2565
  cache: {
2614
2566
  invalidate: internals.cacheInvalidate,
2615
2567
  clear: internals.cacheClear
@@ -2617,13 +2569,27 @@ function buildLifecycleClient(fn, tracker, internals) {
2617
2569
  });
2618
2570
  return lifecycleClient;
2619
2571
  }
2620
- function cancelControllers(activeControllers) {
2572
+ function withLifecycleMetadata(fn, internals) {
2573
+ if (!internals.adaptiveLimiter) return fn;
2574
+ return Object.assign(((req) => fn(req)), {
2575
+ adaptiveLimiter: internals.adaptiveLimiter
2576
+ });
2577
+ }
2578
+ function cancelControllers(activeControllers, prewarmMgr) {
2621
2579
  for (const controller of Array.from(activeControllers)) {
2622
2580
  try {
2623
2581
  controller.abort();
2624
2582
  } catch {
2625
2583
  }
2626
2584
  }
2585
+ if (prewarmMgr) {
2586
+ prewarmMgr.cancelAll();
2587
+ }
2588
+ return asyncSucceed(void 0);
2589
+ }
2590
+ function shutdownClient(activeControllers, prewarmMgr, wireShutdown) {
2591
+ cancelControllers(activeControllers, prewarmMgr);
2592
+ wireShutdown?.();
2627
2593
  return asyncSucceed(void 0);
2628
2594
  }
2629
2595
  function trackRequest(fn, req, tracker, internals) {
@@ -2660,6 +2626,18 @@ function trackRequest(fn, req, tracker, internals) {
2660
2626
  internals.activeControllers.delete(controller);
2661
2627
  if (exit._tag === "Success") {
2662
2628
  tracker.requestCompleted();
2629
+ if (internals.afterResponse && internals.prewarmManager) {
2630
+ try {
2631
+ const originsToWarm = internals.afterResponse(exit.value, req);
2632
+ if (originsToWarm && originsToWarm.length > 0) {
2633
+ for (const origin of originsToWarm) {
2634
+ internals.prewarmManager.warm(origin).catch(() => {
2635
+ });
2636
+ }
2637
+ }
2638
+ } catch {
2639
+ }
2640
+ }
2663
2641
  } else {
2664
2642
  tracker.requestFailed();
2665
2643
  }
@@ -2773,7 +2751,7 @@ function isNodeEnvironment() {
2773
2751
  return typeof process !== "undefined" && process.versions != null && process.versions.node != null;
2774
2752
  }
2775
2753
 
2776
- // src/http/compression/decompressorNode.ts
2754
+ // src/http/compression/decompressor.node.ts
2777
2755
  function createNodeDecompressor(zlib2) {
2778
2756
  return {
2779
2757
  isPassthrough: false,
@@ -2824,7 +2802,7 @@ function createDecompressor() {
2824
2802
 
2825
2803
  // src/http/compression/types.ts
2826
2804
  var SUPPORTED_ENCODINGS = ["br", "gzip", "deflate"];
2827
- function emptyStats2() {
2805
+ function emptyStats() {
2828
2806
  return {
2829
2807
  decompressed: { gzip: 0, br: 0, deflate: 0 },
2830
2808
  compressedBytes: 0,
@@ -2874,7 +2852,6 @@ function processResponse(res, decompressor, enabledEncodings, stats) {
2874
2852
  const encodings = contentEncodingValue.split(",").map((e) => e.trim().toLowerCase());
2875
2853
  const reversedEncodings = [...encodings].reverse();
2876
2854
  let currentData = Buffer.from(res.bodyText, "latin1");
2877
- const originalData = currentData;
2878
2855
  let decompressedCount = 0;
2879
2856
  for (let i = 0; i < reversedEncodings.length; i++) {
2880
2857
  const enc = reversedEncodings[i];
@@ -2944,7 +2921,7 @@ function processResponse(res, decompressor, enabledEncodings, stats) {
2944
2921
  function makeCompressionMiddleware(config) {
2945
2922
  const enabledEncodings = config?.encodings ?? [...SUPPORTED_ENCODINGS];
2946
2923
  const decompressor = createDecompressor();
2947
- const mutableStats = emptyStats2();
2924
+ const mutableStats = emptyStats();
2948
2925
  const middleware = (next) => {
2949
2926
  return (req) => {
2950
2927
  const modifiedReq = injectAcceptEncoding(req, enabledEncodings);
@@ -3174,10 +3151,10 @@ function failEntries(config, key, entries, error) {
3174
3151
  }
3175
3152
  }
3176
3153
  function toFetchError(error) {
3177
- if (isHttpError(error)) return error;
3154
+ if (isHttpError2(error)) return error;
3178
3155
  return { _tag: "FetchError", message: error instanceof Error ? error.message : String(error) };
3179
3156
  }
3180
- function isHttpError(error) {
3157
+ function isHttpError2(error) {
3181
3158
  if (typeof error !== "object" || error === null || !("_tag" in error)) return false;
3182
3159
  const tag = error._tag;
3183
3160
  return tag === "Abort" || tag === "BadUrl" || tag === "FetchError" || tag === "Timeout" || tag === "PoolRejected" || tag === "PoolTimeout";
@@ -3261,7 +3238,7 @@ function runEffect(effect, env, cb) {
3261
3238
  };
3262
3239
  }
3263
3240
 
3264
- // src/http/prewarm.ts
3241
+ // src/http/prewarm/legacy.ts
3265
3242
  function prewarmConnections(config = {}) {
3266
3243
  const fetchImpl = config.fetchImpl ?? globalThis.fetch;
3267
3244
  const method = config.method ?? "HEAD";
@@ -3376,13 +3353,13 @@ function resolveUrl(value, baseUrl) {
3376
3353
  }
3377
3354
  }
3378
3355
  function normalizePrewarmError(error) {
3379
- if (isHttpError2(error)) return error;
3356
+ if (isHttpError3(error)) return error;
3380
3357
  if (typeof error === "object" && error !== null && error.name === "AbortError") {
3381
3358
  return { _tag: "Abort" };
3382
3359
  }
3383
3360
  return { _tag: "FetchError", message: error instanceof Error ? error.message : String(error) };
3384
3361
  }
3385
- function isHttpError2(error) {
3362
+ function isHttpError3(error) {
3386
3363
  if (typeof error !== "object" || error === null || !("_tag" in error)) return false;
3387
3364
  const tag = error._tag;
3388
3365
  return tag === "Abort" || tag === "BadUrl" || tag === "FetchError" || tag === "Timeout" || tag === "PoolRejected" || tag === "PoolTimeout";
@@ -3394,40 +3371,980 @@ function emit2(config, event) {
3394
3371
  } catch {
3395
3372
  }
3396
3373
  }
3374
+
3375
+ // src/http/defaultClient.ts
3376
+ var MINIMAL_PRESET_CONFIG = {
3377
+ timeoutMs: 3e4
3378
+ };
3379
+ var BALANCED_PRESET_CONFIG = {
3380
+ ...MINIMAL_PRESET_CONFIG,
3381
+ dedup: {},
3382
+ priority: {
3383
+ concurrency: 32,
3384
+ queueTimeoutMs: 3e4
3385
+ },
3386
+ retry: {
3387
+ maxRetries: 2,
3388
+ baseDelayMs: 100,
3389
+ maxDelayMs: 1e3,
3390
+ maxElapsedMs: 5e3,
3391
+ respectRetryAfter: true
3392
+ },
3393
+ adaptiveLimiter: makeAdaptiveLimiterConfig("balanced")
3394
+ };
3395
+ var DEFAULT_CACHEABLE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
3396
+ var DEFAULT_PRESET_CONFIG = {
3397
+ ...BALANCED_PRESET_CONFIG,
3398
+ cache: {
3399
+ ttlSeconds: 60,
3400
+ maxEntries: 1024,
3401
+ staleWhileRevalidate: true,
3402
+ cachePolicy: (req, res) => ({
3403
+ cacheable: DEFAULT_CACHEABLE_METHODS.has(req.method) && res.status >= 200 && res.status < 400
3404
+ })
3405
+ },
3406
+ priority: {
3407
+ concurrency: 64,
3408
+ queueTimeoutMs: 3e4
3409
+ },
3410
+ retry: {
3411
+ maxRetries: 3,
3412
+ baseDelayMs: 100,
3413
+ maxDelayMs: 2e3,
3414
+ maxElapsedMs: 1e4,
3415
+ respectRetryAfter: true
3416
+ },
3417
+ adaptiveLimiter: makeAdaptiveLimiterConfig("aggressive")
3418
+ };
3419
+ var PRESET_CONFIGS = {
3420
+ minimal: MINIMAL_PRESET_CONFIG,
3421
+ balanced: BALANCED_PRESET_CONFIG,
3422
+ default: DEFAULT_PRESET_CONFIG
3423
+ };
3424
+ var defaultHttpClientPreset = "default";
3425
+ function makeDefaultHttpClient(config = {}) {
3426
+ validateDefaultHttpClientConfig(config);
3427
+ const {
3428
+ preset = defaultHttpClientPreset,
3429
+ compression,
3430
+ middleware = [],
3431
+ ...lifecycleOverrides
3432
+ } = config;
3433
+ const lifecycleConfig = mergeLifecycleConfig(PRESET_CONFIGS[preset], lifecycleOverrides);
3434
+ let wire = makeLifecycleClient(lifecycleConfig);
3435
+ const compressionResult = compression === false || compression === void 0 && preset === "minimal" ? void 0 : makeCompressionMiddleware(compression === void 0 ? void 0 : compression);
3436
+ if (compressionResult) {
3437
+ wire = wire.with(compressionResult.middleware);
3438
+ }
3439
+ for (const mw of middleware) {
3440
+ wire = wire.with(mw);
3441
+ }
3442
+ const features = featureSnapshot(lifecycleConfig, compressionResult !== void 0, middleware.length);
3443
+ return buildDefaultClient(wire, {
3444
+ preset,
3445
+ features,
3446
+ compressionStats: compressionResult?.stats
3447
+ });
3448
+ }
3449
+ function buildDefaultClient(wire, meta) {
3450
+ const withPromise = (eff) => withAsyncPromise((e, env) => toPromise(e, env))(eff);
3451
+ const requestRaw = (req) => wire(req);
3452
+ const request = (req) => withPromise(requestRaw(req));
3453
+ const get = (url, init) => request(buildReq("GET", url, init));
3454
+ const post = (url, body, init) => request(buildReq("POST", url, init, body));
3455
+ const getText = (url, init) => {
3456
+ const req = buildReq("GET", url, init);
3457
+ return withPromise(
3458
+ mapTryAsync(requestRaw(req), (w) => toResponse(w, w.bodyText))
3459
+ );
3460
+ };
3461
+ const getJson = ((url, init) => {
3462
+ const req = setHeaderIfMissing("accept", "application/json")(
3463
+ buildReq("GET", url, init)
3464
+ );
3465
+ return withPromise(
3466
+ asyncFlatMap(requestRaw(req), (w) => decodeResponse(w, init?.schema, init?.schemaName))
3467
+ );
3468
+ });
3469
+ const postJson = ((url, bodyObj, init) => {
3470
+ return withPromise(
3471
+ asyncFlatMap(
3472
+ encodeJsonBodyEffect(bodyObj, init?.bodySchema, { schemaName: init?.bodySchemaName }),
3473
+ (bodyText) => {
3474
+ const req = setHeaderIfMissing("content-type", "application/json")(
3475
+ setHeaderIfMissing("accept", "application/json")(
3476
+ buildReq("POST", url, init, bodyText)
3477
+ )
3478
+ );
3479
+ return asyncFlatMap(requestRaw(req), (w) => decodeResponse(w, init?.schema, init?.schemaName));
3480
+ }
3481
+ )
3482
+ );
3483
+ });
3484
+ return {
3485
+ request,
3486
+ get,
3487
+ post,
3488
+ getText,
3489
+ getJson,
3490
+ postJson,
3491
+ with: (mw) => buildDefaultClient(wire.with(mw), {
3492
+ ...meta,
3493
+ features: {
3494
+ ...meta.features,
3495
+ middleware: meta.features.middleware + 1
3496
+ }
3497
+ }),
3498
+ wire,
3499
+ stats: () => wire.stats(),
3500
+ cache: wire.cache,
3501
+ cancelAll: wire.cancelAll,
3502
+ shutdown: wire.shutdown,
3503
+ preset: meta.preset,
3504
+ features: meta.features,
3505
+ ...meta.compressionStats ? {
3506
+ compression: {
3507
+ stats: meta.compressionStats
3508
+ }
3509
+ } : {}
3510
+ };
3511
+ }
3512
+ function buildReq(method, url, init, body) {
3513
+ const { headers, timeoutMs, poolKey, schema: schema2, schemaName, bodySchema, bodySchemaName, ...rest } = init ?? {};
3514
+ const normalizedHeaders = normalizeHeadersInit(headers);
3515
+ const req = {
3516
+ method,
3517
+ url,
3518
+ ...body && body.length > 0 ? { body } : {},
3519
+ ...typeof timeoutMs === "number" ? { timeoutMs } : {},
3520
+ ...typeof poolKey === "string" ? { poolKey } : {},
3521
+ init: rest
3522
+ };
3523
+ return normalizedHeaders ? mergeHeaders(normalizedHeaders)(req) : req;
3524
+ }
3525
+ function toResponse(wire, body) {
3526
+ return {
3527
+ status: wire.status,
3528
+ statusText: wire.statusText,
3529
+ headers: wire.headers,
3530
+ body
3531
+ };
3532
+ }
3533
+ function decodeResponse(wire, schema2, schemaName) {
3534
+ return asyncFlatMap(
3535
+ decodeJsonBodyEffect(wire.bodyText, schema2, { schemaName }),
3536
+ (body) => asyncSucceed(toResponse(wire, body))
3537
+ );
3538
+ }
3539
+ function mergeLifecycleConfig(defaults, overrides) {
3540
+ return {
3541
+ ...defaults,
3542
+ ...overrides,
3543
+ headers: mergeRecord(defaults.headers, overrides.headers),
3544
+ dedup: mergeLayer(defaults.dedup, overrides.dedup),
3545
+ batch: mergeLayer(defaults.batch, overrides.batch),
3546
+ cache: mergeLayer(defaults.cache, overrides.cache),
3547
+ priority: mergeLayer(defaults.priority, overrides.priority),
3548
+ retry: mergeLayer(defaults.retry, overrides.retry),
3549
+ prewarm: mergeLayer(defaults.prewarm, overrides.prewarm),
3550
+ adaptiveLimiter: mergeAdaptiveLimiterLayer(defaults.adaptiveLimiter, overrides.adaptiveLimiter),
3551
+ pool: mergeLayer(defaults.pool, overrides.pool)
3552
+ };
3553
+ }
3554
+ function mergeRecord(defaults, overrides) {
3555
+ if (!defaults) return overrides;
3556
+ if (!overrides) return defaults;
3557
+ return { ...defaults, ...overrides };
3558
+ }
3559
+ function mergeLayer(defaults, overrides) {
3560
+ if (overrides === void 0) return defaults;
3561
+ if (overrides === false) return false;
3562
+ if (defaults === void 0 || defaults === false) return overrides;
3563
+ return { ...defaults, ...overrides };
3564
+ }
3565
+ function mergeAdaptiveLimiterLayer(defaults, overrides) {
3566
+ if (overrides === void 0) return defaults;
3567
+ if (overrides === false) return false;
3568
+ if (defaults === void 0 || defaults === false) return overrides;
3569
+ if (overrides.preset !== void 0) return overrides;
3570
+ return { ...defaults, ...overrides };
3571
+ }
3572
+ function featureSnapshot(config, compression, middleware) {
3573
+ return Object.freeze({
3574
+ dedup: isEnabled(config.dedup),
3575
+ batch: isEnabled(config.batch),
3576
+ cache: isEnabled(config.cache),
3577
+ priority: isEnabled(config.priority),
3578
+ retry: isEnabled(config.retry),
3579
+ prewarm: isEnabled(config.prewarm),
3580
+ adaptiveLimiter: isEnabled(config.adaptiveLimiter),
3581
+ compression,
3582
+ middleware
3583
+ });
3584
+ }
3585
+ function isEnabled(value) {
3586
+ return value !== void 0 && value !== false;
3587
+ }
3588
+
3589
+ // src/http/builder.ts
3590
+ var DEFAULT_BUILDER_RETRY = {
3591
+ maxRetries: 2,
3592
+ baseDelayMs: 100,
3593
+ maxDelayMs: 1e3
3594
+ };
3595
+ var cloneConfig = (config) => ({
3596
+ ...config,
3597
+ headers: config.headers ? { ...config.headers } : void 0,
3598
+ middleware: config.middleware ? [...config.middleware] : void 0
3599
+ });
3600
+ var freezeConfig = (config) => ({
3601
+ ...config,
3602
+ headers: config.headers ? { ...config.headers } : void 0,
3603
+ middleware: config.middleware ? [...config.middleware] : void 0
3604
+ });
3605
+ var mergeConfig = (current, next) => ({
3606
+ ...current,
3607
+ ...next,
3608
+ headers: {
3609
+ ...current.headers ?? {},
3610
+ ...next.headers ?? {}
3611
+ },
3612
+ middleware: [
3613
+ ...current.middleware ?? [],
3614
+ ...next.middleware ?? []
3615
+ ]
3616
+ });
3617
+ function makeBuilder(config) {
3618
+ const replace = (patch) => makeBuilder({
3619
+ ...config,
3620
+ ...patch,
3621
+ headers: patch.headers ? { ...patch.headers } : config.headers,
3622
+ middleware: patch.middleware ? [...patch.middleware] : config.middleware
3623
+ });
3624
+ const setLayer = (key, value) => replace({ [key]: value });
3625
+ const middleware = (mw) => makeBuilder({
3626
+ ...config,
3627
+ middleware: [...config.middleware ?? [], mw]
3628
+ });
3629
+ return Object.freeze({
3630
+ config: () => freezeConfig(config),
3631
+ baseUrl: (baseUrl) => replace({ baseUrl }),
3632
+ header: (name, value) => makeBuilder({
3633
+ ...config,
3634
+ headers: {
3635
+ ...config.headers ?? {},
3636
+ [name]: value
3637
+ }
3638
+ }),
3639
+ headers: (headers) => makeBuilder({
3640
+ ...config,
3641
+ headers: {
3642
+ ...config.headers ?? {},
3643
+ ...headers
3644
+ }
3645
+ }),
3646
+ timeoutMs: (timeoutMs) => replace({ timeoutMs }),
3647
+ timeout: (timeoutMs) => replace({ timeoutMs }),
3648
+ preset: (preset) => replace({ preset }),
3649
+ minimal: () => replace({ preset: "minimal" }),
3650
+ balanced: () => replace({ preset: "balanced" }),
3651
+ defaultPreset: () => replace({ preset: "default" }),
3652
+ dedup: (layer = {}) => setLayer("dedup", layer),
3653
+ noDedup: () => setLayer("dedup", false),
3654
+ batch: (layer) => setLayer("batch", layer),
3655
+ noBatch: () => setLayer("batch", false),
3656
+ cache: (layer = {}) => setLayer("cache", layer),
3657
+ noCache: () => setLayer("cache", false),
3658
+ priority: (layer = {}) => setLayer("priority", layer),
3659
+ noPriority: () => setLayer("priority", false),
3660
+ retry: (layer = DEFAULT_BUILDER_RETRY) => setLayer("retry", layer),
3661
+ noRetry: () => setLayer("retry", false),
3662
+ prewarm: (layer = {}) => setLayer("prewarm", layer),
3663
+ noPrewarm: () => setLayer("prewarm", false),
3664
+ adaptiveLimiter: (layer = {}) => setLayer("adaptiveLimiter", layer),
3665
+ adaptiveLimiterPreset: (preset, overrides = {}) => setLayer("adaptiveLimiter", makeAdaptiveLimiterConfig(preset, overrides)),
3666
+ conservativeLimiter: (overrides = {}) => setLayer("adaptiveLimiter", makeAdaptiveLimiterConfig("conservative", overrides)),
3667
+ balancedLimiter: (overrides = {}) => setLayer("adaptiveLimiter", makeAdaptiveLimiterConfig("balanced", overrides)),
3668
+ aggressiveLimiter: (overrides = {}) => setLayer("adaptiveLimiter", makeAdaptiveLimiterConfig("aggressive", overrides)),
3669
+ noAdaptiveLimiter: () => setLayer("adaptiveLimiter", false),
3670
+ pool: (layer = {}) => setLayer("pool", layer),
3671
+ noPool: () => setLayer("pool", false),
3672
+ compression: (layer = {}) => setLayer("compression", layer),
3673
+ noCompression: () => setLayer("compression", false),
3674
+ middleware,
3675
+ use: middleware,
3676
+ configure: (next) => makeBuilder(mergeConfig(config, next)),
3677
+ build: () => makeDefaultHttpClient(freezeConfig(config)),
3678
+ buildWire: () => makeDefaultHttpClient(freezeConfig(config)).wire
3679
+ });
3680
+ }
3681
+ function httpClientBuilder(config = {}) {
3682
+ return makeBuilder(cloneConfig(config));
3683
+ }
3684
+ var makeHttpClientBuilder = httpClientBuilder;
3685
+ var httpBuilder = httpClientBuilder;
3686
+
3687
+ // src/http/server.ts
3688
+ import { createServer } from "http";
3689
+ var DEFAULT_MAX_BODY_BYTES = 1024 * 1024;
3690
+ function route(method, path, optionsOrHandler, maybeHandler) {
3691
+ const options = typeof optionsOrHandler === "function" ? {} : optionsOrHandler;
3692
+ const handler = typeof optionsOrHandler === "function" ? optionsOrHandler : maybeHandler;
3693
+ if (!handler) throw new Error(`Missing handler for HTTP route ${method} ${path}`);
3694
+ return {
3695
+ method: normalizeRouteMethod(method),
3696
+ path: normalizeRoutePath(path),
3697
+ options,
3698
+ handler,
3699
+ match: compileRoutePath(path)
3700
+ };
3701
+ }
3702
+ var httpRoute = route;
3703
+ function makeHttpRouter(routes, options = {}) {
3704
+ const normalizedRoutes = routes.map((item) => ({
3705
+ ...item,
3706
+ method: normalizeRouteMethod(item.method),
3707
+ path: normalizeRoutePath(item.path)
3708
+ }));
3709
+ const router = {
3710
+ routes: normalizedRoutes,
3711
+ match: (method, path) => matchRoute(normalizedRoutes, method, path),
3712
+ handle: (request, existingMatch) => {
3713
+ const matched = existingMatch ?? router.match(request.method, request.path);
3714
+ return handleRouteMatch(request, matched, options);
3715
+ },
3716
+ listen: (serverOptions = {}) => nodeHttpServerResource({
3717
+ ...serverOptions,
3718
+ router
3719
+ })
3720
+ };
3721
+ return router;
3722
+ }
3723
+ function json(body, init = {}) {
3724
+ return {
3725
+ ...init,
3726
+ headers: setHeaderIfMissing2(init.headers, "content-type", "application/json"),
3727
+ body
3728
+ };
3729
+ }
3730
+ function text(body, init = {}) {
3731
+ return {
3732
+ ...init,
3733
+ headers: setHeaderIfMissing2(init.headers, "content-type", "text/plain; charset=utf-8"),
3734
+ body
3735
+ };
3736
+ }
3737
+ function empty(status = 204, headers) {
3738
+ return { status, headers };
3739
+ }
3740
+ function makeRuntimeHealthRoute(options = {}) {
3741
+ const { path = "/health", ...healthOptions } = options;
3742
+ return route(
3743
+ "GET",
3744
+ path,
3745
+ () => asyncFlatMap(
3746
+ makeRuntimeHealth(healthOptions),
3747
+ (report) => asyncSucceed(healthReportToServerResponse(report))
3748
+ )
3749
+ );
3750
+ }
3751
+ function makeRuntimeReadinessRoute(options = {}) {
3752
+ return makeRuntimeHealthRoute({
3753
+ ...options,
3754
+ path: options.path ?? "/ready"
3755
+ });
3756
+ }
3757
+ var runtimeHealthRoute = makeRuntimeHealthRoute;
3758
+ var runtimeReadinessRoute = makeRuntimeReadinessRoute;
3759
+ function withResponseHeader(name, value) {
3760
+ return (next) => (ctx) => asyncFlatMap(
3761
+ next(ctx),
3762
+ (response) => asyncSucceed({
3763
+ ...response,
3764
+ headers: setHeaderIfMissing2(response.headers, name, value)
3765
+ })
3766
+ );
3767
+ }
3768
+ function healthReportToServerResponse(report) {
3769
+ return healthHttpResponseToServerResponse(healthToHttpResponse(report));
3770
+ }
3771
+ function healthHttpResponseToServerResponse(response) {
3772
+ return {
3773
+ status: response.status,
3774
+ headers: response.headers,
3775
+ body: response.body
3776
+ };
3777
+ }
3778
+ function makeNodeHttpServer(options) {
3779
+ return asyncEffect((_env, cb) => {
3780
+ const router = resolveRouter(options.router);
3781
+ const server = createServer((req, res) => {
3782
+ void handleNodeRequest(req, res, router, options);
3783
+ });
3784
+ let settled = false;
3785
+ let closed = false;
3786
+ const close = () => closeNodeServer(server, options);
3787
+ const fail = (error) => {
3788
+ if (settled) {
3789
+ options.onError?.(error);
3790
+ return;
3791
+ }
3792
+ settled = true;
3793
+ cb({
3794
+ _tag: "Failure",
3795
+ cause: {
3796
+ _tag: "Fail",
3797
+ error: {
3798
+ _tag: "ListenError",
3799
+ error,
3800
+ message: error instanceof Error ? error.message : String(error)
3801
+ }
3802
+ }
3803
+ });
3804
+ };
3805
+ server.once("error", fail);
3806
+ server.once("close", () => {
3807
+ closed = true;
3808
+ });
3809
+ server.listen(options.port ?? 0, options.host, () => {
3810
+ if (settled) return;
3811
+ settled = true;
3812
+ server.off("error", fail);
3813
+ server.on("error", (error) => options.onError?.(error));
3814
+ cb({
3815
+ _tag: "Success",
3816
+ value: {
3817
+ server,
3818
+ router,
3819
+ address: () => server.address(),
3820
+ url: () => serverUrl(server),
3821
+ close
3822
+ }
3823
+ });
3824
+ });
3825
+ return () => {
3826
+ if (closed) return;
3827
+ void close().catch((error) => options.onError?.(error));
3828
+ };
3829
+ });
3830
+ }
3831
+ function nodeHttpServerResource(options) {
3832
+ return resource(
3833
+ makeNodeHttpServer(options),
3834
+ (handle) => asyncEffect((_env, cb) => {
3835
+ handle.close().then(() => cb({ _tag: "Success", value: void 0 })).catch(() => cb({ _tag: "Success", value: void 0 }));
3836
+ })
3837
+ );
3838
+ }
3839
+ var makeNodeHttpServerResource = nodeHttpServerResource;
3840
+ var makeHttpServerResource = nodeHttpServerResource;
3841
+ function handleRouteMatch(request, match, options) {
3842
+ if (match._tag === "NotFound") {
3843
+ return asyncSucceed(json({ error: "Not Found" }, { status: 404 }));
3844
+ }
3845
+ if (match._tag === "MethodNotAllowed") {
3846
+ return asyncSucceed(json(
3847
+ { error: "Method Not Allowed", allowed: match.allowed.filter((method) => method !== "ALL") },
3848
+ { status: 405, headers: { allow: match.allowed.filter((method) => method !== "ALL").join(", ") } }
3849
+ ));
3850
+ }
3851
+ const routeWithParams = match.route;
3852
+ const prepared = prepareRouteContext(request, match.params, routeWithParams);
3853
+ if (!prepared.success) return asyncSucceed(validationErrorResponse(prepared.error));
3854
+ const handler = composeMiddleware(
3855
+ [...options.middleware ?? [], ...routeWithParams.options.middleware ?? []],
3856
+ routeWithParams.handler
3857
+ );
3858
+ let handled;
3859
+ try {
3860
+ handled = handler(prepared.ctx);
3861
+ } catch (error) {
3862
+ return asyncSucceed(handlerErrorResponse(error, options));
3863
+ }
3864
+ return asyncFold(
3865
+ handled,
3866
+ (error) => asyncSucceed(handlerErrorResponse(error, options)),
3867
+ (response) => {
3868
+ const normalized = normalizeServerResponse(response);
3869
+ const validation = validateResponseBody(normalized, routeWithParams);
3870
+ return asyncSucceed(validation.success ? normalized : validationErrorResponse(validation.error));
3871
+ }
3872
+ );
3873
+ }
3874
+ function prepareRouteContext(request, params, routeWithSchemas) {
3875
+ const paramsResult = validatePart(params, routeWithSchemas.options.params, {
3876
+ phase: "request",
3877
+ schemaName: routeWithSchemas.options.paramsSchemaName ?? "params",
3878
+ body: JSON.stringify(params)
3879
+ });
3880
+ if (!paramsResult.success) return paramsResult;
3881
+ const queryResult = validatePart(request.query, routeWithSchemas.options.query, {
3882
+ phase: "request",
3883
+ schemaName: routeWithSchemas.options.querySchemaName ?? "query",
3884
+ body: JSON.stringify(request.query)
3885
+ });
3886
+ if (!queryResult.success) return queryResult;
3887
+ const bodyInput = routeWithSchemas.options.body ? parseJsonRequestBody(request.bodyText) : { success: true, data: request.bodyText.length > 0 ? request.bodyText : void 0 };
3888
+ if (!bodyInput.success) {
3889
+ return {
3890
+ success: false,
3891
+ error: makeValidationError({
3892
+ message: bodyInput.message,
3893
+ body: request.bodyText,
3894
+ phase: "request",
3895
+ schema: routeWithSchemas.options.bodySchemaName ?? "body",
3896
+ issues: [makeSchemaIssue([], "valid JSON", request.bodyText, bodyInput.message)]
3897
+ })
3898
+ };
3899
+ }
3900
+ const bodyResult = validatePart(bodyInput.data, routeWithSchemas.options.body, {
3901
+ phase: "request",
3902
+ schemaName: routeWithSchemas.options.bodySchemaName ?? "body",
3903
+ body: request.bodyText
3904
+ });
3905
+ if (!bodyResult.success) return bodyResult;
3906
+ return {
3907
+ success: true,
3908
+ ctx: {
3909
+ ...request,
3910
+ route: routeWithSchemas.path,
3911
+ params: paramsResult.data,
3912
+ query: queryResult.data,
3913
+ body: bodyResult.data
3914
+ }
3915
+ };
3916
+ }
3917
+ function validateResponseBody(response, routeWithSchemas) {
3918
+ const schema2 = routeWithSchemas.options.response;
3919
+ if (!schema2) return { success: true };
3920
+ const result = validateValue(response.body, schema2);
3921
+ if (result.success) return { success: true };
3922
+ return {
3923
+ success: false,
3924
+ error: makeValidationError({
3925
+ message: `HTTP response failed validation: ${formatIssues(result.issues)}`,
3926
+ body: previewJson2(response.body),
3927
+ phase: "response",
3928
+ schema: routeWithSchemas.options.responseSchemaName ?? "response",
3929
+ issues: result.issues
3930
+ })
3931
+ };
3932
+ }
3933
+ function validatePart(input, schema2, options) {
3934
+ if (!schema2) return { success: true, data: input };
3935
+ const result = validateValue(input, schema2);
3936
+ if (result.success) return { success: true, data: result.data };
3937
+ return {
3938
+ success: false,
3939
+ error: makeValidationError({
3940
+ message: `HTTP ${options.schemaName} failed validation: ${formatIssues(result.issues)}`,
3941
+ body: options.body,
3942
+ phase: options.phase,
3943
+ schema: options.schemaName,
3944
+ issues: result.issues
3945
+ })
3946
+ };
3947
+ }
3948
+ function resolveRouter(input) {
3949
+ return isHttpRouter(input) ? input : makeHttpRouter(input);
3950
+ }
3951
+ function isHttpRouter(value) {
3952
+ return !Array.isArray(value) && typeof value === "object" && value !== null && typeof value.match === "function" && typeof value.handle === "function";
3953
+ }
3954
+ function makeValidationError(input) {
3955
+ return {
3956
+ _tag: "ValidationError",
3957
+ message: input.message,
3958
+ body: input.body,
3959
+ phase: input.phase,
3960
+ schema: input.schema,
3961
+ issues: input.issues
3962
+ };
3963
+ }
3964
+ function validationErrorResponse(error) {
3965
+ return json(
3966
+ {
3967
+ error: error.phase === "response" ? "Response validation failed" : "Request validation failed",
3968
+ message: error.message,
3969
+ phase: error.phase,
3970
+ schema: error.schema,
3971
+ issues: error.issues
3972
+ },
3973
+ { status: error.phase === "response" ? 500 : 400 }
3974
+ );
3975
+ }
3976
+ function handlerErrorResponse(error, options) {
3977
+ return json(
3978
+ {
3979
+ error: "Internal Server Error",
3980
+ ...options.includeErrorDetails ? { message: error instanceof Error ? error.message : String(error) } : {}
3981
+ },
3982
+ { status: 500 }
3983
+ );
3984
+ }
3985
+ async function handleNodeRequest(req, res, router, options) {
3986
+ try {
3987
+ const bodyText = await readNodeRequestBody(req, options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES);
3988
+ const serverRequest = nodeRequestToServerRequest(req, bodyText);
3989
+ const match = router.match(serverRequest.method, serverRequest.path);
3990
+ const effect = router.handle(serverRequest, match);
3991
+ const routeLabel = match._tag === "Match" || match._tag === "MethodNotAllowed" ? match.route.path : void 0;
3992
+ const observedInput = {
3993
+ method: serverRequest.method,
3994
+ route: routeLabel,
3995
+ target: serverRequest.target,
3996
+ headers: serverRequest.headers
3997
+ };
3998
+ const response = options.observability ? (await runObservedHttpServerEffect(
3999
+ options.observability,
4000
+ observedInput,
4001
+ effect,
4002
+ {
4003
+ statusCode: (value) => value.status ?? 200,
4004
+ ...options.observabilityOptions ?? {}
4005
+ },
4006
+ options.env,
4007
+ options.runtimeOptions
4008
+ )).value : await (options.runtime ?? new Runtime({
4009
+ env: options.env ?? {},
4010
+ ...options.runtimeOptions ?? {}
4011
+ })).toPromise(effect);
4012
+ writeNodeResponse(res, response);
4013
+ } catch (error) {
4014
+ options.onError?.(error);
4015
+ writeNodeResponse(res, json({ error: "Internal Server Error" }, { status: 500 }));
4016
+ }
4017
+ }
4018
+ function nodeRequestToServerRequest(req, bodyText) {
4019
+ const headers = normalizeNodeHeaders(req.headers);
4020
+ const target = req.url ?? "/";
4021
+ const parsed = parseRequestUrl(target, headers.host);
4022
+ return {
4023
+ method: (req.method ?? "GET").toUpperCase(),
4024
+ url: parsed.toString(),
4025
+ path: parsed.pathname,
4026
+ target,
4027
+ headers,
4028
+ query: queryToObject(parsed.searchParams),
4029
+ params: {},
4030
+ bodyText,
4031
+ raw: req
4032
+ };
4033
+ }
4034
+ function writeNodeResponse(res, response) {
4035
+ const normalized = normalizeServerResponse(response);
4036
+ const encoded = encodeResponseBody(normalized);
4037
+ res.statusCode = normalized.status ?? 200;
4038
+ for (const [name, value] of Object.entries(encoded.headers)) {
4039
+ res.setHeader(name, value);
4040
+ }
4041
+ if (encoded.body === void 0) {
4042
+ res.end();
4043
+ return;
4044
+ }
4045
+ res.end(encoded.body);
4046
+ }
4047
+ function normalizeServerResponse(response) {
4048
+ if (isServerResponse(response)) {
4049
+ return { status: 200, ...response };
4050
+ }
4051
+ return { status: 200, body: response };
4052
+ }
4053
+ function isServerResponse(value) {
4054
+ return typeof value === "object" && value !== null && ("status" in value || "headers" in value || "body" in value);
4055
+ }
4056
+ function encodeResponseBody(response) {
4057
+ const headers = { ...response.headers ?? {} };
4058
+ const body = response.body;
4059
+ if (body === void 0 || response.status === 204 || response.status === 304) return { headers };
4060
+ if (typeof body === "string") {
4061
+ return {
4062
+ headers: setHeaderIfMissing2(headers, "content-type", "text/plain; charset=utf-8"),
4063
+ body
4064
+ };
4065
+ }
4066
+ if (body instanceof Uint8Array) return { headers, body };
4067
+ if (body instanceof ArrayBuffer) return { headers, body: new Uint8Array(body) };
4068
+ return {
4069
+ headers: setHeaderIfMissing2(headers, "content-type", "application/json"),
4070
+ body: JSON.stringify(body)
4071
+ };
4072
+ }
4073
+ function readNodeRequestBody(req, maxBytes) {
4074
+ return new Promise((resolve, reject) => {
4075
+ const chunks = [];
4076
+ let total = 0;
4077
+ req.on("data", (chunk) => {
4078
+ total += chunk.byteLength;
4079
+ if (total > maxBytes) {
4080
+ reject(new Error(`HTTP request body exceeded ${maxBytes} bytes`));
4081
+ req.destroy();
4082
+ return;
4083
+ }
4084
+ chunks.push(chunk);
4085
+ });
4086
+ req.on("error", reject);
4087
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
4088
+ });
4089
+ }
4090
+ function closeNodeServer(server, options) {
4091
+ return new Promise((resolve, reject) => {
4092
+ const startedAt = Date.now();
4093
+ const schedule = options.shutdownPollSchedule ?? defaultShutdownPollSchedule(options.gracefulShutdownMs ?? 5e3);
4094
+ let state = schedule.initial();
4095
+ let finished = false;
4096
+ const done = (error) => {
4097
+ if (finished) return;
4098
+ finished = true;
4099
+ if (timer) clearTimeout(timer);
4100
+ if (error && error.message !== "Server is not running.") reject(error);
4101
+ else resolve();
4102
+ };
4103
+ let timer;
4104
+ const poll = () => {
4105
+ if (finished) return;
4106
+ if (!server.listening) {
4107
+ done();
4108
+ return;
4109
+ }
4110
+ const [decision, nextState] = schedule.step(state, {
4111
+ listening: server.listening,
4112
+ elapsedMs: Date.now() - startedAt
4113
+ });
4114
+ state = nextState;
4115
+ if (!decision.continue) {
4116
+ server.closeAllConnections?.();
4117
+ done();
4118
+ return;
4119
+ }
4120
+ timer = setTimeout(poll, decision.delayMs);
4121
+ timer.unref?.();
4122
+ };
4123
+ server.close(done);
4124
+ poll();
4125
+ });
4126
+ }
4127
+ function defaultShutdownPollSchedule(maxMs) {
4128
+ const intervalMs = 25;
4129
+ return take(fixed(intervalMs), Math.max(1, Math.ceil(Math.max(0, maxMs) / intervalMs)));
4130
+ }
4131
+ function matchRoute(routes, method, path) {
4132
+ const requestedMethod = method.toUpperCase();
4133
+ const allowed = [];
4134
+ let methodMismatch;
4135
+ for (const candidate of routes) {
4136
+ const params = candidate.match(path);
4137
+ if (!params) continue;
4138
+ if (candidate.method === "ALL" || candidate.method === requestedMethod) {
4139
+ return { _tag: "Match", route: candidate, params };
4140
+ }
4141
+ methodMismatch ??= candidate;
4142
+ allowed.push(candidate.method);
4143
+ }
4144
+ if (methodMismatch) {
4145
+ return { _tag: "MethodNotAllowed", route: methodMismatch, allowed };
4146
+ }
4147
+ return { _tag: "NotFound" };
4148
+ }
4149
+ function composeMiddleware(middleware, handler) {
4150
+ return middleware.reduceRight((next, current) => current(next), handler);
4151
+ }
4152
+ function parseJsonRequestBody(bodyText) {
4153
+ if (bodyText.length === 0) return { success: true, data: void 0 };
4154
+ try {
4155
+ return { success: true, data: JSON.parse(bodyText) };
4156
+ } catch (error) {
4157
+ return {
4158
+ success: false,
4159
+ message: `JSON parse error: ${error instanceof Error ? error.message : String(error)}`
4160
+ };
4161
+ }
4162
+ }
4163
+ function compileRoutePath(path) {
4164
+ const keys = [];
4165
+ const normalized = normalizeRoutePath(path);
4166
+ if (normalized === "/") {
4167
+ return (input) => input === "/" ? {} : void 0;
4168
+ }
4169
+ const source = normalized.split("/").filter(Boolean).map((segment) => {
4170
+ if (segment === "*") {
4171
+ keys.push("*");
4172
+ return "(.*)";
4173
+ }
4174
+ if (segment.startsWith(":")) {
4175
+ keys.push(segment.slice(1));
4176
+ return "([^/]+)";
4177
+ }
4178
+ return escapeRegExp(segment);
4179
+ }).join("/");
4180
+ const regex = new RegExp(`^/${source}/?$`);
4181
+ return (input) => {
4182
+ const match = regex.exec(input);
4183
+ if (!match) return void 0;
4184
+ const params = {};
4185
+ keys.forEach((key, index) => {
4186
+ params[key] = decodePathPart(match[index + 1] ?? "");
4187
+ });
4188
+ return params;
4189
+ };
4190
+ }
4191
+ function normalizeRouteMethod(method) {
4192
+ const upper = method.toUpperCase();
4193
+ return upper === "ALL" ? "ALL" : upper;
4194
+ }
4195
+ function normalizeRoutePath(path) {
4196
+ if (!path || path === "*") return "/";
4197
+ const withSlash = path.startsWith("/") ? path : `/${path}`;
4198
+ return withSlash.length > 1 ? withSlash.replace(/\/+$/, "") : withSlash;
4199
+ }
4200
+ function parseRequestUrl(target, host) {
4201
+ try {
4202
+ return new URL(target, `http://${host ?? "localhost"}`);
4203
+ } catch {
4204
+ return new URL("/", `http://${host ?? "localhost"}`);
4205
+ }
4206
+ }
4207
+ function queryToObject(searchParams) {
4208
+ const out = {};
4209
+ for (const [key, value] of searchParams.entries()) {
4210
+ const current = out[key];
4211
+ if (current === void 0) {
4212
+ out[key] = value;
4213
+ } else if (Array.isArray(current)) {
4214
+ current.push(value);
4215
+ } else {
4216
+ out[key] = [current, value];
4217
+ }
4218
+ }
4219
+ return out;
4220
+ }
4221
+ function normalizeNodeHeaders(headers) {
4222
+ const out = {};
4223
+ for (const [key, value] of Object.entries(headers)) {
4224
+ if (value === void 0) continue;
4225
+ out[key.toLowerCase()] = Array.isArray(value) ? value.join(",") : value;
4226
+ }
4227
+ return out;
4228
+ }
4229
+ function setHeaderIfMissing2(headers, name, value) {
4230
+ const out = { ...headers ?? {} };
4231
+ const existing = Object.keys(out).find((key) => key.toLowerCase() === name.toLowerCase());
4232
+ if (!existing) out[name] = value;
4233
+ return out;
4234
+ }
4235
+ function serverUrl(server) {
4236
+ const address = server.address();
4237
+ if (!address || typeof address === "string") return void 0;
4238
+ const host = address.address === "::" || address.address === "0.0.0.0" ? "127.0.0.1" : address.address;
4239
+ return `http://${host}:${address.port}`;
4240
+ }
4241
+ function previewJson2(value) {
4242
+ try {
4243
+ return JSON.stringify(value);
4244
+ } catch {
4245
+ return "[unserializable]";
4246
+ }
4247
+ }
4248
+ function decodePathPart(value) {
4249
+ try {
4250
+ return decodeURIComponent(value);
4251
+ } catch {
4252
+ return value;
4253
+ }
4254
+ }
4255
+ function escapeRegExp(value) {
4256
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4257
+ }
3397
4258
  export {
4259
+ AdaptiveLimiter,
4260
+ ConfigValidationError,
3398
4261
  DEFAULT_CACHE_RELEVANT_HEADERS,
4262
+ EmaComputer,
3399
4263
  HttpConcurrencyPool,
3400
4264
  LRUCache,
4265
+ LatencyWindow,
3401
4266
  LifecycleStatsTracker,
3402
4267
  PriorityQueue,
3403
4268
  SEPARATOR,
3404
4269
  SUPPORTED_ENCODINGS,
4270
+ Schema,
4271
+ SchemaValidationException,
4272
+ adaptiveLimiterPresets,
3405
4273
  backoffDelayMs,
3406
4274
  clampPriority,
3407
4275
  computeCacheKey,
4276
+ computeGradient,
4277
+ computeNewLimit,
4278
+ decodeJsonBody,
4279
+ decodeJsonBodyEffect,
3408
4280
  decorate,
4281
+ defaultHttpClientPreset,
3409
4282
  defaultRetryOnError,
3410
4283
  defaultRetryOnStatus,
3411
4284
  defaultRetryableMethods,
4285
+ detectPlatform,
4286
+ empty,
4287
+ encodeJsonBodyEffect,
4288
+ executeProbe,
4289
+ formatHttpError,
4290
+ formatIssues,
4291
+ httpBuilder,
3412
4292
  httpClient,
4293
+ httpClientBuilder,
3413
4294
  httpClientStream,
3414
4295
  httpClientWithMeta,
4296
+ httpRoute,
4297
+ isCircuitBreakerOpen,
4298
+ isHttpError,
4299
+ isKnownHttpError,
4300
+ isSchema,
4301
+ isValidationError,
4302
+ json,
4303
+ makeAdaptiveLimiterConfig,
4304
+ makeBudgetSemaphore,
3415
4305
  makeCompressionMiddleware,
4306
+ makeConnectionStateMap,
4307
+ makeDefaultHttpClient,
3416
4308
  makeHttp,
3417
4309
  makeHttpClient,
4310
+ makeHttpClientBuilder,
4311
+ makeHttpRouter,
4312
+ makeHttpServerResource,
3418
4313
  makeHttpStream,
3419
4314
  makeLifecycleClient,
4315
+ makeNodeHttpServer,
4316
+ makeNodeHttpServerResource,
4317
+ makePrewarmManager,
3420
4318
  makeRequestCompressionMiddleware,
3421
4319
  makeResponseCompressionMiddleware,
4320
+ makeRuntimeHealthRoute,
4321
+ makeRuntimeReadinessRoute,
4322
+ makeSchemaIssue,
4323
+ matchHttpError,
4324
+ nodeHttpServerResource,
3422
4325
  normalizeHeadersInit,
3423
4326
  normalizeRetryBudget,
3424
4327
  parseCacheKey,
4328
+ parseConfig,
3425
4329
  prewarmConnections,
3426
4330
  prewarmHttpConnections,
4331
+ resolveConfig,
3427
4332
  resolveHttpPoolKey,
3428
4333
  retryAfterMs,
4334
+ route,
4335
+ runtimeHealthRoute,
4336
+ runtimeReadinessRoute,
4337
+ s,
4338
+ schema,
4339
+ text,
4340
+ validateConfig,
4341
+ validateFetchAvailable,
4342
+ validateOrigin,
4343
+ validateValue,
3429
4344
  validatedJson,
4345
+ validatedJsonResponse,
3430
4346
  withAuth,
4347
+ withBatch,
3431
4348
  withCache,
3432
4349
  withCircuitBreaker,
3433
4350
  withConnectionPrewarming,
@@ -3436,6 +4353,7 @@ export {
3436
4353
  withMiddleware,
3437
4354
  withPriority,
3438
4355
  withRequestBatching,
4356
+ withResponseHeader,
3439
4357
  withResponseTransform,
3440
4358
  withRetry,
3441
4359
  withRetryStream,