arthron 0.0.1 → 0.1.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.
- package/README.md +190 -135
- package/dist/index.cjs +243 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +63 -3
- package/dist/index.d.mts +63 -3
- package/dist/index.mjs +243 -19
- package/dist/index.mjs.map +1 -1
- package/llms.txt +99 -0
- package/package.json +5 -2
package/dist/index.cjs
CHANGED
|
@@ -49,12 +49,18 @@ function serializeBody(body, headers) {
|
|
|
49
49
|
*/
|
|
50
50
|
var ArthronError = class extends Error {
|
|
51
51
|
name = "ArthronError";
|
|
52
|
+
/** HTTP status of the failed response; `0` when fetch itself failed (network). */
|
|
52
53
|
status;
|
|
53
54
|
statusText;
|
|
55
|
+
/** Response headers; empty for network failures. */
|
|
54
56
|
headers;
|
|
57
|
+
/** Parsed body: JSON if it parses, else the text, else `undefined`. */
|
|
55
58
|
data;
|
|
59
|
+
/** The context that was sent — url and init after request interceptors. */
|
|
56
60
|
request;
|
|
61
|
+
/** The original Response, still readable (the body was parsed from a clone). Absent for network failures. */
|
|
57
62
|
response;
|
|
63
|
+
/** The underlying error for network failures. */
|
|
58
64
|
cause;
|
|
59
65
|
constructor(init) {
|
|
60
66
|
super(init.message);
|
|
@@ -67,6 +73,7 @@ var ArthronError = class extends Error {
|
|
|
67
73
|
this.cause = init.cause;
|
|
68
74
|
}
|
|
69
75
|
};
|
|
76
|
+
/** `true` for errors arthron produced: a `!ok` response or a network failure. Cancellations and plugin bugs are not ArthronErrors. */
|
|
70
77
|
function isArthronError(error) {
|
|
71
78
|
return error instanceof ArthronError;
|
|
72
79
|
}
|
|
@@ -95,7 +102,7 @@ async function readBody(response) {
|
|
|
95
102
|
}
|
|
96
103
|
}
|
|
97
104
|
async function responseError(response, request, resolve = defaultErrorMessage) {
|
|
98
|
-
const data = await readBody(response);
|
|
105
|
+
const data = await readBody(response.clone());
|
|
99
106
|
return new ArthronError({
|
|
100
107
|
message: resolve(data, response) ?? `Request failed with status ${response.status}`,
|
|
101
108
|
status: response.status,
|
|
@@ -196,7 +203,101 @@ async function runPipeline(plugins, ctx, send) {
|
|
|
196
203
|
return dispatch(0, ctx);
|
|
197
204
|
}
|
|
198
205
|
//#endregion
|
|
206
|
+
//#region src/stream.ts
|
|
207
|
+
/** Yield the body line by line, reassembling lines split across chunks. */
|
|
208
|
+
async function* lines(response) {
|
|
209
|
+
if (!response.body) return;
|
|
210
|
+
const reader = response.body.getReader();
|
|
211
|
+
const decoder = new TextDecoder();
|
|
212
|
+
let buffer = "";
|
|
213
|
+
try {
|
|
214
|
+
while (true) {
|
|
215
|
+
const { value, done } = await reader.read();
|
|
216
|
+
buffer += decoder.decode(value, { stream: !done });
|
|
217
|
+
const parts = buffer.split(/\r\n|\r|\n/);
|
|
218
|
+
buffer = parts.pop() ?? "";
|
|
219
|
+
for (const line of parts) yield line;
|
|
220
|
+
if (done) break;
|
|
221
|
+
}
|
|
222
|
+
if (buffer) yield buffer;
|
|
223
|
+
} finally {
|
|
224
|
+
reader.releaseLock();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Parse a `text/event-stream` body into events. Follows the spec: a blank
|
|
229
|
+
* line dispatches, `data:` lines are joined with `\n`, one leading space
|
|
230
|
+
* after the colon is dropped, lines starting with `:` are comments, and a
|
|
231
|
+
* block without `data` is not dispatched. Provider sentinels like
|
|
232
|
+
* `[DONE]` are passed through untouched — they are not part of the spec.
|
|
233
|
+
*/
|
|
234
|
+
async function* parseSse(response) {
|
|
235
|
+
let data = [];
|
|
236
|
+
let event;
|
|
237
|
+
let id;
|
|
238
|
+
let retry;
|
|
239
|
+
const flush = () => {
|
|
240
|
+
if (data.length === 0) {
|
|
241
|
+
event = id = retry = void 0;
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const out = { data: data.join("\n") };
|
|
245
|
+
if (event !== void 0) out.event = event;
|
|
246
|
+
if (id !== void 0) out.id = id;
|
|
247
|
+
if (retry !== void 0) out.retry = retry;
|
|
248
|
+
data = [];
|
|
249
|
+
event = id = retry = void 0;
|
|
250
|
+
return out;
|
|
251
|
+
};
|
|
252
|
+
for await (const line of lines(response)) {
|
|
253
|
+
if (line === "") {
|
|
254
|
+
const out = flush();
|
|
255
|
+
if (out) yield out;
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
if (line.startsWith(":")) continue;
|
|
259
|
+
const colon = line.indexOf(":");
|
|
260
|
+
const field = colon === -1 ? line : line.slice(0, colon);
|
|
261
|
+
let value = colon === -1 ? "" : line.slice(colon + 1);
|
|
262
|
+
if (value.startsWith(" ")) value = value.slice(1);
|
|
263
|
+
switch (field) {
|
|
264
|
+
case "data":
|
|
265
|
+
data.push(value);
|
|
266
|
+
break;
|
|
267
|
+
case "event":
|
|
268
|
+
event = value;
|
|
269
|
+
break;
|
|
270
|
+
case "id":
|
|
271
|
+
id = value;
|
|
272
|
+
break;
|
|
273
|
+
case "retry": {
|
|
274
|
+
const ms = Number(value);
|
|
275
|
+
if (Number.isInteger(ms) && ms >= 0) retry = ms;
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const out = flush();
|
|
281
|
+
if (out) yield out;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Parse newline-delimited JSON. Blank lines are skipped; a malformed line
|
|
285
|
+
* rejects the iteration with the `SyntaxError` from `JSON.parse`.
|
|
286
|
+
*/
|
|
287
|
+
async function* parseNdjson(response) {
|
|
288
|
+
for await (const line of lines(response)) {
|
|
289
|
+
if (line.trim() === "") continue;
|
|
290
|
+
yield JSON.parse(line);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
//#endregion
|
|
199
294
|
//#region src/promise.ts
|
|
295
|
+
/** Defer the Response until iteration starts, so the helper itself never rejects. */
|
|
296
|
+
function iterate(promise, parse) {
|
|
297
|
+
return { async *[Symbol.asyncIterator]() {
|
|
298
|
+
yield* parse(await promise);
|
|
299
|
+
} };
|
|
300
|
+
}
|
|
200
301
|
/**
|
|
201
302
|
* Sugar over `Promise<Response>`: `.json()` is `(await p).json()`. Awaiting
|
|
202
303
|
* without a helper still yields the untouched `Response`.
|
|
@@ -207,7 +308,9 @@ function toArthronPromise(promise) {
|
|
|
207
308
|
text: () => promise.then((res) => res.text()),
|
|
208
309
|
blob: () => promise.then((res) => res.blob()),
|
|
209
310
|
arrayBuffer: () => promise.then((res) => res.arrayBuffer()),
|
|
210
|
-
formData: () => promise.then((res) => res.formData())
|
|
311
|
+
formData: () => promise.then((res) => res.formData()),
|
|
312
|
+
sse: () => iterate(promise, parseSse),
|
|
313
|
+
ndjson: () => iterate(promise, parseNdjson)
|
|
211
314
|
});
|
|
212
315
|
}
|
|
213
316
|
//#endregion
|
|
@@ -234,15 +337,18 @@ function resolveUrl(baseURL, path, params) {
|
|
|
234
337
|
let url;
|
|
235
338
|
if (isAbsoluteUrl(path)) url = path;
|
|
236
339
|
else {
|
|
237
|
-
if (baseURL === void 0) throw new TypeError(`arthron: relative path "${path}"
|
|
340
|
+
if (baseURL === void 0) throw new TypeError(`arthron: relative path "${path}" needs a baseURL — pass baseURL in arthron({ baseURL }) or use an absolute URL`);
|
|
238
341
|
url = `${baseURL.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
239
342
|
}
|
|
240
343
|
const query = toQueryString(params);
|
|
241
344
|
if (!query) return url;
|
|
242
|
-
|
|
345
|
+
const hash = url.indexOf("#");
|
|
346
|
+
const [base, fragment] = hash === -1 ? [url, ""] : [url.slice(0, hash), url.slice(hash)];
|
|
347
|
+
return `${base}${base.includes("?") ? "&" : "?"}${query}${fragment}`;
|
|
243
348
|
}
|
|
244
349
|
//#endregion
|
|
245
350
|
//#region src/client.ts
|
|
351
|
+
const BODYLESS_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD"]);
|
|
246
352
|
/** Normalize once so `extend` can merge headers and append plugins cheaply. */
|
|
247
353
|
function resolveConfig(config) {
|
|
248
354
|
const { headers, use, ...rest } = config;
|
|
@@ -258,9 +364,9 @@ function resolveConfig(config) {
|
|
|
258
364
|
*/
|
|
259
365
|
function arthron(config = {}) {
|
|
260
366
|
const resolved = resolveConfig(config);
|
|
261
|
-
const fetchImpl = resolved.fetch ?? globalThis.fetch;
|
|
262
367
|
/** The one place a failure becomes an ArthronError. Cancellation passes through. */
|
|
263
368
|
const send = async (ctx) => {
|
|
369
|
+
const fetchImpl = resolved.fetch ?? globalThis.fetch;
|
|
264
370
|
let res;
|
|
265
371
|
try {
|
|
266
372
|
res = await fetchImpl(ctx.url, ctx.init);
|
|
@@ -273,6 +379,7 @@ function arthron(config = {}) {
|
|
|
273
379
|
};
|
|
274
380
|
const dispatch = (path, method, rawBody, { params, headers: callHeaders, ...init }) => {
|
|
275
381
|
try {
|
|
382
|
+
if (rawBody !== void 0 && BODYLESS_METHODS.has(method.toUpperCase())) throw new TypeError(`arthron: ${method.toUpperCase()} requests cannot carry a body — send it as params (query) or switch to POST`);
|
|
276
383
|
const url = resolveUrl(resolved.baseURL, path, params);
|
|
277
384
|
const { body, headers } = serializeBody(rawBody, mergeHeaders(resolved.headers, callHeaders));
|
|
278
385
|
const requestInit = {
|
|
@@ -307,7 +414,7 @@ function arthron(config = {}) {
|
|
|
307
414
|
patch: (path, body, init = {}) => dispatch(path, "PATCH", body, init),
|
|
308
415
|
request: (path, init) => {
|
|
309
416
|
const { method, body, ...rest } = init;
|
|
310
|
-
if (!method) return toArthronPromise(Promise.reject(/* @__PURE__ */ new TypeError("arthron: request()
|
|
417
|
+
if (!method) return toArthronPromise(Promise.reject(/* @__PURE__ */ new TypeError("arthron: request() needs init.method — e.g. request(path, { method: \"OPTIONS\" }); use get/post/... for the common verbs")));
|
|
311
418
|
return dispatch(path, method, body, rest);
|
|
312
419
|
},
|
|
313
420
|
use: (...plugins) => derive({ use: plugins }),
|
|
@@ -316,41 +423,79 @@ function arthron(config = {}) {
|
|
|
316
423
|
}
|
|
317
424
|
//#endregion
|
|
318
425
|
//#region src/define.ts
|
|
426
|
+
/** Identity helper: gives a middleware written in its own file the right parameter types. */
|
|
319
427
|
const defineMiddleware = (middleware) => middleware;
|
|
428
|
+
/** Identity helpers for each interceptor kind — `defineInterceptor.request(fn)` etc. */
|
|
320
429
|
const defineInterceptor = {
|
|
321
430
|
request: (interceptor) => interceptor,
|
|
322
431
|
response: (interceptor) => interceptor,
|
|
323
432
|
error: (interceptor) => interceptor
|
|
324
433
|
};
|
|
434
|
+
/** Identity helper: type-checks a plugin object literal without an annotation. */
|
|
325
435
|
const definePlugin = (plugin) => plugin;
|
|
326
436
|
//#endregion
|
|
327
437
|
//#region src/plugins/logging.ts
|
|
438
|
+
/** Origin + pathname only — the query and userinfo tend to carry secrets. */
|
|
439
|
+
function displayPath(url) {
|
|
440
|
+
try {
|
|
441
|
+
const parsed = new URL(url);
|
|
442
|
+
return `${parsed.origin}${parsed.pathname}`;
|
|
443
|
+
} catch {
|
|
444
|
+
return url.split("?")[0] ?? url;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
function consoleSink(logger) {
|
|
448
|
+
return (e) => {
|
|
449
|
+
if (e.phase === "start") logger.log(`[arthron] → ${e.method} ${e.path}`);
|
|
450
|
+
else if (e.phase === "success") logger.log(`[arthron] ← ${e.method} ${e.path} ${e.status} ${e.ms}ms`);
|
|
451
|
+
else logger.error(`[arthron] ✕ ${e.method} ${e.path} ${e.status ?? "-"} ${e.ms}ms`);
|
|
452
|
+
};
|
|
453
|
+
}
|
|
328
454
|
/**
|
|
329
|
-
* One
|
|
455
|
+
* One event when a request starts, one when it settles, with status and
|
|
330
456
|
* elapsed time. Wraps the whole call, so retries are included in the time.
|
|
331
|
-
*
|
|
457
|
+
* Default output is a console line per event; pass `onEvent` to get the
|
|
458
|
+
* structured event instead. Cancellations are not reported.
|
|
332
459
|
*/
|
|
333
460
|
function logging(options = {}) {
|
|
334
|
-
const { enabled = true, logger = console, now = () => performance.now() } = options;
|
|
461
|
+
const { enabled = true, logger = console, onEvent, now = () => performance.now() } = options;
|
|
462
|
+
const emit = onEvent ?? consoleSink(logger);
|
|
335
463
|
return {
|
|
336
464
|
name: "logging",
|
|
337
465
|
middleware: async (ctx, next) => {
|
|
338
466
|
if (!enabled) return next(ctx);
|
|
339
467
|
const method = ctx.init.method ?? "GET";
|
|
340
|
-
const path = ctx.url
|
|
468
|
+
const path = displayPath(ctx.url);
|
|
341
469
|
const started = now();
|
|
342
|
-
const
|
|
343
|
-
|
|
470
|
+
const ms = () => Math.round(now() - started);
|
|
471
|
+
emit({
|
|
472
|
+
phase: "start",
|
|
473
|
+
method,
|
|
474
|
+
path
|
|
475
|
+
});
|
|
344
476
|
try {
|
|
345
477
|
const res = await next(ctx);
|
|
346
|
-
|
|
478
|
+
emit({
|
|
479
|
+
phase: "success",
|
|
480
|
+
method,
|
|
481
|
+
path,
|
|
482
|
+
status: res.status,
|
|
483
|
+
ms: ms()
|
|
484
|
+
});
|
|
347
485
|
return res;
|
|
348
|
-
} catch (
|
|
349
|
-
if (!isCancellation(
|
|
350
|
-
const status = isArthronError(
|
|
351
|
-
|
|
486
|
+
} catch (error) {
|
|
487
|
+
if (!isCancellation(error)) {
|
|
488
|
+
const status = isArthronError(error) ? error.status : void 0;
|
|
489
|
+
emit({
|
|
490
|
+
phase: "error",
|
|
491
|
+
method,
|
|
492
|
+
path,
|
|
493
|
+
status,
|
|
494
|
+
ms: ms(),
|
|
495
|
+
error
|
|
496
|
+
});
|
|
352
497
|
}
|
|
353
|
-
throw
|
|
498
|
+
throw error;
|
|
354
499
|
}
|
|
355
500
|
}
|
|
356
501
|
};
|
|
@@ -428,6 +573,85 @@ function retry(options = {}) {
|
|
|
428
573
|
};
|
|
429
574
|
}
|
|
430
575
|
//#endregion
|
|
576
|
+
//#region src/plugins/timeout.ts
|
|
577
|
+
/**
|
|
578
|
+
* Wrap `body` so the deadline keeps running until it is fully read, and so an
|
|
579
|
+
* abort while reading surfaces as a stream error instead of a silent stall.
|
|
580
|
+
*/
|
|
581
|
+
function guardBody(body, signal, finish) {
|
|
582
|
+
const reader = body.getReader();
|
|
583
|
+
return new ReadableStream({
|
|
584
|
+
start(controller) {
|
|
585
|
+
signal.addEventListener("abort", () => {
|
|
586
|
+
controller.error(signal.reason);
|
|
587
|
+
reader.cancel(signal.reason).catch(() => {});
|
|
588
|
+
}, { once: true });
|
|
589
|
+
},
|
|
590
|
+
async pull(controller) {
|
|
591
|
+
const { value, done } = await reader.read();
|
|
592
|
+
if (done) {
|
|
593
|
+
finish();
|
|
594
|
+
controller.close();
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
controller.enqueue(value);
|
|
598
|
+
},
|
|
599
|
+
cancel(reason) {
|
|
600
|
+
finish();
|
|
601
|
+
return reader.cancel(reason);
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Abort the request when it takes longer than `ms` — counted from the start
|
|
607
|
+
* of the attempt until the body has been fully read, so a server that sends
|
|
608
|
+
* headers and then stalls still trips it. Rejects with a `TimeoutError` (a
|
|
609
|
+
* `DOMException`, like `AbortSignal.timeout()` would give); it is not an
|
|
610
|
+
* `ArthronError`, so error interceptors and `retry` leave it alone.
|
|
611
|
+
*
|
|
612
|
+
* Built on `AbortController` + `setTimeout` rather than `AbortSignal.any` /
|
|
613
|
+
* `AbortSignal.timeout` so it runs on Node 18.
|
|
614
|
+
*/
|
|
615
|
+
function timeout(ms) {
|
|
616
|
+
return {
|
|
617
|
+
name: "timeout",
|
|
618
|
+
middleware: async (ctx, next) => {
|
|
619
|
+
const controller = new AbortController();
|
|
620
|
+
const outer = ctx.init.signal;
|
|
621
|
+
const forward = () => controller.abort(outer?.reason);
|
|
622
|
+
if (outer?.aborted) forward();
|
|
623
|
+
else outer?.addEventListener("abort", forward, { once: true });
|
|
624
|
+
const timer = setTimeout(() => controller.abort(new DOMException(`arthron: request timed out after ${ms}ms`, "TimeoutError")), ms);
|
|
625
|
+
const finish = () => {
|
|
626
|
+
clearTimeout(timer);
|
|
627
|
+
outer?.removeEventListener("abort", forward);
|
|
628
|
+
};
|
|
629
|
+
let res;
|
|
630
|
+
try {
|
|
631
|
+
res = await next({
|
|
632
|
+
...ctx,
|
|
633
|
+
init: {
|
|
634
|
+
...ctx.init,
|
|
635
|
+
signal: controller.signal
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
} catch (thrown) {
|
|
639
|
+
finish();
|
|
640
|
+
throw thrown;
|
|
641
|
+
}
|
|
642
|
+
if (!res.body) {
|
|
643
|
+
finish();
|
|
644
|
+
return res;
|
|
645
|
+
}
|
|
646
|
+
return new Response(guardBody(res.body, controller.signal, finish), {
|
|
647
|
+
status: res.status,
|
|
648
|
+
statusText: res.statusText,
|
|
649
|
+
headers: res.headers
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
//#endregion
|
|
431
655
|
exports.ArthronError = ArthronError;
|
|
432
656
|
exports.arthron = arthron;
|
|
433
657
|
exports.defineInterceptor = defineInterceptor;
|
|
@@ -436,5 +660,6 @@ exports.definePlugin = definePlugin;
|
|
|
436
660
|
exports.isArthronError = isArthronError;
|
|
437
661
|
exports.logging = logging;
|
|
438
662
|
exports.retry = retry;
|
|
663
|
+
exports.timeout = timeout;
|
|
439
664
|
|
|
440
665
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":[],"sources":["../src/body.ts","../src/error.ts","../src/headers.ts","../src/pipeline.ts","../src/promise.ts","../src/url.ts","../src/client.ts","../src/define.ts","../src/plugins/logging.ts","../src/plugins/retry.ts"],"sourcesContent":["type Serialized = {\n body: BodyInit | undefined;\n headers: Record<string, string>;\n};\n\nconst RAW_CTORS = [\n \"FormData\",\n \"Blob\",\n \"ArrayBuffer\",\n \"URLSearchParams\",\n \"ReadableStream\",\n] as const;\n\n/** Bodies fetch already knows how to send. Content-Type is left to the caller / runtime. */\nfunction isRawBody(body: unknown): body is BodyInit {\n if (typeof body === \"string\") return true;\n if (ArrayBuffer.isView(body)) return true;\n return RAW_CTORS.some((name) => {\n const ctor = (globalThis as Record<string, unknown>)[name];\n return typeof ctor === \"function\" && body instanceof ctor;\n });\n}\n\n/**\n * Turn a caller-supplied body into something fetch accepts. Raw bodies pass\n * through untouched; anything else is JSON with `content-type` set — unless\n * the caller already set one (headers arrive lowercased, so a plain lookup is\n * enough).\n */\nexport function serializeBody(\n body: unknown,\n headers: Record<string, string>,\n): Serialized {\n if (body === undefined) return { body: undefined, headers };\n if (isRawBody(body)) return { body, headers };\n const withType =\n \"content-type\" in headers\n ? headers\n : { ...headers, \"content-type\": \"application/json\" };\n return { body: JSON.stringify(body), headers: withType };\n}\n","import type { RequestContext } from \"./types\";\n\nexport type ErrorMessageResolver = (\n data: unknown,\n response: Response,\n) => string | undefined;\n\ntype ArthronErrorInit = {\n message: string;\n status: number;\n statusText: string;\n headers: Headers;\n data: unknown;\n request: RequestContext;\n response?: Response;\n cause?: unknown;\n};\n\n/**\n * The only error arthron itself produces. Two sources: a response with\n * `ok === false`, or fetch rejecting outright (network failure, `status` 0).\n * Everything else — cancellation, interceptor bugs — is rethrown untouched.\n */\nexport class ArthronError extends Error {\n override readonly name = \"ArthronError\";\n readonly status: number;\n readonly statusText: string;\n readonly headers: Headers;\n readonly data: unknown;\n readonly request: RequestContext;\n readonly response: Response | undefined;\n override readonly cause: unknown;\n\n constructor(init: ArthronErrorInit) {\n super(init.message);\n this.status = init.status;\n this.statusText = init.statusText;\n this.headers = init.headers;\n this.data = init.data;\n this.request = init.request;\n this.response = init.response;\n this.cause = init.cause;\n }\n}\n\nexport function isArthronError(error: unknown): error is ArthronError {\n return error instanceof ArthronError;\n}\n\n/** Cancellation is not an HTTP failure. Matched by name so it works across realms. */\nexport function isCancellation(error: unknown): boolean {\n const name = (error as { name?: unknown } | null | undefined)?.name;\n return name === \"AbortError\" || name === \"TimeoutError\";\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null;\n\n/** `message` → `error` (string) → `error.message` → `detail`. */\nexport function defaultErrorMessage(data: unknown): string | undefined {\n if (!isRecord(data)) return undefined;\n if (typeof data.message === \"string\") return data.message;\n if (typeof data.error === \"string\") return data.error;\n if (isRecord(data.error) && typeof data.error.message === \"string\") {\n return data.error.message;\n }\n if (typeof data.detail === \"string\") return data.detail;\n return undefined;\n}\n\n/** Body as JSON if it parses, else as text, else `undefined` when empty. */\nasync function readBody(response: Response): Promise<unknown> {\n const text = await response.text().catch(() => \"\");\n if (text === \"\") return undefined;\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n}\n\nexport async function responseError(\n response: Response,\n request: RequestContext,\n resolve: ErrorMessageResolver = defaultErrorMessage,\n): Promise<ArthronError> {\n const data = await readBody(response);\n return new ArthronError({\n message:\n resolve(data, response) ??\n `Request failed with status ${response.status}`,\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n data,\n request,\n response,\n });\n}\n\nexport function networkError(\n cause: unknown,\n request: RequestContext,\n): ArthronError {\n return new ArthronError({\n message: cause instanceof Error ? cause.message : \"Network error\",\n status: 0,\n statusText: \"\",\n headers: new Headers(),\n data: undefined,\n request,\n cause,\n });\n}\n","/**\n * Normalize any `HeadersInit` (plain object, `Headers`, tuple array) into a\n * plain record. Keys come back lowercased — `Headers` does that for us.\n */\nexport function toHeaderRecord(\n headers: HeadersInit | undefined,\n): Record<string, string> {\n if (!headers) return {};\n return Object.fromEntries(new Headers(headers).entries());\n}\n\n/**\n * Layer `override` on top of `base`. Names are compared case-insensitively and\n * the override wins, so a caller's `content-type` replaces a default\n * `Content-Type` instead of fetch sending both values joined.\n */\nexport function mergeHeaders(\n base: HeadersInit | undefined,\n override: HeadersInit | undefined,\n): Record<string, string> {\n return { ...toHeaderRecord(base), ...toHeaderRecord(override) };\n}\n","import { isArthronError } from \"./error\";\nimport { toHeaderRecord } from \"./headers\";\nimport type {\n ErrorInterceptor,\n Middleware,\n Plugin,\n RequestContext,\n RequestInterceptor,\n ResponseInterceptor,\n} from \"./types\";\n\n/**\n * Interceptors mutate the context in place. A middleware that calls `next`\n * again (retry) must not see the previous attempt's changes, so every attempt\n * starts from a fresh copy.\n */\nfunction cloneContext({ url, init }: RequestContext): RequestContext {\n return { url, init: { ...init, headers: toHeaderRecord(init.headers) } };\n}\n\n/**\n * Owns the order of one request.\n *\n * Middleware wraps the whole call, outermost first in registration order.\n * Each attempt below it runs request interceptors → `send` → response\n * interceptors. Only an `ArthronError` reaches the error interceptors; whatever\n * they return is thrown. Anything else that is thrown — cancellation, a bug\n * in a plugin — propagates untouched.\n */\nexport async function runPipeline(\n plugins: readonly Plugin[],\n ctx: RequestContext,\n send: (ctx: RequestContext) => Promise<Response>,\n): Promise<Response> {\n const middlewares: Middleware[] = [];\n const request: RequestInterceptor[] = [];\n const response: ResponseInterceptor[] = [];\n const error: ErrorInterceptor[] = [];\n for (const plugin of plugins) {\n if (plugin.middleware) middlewares.push(plugin.middleware);\n if (plugin.request) request.push(plugin.request);\n if (plugin.response) response.push(plugin.response);\n if (plugin.error) error.push(plugin.error);\n }\n\n const attempt = async (input: RequestContext): Promise<Response> => {\n let current = cloneContext(input);\n try {\n for (const interceptor of request) current = await interceptor(current);\n let res = await send(current);\n for (const interceptor of response) res = await interceptor(res, current);\n return res;\n } catch (thrown) {\n if (!isArthronError(thrown)) throw thrown;\n let err = thrown;\n for (const interceptor of error) err = await interceptor(err, current);\n throw err;\n }\n };\n\n const dispatch = (\n index: number,\n input: RequestContext,\n ): Promise<Response> => {\n const middleware = middlewares[index];\n if (!middleware) return attempt(input);\n return middleware(input, (next) => dispatch(index + 1, next));\n };\n return dispatch(0, ctx);\n}\n","import type { ArthronPromise } from \"./types\";\n\n/**\n * Sugar over `Promise<Response>`: `.json()` is `(await p).json()`. Awaiting\n * without a helper still yields the untouched `Response`.\n */\nexport function toArthronPromise(promise: Promise<Response>): ArthronPromise {\n return Object.assign(promise, {\n json: <T = unknown>() => promise.then((res) => res.json() as Promise<T>),\n text: () => promise.then((res) => res.text()),\n blob: () => promise.then((res) => res.blob()),\n arrayBuffer: () => promise.then((res) => res.arrayBuffer()),\n formData: () => promise.then((res) => res.formData()),\n });\n}\n","export type QueryParams = Record<\n string,\n string | number | boolean | null | undefined\n>;\n\nconst ABSOLUTE_URL = /^[a-z][a-z0-9+.-]*:\\/\\//i;\n\nexport function isAbsoluteUrl(path: string): boolean {\n return ABSOLUTE_URL.test(path);\n}\n\nfunction toQueryString(params: QueryParams | undefined): string {\n if (!params) return \"\";\n const search = new URLSearchParams();\n for (const [key, value] of Object.entries(params)) {\n if (value === null || value === undefined) continue;\n search.append(key, String(value));\n }\n return search.toString();\n}\n\n/**\n * Join `baseURL` and `path` by plain string concatenation so a path prefix in\n * the base (`/v1`) survives a leading slash in the path. WHATWG `new URL()`\n * would drop it. Absolute paths skip the base entirely.\n */\nexport function resolveUrl(\n baseURL: string | undefined,\n path: string,\n params?: QueryParams,\n): string {\n let url: string;\n if (isAbsoluteUrl(path)) {\n url = path;\n } else {\n if (baseURL === undefined) {\n throw new TypeError(\n `arthron: relative path \"${path}\" requires a baseURL`,\n );\n }\n url = `${baseURL.replace(/\\/+$/, \"\")}/${path.replace(/^\\/+/, \"\")}`;\n }\n const query = toQueryString(params);\n if (!query) return url;\n return `${url}${url.includes(\"?\") ? \"&\" : \"?\"}${query}`;\n}\n","import { serializeBody } from \"./body\";\nimport { isCancellation, networkError, responseError } from \"./error\";\nimport { mergeHeaders, toHeaderRecord } from \"./headers\";\nimport { runPipeline } from \"./pipeline\";\nimport { toArthronPromise } from \"./promise\";\nimport type {\n Arthron,\n ArthronConfig,\n ArthronPromise,\n Plugin,\n RequestContext,\n RequestInitWithBody,\n RequestOptions,\n} from \"./types\";\nimport { resolveUrl } from \"./url\";\n\ntype Resolved = Omit<ArthronConfig, \"headers\" | \"use\"> & {\n headers: Record<string, string>;\n use: readonly Plugin[];\n};\n\n/** Normalize once so `extend` can merge headers and append plugins cheaply. */\nfunction resolveConfig(config: ArthronConfig): Resolved {\n const { headers, use, ...rest } = config;\n return { ...rest, headers: toHeaderRecord(headers), use: use ?? [] };\n}\n\n/**\n * Build a client from config. The result is immutable — `use` and `extend`\n * return new clients and never touch the one they were called on.\n */\nexport function arthron(config: ArthronConfig = {}): Arthron {\n const resolved = resolveConfig(config);\n const fetchImpl = resolved.fetch ?? globalThis.fetch;\n\n /** The one place a failure becomes an ArthronError. Cancellation passes through. */\n const send = async (ctx: RequestContext): Promise<Response> => {\n let res: Response;\n try {\n res = await fetchImpl(ctx.url, ctx.init);\n } catch (thrown) {\n if (isCancellation(thrown)) throw thrown;\n throw networkError(thrown, ctx);\n }\n if (!res.ok) throw await responseError(res, ctx, resolved.errorMessage);\n return res;\n };\n\n const dispatch = (\n path: string,\n method: string,\n rawBody: unknown,\n { params, headers: callHeaders, ...init }: RequestOptions,\n ): ArthronPromise => {\n try {\n const url = resolveUrl(resolved.baseURL, path, params);\n const { body, headers } = serializeBody(\n rawBody,\n mergeHeaders(resolved.headers, callHeaders),\n );\n // Config credentials sit underneath so a per-call value wins, like every other option.\n const requestInit: RequestInit = {\n ...(resolved.credentials !== undefined && {\n credentials: resolved.credentials,\n }),\n ...init,\n method,\n headers,\n };\n if (body !== undefined) requestInit.body = body;\n return toArthronPromise(\n runPipeline(resolved.use, { url, init: requestInit }, send),\n );\n } catch (thrown) {\n // Argument errors (no baseURL, unserializable body) reject like everything else.\n return toArthronPromise(Promise.reject(thrown));\n }\n };\n\n const derive = (next: Partial<ArthronConfig>): Arthron => {\n const merged: ArthronConfig = {\n ...resolved,\n ...next,\n headers: mergeHeaders(resolved.headers, next.headers),\n use: [...resolved.use, ...(next.use ?? [])],\n };\n return arthron(merged);\n };\n\n return {\n get: (path, init = {}) => dispatch(path, \"GET\", undefined, init),\n delete: (path, init = {}) => dispatch(path, \"DELETE\", undefined, init),\n head: (path, init = {}) => dispatch(path, \"HEAD\", undefined, init),\n post: (path, body, init = {}) => dispatch(path, \"POST\", body, init),\n put: (path, body, init = {}) => dispatch(path, \"PUT\", body, init),\n patch: (path, body, init = {}) => dispatch(path, \"PATCH\", body, init),\n request: (path, init: RequestInitWithBody) => {\n const { method, body, ...rest } = init;\n if (!method) {\n return toArthronPromise(\n Promise.reject(\n new TypeError(\"arthron: request() requires init.method\"),\n ),\n );\n }\n return dispatch(path, method, body, rest);\n },\n use: (...plugins) => derive({ use: plugins }),\n extend: (next) => derive(next),\n };\n}\n","import type {\n ErrorInterceptor,\n Middleware,\n Plugin,\n RequestInterceptor,\n ResponseInterceptor,\n} from \"./types\";\n\n/*\n * Identity helpers. They do nothing at runtime; they exist so a plugin written\n * in its own file gets contextual typing without an explicit annotation.\n */\n\nexport const defineMiddleware = (middleware: Middleware): Middleware =>\n middleware;\n\nexport const defineInterceptor = {\n request: (interceptor: RequestInterceptor): RequestInterceptor => interceptor,\n response: (interceptor: ResponseInterceptor): ResponseInterceptor =>\n interceptor,\n error: (interceptor: ErrorInterceptor): ErrorInterceptor => interceptor,\n};\n\nexport const definePlugin = (plugin: Plugin): Plugin => plugin;\n","import { isArthronError, isCancellation } from \"../error\";\nimport type { Plugin } from \"../types\";\n\nexport type LoggingOptions = {\n /** Default `true`. The library never reads `NODE_ENV` — pass `isDev` yourself. */\n enabled?: boolean;\n logger?: Pick<Console, \"log\" | \"error\">;\n /** Clock in ms. Injectable for tests. */\n now?: () => number;\n};\n\n/**\n * One line when a request starts, one when it settles, with status and\n * elapsed time. Wraps the whole call, so retries are included in the time.\n * The query string is dropped — it tends to carry tokens.\n */\nexport function logging(options: LoggingOptions = {}): Plugin {\n const {\n enabled = true,\n logger = console,\n now = () => performance.now(),\n } = options;\n return {\n name: \"logging\",\n middleware: async (ctx, next) => {\n if (!enabled) return next(ctx);\n const method = ctx.init.method ?? \"GET\";\n const path = ctx.url.split(\"?\")[0];\n const started = now();\n const elapsed = () => `${Math.round(now() - started)}ms`;\n\n logger.log(`[arthron] → ${method} ${path}`);\n try {\n const res = await next(ctx);\n logger.log(`[arthron] ← ${method} ${path} ${res.status} ${elapsed()}`);\n return res;\n } catch (thrown) {\n if (!isCancellation(thrown)) {\n const status = isArthronError(thrown) ? thrown.status : \"-\";\n logger.error(`[arthron] ✕ ${method} ${path} ${status} ${elapsed()}`);\n }\n throw thrown;\n }\n },\n };\n}\n","import { type ArthronError, isArthronError } from \"../error\";\nimport type { Plugin, RequestContext } from \"../types\";\n\nexport type RetryOptions = {\n /** Number of retries after the first attempt. Default 2 (3 requests total). */\n retries?: number;\n /** Statuses worth retrying. Network errors (status 0) always are. */\n retryOn?: readonly number[];\n /** Methods safe to repeat. Compared case-insensitively. */\n methods?: readonly string[];\n /** Backoff in ms for retry number `retry` (0-based). Default 300 · 2^retry. */\n delay?: (retry: number, error: ArthronError) => number;\n /** Injectable for tests. Default `setTimeout`; rejects with the abort reason. */\n sleep?: (ms: number, signal?: AbortSignal | null) => Promise<void>;\n /**\n * Longest `Retry-After` (ms) worth honouring. Beyond it the request is not\n * retried at all. Default 60 000. Also guards `setTimeout`'s 2^31-1 ms cap,\n * past which timers fire immediately.\n */\n maxRetryAfter?: number;\n};\n\nconst DEFAULT_RETRY_ON = [408, 429, 500, 502, 503, 504] as const;\nconst DEFAULT_METHODS = [\"GET\", \"HEAD\", \"OPTIONS\", \"PUT\", \"DELETE\"] as const;\nconst DEFAULT_MAX_RETRY_AFTER = 60_000;\n\nfunction defaultSleep(ms: number, signal?: AbortSignal | null): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) return reject(signal.reason);\n const onAbort = () => {\n clearTimeout(timer);\n reject(signal?.reason);\n };\n const timer = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\n/** `Retry-After` as ms — seconds or an HTTP-date. `undefined` when absent or unparsable. */\nfunction retryAfterMs(error: ArthronError): number | undefined {\n const value = error.headers.get(\"retry-after\")?.trim();\n if (!value) return undefined;\n const seconds = Number(value);\n if (!Number.isNaN(seconds)) return Math.max(0, seconds * 1000);\n const at = Date.parse(value);\n if (Number.isNaN(at)) return undefined;\n return Math.max(0, at - Date.now());\n}\n\nfunction isStreamBody(ctx: RequestContext): boolean {\n return (\n typeof ReadableStream !== \"undefined\" &&\n ctx.init.body instanceof ReadableStream\n );\n}\n\n/**\n * Retry on network errors and the configured statuses by calling `next`\n * again — the whole attempt (request interceptors included) is re-run.\n * Only `ArthronError` is considered; cancellation and plugin bugs pass through.\n */\nexport function retry(options: RetryOptions = {}): Plugin {\n const {\n retries = 2,\n retryOn = DEFAULT_RETRY_ON,\n methods = DEFAULT_METHODS,\n delay = (retry) => 300 * 2 ** retry,\n sleep = defaultSleep,\n maxRetryAfter = DEFAULT_MAX_RETRY_AFTER,\n } = options;\n const allowed = new Set(methods.map((m) => m.toUpperCase()));\n\n return {\n name: \"retry\",\n middleware: async (ctx, next) => {\n const method = (ctx.init.method ?? \"GET\").toUpperCase();\n const eligible = allowed.has(method) && !isStreamBody(ctx);\n\n for (let attempt = 0; ; attempt += 1) {\n try {\n return await next(ctx);\n } catch (thrown) {\n if (!eligible || attempt >= retries) throw thrown;\n if (!isArthronError(thrown)) throw thrown;\n if (thrown.status !== 0 && !retryOn.includes(thrown.status)) {\n throw thrown;\n }\n if (ctx.init.signal?.aborted) throw thrown;\n const retryAfter = retryAfterMs(thrown);\n if (retryAfter !== undefined && retryAfter > maxRetryAfter) {\n throw thrown;\n }\n await sleep(retryAfter ?? delay(attempt, thrown), ctx.init.signal);\n }\n }\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../src/body.ts","../src/error.ts","../src/headers.ts","../src/pipeline.ts","../src/stream.ts","../src/promise.ts","../src/url.ts","../src/client.ts","../src/define.ts","../src/plugins/logging.ts","../src/plugins/retry.ts","../src/plugins/timeout.ts"],"sourcesContent":["type Serialized = {\n body: BodyInit | undefined;\n headers: Record<string, string>;\n};\n\nconst RAW_CTORS = [\n \"FormData\",\n \"Blob\",\n \"ArrayBuffer\",\n \"URLSearchParams\",\n \"ReadableStream\",\n] as const;\n\n/** Bodies fetch already knows how to send. Content-Type is left to the caller / runtime. */\nfunction isRawBody(body: unknown): body is BodyInit {\n if (typeof body === \"string\") return true;\n if (ArrayBuffer.isView(body)) return true;\n return RAW_CTORS.some((name) => {\n const ctor = (globalThis as Record<string, unknown>)[name];\n return typeof ctor === \"function\" && body instanceof ctor;\n });\n}\n\n/**\n * Turn a caller-supplied body into something fetch accepts. Raw bodies pass\n * through untouched; anything else is JSON with `content-type` set — unless\n * the caller already set one (headers arrive lowercased, so a plain lookup is\n * enough).\n */\nexport function serializeBody(\n body: unknown,\n headers: Record<string, string>,\n): Serialized {\n if (body === undefined) return { body: undefined, headers };\n if (isRawBody(body)) return { body, headers };\n const withType =\n \"content-type\" in headers\n ? headers\n : { ...headers, \"content-type\": \"application/json\" };\n return { body: JSON.stringify(body), headers: withType };\n}\n","import type { RequestContext } from \"./types\";\n\nexport type ErrorMessageResolver = (\n data: unknown,\n response: Response,\n) => string | undefined;\n\ntype ArthronErrorInit = {\n message: string;\n status: number;\n statusText: string;\n headers: Headers;\n data: unknown;\n request: RequestContext;\n response?: Response;\n cause?: unknown;\n};\n\n/**\n * The only error arthron itself produces. Two sources: a response with\n * `ok === false`, or fetch rejecting outright (network failure, `status` 0).\n * Everything else — cancellation, interceptor bugs — is rethrown untouched.\n */\nexport class ArthronError extends Error {\n override readonly name = \"ArthronError\";\n /** HTTP status of the failed response; `0` when fetch itself failed (network). */\n readonly status: number;\n readonly statusText: string;\n /** Response headers; empty for network failures. */\n readonly headers: Headers;\n /** Parsed body: JSON if it parses, else the text, else `undefined`. */\n readonly data: unknown;\n /** The context that was sent — url and init after request interceptors. */\n readonly request: RequestContext;\n /** The original Response, still readable (the body was parsed from a clone). Absent for network failures. */\n readonly response: Response | undefined;\n /** The underlying error for network failures. */\n override readonly cause: unknown;\n\n constructor(init: ArthronErrorInit) {\n super(init.message);\n this.status = init.status;\n this.statusText = init.statusText;\n this.headers = init.headers;\n this.data = init.data;\n this.request = init.request;\n this.response = init.response;\n this.cause = init.cause;\n }\n}\n\n/** `true` for errors arthron produced: a `!ok` response or a network failure. Cancellations and plugin bugs are not ArthronErrors. */\nexport function isArthronError(error: unknown): error is ArthronError {\n return error instanceof ArthronError;\n}\n\n/** Cancellation is not an HTTP failure. Matched by name so it works across realms. */\nexport function isCancellation(error: unknown): boolean {\n const name = (error as { name?: unknown } | null | undefined)?.name;\n return name === \"AbortError\" || name === \"TimeoutError\";\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null;\n\n/** `message` → `error` (string) → `error.message` → `detail`. */\nexport function defaultErrorMessage(data: unknown): string | undefined {\n if (!isRecord(data)) return undefined;\n if (typeof data.message === \"string\") return data.message;\n if (typeof data.error === \"string\") return data.error;\n if (isRecord(data.error) && typeof data.error.message === \"string\") {\n return data.error.message;\n }\n if (typeof data.detail === \"string\") return data.detail;\n return undefined;\n}\n\n/** Body as JSON if it parses, else as text, else `undefined` when empty. */\nasync function readBody(response: Response): Promise<unknown> {\n const text = await response.text().catch(() => \"\");\n if (text === \"\") return undefined;\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n}\n\nexport async function responseError(\n response: Response,\n request: RequestContext,\n resolve: ErrorMessageResolver = defaultErrorMessage,\n): Promise<ArthronError> {\n // Read from a clone so `error.response` stays usable by the caller.\n const data = await readBody(response.clone());\n return new ArthronError({\n message:\n resolve(data, response) ??\n `Request failed with status ${response.status}`,\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n data,\n request,\n response,\n });\n}\n\nexport function networkError(\n cause: unknown,\n request: RequestContext,\n): ArthronError {\n return new ArthronError({\n message: cause instanceof Error ? cause.message : \"Network error\",\n status: 0,\n statusText: \"\",\n headers: new Headers(),\n data: undefined,\n request,\n cause,\n });\n}\n","/**\n * Normalize any `HeadersInit` (plain object, `Headers`, tuple array) into a\n * plain record. Keys come back lowercased — `Headers` does that for us.\n */\nexport function toHeaderRecord(\n headers: HeadersInit | undefined,\n): Record<string, string> {\n if (!headers) return {};\n return Object.fromEntries(new Headers(headers).entries());\n}\n\n/**\n * Layer `override` on top of `base`. Names are compared case-insensitively and\n * the override wins, so a caller's `content-type` replaces a default\n * `Content-Type` instead of fetch sending both values joined.\n */\nexport function mergeHeaders(\n base: HeadersInit | undefined,\n override: HeadersInit | undefined,\n): Record<string, string> {\n return { ...toHeaderRecord(base), ...toHeaderRecord(override) };\n}\n","import { isArthronError } from \"./error\";\nimport { toHeaderRecord } from \"./headers\";\nimport type {\n ErrorInterceptor,\n Middleware,\n Plugin,\n RequestContext,\n RequestInterceptor,\n ResponseInterceptor,\n} from \"./types\";\n\n/**\n * Interceptors mutate the context in place. A middleware that calls `next`\n * again (retry) must not see the previous attempt's changes, so every attempt\n * starts from a fresh copy.\n */\nfunction cloneContext({ url, init }: RequestContext): RequestContext {\n return { url, init: { ...init, headers: toHeaderRecord(init.headers) } };\n}\n\n/**\n * Owns the order of one request.\n *\n * Middleware wraps the whole call, outermost first in registration order.\n * Each attempt below it runs request interceptors → `send` → response\n * interceptors. Only an `ArthronError` reaches the error interceptors; whatever\n * they return is thrown. Anything else that is thrown — cancellation, a bug\n * in a plugin — propagates untouched.\n */\nexport async function runPipeline(\n plugins: readonly Plugin[],\n ctx: RequestContext,\n send: (ctx: RequestContext) => Promise<Response>,\n): Promise<Response> {\n const middlewares: Middleware[] = [];\n const request: RequestInterceptor[] = [];\n const response: ResponseInterceptor[] = [];\n const error: ErrorInterceptor[] = [];\n for (const plugin of plugins) {\n if (plugin.middleware) middlewares.push(plugin.middleware);\n if (plugin.request) request.push(plugin.request);\n if (plugin.response) response.push(plugin.response);\n if (plugin.error) error.push(plugin.error);\n }\n\n const attempt = async (input: RequestContext): Promise<Response> => {\n let current = cloneContext(input);\n try {\n for (const interceptor of request) current = await interceptor(current);\n let res = await send(current);\n for (const interceptor of response) res = await interceptor(res, current);\n return res;\n } catch (thrown) {\n if (!isArthronError(thrown)) throw thrown;\n let err = thrown;\n for (const interceptor of error) err = await interceptor(err, current);\n throw err;\n }\n };\n\n const dispatch = (\n index: number,\n input: RequestContext,\n ): Promise<Response> => {\n const middleware = middlewares[index];\n if (!middleware) return attempt(input);\n return middleware(input, (next) => dispatch(index + 1, next));\n };\n return dispatch(0, ctx);\n}\n","/** One server-sent event, as described by the WHATWG EventSource spec. */\nexport type SseEvent = {\n /** The `event:` field, when the server sent one. */\n event?: string;\n /** All `data:` lines of the block, joined with `\\n`. */\n data: string;\n /** The `id:` field, when present. */\n id?: string;\n /** The `retry:` field in ms, when present and numeric. */\n retry?: number;\n};\n\n/** Yield the body line by line, reassembling lines split across chunks. */\nasync function* lines(response: Response): AsyncGenerator<string> {\n if (!response.body) return;\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n try {\n while (true) {\n const { value, done } = await reader.read();\n buffer += decoder.decode(value, { stream: !done });\n const parts = buffer.split(/\\r\\n|\\r|\\n/);\n buffer = parts.pop() ?? \"\";\n for (const line of parts) yield line;\n if (done) break;\n }\n if (buffer) yield buffer;\n } finally {\n reader.releaseLock();\n }\n}\n\n/**\n * Parse a `text/event-stream` body into events. Follows the spec: a blank\n * line dispatches, `data:` lines are joined with `\\n`, one leading space\n * after the colon is dropped, lines starting with `:` are comments, and a\n * block without `data` is not dispatched. Provider sentinels like\n * `[DONE]` are passed through untouched — they are not part of the spec.\n */\nexport async function* parseSse(response: Response): AsyncGenerator<SseEvent> {\n let data: string[] = [];\n let event: string | undefined;\n let id: string | undefined;\n let retry: number | undefined;\n\n const flush = (): SseEvent | undefined => {\n if (data.length === 0) {\n event = id = retry = undefined;\n return undefined;\n }\n const out: SseEvent = { data: data.join(\"\\n\") };\n if (event !== undefined) out.event = event;\n if (id !== undefined) out.id = id;\n if (retry !== undefined) out.retry = retry;\n data = [];\n event = id = retry = undefined;\n return out;\n };\n\n for await (const line of lines(response)) {\n if (line === \"\") {\n const out = flush();\n if (out) yield out;\n continue;\n }\n if (line.startsWith(\":\")) continue;\n const colon = line.indexOf(\":\");\n const field = colon === -1 ? line : line.slice(0, colon);\n let value = colon === -1 ? \"\" : line.slice(colon + 1);\n if (value.startsWith(\" \")) value = value.slice(1);\n switch (field) {\n case \"data\":\n data.push(value);\n break;\n case \"event\":\n event = value;\n break;\n case \"id\":\n id = value;\n break;\n case \"retry\": {\n const ms = Number(value);\n if (Number.isInteger(ms) && ms >= 0) retry = ms;\n break;\n }\n default:\n break; // unknown fields are ignored, per spec\n }\n }\n const out = flush();\n if (out) yield out;\n}\n\n/**\n * Parse newline-delimited JSON. Blank lines are skipped; a malformed line\n * rejects the iteration with the `SyntaxError` from `JSON.parse`.\n */\nexport async function* parseNdjson<T = unknown>(\n response: Response,\n): AsyncGenerator<T> {\n for await (const line of lines(response)) {\n if (line.trim() === \"\") continue;\n yield JSON.parse(line) as T;\n }\n}\n","import { parseNdjson, parseSse } from \"./stream\";\nimport type { ArthronPromise } from \"./types\";\n\n/** Defer the Response until iteration starts, so the helper itself never rejects. */\nfunction iterate<T>(\n promise: Promise<Response>,\n parse: (res: Response) => AsyncGenerator<T>,\n): AsyncIterable<T> {\n return {\n async *[Symbol.asyncIterator]() {\n yield* parse(await promise);\n },\n };\n}\n\n/**\n * Sugar over `Promise<Response>`: `.json()` is `(await p).json()`. Awaiting\n * without a helper still yields the untouched `Response`.\n */\nexport function toArthronPromise(promise: Promise<Response>): ArthronPromise {\n return Object.assign(promise, {\n json: <T = unknown>() => promise.then((res) => res.json() as Promise<T>),\n text: () => promise.then((res) => res.text()),\n blob: () => promise.then((res) => res.blob()),\n arrayBuffer: () => promise.then((res) => res.arrayBuffer()),\n formData: () => promise.then((res) => res.formData()),\n sse: () => iterate(promise, parseSse),\n ndjson: <T = unknown>() => iterate(promise, parseNdjson<T>),\n });\n}\n","export type QueryParams = Record<\n string,\n string | number | boolean | null | undefined\n>;\n\nconst ABSOLUTE_URL = /^[a-z][a-z0-9+.-]*:\\/\\//i;\n\nexport function isAbsoluteUrl(path: string): boolean {\n return ABSOLUTE_URL.test(path);\n}\n\nfunction toQueryString(params: QueryParams | undefined): string {\n if (!params) return \"\";\n const search = new URLSearchParams();\n for (const [key, value] of Object.entries(params)) {\n if (value === null || value === undefined) continue;\n search.append(key, String(value));\n }\n return search.toString();\n}\n\n/**\n * Join `baseURL` and `path` by plain string concatenation so a path prefix in\n * the base (`/v1`) survives a leading slash in the path. WHATWG `new URL()`\n * would drop it. Absolute paths skip the base entirely.\n */\nexport function resolveUrl(\n baseURL: string | undefined,\n path: string,\n params?: QueryParams,\n): string {\n let url: string;\n if (isAbsoluteUrl(path)) {\n url = path;\n } else {\n if (baseURL === undefined) {\n throw new TypeError(\n `arthron: relative path \"${path}\" needs a baseURL — pass baseURL in arthron({ baseURL }) or use an absolute URL`,\n );\n }\n url = `${baseURL.replace(/\\/+$/, \"\")}/${path.replace(/^\\/+/, \"\")}`;\n }\n const query = toQueryString(params);\n if (!query) return url;\n // The query goes before any #fragment, or the server never sees it.\n const hash = url.indexOf(\"#\");\n const [base, fragment] =\n hash === -1 ? [url, \"\"] : [url.slice(0, hash), url.slice(hash)];\n return `${base}${base.includes(\"?\") ? \"&\" : \"?\"}${query}${fragment}`;\n}\n","import { serializeBody } from \"./body\";\nimport { isCancellation, networkError, responseError } from \"./error\";\nimport { mergeHeaders, toHeaderRecord } from \"./headers\";\nimport { runPipeline } from \"./pipeline\";\nimport { toArthronPromise } from \"./promise\";\nimport type {\n Arthron,\n ArthronConfig,\n ArthronPromise,\n Plugin,\n RequestContext,\n RequestInitWithBody,\n RequestOptions,\n} from \"./types\";\nimport { resolveUrl } from \"./url\";\n\nconst BODYLESS_METHODS = new Set([\"GET\", \"HEAD\"]);\n\ntype Resolved = Omit<ArthronConfig, \"headers\" | \"use\"> & {\n headers: Record<string, string>;\n use: readonly Plugin[];\n};\n\n/** Normalize once so `extend` can merge headers and append plugins cheaply. */\nfunction resolveConfig(config: ArthronConfig): Resolved {\n const { headers, use, ...rest } = config;\n return { ...rest, headers: toHeaderRecord(headers), use: use ?? [] };\n}\n\n/**\n * Build a client from config. The result is immutable — `use` and `extend`\n * return new clients and never touch the one they were called on.\n */\nexport function arthron(config: ArthronConfig = {}): Arthron {\n const resolved = resolveConfig(config);\n\n /** The one place a failure becomes an ArthronError. Cancellation passes through. */\n const send = async (ctx: RequestContext): Promise<Response> => {\n // Resolved per call, not per client: a global patched after the client was\n // created (msw's `server.listen()`, for one) must still be honoured.\n const fetchImpl = resolved.fetch ?? globalThis.fetch;\n let res: Response;\n try {\n res = await fetchImpl(ctx.url, ctx.init);\n } catch (thrown) {\n if (isCancellation(thrown)) throw thrown;\n throw networkError(thrown, ctx);\n }\n if (!res.ok) throw await responseError(res, ctx, resolved.errorMessage);\n return res;\n };\n\n const dispatch = (\n path: string,\n method: string,\n rawBody: unknown,\n { params, headers: callHeaders, ...init }: RequestOptions,\n ): ArthronPromise => {\n try {\n if (rawBody !== undefined && BODYLESS_METHODS.has(method.toUpperCase())) {\n // fetch rejects these with a TypeError that would otherwise be mistaken for a network failure and retried.\n throw new TypeError(\n `arthron: ${method.toUpperCase()} requests cannot carry a body — send it as params (query) or switch to POST`,\n );\n }\n const url = resolveUrl(resolved.baseURL, path, params);\n const { body, headers } = serializeBody(\n rawBody,\n mergeHeaders(resolved.headers, callHeaders),\n );\n // Config credentials sit underneath so a per-call value wins, like every other option.\n const requestInit: RequestInit = {\n ...(resolved.credentials !== undefined && {\n credentials: resolved.credentials,\n }),\n ...init,\n method,\n headers,\n };\n if (body !== undefined) requestInit.body = body;\n return toArthronPromise(\n runPipeline(resolved.use, { url, init: requestInit }, send),\n );\n } catch (thrown) {\n // Argument errors (no baseURL, unserializable body) reject like everything else.\n return toArthronPromise(Promise.reject(thrown));\n }\n };\n\n const derive = (next: Partial<ArthronConfig>): Arthron => {\n const merged: ArthronConfig = {\n ...resolved,\n ...next,\n headers: mergeHeaders(resolved.headers, next.headers),\n use: [...resolved.use, ...(next.use ?? [])],\n };\n return arthron(merged);\n };\n\n return {\n get: (path, init = {}) => dispatch(path, \"GET\", undefined, init),\n delete: (path, init = {}) => dispatch(path, \"DELETE\", undefined, init),\n head: (path, init = {}) => dispatch(path, \"HEAD\", undefined, init),\n post: (path, body, init = {}) => dispatch(path, \"POST\", body, init),\n put: (path, body, init = {}) => dispatch(path, \"PUT\", body, init),\n patch: (path, body, init = {}) => dispatch(path, \"PATCH\", body, init),\n request: (path, init: RequestInitWithBody) => {\n const { method, body, ...rest } = init;\n if (!method) {\n return toArthronPromise(\n Promise.reject(\n new TypeError(\n 'arthron: request() needs init.method — e.g. request(path, { method: \"OPTIONS\" }); use get/post/... for the common verbs',\n ),\n ),\n );\n }\n return dispatch(path, method, body, rest);\n },\n use: (...plugins) => derive({ use: plugins }),\n extend: (next) => derive(next),\n };\n}\n","import type {\n ErrorInterceptor,\n Middleware,\n Plugin,\n RequestInterceptor,\n ResponseInterceptor,\n} from \"./types\";\n\n/*\n * Identity helpers. They do nothing at runtime; they exist so a plugin written\n * in its own file gets contextual typing without an explicit annotation.\n */\n\n/** Identity helper: gives a middleware written in its own file the right parameter types. */\nexport const defineMiddleware = (middleware: Middleware): Middleware =>\n middleware;\n\n/** Identity helpers for each interceptor kind — `defineInterceptor.request(fn)` etc. */\nexport const defineInterceptor = {\n request: (interceptor: RequestInterceptor): RequestInterceptor => interceptor,\n response: (interceptor: ResponseInterceptor): ResponseInterceptor =>\n interceptor,\n error: (interceptor: ErrorInterceptor): ErrorInterceptor => interceptor,\n};\n\n/** Identity helper: type-checks a plugin object literal without an annotation. */\nexport const definePlugin = (plugin: Plugin): Plugin => plugin;\n","import { isArthronError, isCancellation } from \"../error\";\nimport type { Plugin } from \"../types\";\n\ntype LogEventBase = { method: string; path: string };\nexport type LogEvent =\n | ({ phase: \"start\" } & LogEventBase)\n | ({ phase: \"success\"; status: number; ms: number } & LogEventBase)\n | ({\n phase: \"error\";\n /** HTTP status for an ArthronError (0 = network); undefined for anything else. */\n status: number | undefined;\n ms: number;\n error: unknown;\n } & LogEventBase);\n\nexport type LoggingOptions = {\n /** Default `true`. The library never reads `NODE_ENV` — pass `isDev` yourself. */\n enabled?: boolean;\n logger?: Pick<Console, \"log\" | \"error\">;\n /** Receive structured events instead of the default console lines. */\n onEvent?: (event: LogEvent) => void;\n /** Clock in ms. Injectable for tests. */\n now?: () => number;\n};\n\n/** Origin + pathname only — the query and userinfo tend to carry secrets. */\nfunction displayPath(url: string): string {\n try {\n const parsed = new URL(url);\n return `${parsed.origin}${parsed.pathname}`;\n } catch {\n return url.split(\"?\")[0] ?? url;\n }\n}\n\nfunction consoleSink(logger: Pick<Console, \"log\" | \"error\">) {\n return (e: LogEvent) => {\n if (e.phase === \"start\") {\n logger.log(`[arthron] → ${e.method} ${e.path}`);\n } else if (e.phase === \"success\") {\n logger.log(`[arthron] ← ${e.method} ${e.path} ${e.status} ${e.ms}ms`);\n } else {\n logger.error(\n `[arthron] ✕ ${e.method} ${e.path} ${e.status ?? \"-\"} ${e.ms}ms`,\n );\n }\n };\n}\n\n/**\n * One event when a request starts, one when it settles, with status and\n * elapsed time. Wraps the whole call, so retries are included in the time.\n * Default output is a console line per event; pass `onEvent` to get the\n * structured event instead. Cancellations are not reported.\n */\nexport function logging(options: LoggingOptions = {}): Plugin {\n const {\n enabled = true,\n logger = console,\n onEvent,\n now = () => performance.now(),\n } = options;\n const emit = onEvent ?? consoleSink(logger);\n return {\n name: \"logging\",\n middleware: async (ctx, next) => {\n if (!enabled) return next(ctx);\n const method = ctx.init.method ?? \"GET\";\n const path = displayPath(ctx.url);\n const started = now();\n const ms = () => Math.round(now() - started);\n\n emit({ phase: \"start\", method, path });\n try {\n const res = await next(ctx);\n emit({ phase: \"success\", method, path, status: res.status, ms: ms() });\n return res;\n } catch (error) {\n if (!isCancellation(error)) {\n const status = isArthronError(error) ? error.status : undefined;\n emit({ phase: \"error\", method, path, status, ms: ms(), error });\n }\n throw error;\n }\n },\n };\n}\n","import { type ArthronError, isArthronError } from \"../error\";\nimport type { Plugin, RequestContext } from \"../types\";\n\nexport type RetryOptions = {\n /** Number of retries after the first attempt. Default 2 (3 requests total). */\n retries?: number;\n /** Statuses worth retrying. Network errors (status 0) always are. */\n retryOn?: readonly number[];\n /** Methods safe to repeat. Compared case-insensitively. */\n methods?: readonly string[];\n /** Backoff in ms for retry number `retry` (0-based). Default 300 · 2^retry. */\n delay?: (retry: number, error: ArthronError) => number;\n /** Injectable for tests. Default `setTimeout`; rejects with the abort reason. */\n sleep?: (ms: number, signal?: AbortSignal | null) => Promise<void>;\n /**\n * Longest `Retry-After` (ms) worth honouring. Beyond it the request is not\n * retried at all. Default 60 000. Also guards `setTimeout`'s 2^31-1 ms cap,\n * past which timers fire immediately.\n */\n maxRetryAfter?: number;\n};\n\nconst DEFAULT_RETRY_ON = [408, 429, 500, 502, 503, 504] as const;\nconst DEFAULT_METHODS = [\"GET\", \"HEAD\", \"OPTIONS\", \"PUT\", \"DELETE\"] as const;\nconst DEFAULT_MAX_RETRY_AFTER = 60_000;\n\nfunction defaultSleep(ms: number, signal?: AbortSignal | null): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) return reject(signal.reason);\n const onAbort = () => {\n clearTimeout(timer);\n reject(signal?.reason);\n };\n const timer = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\n/** `Retry-After` as ms — seconds or an HTTP-date. `undefined` when absent or unparsable. */\nfunction retryAfterMs(error: ArthronError): number | undefined {\n const value = error.headers.get(\"retry-after\")?.trim();\n if (!value) return undefined;\n const seconds = Number(value);\n if (!Number.isNaN(seconds)) return Math.max(0, seconds * 1000);\n const at = Date.parse(value);\n if (Number.isNaN(at)) return undefined;\n return Math.max(0, at - Date.now());\n}\n\nfunction isStreamBody(ctx: RequestContext): boolean {\n return (\n typeof ReadableStream !== \"undefined\" &&\n ctx.init.body instanceof ReadableStream\n );\n}\n\n/**\n * Retry on network errors and the configured statuses by calling `next`\n * again — the whole attempt (request interceptors included) is re-run.\n * Only `ArthronError` is considered; cancellation and plugin bugs pass through.\n */\nexport function retry(options: RetryOptions = {}): Plugin {\n const {\n retries = 2,\n retryOn = DEFAULT_RETRY_ON,\n methods = DEFAULT_METHODS,\n delay = (retry) => 300 * 2 ** retry,\n sleep = defaultSleep,\n maxRetryAfter = DEFAULT_MAX_RETRY_AFTER,\n } = options;\n const allowed = new Set(methods.map((m) => m.toUpperCase()));\n\n return {\n name: \"retry\",\n middleware: async (ctx, next) => {\n const method = (ctx.init.method ?? \"GET\").toUpperCase();\n const eligible = allowed.has(method) && !isStreamBody(ctx);\n\n for (let attempt = 0; ; attempt += 1) {\n try {\n return await next(ctx);\n } catch (thrown) {\n if (!eligible || attempt >= retries) throw thrown;\n if (!isArthronError(thrown)) throw thrown;\n if (thrown.status !== 0 && !retryOn.includes(thrown.status)) {\n throw thrown;\n }\n if (ctx.init.signal?.aborted) throw thrown;\n const retryAfter = retryAfterMs(thrown);\n if (retryAfter !== undefined && retryAfter > maxRetryAfter) {\n throw thrown;\n }\n await sleep(retryAfter ?? delay(attempt, thrown), ctx.init.signal);\n }\n }\n },\n };\n}\n","import type { Plugin } from \"../types\";\n\n/**\n * Wrap `body` so the deadline keeps running until it is fully read, and so an\n * abort while reading surfaces as a stream error instead of a silent stall.\n */\nfunction guardBody(\n body: ReadableStream<Uint8Array>,\n signal: AbortSignal,\n finish: () => void,\n): ReadableStream<Uint8Array> {\n const reader = body.getReader();\n return new ReadableStream<Uint8Array>({\n start(controller) {\n signal.addEventListener(\n \"abort\",\n () => {\n controller.error(signal.reason);\n reader.cancel(signal.reason).catch(() => {});\n },\n { once: true },\n );\n },\n async pull(controller) {\n const { value, done } = await reader.read();\n if (done) {\n finish();\n controller.close();\n return;\n }\n controller.enqueue(value);\n },\n cancel(reason) {\n finish();\n return reader.cancel(reason);\n },\n });\n}\n\n/**\n * Abort the request when it takes longer than `ms` — counted from the start\n * of the attempt until the body has been fully read, so a server that sends\n * headers and then stalls still trips it. Rejects with a `TimeoutError` (a\n * `DOMException`, like `AbortSignal.timeout()` would give); it is not an\n * `ArthronError`, so error interceptors and `retry` leave it alone.\n *\n * Built on `AbortController` + `setTimeout` rather than `AbortSignal.any` /\n * `AbortSignal.timeout` so it runs on Node 18.\n */\nexport function timeout(ms: number): Plugin {\n return {\n name: \"timeout\",\n middleware: async (ctx, next) => {\n const controller = new AbortController();\n const outer = ctx.init.signal;\n const forward = () => controller.abort(outer?.reason);\n if (outer?.aborted) forward();\n else outer?.addEventListener(\"abort\", forward, { once: true });\n\n const timer = setTimeout(\n () =>\n controller.abort(\n new DOMException(\n `arthron: request timed out after ${ms}ms`,\n \"TimeoutError\",\n ),\n ),\n ms,\n );\n const finish = () => {\n clearTimeout(timer);\n outer?.removeEventListener(\"abort\", forward);\n };\n\n let res: Response;\n try {\n res = await next({\n ...ctx,\n init: { ...ctx.init, signal: controller.signal },\n });\n } catch (thrown) {\n finish();\n throw thrown;\n }\n if (!res.body) {\n finish();\n return res;\n }\n return new Response(guardBody(res.body, controller.signal, finish), {\n status: res.status,\n statusText: res.statusText,\n headers: res.headers,\n });\n },\n };\n}\n"],"mappings":";;AAKA,MAAM,YAAY;CAChB;CACA;CACA;CACA;CACA;AACF;;AAGA,SAAS,UAAU,MAAiC;CAClD,IAAI,OAAO,SAAS,UAAU,OAAO;CACrC,IAAI,YAAY,OAAO,IAAI,GAAG,OAAO;CACrC,OAAO,UAAU,MAAM,SAAS;EAC9B,MAAM,OAAQ,WAAuC;EACrD,OAAO,OAAO,SAAS,cAAc,gBAAgB;CACvD,CAAC;AACH;;;;;;;AAQA,SAAgB,cACd,MACA,SACY;CACZ,IAAI,SAAS,KAAA,GAAW,OAAO;EAAE,MAAM,KAAA;EAAW;CAAQ;CAC1D,IAAI,UAAU,IAAI,GAAG,OAAO;EAAE;EAAM;CAAQ;CAC5C,MAAM,WACJ,kBAAkB,UACd,UACA;EAAE,GAAG;EAAS,gBAAgB;CAAmB;CACvD,OAAO;EAAE,MAAM,KAAK,UAAU,IAAI;EAAG,SAAS;CAAS;AACzD;;;;;;;;ACjBA,IAAa,eAAb,cAAkC,MAAM;CACtC,OAAyB;;CAEzB;CACA;;CAEA;;CAEA;;CAEA;;CAEA;;CAEA;CAEA,YAAY,MAAwB;EAClC,MAAM,KAAK,OAAO;EAClB,KAAK,SAAS,KAAK;EACnB,KAAK,aAAa,KAAK;EACvB,KAAK,UAAU,KAAK;EACpB,KAAK,OAAO,KAAK;EACjB,KAAK,UAAU,KAAK;EACpB,KAAK,WAAW,KAAK;EACrB,KAAK,QAAQ,KAAK;CACpB;AACF;;AAGA,SAAgB,eAAe,OAAuC;CACpE,OAAO,iBAAiB;AAC1B;;AAGA,SAAgB,eAAe,OAAyB;CACtD,MAAM,OAAQ,OAAiD;CAC/D,OAAO,SAAS,gBAAgB,SAAS;AAC3C;AAEA,MAAM,YAAY,UAChB,OAAO,UAAU,YAAY,UAAU;;AAGzC,SAAgB,oBAAoB,MAAmC;CACrE,IAAI,CAAC,SAAS,IAAI,GAAG,OAAO,KAAA;CAC5B,IAAI,OAAO,KAAK,YAAY,UAAU,OAAO,KAAK;CAClD,IAAI,OAAO,KAAK,UAAU,UAAU,OAAO,KAAK;CAChD,IAAI,SAAS,KAAK,KAAK,KAAK,OAAO,KAAK,MAAM,YAAY,UACxD,OAAO,KAAK,MAAM;CAEpB,IAAI,OAAO,KAAK,WAAW,UAAU,OAAO,KAAK;AAEnD;;AAGA,eAAe,SAAS,UAAsC;CAC5D,MAAM,OAAO,MAAM,SAAS,KAAK,CAAC,CAAC,YAAY,EAAE;CACjD,IAAI,SAAS,IAAI,OAAO,KAAA;CACxB,IAAI;EACF,OAAO,KAAK,MAAM,IAAI;CACxB,QAAQ;EACN,OAAO;CACT;AACF;AAEA,eAAsB,cACpB,UACA,SACA,UAAgC,qBACT;CAEvB,MAAM,OAAO,MAAM,SAAS,SAAS,MAAM,CAAC;CAC5C,OAAO,IAAI,aAAa;EACtB,SACE,QAAQ,MAAM,QAAQ,KACtB,8BAA8B,SAAS;EACzC,QAAQ,SAAS;EACjB,YAAY,SAAS;EACrB,SAAS,SAAS;EAClB;EACA;EACA;CACF,CAAC;AACH;AAEA,SAAgB,aACd,OACA,SACc;CACd,OAAO,IAAI,aAAa;EACtB,SAAS,iBAAiB,QAAQ,MAAM,UAAU;EAClD,QAAQ;EACR,YAAY;EACZ,SAAS,IAAI,QAAQ;EACrB,MAAM,KAAA;EACN;EACA;CACF,CAAC;AACH;;;;;;;ACrHA,SAAgB,eACd,SACwB;CACxB,IAAI,CAAC,SAAS,OAAO,CAAC;CACtB,OAAO,OAAO,YAAY,IAAI,QAAQ,OAAO,CAAC,CAAC,QAAQ,CAAC;AAC1D;;;;;;AAOA,SAAgB,aACd,MACA,UACwB;CACxB,OAAO;EAAE,GAAG,eAAe,IAAI;EAAG,GAAG,eAAe,QAAQ;CAAE;AAChE;;;;;;;;ACLA,SAAS,aAAa,EAAE,KAAK,QAAwC;CACnE,OAAO;EAAE;EAAK,MAAM;GAAE,GAAG;GAAM,SAAS,eAAe,KAAK,OAAO;EAAE;CAAE;AACzE;;;;;;;;;;AAWA,eAAsB,YACpB,SACA,KACA,MACmB;CACnB,MAAM,cAA4B,CAAC;CACnC,MAAM,UAAgC,CAAC;CACvC,MAAM,WAAkC,CAAC;CACzC,MAAM,QAA4B,CAAC;CACnC,KAAK,MAAM,UAAU,SAAS;EAC5B,IAAI,OAAO,YAAY,YAAY,KAAK,OAAO,UAAU;EACzD,IAAI,OAAO,SAAS,QAAQ,KAAK,OAAO,OAAO;EAC/C,IAAI,OAAO,UAAU,SAAS,KAAK,OAAO,QAAQ;EAClD,IAAI,OAAO,OAAO,MAAM,KAAK,OAAO,KAAK;CAC3C;CAEA,MAAM,UAAU,OAAO,UAA6C;EAClE,IAAI,UAAU,aAAa,KAAK;EAChC,IAAI;GACF,KAAK,MAAM,eAAe,SAAS,UAAU,MAAM,YAAY,OAAO;GACtE,IAAI,MAAM,MAAM,KAAK,OAAO;GAC5B,KAAK,MAAM,eAAe,UAAU,MAAM,MAAM,YAAY,KAAK,OAAO;GACxE,OAAO;EACT,SAAS,QAAQ;GACf,IAAI,CAAC,eAAe,MAAM,GAAG,MAAM;GACnC,IAAI,MAAM;GACV,KAAK,MAAM,eAAe,OAAO,MAAM,MAAM,YAAY,KAAK,OAAO;GACrE,MAAM;EACR;CACF;CAEA,MAAM,YACJ,OACA,UACsB;EACtB,MAAM,aAAa,YAAY;EAC/B,IAAI,CAAC,YAAY,OAAO,QAAQ,KAAK;EACrC,OAAO,WAAW,QAAQ,SAAS,SAAS,QAAQ,GAAG,IAAI,CAAC;CAC9D;CACA,OAAO,SAAS,GAAG,GAAG;AACxB;;;;ACxDA,gBAAgB,MAAM,UAA4C;CAChE,IAAI,CAAC,SAAS,MAAM;CACpB,MAAM,SAAS,SAAS,KAAK,UAAU;CACvC,MAAM,UAAU,IAAI,YAAY;CAChC,IAAI,SAAS;CACb,IAAI;EACF,OAAO,MAAM;GACX,MAAM,EAAE,OAAO,SAAS,MAAM,OAAO,KAAK;GAC1C,UAAU,QAAQ,OAAO,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC;GACjD,MAAM,QAAQ,OAAO,MAAM,YAAY;GACvC,SAAS,MAAM,IAAI,KAAK;GACxB,KAAK,MAAM,QAAQ,OAAO,MAAM;GAChC,IAAI,MAAM;EACZ;EACA,IAAI,QAAQ,MAAM;CACpB,UAAU;EACR,OAAO,YAAY;CACrB;AACF;;;;;;;;AASA,gBAAuB,SAAS,UAA8C;CAC5E,IAAI,OAAiB,CAAC;CACtB,IAAI;CACJ,IAAI;CACJ,IAAI;CAEJ,MAAM,cAAoC;EACxC,IAAI,KAAK,WAAW,GAAG;GACrB,QAAQ,KAAK,QAAQ,KAAA;GACrB;EACF;EACA,MAAM,MAAgB,EAAE,MAAM,KAAK,KAAK,IAAI,EAAE;EAC9C,IAAI,UAAU,KAAA,GAAW,IAAI,QAAQ;EACrC,IAAI,OAAO,KAAA,GAAW,IAAI,KAAK;EAC/B,IAAI,UAAU,KAAA,GAAW,IAAI,QAAQ;EACrC,OAAO,CAAC;EACR,QAAQ,KAAK,QAAQ,KAAA;EACrB,OAAO;CACT;CAEA,WAAW,MAAM,QAAQ,MAAM,QAAQ,GAAG;EACxC,IAAI,SAAS,IAAI;GACf,MAAM,MAAM,MAAM;GAClB,IAAI,KAAK,MAAM;GACf;EACF;EACA,IAAI,KAAK,WAAW,GAAG,GAAG;EAC1B,MAAM,QAAQ,KAAK,QAAQ,GAAG;EAC9B,MAAM,QAAQ,UAAU,KAAK,OAAO,KAAK,MAAM,GAAG,KAAK;EACvD,IAAI,QAAQ,UAAU,KAAK,KAAK,KAAK,MAAM,QAAQ,CAAC;EACpD,IAAI,MAAM,WAAW,GAAG,GAAG,QAAQ,MAAM,MAAM,CAAC;EAChD,QAAQ,OAAR;GACE,KAAK;IACH,KAAK,KAAK,KAAK;IACf;GACF,KAAK;IACH,QAAQ;IACR;GACF,KAAK;IACH,KAAK;IACL;GACF,KAAK,SAAS;IACZ,MAAM,KAAK,OAAO,KAAK;IACvB,IAAI,OAAO,UAAU,EAAE,KAAK,MAAM,GAAG,QAAQ;IAC7C;GACF;EAGF;CACF;CACA,MAAM,MAAM,MAAM;CAClB,IAAI,KAAK,MAAM;AACjB;;;;;AAMA,gBAAuB,YACrB,UACmB;CACnB,WAAW,MAAM,QAAQ,MAAM,QAAQ,GAAG;EACxC,IAAI,KAAK,KAAK,MAAM,IAAI;EACxB,MAAM,KAAK,MAAM,IAAI;CACvB;AACF;;;;ACrGA,SAAS,QACP,SACA,OACkB;CAClB,OAAO,EACL,QAAQ,OAAO,iBAAiB;EAC9B,OAAO,MAAM,MAAM,OAAO;CAC5B,EACF;AACF;;;;;AAMA,SAAgB,iBAAiB,SAA4C;CAC3E,OAAO,OAAO,OAAO,SAAS;EAC5B,YAAyB,QAAQ,MAAM,QAAQ,IAAI,KAAK,CAAe;EACvE,YAAY,QAAQ,MAAM,QAAQ,IAAI,KAAK,CAAC;EAC5C,YAAY,QAAQ,MAAM,QAAQ,IAAI,KAAK,CAAC;EAC5C,mBAAmB,QAAQ,MAAM,QAAQ,IAAI,YAAY,CAAC;EAC1D,gBAAgB,QAAQ,MAAM,QAAQ,IAAI,SAAS,CAAC;EACpD,WAAW,QAAQ,SAAS,QAAQ;EACpC,cAA2B,QAAQ,SAAS,WAAc;CAC5D,CAAC;AACH;;;ACxBA,MAAM,eAAe;AAErB,SAAgB,cAAc,MAAuB;CACnD,OAAO,aAAa,KAAK,IAAI;AAC/B;AAEA,SAAS,cAAc,QAAyC;CAC9D,IAAI,CAAC,QAAQ,OAAO;CACpB,MAAM,SAAS,IAAI,gBAAgB;CACnC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,GAAG;EACjD,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW;EAC3C,OAAO,OAAO,KAAK,OAAO,KAAK,CAAC;CAClC;CACA,OAAO,OAAO,SAAS;AACzB;;;;;;AAOA,SAAgB,WACd,SACA,MACA,QACQ;CACR,IAAI;CACJ,IAAI,cAAc,IAAI,GACpB,MAAM;MACD;EACL,IAAI,YAAY,KAAA,GACd,MAAM,IAAI,UACR,2BAA2B,KAAK,gFAClC;EAEF,MAAM,GAAG,QAAQ,QAAQ,QAAQ,EAAE,EAAE,GAAG,KAAK,QAAQ,QAAQ,EAAE;CACjE;CACA,MAAM,QAAQ,cAAc,MAAM;CAClC,IAAI,CAAC,OAAO,OAAO;CAEnB,MAAM,OAAO,IAAI,QAAQ,GAAG;CAC5B,MAAM,CAAC,MAAM,YACX,SAAS,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,MAAM,GAAG,IAAI,GAAG,IAAI,MAAM,IAAI,CAAC;CAChE,OAAO,GAAG,OAAO,KAAK,SAAS,GAAG,IAAI,MAAM,MAAM,QAAQ;AAC5D;;;ACjCA,MAAM,mCAAmB,IAAI,IAAI,CAAC,OAAO,MAAM,CAAC;;AAQhD,SAAS,cAAc,QAAiC;CACtD,MAAM,EAAE,SAAS,KAAK,GAAG,SAAS;CAClC,OAAO;EAAE,GAAG;EAAM,SAAS,eAAe,OAAO;EAAG,KAAK,OAAO,CAAC;CAAE;AACrE;;;;;AAMA,SAAgB,QAAQ,SAAwB,CAAC,GAAY;CAC3D,MAAM,WAAW,cAAc,MAAM;;CAGrC,MAAM,OAAO,OAAO,QAA2C;EAG7D,MAAM,YAAY,SAAS,SAAS,WAAW;EAC/C,IAAI;EACJ,IAAI;GACF,MAAM,MAAM,UAAU,IAAI,KAAK,IAAI,IAAI;EACzC,SAAS,QAAQ;GACf,IAAI,eAAe,MAAM,GAAG,MAAM;GAClC,MAAM,aAAa,QAAQ,GAAG;EAChC;EACA,IAAI,CAAC,IAAI,IAAI,MAAM,MAAM,cAAc,KAAK,KAAK,SAAS,YAAY;EACtE,OAAO;CACT;CAEA,MAAM,YACJ,MACA,QACA,SACA,EAAE,QAAQ,SAAS,aAAa,GAAG,WAChB;EACnB,IAAI;GACF,IAAI,YAAY,KAAA,KAAa,iBAAiB,IAAI,OAAO,YAAY,CAAC,GAEpE,MAAM,IAAI,UACR,YAAY,OAAO,YAAY,EAAE,4EACnC;GAEF,MAAM,MAAM,WAAW,SAAS,SAAS,MAAM,MAAM;GACrD,MAAM,EAAE,MAAM,YAAY,cACxB,SACA,aAAa,SAAS,SAAS,WAAW,CAC5C;GAEA,MAAM,cAA2B;IAC/B,GAAI,SAAS,gBAAgB,KAAA,KAAa,EACxC,aAAa,SAAS,YACxB;IACA,GAAG;IACH;IACA;GACF;GACA,IAAI,SAAS,KAAA,GAAW,YAAY,OAAO;GAC3C,OAAO,iBACL,YAAY,SAAS,KAAK;IAAE;IAAK,MAAM;GAAY,GAAG,IAAI,CAC5D;EACF,SAAS,QAAQ;GAEf,OAAO,iBAAiB,QAAQ,OAAO,MAAM,CAAC;EAChD;CACF;CAEA,MAAM,UAAU,SAA0C;EAOxD,OAAO,QAAQ;GALb,GAAG;GACH,GAAG;GACH,SAAS,aAAa,SAAS,SAAS,KAAK,OAAO;GACpD,KAAK,CAAC,GAAG,SAAS,KAAK,GAAI,KAAK,OAAO,CAAC,CAAE;EAExB,CAAC;CACvB;CAEA,OAAO;EACL,MAAM,MAAM,OAAO,CAAC,MAAM,SAAS,MAAM,OAAO,KAAA,GAAW,IAAI;EAC/D,SAAS,MAAM,OAAO,CAAC,MAAM,SAAS,MAAM,UAAU,KAAA,GAAW,IAAI;EACrE,OAAO,MAAM,OAAO,CAAC,MAAM,SAAS,MAAM,QAAQ,KAAA,GAAW,IAAI;EACjE,OAAO,MAAM,MAAM,OAAO,CAAC,MAAM,SAAS,MAAM,QAAQ,MAAM,IAAI;EAClE,MAAM,MAAM,MAAM,OAAO,CAAC,MAAM,SAAS,MAAM,OAAO,MAAM,IAAI;EAChE,QAAQ,MAAM,MAAM,OAAO,CAAC,MAAM,SAAS,MAAM,SAAS,MAAM,IAAI;EACpE,UAAU,MAAM,SAA8B;GAC5C,MAAM,EAAE,QAAQ,MAAM,GAAG,SAAS;GAClC,IAAI,CAAC,QACH,OAAO,iBACL,QAAQ,uBACN,IAAI,UACF,2HACF,CACF,CACF;GAEF,OAAO,SAAS,MAAM,QAAQ,MAAM,IAAI;EAC1C;EACA,MAAM,GAAG,YAAY,OAAO,EAAE,KAAK,QAAQ,CAAC;EAC5C,SAAS,SAAS,OAAO,IAAI;CAC/B;AACF;;;;AC5GA,MAAa,oBAAoB,eAC/B;;AAGF,MAAa,oBAAoB;CAC/B,UAAU,gBAAwD;CAClE,WAAW,gBACT;CACF,QAAQ,gBAAoD;AAC9D;;AAGA,MAAa,gBAAgB,WAA2B;;;;ACAxD,SAAS,YAAY,KAAqB;CACxC,IAAI;EACF,MAAM,SAAS,IAAI,IAAI,GAAG;EAC1B,OAAO,GAAG,OAAO,SAAS,OAAO;CACnC,QAAQ;EACN,OAAO,IAAI,MAAM,GAAG,CAAC,CAAC,MAAM;CAC9B;AACF;AAEA,SAAS,YAAY,QAAwC;CAC3D,QAAQ,MAAgB;EACtB,IAAI,EAAE,UAAU,SACd,OAAO,IAAI,eAAe,EAAE,OAAO,GAAG,EAAE,MAAM;OACzC,IAAI,EAAE,UAAU,WACrB,OAAO,IAAI,eAAe,EAAE,OAAO,GAAG,EAAE,KAAK,GAAG,EAAE,OAAO,GAAG,EAAE,GAAG,GAAG;OAEpE,OAAO,MACL,eAAe,EAAE,OAAO,GAAG,EAAE,KAAK,GAAG,EAAE,UAAU,IAAI,GAAG,EAAE,GAAG,GAC/D;CAEJ;AACF;;;;;;;AAQA,SAAgB,QAAQ,UAA0B,CAAC,GAAW;CAC5D,MAAM,EACJ,UAAU,MACV,SAAS,SACT,SACA,YAAY,YAAY,IAAI,MAC1B;CACJ,MAAM,OAAO,WAAW,YAAY,MAAM;CAC1C,OAAO;EACL,MAAM;EACN,YAAY,OAAO,KAAK,SAAS;GAC/B,IAAI,CAAC,SAAS,OAAO,KAAK,GAAG;GAC7B,MAAM,SAAS,IAAI,KAAK,UAAU;GAClC,MAAM,OAAO,YAAY,IAAI,GAAG;GAChC,MAAM,UAAU,IAAI;GACpB,MAAM,WAAW,KAAK,MAAM,IAAI,IAAI,OAAO;GAE3C,KAAK;IAAE,OAAO;IAAS;IAAQ;GAAK,CAAC;GACrC,IAAI;IACF,MAAM,MAAM,MAAM,KAAK,GAAG;IAC1B,KAAK;KAAE,OAAO;KAAW;KAAQ;KAAM,QAAQ,IAAI;KAAQ,IAAI,GAAG;IAAE,CAAC;IACrE,OAAO;GACT,SAAS,OAAO;IACd,IAAI,CAAC,eAAe,KAAK,GAAG;KAC1B,MAAM,SAAS,eAAe,KAAK,IAAI,MAAM,SAAS,KAAA;KACtD,KAAK;MAAE,OAAO;MAAS;MAAQ;MAAM;MAAQ,IAAI,GAAG;MAAG;KAAM,CAAC;IAChE;IACA,MAAM;GACR;EACF;CACF;AACF;;;AChEA,MAAM,mBAAmB;CAAC;CAAK;CAAK;CAAK;CAAK;CAAK;AAAG;AACtD,MAAM,kBAAkB;CAAC;CAAO;CAAQ;CAAW;CAAO;AAAQ;AAClE,MAAM,0BAA0B;AAEhC,SAAS,aAAa,IAAY,QAA4C;CAC5E,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,IAAI,QAAQ,SAAS,OAAO,OAAO,OAAO,MAAM;EAChD,MAAM,gBAAgB;GACpB,aAAa,KAAK;GAClB,OAAO,QAAQ,MAAM;EACvB;EACA,MAAM,QAAQ,iBAAiB;GAC7B,QAAQ,oBAAoB,SAAS,OAAO;GAC5C,QAAQ;EACV,GAAG,EAAE;EACL,QAAQ,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;CAC3D,CAAC;AACH;;AAGA,SAAS,aAAa,OAAyC;CAC7D,MAAM,QAAQ,MAAM,QAAQ,IAAI,aAAa,CAAC,EAAE,KAAK;CACrD,IAAI,CAAC,OAAO,OAAO,KAAA;CACnB,MAAM,UAAU,OAAO,KAAK;CAC5B,IAAI,CAAC,OAAO,MAAM,OAAO,GAAG,OAAO,KAAK,IAAI,GAAG,UAAU,GAAI;CAC7D,MAAM,KAAK,KAAK,MAAM,KAAK;CAC3B,IAAI,OAAO,MAAM,EAAE,GAAG,OAAO,KAAA;CAC7B,OAAO,KAAK,IAAI,GAAG,KAAK,KAAK,IAAI,CAAC;AACpC;AAEA,SAAS,aAAa,KAA8B;CAClD,OACE,OAAO,mBAAmB,eAC1B,IAAI,KAAK,gBAAgB;AAE7B;;;;;;AAOA,SAAgB,MAAM,UAAwB,CAAC,GAAW;CACxD,MAAM,EACJ,UAAU,GACV,UAAU,kBACV,UAAU,iBACV,SAAS,UAAU,MAAM,KAAK,OAC9B,QAAQ,cACR,gBAAgB,4BACd;CACJ,MAAM,UAAU,IAAI,IAAI,QAAQ,KAAK,MAAM,EAAE,YAAY,CAAC,CAAC;CAE3D,OAAO;EACL,MAAM;EACN,YAAY,OAAO,KAAK,SAAS;GAC/B,MAAM,UAAU,IAAI,KAAK,UAAU,MAAA,CAAO,YAAY;GACtD,MAAM,WAAW,QAAQ,IAAI,MAAM,KAAK,CAAC,aAAa,GAAG;GAEzD,KAAK,IAAI,UAAU,IAAK,WAAW,GACjC,IAAI;IACF,OAAO,MAAM,KAAK,GAAG;GACvB,SAAS,QAAQ;IACf,IAAI,CAAC,YAAY,WAAW,SAAS,MAAM;IAC3C,IAAI,CAAC,eAAe,MAAM,GAAG,MAAM;IACnC,IAAI,OAAO,WAAW,KAAK,CAAC,QAAQ,SAAS,OAAO,MAAM,GACxD,MAAM;IAER,IAAI,IAAI,KAAK,QAAQ,SAAS,MAAM;IACpC,MAAM,aAAa,aAAa,MAAM;IACtC,IAAI,eAAe,KAAA,KAAa,aAAa,eAC3C,MAAM;IAER,MAAM,MAAM,cAAc,MAAM,SAAS,MAAM,GAAG,IAAI,KAAK,MAAM;GACnE;EAEJ;CACF;AACF;;;;;;;AC9FA,SAAS,UACP,MACA,QACA,QAC4B;CAC5B,MAAM,SAAS,KAAK,UAAU;CAC9B,OAAO,IAAI,eAA2B;EACpC,MAAM,YAAY;GAChB,OAAO,iBACL,eACM;IACJ,WAAW,MAAM,OAAO,MAAM;IAC9B,OAAO,OAAO,OAAO,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;GAC7C,GACA,EAAE,MAAM,KAAK,CACf;EACF;EACA,MAAM,KAAK,YAAY;GACrB,MAAM,EAAE,OAAO,SAAS,MAAM,OAAO,KAAK;GAC1C,IAAI,MAAM;IACR,OAAO;IACP,WAAW,MAAM;IACjB;GACF;GACA,WAAW,QAAQ,KAAK;EAC1B;EACA,OAAO,QAAQ;GACb,OAAO;GACP,OAAO,OAAO,OAAO,MAAM;EAC7B;CACF,CAAC;AACH;;;;;;;;;;;AAYA,SAAgB,QAAQ,IAAoB;CAC1C,OAAO;EACL,MAAM;EACN,YAAY,OAAO,KAAK,SAAS;GAC/B,MAAM,aAAa,IAAI,gBAAgB;GACvC,MAAM,QAAQ,IAAI,KAAK;GACvB,MAAM,gBAAgB,WAAW,MAAM,OAAO,MAAM;GACpD,IAAI,OAAO,SAAS,QAAQ;QACvB,OAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;GAE7D,MAAM,QAAQ,iBAEV,WAAW,MACT,IAAI,aACF,oCAAoC,GAAG,KACvC,cACF,CACF,GACF,EACF;GACA,MAAM,eAAe;IACnB,aAAa,KAAK;IAClB,OAAO,oBAAoB,SAAS,OAAO;GAC7C;GAEA,IAAI;GACJ,IAAI;IACF,MAAM,MAAM,KAAK;KACf,GAAG;KACH,MAAM;MAAE,GAAG,IAAI;MAAM,QAAQ,WAAW;KAAO;IACjD,CAAC;GACH,SAAS,QAAQ;IACf,OAAO;IACP,MAAM;GACR;GACA,IAAI,CAAC,IAAI,MAAM;IACb,OAAO;IACP,OAAO;GACT;GACA,OAAO,IAAI,SAAS,UAAU,IAAI,MAAM,WAAW,QAAQ,MAAM,GAAG;IAClE,QAAQ,IAAI;IACZ,YAAY,IAAI;IAChB,SAAS,IAAI;GACf,CAAC;EACH;CACF;AACF"}
|