brass-runtime 1.13.8 → 1.14.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 (36) hide show
  1. package/dist/agent/cli/main.cjs +43 -43
  2. package/dist/agent/cli/main.js +2 -2
  3. package/dist/agent/cli/main.mjs +2 -2
  4. package/dist/agent/index.cjs +3 -3
  5. package/dist/agent/index.d.ts +1 -1
  6. package/dist/agent/index.js +2 -2
  7. package/dist/agent/index.mjs +2 -2
  8. package/dist/chunk-4N2JEK4H.mjs +3897 -0
  9. package/dist/chunk-BKBFSOGT.cjs +3897 -0
  10. package/dist/{chunk-XNOTJSMZ.mjs → chunk-BMRF4FN6.js} +268 -8
  11. package/dist/chunk-JT7D6M5H.js +3897 -0
  12. package/dist/{chunk-3R7ZYRK2.mjs → chunk-MQF7HZ7Y.mjs} +1 -1
  13. package/dist/chunk-SKVY72E5.cjs +667 -0
  14. package/dist/{chunk-ATHSSDUF.js → chunk-UWMMYKVK.mjs} +268 -8
  15. package/dist/{chunk-INZBKOHY.js → chunk-WJESVBWN.js} +1 -1
  16. package/dist/{chunk-XDINDYNA.cjs → chunk-XTMZTVIT.cjs} +134 -134
  17. package/dist/{effect-ISvXPLgc.d.ts → effect-DM56H743.d.ts} +191 -21
  18. package/dist/http/index.cjs +808 -140
  19. package/dist/http/index.d.ts +181 -8
  20. package/dist/http/index.js +793 -125
  21. package/dist/http/index.mjs +793 -125
  22. package/dist/index.cjs +1785 -137
  23. package/dist/index.d.ts +979 -36
  24. package/dist/index.js +1675 -27
  25. package/dist/index.mjs +1675 -27
  26. package/dist/stream-Oqe6WeLE.d.ts +173 -0
  27. package/package.json +1 -1
  28. package/wasm/pkg/brass_runtime_wasm_engine.d.ts +95 -16
  29. package/wasm/pkg/brass_runtime_wasm_engine.js +715 -15
  30. package/wasm/pkg/brass_runtime_wasm_engine_bg.wasm +0 -0
  31. package/wasm/pkg/brass_runtime_wasm_engine_bg.wasm.d.ts +78 -7
  32. package/dist/chunk-2P4PD6D7.cjs +0 -2557
  33. package/dist/chunk-7F2R7A2V.mjs +0 -2557
  34. package/dist/chunk-L6KKKM66.js +0 -2557
  35. package/dist/chunk-ZTDK2DLG.cjs +0 -407
  36. package/dist/stream-BvukHxCv.d.ts +0 -66
@@ -1,8 +1,8 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2;
2
2
 
3
- var _chunkZTDK2DLGcjs = require('../chunk-ZTDK2DLG.cjs');
4
3
 
5
4
 
5
+ var _chunkSKVY72E5cjs = require('../chunk-SKVY72E5.cjs');
6
6
 
7
7
 
8
8
 
@@ -10,7 +10,10 @@ var _chunkZTDK2DLGcjs = require('../chunk-ZTDK2DLG.cjs');
10
10
 
11
11
 
12
12
 
13
- var _chunk2P4PD6D7cjs = require('../chunk-2P4PD6D7.cjs');
13
+
14
+
15
+
16
+ var _chunkBKBFSOGTcjs = require('../chunk-BKBFSOGT.cjs');
14
17
 
15
18
  // src/http/optics/lens.ts
16
19
  var Lens = {
@@ -39,36 +42,389 @@ var mergeHeaders = (extra) => (req) => Lens.over(Request.headers, (h) => ({ ...h
39
42
  var mergeHeadersUnder = (under) => (req) => Lens.over(Request.headers, (h) => ({ ...under, ...h }))(req);
40
43
  var setHeaderIfMissing = (k, v) => (req) => Lens.over(Request.headers, (h) => h[k] ? h : { ...h, [k]: v })(req);
41
44
 
42
- // src/http/sleep.ts
43
- var isHttpError = (e) => typeof e === "object" && e !== null && "_tag" in e;
44
- var normalizeHttpError = (e) => {
45
- if (isHttpError(e)) return e;
46
- if (typeof e === "object" && e !== null && e.name === "AbortError") {
47
- return { _tag: "Abort" };
45
+ // src/http/wasmPermitPool.ts
46
+ var DECISION_RUN_NOW = 0;
47
+ var DECISION_QUEUED = 1;
48
+ var WasmHttpPermitPoolBridge = (_class = class {
49
+
50
+ __init() {this.keyCache = /* @__PURE__ */ new Map()}
51
+ constructor(Ctor, options) {;_class.prototype.__init.call(this);
52
+ this.pool = new Ctor(options.concurrency, options.maxQueue, toU64(options.queueTimeoutMs));
48
53
  }
49
- return { _tag: "FetchError", message: String(e) };
54
+ acquire(key, subjectId, nowMs = Date.now()) {
55
+ const keyId = this.internKey(key);
56
+ const decision = this.pool.acquire(subjectId, keyId, toU64(nowMs));
57
+ const permitId = this.pool.last_permit_id();
58
+ if (decision === DECISION_RUN_NOW) return { kind: "run", keyId, permitId };
59
+ if (decision === DECISION_QUEUED) return { kind: "queued", keyId, permitId };
60
+ return { kind: "rejected", keyId, permitId };
61
+ }
62
+ release(keyId, nowMs = Date.now()) {
63
+ const ptr = this.pool.release(keyId, toU64(nowMs));
64
+ return this.readEvents(ptr, this.pool.permit_events_len());
65
+ }
66
+ cancel(permitId) {
67
+ this.pool.cancel(permitId);
68
+ }
69
+ advanceTime(nowMs = Date.now()) {
70
+ const ptr = this.pool.advance_time(toU64(nowMs));
71
+ return this.readEvents(ptr, this.pool.permit_events_len());
72
+ }
73
+ nextDeadlineMs() {
74
+ return this.pool.next_deadline_ms();
75
+ }
76
+ stats() {
77
+ return {
78
+ running: this.pool.metric_u64(0),
79
+ queued: this.pool.metric_u64(1),
80
+ acquired: this.pool.metric_u64(2),
81
+ released: this.pool.metric_u64(3),
82
+ rejected: this.pool.metric_u64(4),
83
+ queueTimeouts: this.pool.metric_u64(5),
84
+ keys: this.pool.metric_u64(6)
85
+ };
86
+ }
87
+ internKey(key) {
88
+ const normalized = key.trim().slice(0, 160) || "global";
89
+ let id = this.keyCache.get(normalized);
90
+ if (id === void 0) {
91
+ id = this.pool.intern_key(normalized);
92
+ this.keyCache.set(normalized, id);
93
+ }
94
+ return id;
95
+ }
96
+ readEvents(ptr, len) {
97
+ if (ptr === 0 || len <= 1) return [];
98
+ const words = new Uint32Array(this.pool.memory().buffer, ptr, len);
99
+ const count = words[0] >>> 0;
100
+ const out = [];
101
+ for (let i = 0; i < count; i++) {
102
+ const base = 1 + i * 3;
103
+ if (base + 2 >= words.length) break;
104
+ out.push({
105
+ subjectId: words[base] >>> 0,
106
+ permitId: words[base + 1] >>> 0,
107
+ keyId: words[base + 2] >>> 0
108
+ });
109
+ }
110
+ return out;
111
+ }
112
+ }, _class);
113
+ function makeWasmHttpPermitPool(options) {
114
+ const mod = _chunkBKBFSOGTcjs.resolveWasmModule.call(void 0, );
115
+ const Ctor = _optionalChain([mod, 'optionalAccess', _ => _.BrassWasmHttpPermitPool]);
116
+ if (!Ctor) throw new Error("brass-runtime wasm HTTP permit pool is not available. Run npm run build:wasm first.");
117
+ return new WasmHttpPermitPoolBridge(Ctor, options);
118
+ }
119
+ function toU64(value) {
120
+ return BigInt(Math.max(0, Math.floor(value)));
121
+ }
122
+
123
+ // src/http/pool.ts
124
+ var DEFAULT_CONCURRENCY = 64;
125
+ var DEFAULT_MAX_QUEUE = 256;
126
+ var clampInt = (n, fallback, min) => {
127
+ if (n === void 0 || !Number.isFinite(n)) return fallback;
128
+ return Math.max(min, Math.floor(n));
50
129
  };
51
- var sleepMs = (ms) => _chunk2P4PD6D7cjs.fromPromiseAbortable.call(void 0,
52
- (signal) => new Promise((resolve, reject) => {
53
- if (signal.aborted) return reject({ _tag: "Abort" });
54
- const id = setTimeout(resolve, ms);
55
- const onAbort = () => {
56
- clearTimeout(id);
57
- reject({ _tag: "Abort" });
130
+ var queueTimeoutError = (key, timeoutMs) => ({
131
+ _tag: "PoolTimeout",
132
+ key,
133
+ timeoutMs,
134
+ message: `HTTP pool '${key}' did not grant a slot within ${timeoutMs}ms`
135
+ });
136
+ var poolRejectedError = (key, maxQueue) => ({
137
+ _tag: "PoolRejected",
138
+ key,
139
+ limit: maxQueue,
140
+ message: `HTTP pool '${key}' queue is full`
141
+ });
142
+ var abortError = () => ({ _tag: "Abort" });
143
+ function resolveHttpPoolEngine(config) {
144
+ if (config.engine !== void 0) {
145
+ if (config.engine === "ts" || config.engine === "wasm") return config.engine;
146
+ throw new Error(`brass-runtime HTTP pool engine must be 'ts' or 'wasm'; received '${String(config.engine)}'`);
147
+ }
148
+ if (config.wasm === true) return "wasm";
149
+ if (config.wasm === false) return "ts";
150
+ return "ts";
151
+ }
152
+ function resolveHttpPoolKey(resolver, req, url) {
153
+ const custom = _optionalChain([req, 'access', _2 => _2.poolKey, 'optionalAccess', _3 => _3.trim, 'call', _4 => _4()]);
154
+ if (custom) return custom.slice(0, 160);
155
+ const r = _nullishCoalesce(resolver, () => ( "origin"));
156
+ if (typeof r === "function") return r(req, url).trim().slice(0, 160) || "global";
157
+ if (r === "global") return "global";
158
+ if (r === "host") return url.host;
159
+ return url.origin;
160
+ }
161
+ var HttpConcurrencyPool = (_class2 = class {
162
+ __init2() {this.states = /* @__PURE__ */ new Map()}
163
+
164
+
165
+
166
+
167
+
168
+ __init3() {this.wasmWaiters = /* @__PURE__ */ new Map()}
169
+
170
+ __init4() {this.nextSubjectId = 1}
171
+ constructor(config = {}) {;_class2.prototype.__init2.call(this);_class2.prototype.__init3.call(this);_class2.prototype.__init4.call(this);
172
+ this.concurrency = clampInt(config.concurrency, DEFAULT_CONCURRENCY, 1);
173
+ this.maxQueue = clampInt(config.maxQueue, DEFAULT_MAX_QUEUE, 0);
174
+ this.queueTimeoutMs = config.queueTimeoutMs !== void 0 && Number.isFinite(config.queueTimeoutMs) ? Math.max(0, Math.floor(config.queueTimeoutMs)) : void 0;
175
+ this.keyResolver = config.key;
176
+ const engine = resolveHttpPoolEngine(config);
177
+ this.wasm = engine === "wasm" ? makeWasmHttpPermitPool({
178
+ concurrency: this.concurrency,
179
+ maxQueue: this.maxQueue,
180
+ queueTimeoutMs: _nullishCoalesce(this.queueTimeoutMs, () => ( 0))
181
+ }) : void 0;
182
+ }
183
+ acquire(key, signal) {
184
+ return this.wasm ? this.acquireWasm(key, signal) : this.acquireJs(key, signal);
185
+ }
186
+ stats() {
187
+ const keys = Array.from(this.states.values()).map((state) => ({
188
+ key: state.key,
189
+ running: state.running,
190
+ queued: state.queue.length,
191
+ concurrency: this.concurrency,
192
+ maxQueue: this.maxQueue,
193
+ acquired: state.acquired,
194
+ released: state.released,
195
+ rejected: state.rejected,
196
+ queueTimeouts: state.queueTimeouts,
197
+ abortedWhileQueued: state.abortedWhileQueued
198
+ })).sort((a, b) => b.running + b.queued - (a.running + a.queued) || a.key.localeCompare(b.key));
199
+ return keys.reduce((acc, key) => ({
200
+ running: acc.running + key.running,
201
+ queued: acc.queued + key.queued,
202
+ acquired: acc.acquired + key.acquired,
203
+ released: acc.released + key.released,
204
+ rejected: acc.rejected + key.rejected,
205
+ queueTimeouts: acc.queueTimeouts + key.queueTimeouts,
206
+ abortedWhileQueued: acc.abortedWhileQueued + key.abortedWhileQueued,
207
+ wasm: _optionalChain([this, 'access', _5 => _5.wasm, 'optionalAccess', _6 => _6.stats, 'call', _7 => _7()]),
208
+ keys: acc.keys.concat(key)
209
+ }), {
210
+ running: 0,
211
+ queued: 0,
212
+ acquired: 0,
213
+ released: 0,
214
+ rejected: 0,
215
+ queueTimeouts: 0,
216
+ abortedWhileQueued: 0,
217
+ ...this.wasm ? { wasm: this.wasm.stats() } : {},
218
+ keys: []
219
+ });
220
+ }
221
+ acquireJs(key, signal) {
222
+ const state = this.getState(key);
223
+ if (signal.aborted) return Promise.reject(abortError());
224
+ if (state.running < this.concurrency) {
225
+ state.running++;
226
+ state.acquired++;
227
+ return Promise.resolve(this.makeLease(state));
228
+ }
229
+ if (state.queue.length >= this.maxQueue) {
230
+ state.rejected++;
231
+ return Promise.reject(poolRejectedError(key, this.maxQueue));
232
+ }
233
+ return new Promise((resolve, reject) => {
234
+ const waiter = { signal, resolve, reject };
235
+ const removeWaiter = () => this.removeWaiter(state, waiter);
236
+ const cleanup = () => this.cleanupWaiter(waiter);
237
+ waiter.abort = () => {
238
+ cleanup();
239
+ removeWaiter();
240
+ state.abortedWhileQueued++;
241
+ reject(abortError());
242
+ };
243
+ signal.addEventListener("abort", waiter.abort, { once: true });
244
+ if (this.queueTimeoutMs !== void 0 && this.queueTimeoutMs > 0) {
245
+ waiter.timer = setTimeout(() => {
246
+ cleanup();
247
+ removeWaiter();
248
+ state.queueTimeouts++;
249
+ reject(queueTimeoutError(key, this.queueTimeoutMs));
250
+ }, this.queueTimeoutMs);
251
+ }
252
+ state.queue.push(waiter);
253
+ });
254
+ }
255
+ acquireWasm(key, signal) {
256
+ const wasm = this.wasm;
257
+ const state = this.getState(key);
258
+ if (signal.aborted) return Promise.reject(abortError());
259
+ const subjectId = this.allocateSubjectId();
260
+ const decision = wasm.acquire(key, subjectId);
261
+ if (decision.kind === "run") {
262
+ state.running++;
263
+ state.acquired++;
264
+ return Promise.resolve(this.makeLease(state, decision.keyId));
265
+ }
266
+ if (decision.kind === "rejected") {
267
+ state.rejected++;
268
+ return Promise.reject(poolRejectedError(key, this.maxQueue));
269
+ }
270
+ return new Promise((resolve, reject) => {
271
+ const waiter = { signal, resolve, reject };
272
+ const removeWaiter = () => this.removeWaiter(state, waiter);
273
+ const cleanup = () => this.cleanupWaiter(waiter);
274
+ waiter.abort = () => {
275
+ cleanup();
276
+ removeWaiter();
277
+ wasm.cancel(decision.permitId);
278
+ this.wasmWaiters.delete(decision.permitId);
279
+ state.abortedWhileQueued++;
280
+ reject(abortError());
281
+ };
282
+ signal.addEventListener("abort", waiter.abort, { once: true });
283
+ state.queue.push(waiter);
284
+ this.wasmWaiters.set(decision.permitId, { waiter, state, keyId: decision.keyId });
285
+ this.scheduleWasmTimeoutPump();
286
+ });
287
+ }
288
+ getState(key) {
289
+ const k = key.trim().slice(0, 160) || "global";
290
+ const existing = this.states.get(k);
291
+ if (existing) return existing;
292
+ const created = {
293
+ key: k,
294
+ running: 0,
295
+ queue: [],
296
+ acquired: 0,
297
+ released: 0,
298
+ rejected: 0,
299
+ queueTimeouts: 0,
300
+ abortedWhileQueued: 0
301
+ };
302
+ this.states.set(k, created);
303
+ return created;
304
+ }
305
+ makeLease(state, wasmKeyId) {
306
+ let released = false;
307
+ return {
308
+ key: state.key,
309
+ release: () => {
310
+ if (released) return;
311
+ released = true;
312
+ if (state.running > 0) state.running--;
313
+ state.released++;
314
+ if (this.wasm && wasmKeyId !== void 0) {
315
+ this.handleWasmGrants(this.wasm.release(wasmKeyId));
316
+ this.scheduleWasmTimeoutPump();
317
+ return;
318
+ }
319
+ this.drain(state);
320
+ }
58
321
  };
59
- signal.addEventListener("abort", onAbort, { once: true });
60
- }),
61
- normalizeHttpError
62
- );
322
+ }
323
+ drain(state) {
324
+ while (state.running < this.concurrency && state.queue.length > 0) {
325
+ const waiter = state.queue.shift();
326
+ this.cleanupWaiter(waiter);
327
+ if (waiter.signal.aborted) {
328
+ state.abortedWhileQueued++;
329
+ waiter.reject(abortError());
330
+ continue;
331
+ }
332
+ state.running++;
333
+ state.acquired++;
334
+ waiter.resolve(this.makeLease(state));
335
+ }
336
+ }
337
+ handleWasmGrants(events) {
338
+ for (const event of events) {
339
+ const pending = this.wasmWaiters.get(event.permitId);
340
+ if (!pending) continue;
341
+ this.wasmWaiters.delete(event.permitId);
342
+ this.cleanupWaiter(pending.waiter);
343
+ this.removeWaiter(pending.state, pending.waiter);
344
+ if (pending.waiter.signal.aborted) {
345
+ pending.state.abortedWhileQueued++;
346
+ pending.waiter.reject(abortError());
347
+ continue;
348
+ }
349
+ pending.state.running++;
350
+ pending.state.acquired++;
351
+ pending.waiter.resolve(this.makeLease(pending.state, event.keyId));
352
+ }
353
+ }
354
+ handleWasmTimeouts(events) {
355
+ for (const event of events) {
356
+ const pending = this.wasmWaiters.get(event.permitId);
357
+ if (!pending) continue;
358
+ this.wasmWaiters.delete(event.permitId);
359
+ this.cleanupWaiter(pending.waiter);
360
+ this.removeWaiter(pending.state, pending.waiter);
361
+ pending.state.queueTimeouts++;
362
+ pending.waiter.reject(queueTimeoutError(pending.state.key, _nullishCoalesce(this.queueTimeoutMs, () => ( 0))));
363
+ }
364
+ }
365
+ scheduleWasmTimeoutPump() {
366
+ if (!this.wasm) return;
367
+ if (this.wasmTimer !== void 0) clearTimeout(this.wasmTimer);
368
+ this.wasmTimer = void 0;
369
+ const next = this.wasm.nextDeadlineMs();
370
+ if (!Number.isFinite(next) || next < 0) return;
371
+ const delay = Math.max(0, Math.min(2 ** 31 - 1, Math.floor(next - Date.now())));
372
+ this.wasmTimer = setTimeout(() => {
373
+ this.wasmTimer = void 0;
374
+ if (!this.wasm) return;
375
+ this.handleWasmTimeouts(this.wasm.advanceTime());
376
+ this.scheduleWasmTimeoutPump();
377
+ }, delay);
378
+ if (typeof this.wasmTimer.unref === "function") this.wasmTimer.unref();
379
+ }
380
+ cleanupWaiter(waiter) {
381
+ if (waiter.timer !== void 0) {
382
+ clearTimeout(waiter.timer);
383
+ waiter.timer = void 0;
384
+ }
385
+ if (waiter.abort) {
386
+ waiter.signal.removeEventListener("abort", waiter.abort);
387
+ waiter.abort = void 0;
388
+ }
389
+ }
390
+ removeWaiter(state, waiter) {
391
+ const idx = state.queue.indexOf(waiter);
392
+ if (idx >= 0) state.queue.splice(idx, 1);
393
+ }
394
+ allocateSubjectId() {
395
+ const id = this.nextSubjectId >>> 0;
396
+ this.nextSubjectId = this.nextSubjectId + 1 >>> 0;
397
+ if (this.nextSubjectId === 0) this.nextSubjectId = 1;
398
+ return id === 0 ? this.allocateSubjectId() : id;
399
+ }
400
+ }, _class2);
63
401
 
64
402
  // src/http/client.ts
65
- var withMiddleware = (mw) => (c) => decorate(mw(c));
66
- var decorate = (run) => Object.assign(((req) => run(req)), {
67
- with: (mw) => decorate(mw(run))
403
+ var emptyStats = () => ({
404
+ inFlight: 0,
405
+ started: 0,
406
+ succeeded: 0,
407
+ failed: 0,
408
+ aborted: 0,
409
+ timedOut: 0,
410
+ poolRejected: 0,
411
+ poolTimeouts: 0
412
+ });
413
+ var decorate = (run, stats = emptyStats) => Object.assign(((req) => run(req)), {
414
+ with: (mw) => decorate(mw(run), stats),
415
+ stats
68
416
  });
69
- var normalizeHttpError2 = (e) => {
70
- if (e instanceof DOMException && e.name === "AbortError") return { _tag: "Abort" };
71
- if (typeof e === "object" && e && "_tag" in e) return e;
417
+ var withMiddleware = (mw) => (c) => decorate(mw(c), c.stats);
418
+ var decorateStream = (run, stats = emptyStats) => Object.assign(((req) => run(req)), { stats });
419
+ var isTaggedHttpError = (e) => {
420
+ if (typeof e !== "object" || e === null || !("_tag" in e)) return false;
421
+ const tag = e._tag;
422
+ return tag === "Abort" || tag === "BadUrl" || tag === "FetchError" || tag === "Timeout" || tag === "PoolRejected" || tag === "PoolTimeout";
423
+ };
424
+ var isAbortError = (e) => typeof e === "object" && e !== null && "name" in e && e.name === "AbortError";
425
+ var normalizeHttpError = (e) => {
426
+ if (isTaggedHttpError(e)) return e;
427
+ if (isAbortError(e)) return { _tag: "Abort" };
72
428
  return { _tag: "FetchError", message: String(e) };
73
429
  };
74
430
  var normalizeHeadersInit = (h) => {
@@ -84,87 +440,202 @@ var normalizeHeadersInit = (h) => {
84
440
  };
85
441
  var normalizeRequest = (defaultHeaders) => (req0) => {
86
442
  let req = Object.keys(defaultHeaders).length ? mergeHeadersUnder(defaultHeaders)(req0) : req0;
87
- const initHeaders = normalizeHeadersInit(_optionalChain([req0, 'access', _ => _.init, 'optionalAccess', _2 => _2.headers]));
443
+ const initHeaders = normalizeHeadersInit(_optionalChain([req0, 'access', _8 => _8.init, 'optionalAccess', _9 => _9.headers]));
88
444
  if (initHeaders && Object.keys(initHeaders).length) {
89
445
  req = mergeHeadersUnder(initHeaders)(req);
90
446
  }
91
447
  return req;
92
448
  };
449
+ var resolvePositiveTimeout = (value) => {
450
+ if (value === void 0 || !Number.isFinite(value)) return void 0;
451
+ const n = Math.floor(value);
452
+ return n > 0 ? n : void 0;
453
+ };
454
+ var makeHttpStats = (pool) => {
455
+ const stats = {
456
+ inFlight: 0,
457
+ started: 0,
458
+ succeeded: 0,
459
+ failed: 0,
460
+ aborted: 0,
461
+ timedOut: 0,
462
+ poolRejected: 0,
463
+ poolTimeouts: 0
464
+ };
465
+ const onStart = () => {
466
+ stats.inFlight++;
467
+ stats.started++;
468
+ };
469
+ const onFinish = (finish) => {
470
+ if (stats.inFlight > 0) stats.inFlight--;
471
+ stats.lastDurationMs = finish.durationMs;
472
+ if (finish.outcome === "success") {
473
+ stats.succeeded++;
474
+ return;
475
+ }
476
+ if (finish.outcome === "interrupt") {
477
+ stats.aborted++;
478
+ return;
479
+ }
480
+ if (finish.outcome === "timeout") {
481
+ stats.timedOut++;
482
+ return;
483
+ }
484
+ const err = normalizeHttpError(finish.error);
485
+ switch (err._tag) {
486
+ case "Abort":
487
+ stats.aborted++;
488
+ return;
489
+ case "Timeout":
490
+ stats.timedOut++;
491
+ return;
492
+ case "PoolRejected":
493
+ stats.poolRejected++;
494
+ stats.failed++;
495
+ return;
496
+ case "PoolTimeout":
497
+ stats.poolTimeouts++;
498
+ stats.failed++;
499
+ return;
500
+ default:
501
+ stats.failed++;
502
+ return;
503
+ }
504
+ };
505
+ const snapshot = () => ({
506
+ ...stats,
507
+ ...pool ? { pool: pool.stats() } : {}
508
+ });
509
+ return { onStart, onFinish, snapshot };
510
+ };
511
+ var makePool = (cfg) => cfg.pool === void 0 || cfg.pool === false ? void 0 : new HttpConcurrencyPool(cfg.pool);
512
+ var resolveRequestUrl = (req, baseUrl) => {
513
+ try {
514
+ return new URL(req.url, baseUrl);
515
+ } catch (e2) {
516
+ return { _tag: "BadUrl", message: `URL inv\xE1lida: ${req.url}` };
517
+ }
518
+ };
519
+ var headersOf = (res) => {
520
+ const headers = {};
521
+ res.headers.forEach((v, k) => headers[k] = v);
522
+ return headers;
523
+ };
524
+ var fetchLabel = (req, url) => `http:${req.method}:${url.origin}`;
525
+ var timeoutReason = (req, url, timeoutMs) => ({
526
+ _tag: "Timeout",
527
+ timeoutMs,
528
+ phase: "request",
529
+ message: `HTTP ${req.method} ${url.origin} timed out after ${timeoutMs}ms`
530
+ });
93
531
  function makeHttpStream(cfg = {}) {
94
532
  const baseUrl = _nullishCoalesce(cfg.baseUrl, () => ( ""));
95
533
  const defaultHeaders = _nullishCoalesce(cfg.headers, () => ( {}));
96
534
  const normalize = normalizeRequest(defaultHeaders);
97
- return (req0) => _chunk2P4PD6D7cjs.fromPromiseAbortable.call(void 0,
98
- async (signal) => {
99
- const req = normalize(req0);
100
- let url;
101
- try {
102
- url = new URL(req.url, baseUrl);
103
- } catch (e2) {
104
- throw { _tag: "BadUrl", message: `URL inv\xE1lida: ${req.url}` };
535
+ const pool = makePool(cfg);
536
+ const metrics = makeHttpStats(pool);
537
+ const run = (req0) => {
538
+ const req = normalize(req0);
539
+ const url = resolveRequestUrl(req, baseUrl);
540
+ if (!(url instanceof URL)) return _chunkBKBFSOGTcjs.asyncFail.call(void 0, url);
541
+ const timeoutMs = resolvePositiveTimeout(_nullishCoalesce(req.timeoutMs, () => ( cfg.timeoutMs)));
542
+ return _chunkBKBFSOGTcjs.fromPromiseAbortable.call(void 0,
543
+ async (signal) => {
544
+ let lease;
545
+ try {
546
+ if (pool) {
547
+ const key = resolveHttpPoolKey(pool.keyResolver, req, url);
548
+ lease = await pool.acquire(key, signal);
549
+ }
550
+ const started = performance.now();
551
+ const res = await fetch(url, {
552
+ ..._nullishCoalesce(req.init, () => ( {})),
553
+ method: req.method,
554
+ headers: Request.headers.get(req),
555
+ body: req.body,
556
+ signal
557
+ });
558
+ const headers = headersOf(res);
559
+ const body = _chunkSKVY72E5cjs.streamFromReadableStream.call(void 0, res.body, normalizeHttpError);
560
+ _optionalChain([lease, 'optionalAccess', _10 => _10.release, 'call', _11 => _11()]);
561
+ lease = void 0;
562
+ return {
563
+ status: res.status,
564
+ statusText: res.statusText,
565
+ headers,
566
+ body,
567
+ ms: Math.round(performance.now() - started)
568
+ };
569
+ } finally {
570
+ _optionalChain([lease, 'optionalAccess', _12 => _12.release, 'call', _13 => _13()]);
571
+ }
572
+ },
573
+ normalizeHttpError,
574
+ {
575
+ label: fetchLabel(req, url),
576
+ timeoutMs,
577
+ timeoutReason: timeoutMs ? () => timeoutReason(req, url, timeoutMs) : void 0,
578
+ onStart: metrics.onStart,
579
+ onFinish: metrics.onFinish
105
580
  }
106
- const started = performance.now();
107
- const res = await fetch(url, {
108
- ..._nullishCoalesce(req.init, () => ( {})),
109
- method: req.method,
110
- headers: Request.headers.get(req),
111
- // 👈 optics: headers ya normalizados
112
- body: req.body,
113
- signal
114
- });
115
- const headers = {};
116
- res.headers.forEach((v, k) => headers[k] = v);
117
- const body = _chunkZTDK2DLGcjs.streamFromReadableStream.call(void 0, res.body, normalizeHttpError2);
118
- return {
119
- status: res.status,
120
- statusText: res.statusText,
121
- headers,
122
- body,
123
- ms: Math.round(performance.now() - started)
124
- };
125
- },
126
- normalizeHttpError2
127
- );
581
+ );
582
+ };
583
+ return decorateStream(run, metrics.snapshot);
128
584
  }
129
585
  function makeHttp(cfg = {}) {
130
586
  const baseUrl = _nullishCoalesce(cfg.baseUrl, () => ( ""));
131
587
  const defaultHeaders = _nullishCoalesce(cfg.headers, () => ( {}));
132
588
  const normalize = normalizeRequest(defaultHeaders);
133
- const run = (req0) => _chunk2P4PD6D7cjs.fromPromiseAbortable.call(void 0,
134
- async (signal) => {
135
- const req = normalize(req0);
136
- let url;
137
- try {
138
- url = new URL(req.url, baseUrl);
139
- } catch (e3) {
140
- throw { _tag: "BadUrl", message: `URL inv\xE1lida: ${req.url}` };
589
+ const pool = makePool(cfg);
590
+ const metrics = makeHttpStats(pool);
591
+ const run = (req0) => {
592
+ const req = normalize(req0);
593
+ const url = resolveRequestUrl(req, baseUrl);
594
+ if (!(url instanceof URL)) return _chunkBKBFSOGTcjs.asyncFail.call(void 0, url);
595
+ const timeoutMs = resolvePositiveTimeout(_nullishCoalesce(req.timeoutMs, () => ( cfg.timeoutMs)));
596
+ return _chunkBKBFSOGTcjs.fromPromiseAbortable.call(void 0,
597
+ async (signal) => {
598
+ let lease;
599
+ try {
600
+ if (pool) {
601
+ const key = resolveHttpPoolKey(pool.keyResolver, req, url);
602
+ lease = await pool.acquire(key, signal);
603
+ }
604
+ const started = performance.now();
605
+ const res = await fetch(url, {
606
+ ..._nullishCoalesce(req.init, () => ( {})),
607
+ method: req.method,
608
+ headers: Request.headers.get(req),
609
+ body: req.body,
610
+ signal
611
+ });
612
+ const bodyText = await res.text();
613
+ const headers = headersOf(res);
614
+ return {
615
+ status: res.status,
616
+ statusText: res.statusText,
617
+ headers,
618
+ bodyText,
619
+ ms: Math.round(performance.now() - started)
620
+ };
621
+ } finally {
622
+ _optionalChain([lease, 'optionalAccess', _14 => _14.release, 'call', _15 => _15()]);
623
+ }
624
+ },
625
+ normalizeHttpError,
626
+ {
627
+ label: fetchLabel(req, url),
628
+ timeoutMs,
629
+ timeoutReason: timeoutMs ? () => timeoutReason(req, url, timeoutMs) : void 0,
630
+ onStart: metrics.onStart,
631
+ onFinish: metrics.onFinish
141
632
  }
142
- const started = performance.now();
143
- const res = await fetch(url, {
144
- ..._nullishCoalesce(req.init, () => ( {})),
145
- method: req.method,
146
- headers: Request.headers.get(req),
147
- // 👈 optics
148
- body: req.body,
149
- signal
150
- });
151
- const bodyText = await res.text();
152
- const headers = {};
153
- res.headers.forEach((v, k) => headers[k] = v);
154
- return {
155
- status: res.status,
156
- statusText: res.statusText,
157
- headers,
158
- bodyText,
159
- ms: Math.round(performance.now() - started)
160
- };
161
- },
162
- normalizeHttpError2
163
- );
164
- return decorate(run);
633
+ );
634
+ };
635
+ return decorate(run, metrics.snapshot);
165
636
  }
166
637
  var clamp = (n, min, max) => Math.max(min, Math.min(max, n));
167
- var defaultRetryOnError = (e) => e._tag === "FetchError";
638
+ var defaultRetryOnError = (e) => e._tag === "FetchError" || e._tag === "Timeout" || e._tag === "PoolTimeout";
168
639
  var defaultRetryOnStatus = (s) => s === 408 || s === 429 || s === 500 || s === 502 || s === 503 || s === 504;
169
640
  var backoffDelayMs = (attempt, base, cap) => {
170
641
  const exp = base * Math.pow(2, attempt);
@@ -174,7 +645,7 @@ var backoffDelayMs = (attempt, base, cap) => {
174
645
  var retryAfterMs = (headers) => {
175
646
  const key = Object.keys(headers).find((k) => k.toLowerCase() === "retry-after");
176
647
  if (!key) return void 0;
177
- const v = _optionalChain([headers, 'access', _3 => _3[key], 'optionalAccess', _4 => _4.trim, 'call', _5 => _5()]);
648
+ const v = _optionalChain([headers, 'access', _16 => _16[key], 'optionalAccess', _17 => _17.trim, 'call', _18 => _18()]);
178
649
  if (!v) return void 0;
179
650
  const secs = Number(v);
180
651
  if (Number.isFinite(secs)) return Math.max(0, Math.floor(secs * 1e3));
@@ -182,31 +653,85 @@ var retryAfterMs = (headers) => {
182
653
  if (Number.isFinite(t)) return Math.max(0, t - Date.now());
183
654
  return void 0;
184
655
  };
185
- var withRetryStream = (p) => (next) => ((req) => {
186
- const loop = (attempt) => _chunk2P4PD6D7cjs.asyncFold.call(void 0,
187
- next(req),
188
- (e) => {
189
- if (e._tag === "Abort" || e._tag === "BadUrl") return _chunk2P4PD6D7cjs.asyncFail.call(void 0, e);
190
- const canRetry = attempt < p.maxRetries && (_nullishCoalesce(p.retryOnError, () => ( defaultRetryOnError)))(e);
191
- if (!canRetry) return _chunk2P4PD6D7cjs.asyncFail.call(void 0, e);
192
- const d = backoffDelayMs(attempt, p.baseDelayMs, p.maxDelayMs);
193
- return _chunk2P4PD6D7cjs.asyncFlatMap.call(void 0, sleepMs(d), () => loop(attempt + 1));
194
- },
195
- (w) => {
196
- const canRetry = attempt < p.maxRetries && (_nullishCoalesce(p.retryOnStatus, () => ( defaultRetryOnStatus)))(w.status);
197
- if (!canRetry) return _chunk2P4PD6D7cjs.asyncSucceed.call(void 0, w);
198
- const ra = retryAfterMs(w.headers);
199
- const d = _nullishCoalesce(ra, () => ( backoffDelayMs(attempt, p.baseDelayMs, p.maxDelayMs)));
200
- return _chunk2P4PD6D7cjs.asyncFlatMap.call(void 0, sleepMs(d), () => loop(attempt + 1));
201
- }
202
- );
203
- return loop(0);
204
- });
656
+ var withRetryStream = (p) => (next) => {
657
+ const retryOnStatus = _nullishCoalesce(p.retryOnStatus, () => ( defaultRetryOnStatus));
658
+ const retryOnError = _nullishCoalesce(p.retryOnError, () => ( defaultRetryOnError));
659
+ const maxElapsedMs = p.maxElapsedMs !== void 0 && Number.isFinite(p.maxElapsedMs) ? Math.max(0, Math.floor(p.maxElapsedMs)) : void 0;
660
+ const run = (req) => {
661
+ const startedAt = performance.now();
662
+ const remainingBudget = () => maxElapsedMs === void 0 ? Number.POSITIVE_INFINITY : maxElapsedMs - (performance.now() - startedAt);
663
+ const delayWithinBudget = (delayMs) => Math.max(0, Math.min(delayMs, remainingBudget()));
664
+ const loop = (attempt) => _chunkBKBFSOGTcjs.asyncFold.call(void 0,
665
+ next(req),
666
+ (e) => {
667
+ if (e._tag === "Abort" || e._tag === "BadUrl" || e._tag === "PoolRejected") return _chunkBKBFSOGTcjs.asyncFail.call(void 0, e);
668
+ const canRetry = attempt < p.maxRetries && retryOnError(e) && remainingBudget() > 0;
669
+ if (!canRetry) return _chunkBKBFSOGTcjs.asyncFail.call(void 0, e);
670
+ const d = delayWithinBudget(backoffDelayMs(attempt, p.baseDelayMs, p.maxDelayMs));
671
+ if (d <= 0 && maxElapsedMs !== void 0) return _chunkBKBFSOGTcjs.asyncFail.call(void 0, e);
672
+ return _chunkBKBFSOGTcjs.asyncFlatMap.call(void 0, _chunkSKVY72E5cjs.sleep.call(void 0, d), () => loop(attempt + 1));
673
+ },
674
+ (w) => {
675
+ const canRetry = attempt < p.maxRetries && retryOnStatus(w.status) && remainingBudget() > 0;
676
+ if (!canRetry) return _chunkBKBFSOGTcjs.asyncSucceed.call(void 0, w);
677
+ const ra = p.respectRetryAfter === false ? void 0 : retryAfterMs(w.headers);
678
+ const rawDelay = ra === void 0 ? backoffDelayMs(attempt, p.baseDelayMs, p.maxDelayMs) : Math.min(ra, p.maxDelayMs);
679
+ const d = delayWithinBudget(rawDelay);
680
+ if (d <= 0 && maxElapsedMs !== void 0) return _chunkBKBFSOGTcjs.asyncSucceed.call(void 0, w);
681
+ return _chunkBKBFSOGTcjs.asyncFlatMap.call(void 0, _chunkSKVY72E5cjs.sleep.call(void 0, d), () => loop(attempt + 1));
682
+ }
683
+ );
684
+ return loop(0);
685
+ };
686
+ return decorateStream(run, next.stats);
687
+ };
688
+
689
+ // src/http/retry/wasmRetryPlanner.ts
690
+ var WasmRetryPlannerBridge = class {
691
+
692
+ constructor(Ctor) {
693
+ this.planner = new Ctor();
694
+ }
695
+ start(options) {
696
+ return this.planner.start(
697
+ options.nowMs,
698
+ options.maxRetries,
699
+ options.baseDelayMs,
700
+ options.maxDelayMs,
701
+ _nullishCoalesce(options.maxElapsedMs, () => ( -1)),
702
+ BigInt(this.seed())
703
+ );
704
+ }
705
+ nextDelayMs(retryId, options) {
706
+ const delay = this.planner.next_delay_ms(retryId, options.nowMs, options.retryable, _nullishCoalesce(options.retryAfterMs, () => ( -1)));
707
+ return delay < 0 ? void 0 : delay;
708
+ }
709
+ drop(retryId) {
710
+ this.planner.drop_state(retryId);
711
+ }
712
+ stats() {
713
+ return {
714
+ live: this.planner.metric_u64(0),
715
+ planned: this.planner.metric_u64(1),
716
+ exhausted: this.planner.metric_u64(2),
717
+ dropped: this.planner.metric_u64(3)
718
+ };
719
+ }
720
+ seed() {
721
+ return Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
722
+ }
723
+ };
724
+ function makeWasmRetryPlanner() {
725
+ const mod = _chunkBKBFSOGTcjs.resolveWasmModule.call(void 0, );
726
+ const Ctor = _optionalChain([mod, 'optionalAccess', _19 => _19.BrassWasmRetryPlanner]);
727
+ if (!Ctor) throw new Error("brass-runtime wasm retry planner is not available. Run npm run build:wasm first.");
728
+ return new WasmRetryPlannerBridge(Ctor);
729
+ }
205
730
 
206
731
  // src/http/retry/retry.ts
207
732
  var defaultRetryableMethods = ["GET", "HEAD", "OPTIONS"];
208
733
  var defaultRetryOnStatus2 = (s) => s === 408 || s === 429 || s === 500 || s === 502 || s === 503 || s === 504;
209
- var defaultRetryOnError2 = (e) => e._tag === "FetchError";
734
+ var defaultRetryOnError2 = (e) => e._tag === "FetchError" || e._tag === "Timeout" || e._tag === "PoolTimeout";
210
735
  var clamp2 = (n, min, max) => Math.max(min, Math.min(max, n));
211
736
  var backoffDelayMs2 = (attempt, base, cap) => {
212
737
  const b = Math.max(0, base);
@@ -220,7 +745,7 @@ var headerCI = (h, name) => {
220
745
  return k ? h[k] : void 0;
221
746
  };
222
747
  var retryAfterMs2 = (headers) => {
223
- const v = _optionalChain([headerCI, 'call', _6 => _6(headers, "retry-after"), 'optionalAccess', _7 => _7.trim, 'call', _8 => _8()]);
748
+ const v = _optionalChain([headerCI, 'call', _20 => _20(headers, "retry-after"), 'optionalAccess', _21 => _21.trim, 'call', _22 => _22()]);
224
749
  if (!v) return void 0;
225
750
  const secs = Number(v);
226
751
  if (Number.isFinite(secs)) return Math.max(0, Math.floor(secs * 1e3));
@@ -228,50 +753,106 @@ var retryAfterMs2 = (headers) => {
228
753
  if (Number.isFinite(t)) return Math.max(0, t - Date.now());
229
754
  return void 0;
230
755
  };
756
+ var normalizeBudget = (ms) => {
757
+ if (ms === void 0 || !Number.isFinite(ms)) return void 0;
758
+ return Math.max(0, Math.floor(ms));
759
+ };
760
+ var resolveRetryEngine = (p) => {
761
+ if (p.engine !== void 0) {
762
+ if (p.engine === "ts" || p.engine === "wasm") return p.engine;
763
+ throw new Error(`brass-runtime retry engine must be 'ts' or 'wasm'; received '${String(p.engine)}'`);
764
+ }
765
+ if (p.wasm === true) return "wasm";
766
+ if (p.wasm === false) return "ts";
767
+ return "ts";
768
+ };
231
769
  var withRetry = (p) => (next) => {
232
770
  const retryOnMethods = _nullishCoalesce(p.retryOnMethods, () => ( defaultRetryableMethods));
233
771
  const retryOnStatus = _nullishCoalesce(p.retryOnStatus, () => ( defaultRetryOnStatus2));
234
772
  const retryOnError = _nullishCoalesce(p.retryOnError, () => ( defaultRetryOnError2));
773
+ const maxElapsedMs = normalizeBudget(p.maxElapsedMs);
774
+ const retryEngine = resolveRetryEngine(p);
775
+ const wasmPlanner = retryEngine === "wasm" ? makeWasmRetryPlanner() : void 0;
235
776
  const isMethodRetryable = (req) => retryOnMethods.includes(req.method);
236
- const loop = (req, attempt) => {
777
+ const nextDelay = (retryId, attempt, startedAt, retryable, retryAfter) => {
778
+ if (!retryable) return void 0;
779
+ if (wasmPlanner && retryId !== void 0) {
780
+ return wasmPlanner.nextDelayMs(retryId, {
781
+ nowMs: performance.now(),
782
+ retryable,
783
+ retryAfterMs: retryAfter
784
+ });
785
+ }
786
+ const remainingBudget = maxElapsedMs === void 0 ? Number.POSITIVE_INFINITY : maxElapsedMs - (performance.now() - startedAt);
787
+ if (remainingBudget <= 0) return void 0;
788
+ const rawDelay = retryAfter === void 0 ? backoffDelayMs2(attempt, p.baseDelayMs, p.maxDelayMs) : Math.min(retryAfter, p.maxDelayMs);
789
+ return Math.max(0, Math.min(rawDelay, remainingBudget));
790
+ };
791
+ const dropPlanner = (retryId) => {
792
+ if (retryId !== void 0) _optionalChain([wasmPlanner, 'optionalAccess', _23 => _23.drop, 'call', _24 => _24(retryId)]);
793
+ };
794
+ const loop = (req, attempt, startedAt, retryId) => {
237
795
  if (!isMethodRetryable(req)) return next(req);
238
- return _chunk2P4PD6D7cjs.asyncFold.call(void 0,
796
+ const remainingBudget = () => maxElapsedMs === void 0 ? Number.POSITIVE_INFINITY : maxElapsedMs - (performance.now() - startedAt);
797
+ return _chunkBKBFSOGTcjs.asyncFold.call(void 0,
239
798
  next(req),
240
799
  (e) => {
241
- if (e._tag === "Abort" || e._tag === "BadUrl") return _chunk2P4PD6D7cjs.asyncFail.call(void 0, e);
242
- const canRetry = attempt < p.maxRetries && retryOnError(e);
243
- if (!canRetry) return _chunk2P4PD6D7cjs.asyncFail.call(void 0, e);
244
- const d = backoffDelayMs2(attempt, p.baseDelayMs, p.maxDelayMs);
245
- return _chunk2P4PD6D7cjs.asyncFlatMap.call(void 0, sleepMs(d), () => loop(req, attempt + 1));
800
+ if (e._tag === "Abort" || e._tag === "BadUrl" || e._tag === "PoolRejected") {
801
+ dropPlanner(retryId);
802
+ return _chunkBKBFSOGTcjs.asyncFail.call(void 0, e);
803
+ }
804
+ const retryable = attempt < p.maxRetries && retryOnError(e) && remainingBudget() > 0;
805
+ const d = nextDelay(retryId, attempt, startedAt, retryable);
806
+ if (d === void 0 || d <= 0 && maxElapsedMs !== void 0) {
807
+ dropPlanner(retryId);
808
+ return _chunkBKBFSOGTcjs.asyncFail.call(void 0, e);
809
+ }
810
+ return _chunkBKBFSOGTcjs.asyncFlatMap.call(void 0, _chunkSKVY72E5cjs.sleep.call(void 0, d), () => loop(req, attempt + 1, startedAt, retryId));
246
811
  },
247
812
  (w) => {
248
- const canRetry = attempt < p.maxRetries && retryOnStatus(w.status);
249
- if (!canRetry) return _chunk2P4PD6D7cjs.asyncSucceed.call(void 0, w);
250
- const ra = retryAfterMs2(w.headers);
251
- const d = _nullishCoalesce(ra, () => ( backoffDelayMs2(attempt, p.baseDelayMs, p.maxDelayMs)));
252
- return _chunk2P4PD6D7cjs.asyncFlatMap.call(void 0, sleepMs(d), () => loop(req, attempt + 1));
813
+ const retryable = attempt < p.maxRetries && retryOnStatus(w.status) && remainingBudget() > 0;
814
+ const ra = p.respectRetryAfter === false ? void 0 : retryAfterMs2(w.headers);
815
+ const d = nextDelay(retryId, attempt, startedAt, retryable, ra);
816
+ if (d === void 0 || d <= 0 && maxElapsedMs !== void 0) {
817
+ dropPlanner(retryId);
818
+ return _chunkBKBFSOGTcjs.asyncSucceed.call(void 0, w);
819
+ }
820
+ return _chunkBKBFSOGTcjs.asyncFlatMap.call(void 0, _chunkSKVY72E5cjs.sleep.call(void 0, d), () => loop(req, attempt + 1, startedAt, retryId));
253
821
  }
254
822
  );
255
823
  };
256
- return (req) => loop(req, 0);
824
+ return (req) => {
825
+ if (!isMethodRetryable(req)) return next(req);
826
+ const startedAt = performance.now();
827
+ const retryId = _optionalChain([wasmPlanner, 'optionalAccess', _25 => _25.start, 'call', _26 => _26({
828
+ nowMs: startedAt,
829
+ maxRetries: p.maxRetries,
830
+ baseDelayMs: p.baseDelayMs,
831
+ maxDelayMs: p.maxDelayMs,
832
+ maxElapsedMs
833
+ })]);
834
+ return loop(req, 0, startedAt, retryId);
835
+ };
257
836
  };
258
837
 
259
838
  // src/http/httpClient.ts
260
839
  var resolveFinalUrl = (baseUrl, url) => {
261
840
  try {
262
841
  return new URL(url, _nullishCoalesce(baseUrl, () => ( ""))).toString();
263
- } catch (e4) {
842
+ } catch (e3) {
264
843
  return (_nullishCoalesce(baseUrl, () => ( ""))) + url;
265
844
  }
266
845
  };
267
846
  var createHttpCore = (cfg = {}) => {
268
847
  const wire = makeHttp(cfg);
269
- const withPromise = (eff) => _chunk2P4PD6D7cjs.withAsyncPromise.call(void 0, (e, env) => _chunk2P4PD6D7cjs.toPromise.call(void 0, e, env))(eff);
848
+ const withPromise = (eff) => _chunkBKBFSOGTcjs.withAsyncPromise.call(void 0, (e, env) => _chunkBKBFSOGTcjs.toPromise.call(void 0, e, env))(eff);
270
849
  const requestRaw = (req) => wire(req);
271
850
  const splitInit = (init) => {
272
- const { headers, ...rest } = _nullishCoalesce(init, () => ( {}));
851
+ const { headers, timeoutMs, poolKey, ...rest } = _nullishCoalesce(init, () => ( {}));
273
852
  return {
274
853
  headers: normalizeHeadersInit(headers),
854
+ timeoutMs: typeof timeoutMs === "number" ? timeoutMs : void 0,
855
+ poolKey: typeof poolKey === "string" ? poolKey : void 0,
275
856
  init: rest
276
857
  };
277
858
  };
@@ -282,6 +863,8 @@ var createHttpCore = (cfg = {}) => {
282
863
  method,
283
864
  url,
284
865
  ...body && body.length > 0 ? { body } : {},
866
+ ...s.timeoutMs !== void 0 ? { timeoutMs: s.timeoutMs } : {},
867
+ ...s.poolKey !== void 0 ? { poolKey: s.poolKey } : {},
285
868
  init: s.init
286
869
  };
287
870
  return applyInitHeaders(s.headers)(req);
@@ -312,12 +895,12 @@ function httpClient(cfg = {}) {
312
895
  const post = (url, body, init) => request(core.buildReq("POST", url, init, body));
313
896
  const getText = (url, init) => {
314
897
  const req = core.buildReq("GET", url, init);
315
- return core.withPromise(_chunk2P4PD6D7cjs.mapTryAsync.call(void 0, requestRaw(req), (w) => core.toResponse(w, w.bodyText)));
898
+ return core.withPromise(_chunkBKBFSOGTcjs.mapTryAsync.call(void 0, requestRaw(req), (w) => core.toResponse(w, w.bodyText)));
316
899
  };
317
900
  const getJson = (url, init) => {
318
901
  const base = core.buildReq("GET", url, init);
319
902
  const req = setHeaderIfMissing("accept", "application/json")(base);
320
- return core.withPromise(_chunk2P4PD6D7cjs.mapTryAsync.call(void 0, requestRaw(req), (w) => core.toResponse(w, JSON.parse(w.bodyText))));
903
+ return core.withPromise(_chunkBKBFSOGTcjs.mapTryAsync.call(void 0, requestRaw(req), (w) => core.toResponse(w, JSON.parse(w.bodyText))));
321
904
  };
322
905
  const postJson = (url, bodyObj, init) => {
323
906
  const base = core.buildReq("POST", url, init, JSON.stringify(_nullishCoalesce(bodyObj, () => ( {}))));
@@ -325,7 +908,7 @@ function httpClient(cfg = {}) {
325
908
  setHeaderIfMissing("accept", "application/json")(base)
326
909
  );
327
910
  return core.withPromise(
328
- _chunk2P4PD6D7cjs.mapTryAsync.call(void 0, requestRaw(req), (w) => core.toResponse(w, JSON.parse(w.bodyText)))
911
+ _chunkBKBFSOGTcjs.mapTryAsync.call(void 0, requestRaw(req), (w) => core.toResponse(w, JSON.parse(w.bodyText)))
329
912
  );
330
913
  };
331
914
  return {
@@ -337,7 +920,8 @@ function httpClient(cfg = {}) {
337
920
  postJson,
338
921
  with: (mw) => make(wire.with(mw)),
339
922
  withRetry: (p) => make(wire.with(withRetry(p))),
340
- wire
923
+ wire,
924
+ stats: () => wire.stats()
341
925
  };
342
926
  };
343
927
  return make(core.wire);
@@ -353,7 +937,7 @@ function httpClientWithMeta(cfg = {}) {
353
937
  const request = (req) => {
354
938
  const startedAt = Date.now();
355
939
  return core.withPromise(
356
- _chunk2P4PD6D7cjs.mapTryAsync.call(void 0, core.requestRaw(req), (w) => ({
940
+ _chunkBKBFSOGTcjs.mapTryAsync.call(void 0, core.requestRaw(req), (w) => ({
357
941
  wire: w,
358
942
  meta: mkMeta(req, w, startedAt)
359
943
  }))
@@ -374,7 +958,7 @@ function httpClientWithMeta(cfg = {}) {
374
958
  );
375
959
  const startedAt = Date.now();
376
960
  return core.withPromise(
377
- _chunk2P4PD6D7cjs.mapTryAsync.call(void 0, core.requestRaw(req), (w) => ({
961
+ _chunkBKBFSOGTcjs.mapTryAsync.call(void 0, core.requestRaw(req), (w) => ({
378
962
  wire: w,
379
963
  response: core.toResponse(w, JSON.parse(w.bodyText)),
380
964
  meta: mkMeta(req, w, startedAt)
@@ -385,7 +969,7 @@ function httpClientWithMeta(cfg = {}) {
385
969
  const req = core.buildReq("GET", url, init);
386
970
  const startedAt = Date.now();
387
971
  return core.withPromise(
388
- _chunk2P4PD6D7cjs.mapTryAsync.call(void 0, core.requestRaw(req), (w) => ({
972
+ _chunkBKBFSOGTcjs.mapTryAsync.call(void 0, core.requestRaw(req), (w) => ({
389
973
  wire: w,
390
974
  response: core.toResponse(w, w.bodyText),
391
975
  meta: mkMeta(req, w, startedAt)
@@ -397,7 +981,7 @@ function httpClientWithMeta(cfg = {}) {
397
981
  const req = setHeaderIfMissing("accept", "application/json")(base);
398
982
  const startedAt = Date.now();
399
983
  return core.withPromise(
400
- _chunk2P4PD6D7cjs.mapTryAsync.call(void 0, core.requestRaw(req), (w) => ({
984
+ _chunkBKBFSOGTcjs.mapTryAsync.call(void 0, core.requestRaw(req), (w) => ({
401
985
  wire: w,
402
986
  response: core.toResponse(w, JSON.parse(w.bodyText)),
403
987
  meta: mkMeta(req, w, startedAt)
@@ -422,7 +1006,7 @@ function httpClientWithMeta(cfg = {}) {
422
1006
  function httpClientStream(cfg = {}) {
423
1007
  const wire = makeHttpStream(cfg);
424
1008
  const make = (w) => {
425
- const withPromise = (eff) => _chunk2P4PD6D7cjs.withAsyncPromise.call(void 0, (e, env) => _chunk2P4PD6D7cjs.toPromise.call(void 0, e, env))(eff);
1009
+ const withPromise = (eff) => _chunkBKBFSOGTcjs.withAsyncPromise.call(void 0, (e, env) => _chunkBKBFSOGTcjs.toPromise.call(void 0, e, env))(eff);
426
1010
  const request = (req) => withPromise(w(req));
427
1011
  const getStream = (url, init) => {
428
1012
  const base = { method: "GET", url, init };
@@ -435,12 +1019,96 @@ function httpClientStream(cfg = {}) {
435
1019
  get: getStream,
436
1020
  with: (mw) => make(mw(w)),
437
1021
  withRetry: (p) => make(withRetryStream(p)(w)),
438
- wire: w
1022
+ wire: w,
1023
+ stats: () => w.stats()
439
1024
  };
440
1025
  };
441
1026
  return make(wire);
442
1027
  }
443
1028
 
1029
+ // src/http/circuitBreaker.ts
1030
+ function withCircuitBreaker(config = {}) {
1031
+ if (config.perOrigin) {
1032
+ const breakers = /* @__PURE__ */ new Map();
1033
+ const getBreaker = (url) => {
1034
+ try {
1035
+ const origin = new URL(url).origin;
1036
+ if (!breakers.has(origin)) {
1037
+ breakers.set(origin, _chunkSKVY72E5cjs.makeCircuitBreaker.call(void 0, config));
1038
+ }
1039
+ return breakers.get(origin);
1040
+ } catch (e4) {
1041
+ if (!breakers.has("__global__")) {
1042
+ breakers.set("__global__", _chunkSKVY72E5cjs.makeCircuitBreaker.call(void 0, config));
1043
+ }
1044
+ return breakers.get("__global__");
1045
+ }
1046
+ };
1047
+ return (next) => (req) => {
1048
+ const breaker2 = getBreaker(req.url);
1049
+ return breaker2.protect(next(req));
1050
+ };
1051
+ }
1052
+ const breaker = _chunkSKVY72E5cjs.makeCircuitBreaker.call(void 0, {
1053
+ ...config,
1054
+ isFailure: _nullishCoalesce(config.isFailure, () => ( ((e) => {
1055
+ const err = e;
1056
+ return err._tag !== "BadUrl" && err._tag !== "Abort";
1057
+ })))
1058
+ });
1059
+ return (next) => (req) => {
1060
+ return breaker.protect(next(req));
1061
+ };
1062
+ }
1063
+
1064
+ // src/http/tracing.ts
1065
+ function withTracing(tracer) {
1066
+ return (next) => (req) => {
1067
+ return tracer.span(
1068
+ `HTTP ${req.method} ${req.url}`,
1069
+ next(req),
1070
+ {
1071
+ "http.method": req.method,
1072
+ "http.url": req.url,
1073
+ ..._optionalChain([req, 'access', _27 => _27.headers, 'optionalAccess', _28 => _28["content-type"]]) ? { "http.content_type": req.headers["content-type"] } : {}
1074
+ }
1075
+ );
1076
+ };
1077
+ }
1078
+
1079
+ // src/http/validation.ts
1080
+ function validatedJson(client, validator) {
1081
+ return (req) => _chunkBKBFSOGTcjs.asyncFold.call(void 0,
1082
+ client(req),
1083
+ (error) => _chunkBKBFSOGTcjs.asyncFail.call(void 0, error),
1084
+ (response) => {
1085
+ try {
1086
+ const parsed = JSON.parse(response.bodyText);
1087
+ const result = validator(parsed);
1088
+ if (result.success) {
1089
+ return _chunkBKBFSOGTcjs.asyncSucceed.call(void 0, result.data);
1090
+ }
1091
+ return _chunkBKBFSOGTcjs.asyncFail.call(void 0, {
1092
+ _tag: "ValidationError",
1093
+ message: result.error,
1094
+ body: response.bodyText
1095
+ });
1096
+ } catch (e) {
1097
+ return _chunkBKBFSOGTcjs.asyncFail.call(void 0, {
1098
+ _tag: "ValidationError",
1099
+ message: `JSON parse error: ${String(e)}`,
1100
+ body: response.bodyText
1101
+ });
1102
+ }
1103
+ }
1104
+ );
1105
+ }
1106
+
1107
+
1108
+
1109
+
1110
+
1111
+
444
1112
 
445
1113
 
446
1114
 
@@ -450,4 +1118,4 @@ function httpClientStream(cfg = {}) {
450
1118
 
451
1119
 
452
1120
 
453
- exports.decorate = decorate; exports.httpClient = httpClient; exports.httpClientStream = httpClientStream; exports.httpClientWithMeta = httpClientWithMeta; exports.makeHttp = makeHttp; exports.makeHttpStream = makeHttpStream; exports.normalizeHeadersInit = normalizeHeadersInit; exports.withMiddleware = withMiddleware; exports.withRetryStream = withRetryStream;
1121
+ exports.HttpConcurrencyPool = HttpConcurrencyPool; exports.decorate = decorate; exports.httpClient = httpClient; exports.httpClientStream = httpClientStream; exports.httpClientWithMeta = httpClientWithMeta; exports.makeHttp = makeHttp; exports.makeHttpStream = makeHttpStream; exports.normalizeHeadersInit = normalizeHeadersInit; exports.resolveHttpPoolKey = resolveHttpPoolKey; exports.validatedJson = validatedJson; exports.withCircuitBreaker = withCircuitBreaker; exports.withMiddleware = withMiddleware; exports.withRetryStream = withRetryStream; exports.withTracing = withTracing;