arthron 1.1.0 → 1.2.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 CHANGED
@@ -127,7 +127,7 @@ const authed = base.use(bearerAuth({ origin: API, token })); // base + auth
127
127
  const admin = authed.extend({ headers: { "x-role": "admin" } }); // authed + header
128
128
  ```
129
129
 
130
- `extend` shallow-merges the config, merges headers case-insensitively (child wins), and adds plugins the way `use` does: a plugin whose `name` is already there replaces it in place — so `streaming = base.use(timeout(120_000))` loosens the inherited `timeout`, and a second `retry()` does not multiply attempts — while unnamed plugins append. `without("retry")` returns a client without that plugin. The built-ins are named `bearerAuth`, `retry`, `timeout` and `logging`.
130
+ `extend` shallow-merges the config, merges headers case-insensitively (child wins), and adds plugins the way `use` does: a plugin whose `name` is already there replaces it in place — so `streaming = base.use(timeout(120_000))` loosens the inherited `timeout`, and a second `retry()` does not multiply attempts — while unnamed plugins append. `without("retry")` returns a client without that plugin. The built-ins are named `bearerAuth`, `logging`, `retry`, `timeout` and `uploadProgress`.
131
131
 
132
132
  ## Plugins
133
133
 
@@ -146,6 +146,7 @@ type RequestContext = {
146
146
  url: string;
147
147
  init: Omit<RequestInit, "headers"> & { headers: Record<string, string> }; // lowercase names
148
148
  meta: ArthronMeta; // per-call plugin settings, never sent
149
+ fetch?: typeof fetch; // the fetch this attempt is sent with — replace it to change the transport, wrap it to observe
149
150
  };
150
151
  ```
151
152
 
@@ -173,7 +174,7 @@ Every attempt starts from a fresh copy of the context, so a header set by an int
173
174
 
174
175
  ### Built in
175
176
 
176
- The built-in plugins come from `arthron/plugins`: `import { bearerAuth, logging, retry, timeout } from "arthron/plugins"`. They are plain plugins — anything they do, yours can. `logging`, `retry` and `timeout` are also exported from `arthron` itself, as they were in 1.0; those exports are deprecated and go in the next major.
177
+ The built-in plugins come from `arthron/plugins`: `import { bearerAuth, logging, retry, timeout, uploadProgress } from "arthron/plugins"`. They are plain plugins — anything they do, yours can. `logging`, `retry` and `timeout` are also exported from `arthron` itself, as they were in 1.0; those exports are deprecated and go in the next major.
177
178
 
178
179
  | plugin | slots | per call |
179
180
  |---|---|---|
@@ -181,6 +182,7 @@ The built-in plugins come from `arthron/plugins`: `import { bearerAuth, logging,
181
182
  | `retry` | middleware | `meta: { retry: false \| options }` |
182
183
  | `timeout` | middleware | `meta: { timeout: ms }` |
183
184
  | `logging` | middleware | — |
185
+ | `uploadProgress` | request interceptor (picks the transport) | `meta: { onUploadProgress }` — only calls that pass it |
184
186
 
185
187
  Order is meaning: the first plugin is outermost. A good default:
186
188
 
@@ -275,6 +277,29 @@ Per call, `meta: { bearerAuth: false }` sends no token and never refreshes. Use
275
277
 
276
278
  The token is treated as opaque: to refresh a JWT ahead of its `exp`, do it inside `token()`. It is not for cookie sessions: it tells whether another call already refreshed by comparing the `authorization` header a call sent with the current one, and an httpOnly cookie cannot be read to compare.
277
279
 
280
+ #### `uploadProgress()`
281
+
282
+ Does nothing by itself — only a call that passes `meta.onUploadProgress` reports progress. Every other call, on the same client too, goes out exactly as before.
283
+
284
+ ```ts
285
+ const api = arthron({ baseURL: API, use: [retry(), uploadProgress()] }); // last in use
286
+
287
+ await api.get("/me"); // as usual
288
+ await api.post("/files", file, {
289
+ meta: { onUploadProgress: ({ loaded, total, progress }) => setBar(progress) }, // this call only
290
+ });
291
+ ```
292
+
293
+ The event: `loaded` (bytes sent), `total` (bytes in the body, `undefined` when unknown — a stream without `content-length`), `progress` (`loaded / total` from 0 to 1, `undefined` with `total`). Progress counts what was handed to the network, so it reaches 1 a moment before the server has it all; the response arriving is the end. Each attempt counts from 0 again, so a retried upload starts the bar over.
294
+
295
+ How that call is sent depends on where it runs:
296
+
297
+ - **Browser:** over `XMLHttpRequest`, the only browser API that reports upload progress. On another origin, the upload listener makes every such call send a CORS preflight — an S3 bucket's CORS rules must allow the method and headers. The response arrives whole: `.sse()` / `.ndjson()` are not live on that call. What XHR cannot honour and would change the call is refused with a `TypeError` before sending: `redirect: "manual" | "error"`, `mode: "no-cors"`, and `credentials: "omit"` to the page's own origin (XHR always sends its cookies there). What only tunes fetch — `cache`, `priority`, `referrerPolicy`, `integrity`, `keepalive` — is ignored on that call. A `ReadableStream` body is sent as usual, without progress.
298
+ - **Node:** with `fetch` as usual, the body counted as it is read, in steps of at most 64 KiB. `content-length` and the content type a `Blob` would set are kept, so an S3 presigned `PUT` accepts it. A `FormData` body is encoded with its length known up front, its files streamed rather than read into memory. jsdom on Node counts the same way.
299
+ - **Anywhere else** — Deno, Bun, Cloudflare Workers, an Electron main process, a service worker — and on a client with an injected `fetch` or another plugin's transport: sent as usual, without progress.
300
+
301
+ A callback that throws fails the call with its own error, untouched: not an `ArthronError`, not retried. Put `uploadProgress()` last in `use`: a request interceptor after it that replaces `init.signal` would cut that path, and the error would come back as a network failure.
302
+
278
303
  ### Write your own
279
304
 
280
305
  `definePlugin`, `defineMiddleware` and `defineInterceptor.{request,response,error}` are identity functions that give you the parameter types without annotations.
@@ -11,11 +11,19 @@ interface RequestContext {
11
11
  };
12
12
  /** Per-call settings for plugins (`RequestOptions.meta`); never sent. */
13
13
  meta: ArthronMeta;
14
+ /**
15
+ * The fetch this attempt is sent with — the client's `fetch`, else the
16
+ * global one. Always set on contexts arthron creates; replace it to change
17
+ * the transport for this attempt, wrap it to observe. Optional so a
18
+ * context you build yourself stays valid.
19
+ */
20
+ fetch?: typeof fetch;
14
21
  }
15
22
  /**
16
23
  * Per-call settings for plugins, passed as `api.get(path, { meta })` and
17
24
  * carried next to `init` — never sent to fetch. The built-in plugins read
18
- * `bearerAuth`, `retry` and `timeout`; your own add keys by augmentation:
25
+ * `bearerAuth`, `onUploadProgress`, `retry` and `timeout`; your own add keys
26
+ * by augmentation:
19
27
  *
20
28
  * ```ts
21
29
  * declare module "arthron" {
@@ -29,11 +37,28 @@ interface ArthronMeta {
29
37
  * on its 401 — for the login and token-refresh endpoints.
30
38
  */
31
39
  bearerAuth?: false;
40
+ /**
41
+ * Upload progress for this call, with `uploadProgress()` on the client.
42
+ * Never called without that plugin or without a body, nor where progress
43
+ * cannot be had: a client with an injected `fetch`, another plugin's
44
+ * transport, Deno, Bun, Cloudflare Workers, a service worker, or a
45
+ * `ReadableStream` body in a browser — those calls are sent as usual.
46
+ */
47
+ onUploadProgress?: (event: UploadProgressEvent) => void;
32
48
  /** `false` turns `retry()` off for this call; an object overrides its options. */
33
49
  retry?: false | Omit<RetryOptions, "sleep">;
34
50
  /** Deadline for this call in ms, overriding `timeout(ms)`. `Infinity` lifts it. */
35
51
  timeout?: number;
36
52
  }
53
+ /** One step of an upload, as `meta.onUploadProgress` receives it. */
54
+ type UploadProgressEvent = {
55
+ /** Bytes sent so far. */
56
+ loaded: number;
57
+ /** Bytes in the whole body; `undefined` when it cannot be known (a stream without content-length). */
58
+ total: number | undefined;
59
+ /** `loaded / total`, from 0 to 1; `undefined` when `total` is. */
60
+ progress: number | undefined;
61
+ };
37
62
  type RetryOptions = {
38
63
  /** Number of retries after the first attempt. Default 2 (3 requests total). */
39
64
  retries?: number;
@@ -160,7 +185,7 @@ interface Plugin {
160
185
  /**
161
186
  * Identity within a client: `use`/`extend` replace the plugin of the same
162
187
  * name in place, `without(name)` removes it. The built-ins are named
163
- * "bearerAuth", "logging", "retry" and "timeout". Leave it out to always
188
+ * "bearerAuth", "logging", "retry", "timeout" and "uploadProgress". Leave it out to always
164
189
  * append.
165
190
  */
166
191
  name?: string;
@@ -289,5 +314,20 @@ declare function timeout(options: TimeoutOptions): Plugin;
289
314
  /** @deprecated Use `timeout({ total: ms, idle })`; this form goes in the next major. */
290
315
  declare function timeout(ms: number, options: Pick<TimeoutOptions, "idle">): Plugin;
291
316
  //#endregion
292
- export { HeadersOption as _, LogEvent as a, RetryOptions as b, BearerAuthOptions as c, Plugin as d, RequestInterceptor as f, ArthronMeta as g, isArthronError as h, logging as i, ErrorInterceptor as l, ArthronError as m, TimeoutOptions as n, LoggingOptions as o, ResponseInterceptor as p, retry as r, bearerAuth as s, timeout as t, Middleware as u, QueryParams as v, RequestContext as y };
293
- //# sourceMappingURL=index-Bz1P80JN.d.cts.map
317
+ //#region src/plugins/upload-progress/upload-progress.d.ts
318
+ /**
319
+ * Does nothing by itself — only a call that passes `meta.onUploadProgress`
320
+ * reports upload progress. On such a call it changes how that one attempt
321
+ * is sent: over XMLHttpRequest in a browser, with a counted body in Node.
322
+ * Every other call, on this client too, goes out exactly as before.
323
+ *
324
+ * Progress needs the runtime's own fetch: with an injected `fetch`, another
325
+ * plugin's transport, or a runtime that is neither (Deno, Bun, Cloudflare
326
+ * Workers, a service worker), the call is sent as usual without progress.
327
+ * Put it last in `use`: the call's signal carries a throwing callback's
328
+ * error, and an interceptor after it that replaces the signal would cut it.
329
+ */
330
+ declare function uploadProgress(): Plugin;
331
+ //#endregion
332
+ export { UploadProgressEvent as S, ArthronMeta as _, logging as a, RequestContext as b, bearerAuth as c, Middleware as d, Plugin as f, isArthronError as g, ArthronError as h, retry as i, BearerAuthOptions as l, ResponseInterceptor as m, timeout as n, LogEvent as o, RequestInterceptor as p, TimeoutOptions as r, LoggingOptions as s, uploadProgress as t, ErrorInterceptor as u, HeadersOption as v, RetryOptions as x, QueryParams as y };
333
+ //# sourceMappingURL=index-C3lylSNd.d.cts.map
@@ -11,11 +11,19 @@ interface RequestContext {
11
11
  };
12
12
  /** Per-call settings for plugins (`RequestOptions.meta`); never sent. */
13
13
  meta: ArthronMeta;
14
+ /**
15
+ * The fetch this attempt is sent with — the client's `fetch`, else the
16
+ * global one. Always set on contexts arthron creates; replace it to change
17
+ * the transport for this attempt, wrap it to observe. Optional so a
18
+ * context you build yourself stays valid.
19
+ */
20
+ fetch?: typeof fetch;
14
21
  }
15
22
  /**
16
23
  * Per-call settings for plugins, passed as `api.get(path, { meta })` and
17
24
  * carried next to `init` — never sent to fetch. The built-in plugins read
18
- * `bearerAuth`, `retry` and `timeout`; your own add keys by augmentation:
25
+ * `bearerAuth`, `onUploadProgress`, `retry` and `timeout`; your own add keys
26
+ * by augmentation:
19
27
  *
20
28
  * ```ts
21
29
  * declare module "arthron" {
@@ -29,11 +37,28 @@ interface ArthronMeta {
29
37
  * on its 401 — for the login and token-refresh endpoints.
30
38
  */
31
39
  bearerAuth?: false;
40
+ /**
41
+ * Upload progress for this call, with `uploadProgress()` on the client.
42
+ * Never called without that plugin or without a body, nor where progress
43
+ * cannot be had: a client with an injected `fetch`, another plugin's
44
+ * transport, Deno, Bun, Cloudflare Workers, a service worker, or a
45
+ * `ReadableStream` body in a browser — those calls are sent as usual.
46
+ */
47
+ onUploadProgress?: (event: UploadProgressEvent) => void;
32
48
  /** `false` turns `retry()` off for this call; an object overrides its options. */
33
49
  retry?: false | Omit<RetryOptions, "sleep">;
34
50
  /** Deadline for this call in ms, overriding `timeout(ms)`. `Infinity` lifts it. */
35
51
  timeout?: number;
36
52
  }
53
+ /** One step of an upload, as `meta.onUploadProgress` receives it. */
54
+ type UploadProgressEvent = {
55
+ /** Bytes sent so far. */
56
+ loaded: number;
57
+ /** Bytes in the whole body; `undefined` when it cannot be known (a stream without content-length). */
58
+ total: number | undefined;
59
+ /** `loaded / total`, from 0 to 1; `undefined` when `total` is. */
60
+ progress: number | undefined;
61
+ };
37
62
  type RetryOptions = {
38
63
  /** Number of retries after the first attempt. Default 2 (3 requests total). */
39
64
  retries?: number;
@@ -160,7 +185,7 @@ interface Plugin {
160
185
  /**
161
186
  * Identity within a client: `use`/`extend` replace the plugin of the same
162
187
  * name in place, `without(name)` removes it. The built-ins are named
163
- * "bearerAuth", "logging", "retry" and "timeout". Leave it out to always
188
+ * "bearerAuth", "logging", "retry", "timeout" and "uploadProgress". Leave it out to always
164
189
  * append.
165
190
  */
166
191
  name?: string;
@@ -289,5 +314,20 @@ declare function timeout(options: TimeoutOptions): Plugin;
289
314
  /** @deprecated Use `timeout({ total: ms, idle })`; this form goes in the next major. */
290
315
  declare function timeout(ms: number, options: Pick<TimeoutOptions, "idle">): Plugin;
291
316
  //#endregion
292
- export { HeadersOption as _, LogEvent as a, RetryOptions as b, BearerAuthOptions as c, Plugin as d, RequestInterceptor as f, ArthronMeta as g, isArthronError as h, logging as i, ErrorInterceptor as l, ArthronError as m, TimeoutOptions as n, LoggingOptions as o, ResponseInterceptor as p, retry as r, bearerAuth as s, timeout as t, Middleware as u, QueryParams as v, RequestContext as y };
293
- //# sourceMappingURL=index-Bz1P80JN.d.mts.map
317
+ //#region src/plugins/upload-progress/upload-progress.d.ts
318
+ /**
319
+ * Does nothing by itself — only a call that passes `meta.onUploadProgress`
320
+ * reports upload progress. On such a call it changes how that one attempt
321
+ * is sent: over XMLHttpRequest in a browser, with a counted body in Node.
322
+ * Every other call, on this client too, goes out exactly as before.
323
+ *
324
+ * Progress needs the runtime's own fetch: with an injected `fetch`, another
325
+ * plugin's transport, or a runtime that is neither (Deno, Bun, Cloudflare
326
+ * Workers, a service worker), the call is sent as usual without progress.
327
+ * Put it last in `use`: the call's signal carries a throwing callback's
328
+ * error, and an interceptor after it that replaces the signal would cut it.
329
+ */
330
+ declare function uploadProgress(): Plugin;
331
+ //#endregion
332
+ export { UploadProgressEvent as S, ArthronMeta as _, logging as a, RequestContext as b, bearerAuth as c, Middleware as d, Plugin as f, isArthronError as g, ArthronError as h, retry as i, BearerAuthOptions as l, ResponseInterceptor as m, timeout as n, LogEvent as o, RequestInterceptor as p, TimeoutOptions as r, LoggingOptions as s, uploadProgress as t, ErrorInterceptor as u, HeadersOption as v, RetryOptions as x, QueryParams as y };
333
+ //# sourceMappingURL=index-C3lylSNd.d.mts.map
package/dist/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_plugins = require("./plugins-CVrzb8x6.cjs");
2
+ const require_plugins = require("./plugins-Cigqz2SS.cjs");
3
3
  //#region src/request/headers.ts
4
4
  const isPlainRecord = (headers) => !Array.isArray(headers) && Object.prototype.toString.call(headers) !== "[object Headers]";
5
5
  /**
@@ -39,17 +39,19 @@ function cloneBody(body) {
39
39
  * again (retry) must not see the previous attempt's changes, so every attempt
40
40
  * starts from a fresh copy of the headers and of a FormData/URLSearchParams body.
41
41
  */
42
- function cloneContext({ url, init, meta }) {
42
+ function cloneContext({ url, init, meta, fetch }) {
43
43
  const copy = {
44
44
  ...init,
45
45
  headers: toHeaderRecord(init.headers)
46
46
  };
47
47
  if (init.body) copy.body = cloneBody(init.body);
48
- return {
48
+ const out = {
49
49
  url,
50
50
  init: copy,
51
51
  meta: { ...meta }
52
52
  };
53
+ if (fetch) out.fetch = fetch;
54
+ return out;
53
55
  }
54
56
  /**
55
57
  * Put headers back into a plain lowercase record. A plugin written in JS (or
@@ -57,7 +59,7 @@ function cloneContext({ url, init, meta }) {
57
59
  * `{ ...ctx.init.headers }` would otherwise drop every header.
58
60
  */
59
61
  function normalize(ctx, previous) {
60
- return {
62
+ const out = {
61
63
  ...ctx,
62
64
  init: {
63
65
  ...ctx.init,
@@ -65,6 +67,9 @@ function normalize(ctx, previous) {
65
67
  },
66
68
  meta: ctx.meta ?? previous.meta
67
69
  };
70
+ const fetch = ctx.fetch ?? previous.fetch;
71
+ if (fetch) out.fetch = fetch;
72
+ return out;
68
73
  }
69
74
  /**
70
75
  * A plugin written in JS (or with a cast) that forgets to return would hand
@@ -113,10 +118,15 @@ async function runPipeline(plugins, ctx, send) {
113
118
  const dispatch = async (index, input) => {
114
119
  const middleware = middlewares[index];
115
120
  if (!middleware) return attempt(input);
116
- return returned(await middleware(input, (next) => dispatch(index + 1, {
117
- ...next,
118
- meta: next.meta ?? input.meta
119
- })), "a middleware", "the Response, usually `return next(ctx)`", void 0, "resolved with");
121
+ return returned(await middleware(input, (next) => {
122
+ const ctx = {
123
+ ...next,
124
+ meta: next.meta ?? input.meta
125
+ };
126
+ const fetch = next.fetch ?? input.fetch;
127
+ if (fetch) ctx.fetch = fetch;
128
+ return dispatch(index + 1, ctx);
129
+ }), "a middleware", "the Response, usually `return next(ctx)`", void 0, "resolved with");
120
130
  };
121
131
  return dispatch(0, ctx);
122
132
  }
@@ -377,7 +387,7 @@ function failedByDefault(res, init) {
377
387
  }
378
388
  /** The one place a failure becomes an ArthronError. Cancellation passes through. */
379
389
  async function send(resolved, ctx, throwOn) {
380
- const fetchImpl = resolved.fetch ?? globalThis.fetch;
390
+ const fetchImpl = ctx.fetch ?? resolved.fetch ?? globalThis.fetch;
381
391
  if (resolved.fetch === void 0) {
382
392
  const method = ctx.init.method ?? "GET";
383
393
  assertSendable(ctx.url, method);
@@ -438,7 +448,8 @@ function arthron(config = {}) {
438
448
  return toArthronPromise(runPipeline(resolved.use, {
439
449
  url,
440
450
  init: requestInit,
441
- meta: { ...meta }
451
+ meta: { ...meta },
452
+ fetch: resolved.fetch ?? globalThis.fetch
442
453
  }, (ctx) => send(resolved, ctx, throwOn)), init.signal);
443
454
  } catch (thrown) {
444
455
  return toArthronPromise(Promise.reject(thrown));
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["isArthronError","cancellationError","redactUrlIn","redactUrl","isReadableStream","runtimeStreamsRequestBody","isCancellation","cancellationError","networkError","responseError","fillPath","resolveUrl","leavesBase","serializeBody","isReadableStream"],"sources":["../src/request/headers.ts","../src/pipeline/pipeline.ts","../src/response/schema-validation.ts","../src/response/stream-parser.ts","../src/response/arthron-promise.ts","../src/client/resolve-config.ts","../src/client/send.ts","../src/client/client.ts","../src/pipeline/define-plugin.ts","../src/index.ts"],"sourcesContent":["import type { HeadersOption } from \"../types/request.types\";\n\nconst isPlainRecord = (\n headers: HeadersOption,\n): headers is Record<string, string | null | undefined> =>\n !Array.isArray(headers) &&\n Object.prototype.toString.call(headers) !== \"[object Headers]\";\n\n/**\n * Layer header sets left to right into one plain record. Names are compared\n * case-insensitively and come back lowercased; a later layer wins, so a\n * caller's `content-type` replaces a default `Content-Type` instead of fetch\n * sending both values joined. `Headers` validates every value it sets.\n */\nexport function mergeHeaders(\n ...layers: (HeadersOption | undefined)[]\n): Record<string, string> {\n const merged = new Headers();\n for (const layer of layers) {\n if (!layer) continue;\n if (isPlainRecord(layer)) {\n for (const [name, value] of Object.entries(layer)) {\n if (value == null) merged.delete(name);\n else merged.set(name, value);\n }\n } else {\n for (const [name, value] of new Headers(layer)) merged.set(name, value);\n }\n }\n return Object.fromEntries(merged);\n}\n\n/** Normalize any header shape into a plain record with lowercased names. */\nexport function toHeaderRecord(\n headers: HeadersOption | undefined,\n): Record<string, string> {\n return mergeHeaders(headers);\n}\n","import { isArthronError } from \"../error/arthron-error\";\nimport { toHeaderRecord } from \"../request/headers\";\nimport type {\n ErrorInterceptor,\n Middleware,\n Plugin,\n RequestInterceptor,\n ResponseInterceptor,\n} from \"../types/plugin.types\";\nimport type { RequestContext } from \"../types/request.types\";\n\n/** The mutable body kinds an interceptor can `append` to; anything else is shared as is. */\nfunction cloneBody(body: BodyInit): BodyInit {\n if (typeof FormData !== \"undefined\" && body instanceof FormData) {\n const copy = new FormData();\n for (const [name, value] of body) copy.append(name, value);\n return copy;\n }\n if (body instanceof URLSearchParams) return new URLSearchParams(body);\n return body;\n}\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 of the headers and of a FormData/URLSearchParams body.\n */\nfunction cloneContext({ url, init, meta }: RequestContext): RequestContext {\n const copy: RequestContext[\"init\"] = {\n ...init,\n headers: toHeaderRecord(init.headers),\n };\n if (init.body) copy.body = cloneBody(init.body);\n return { url, init: copy, meta: { ...meta } };\n}\n\n/**\n * Put headers back into a plain lowercase record. A plugin written in JS (or\n * with a cast) may hand back a `Headers` object, and the next plugin's\n * `{ ...ctx.init.headers }` would otherwise drop every header.\n */\nfunction normalize(\n ctx: RequestContext,\n previous: RequestContext,\n): RequestContext {\n return {\n ...ctx,\n init: { ...ctx.init, headers: toHeaderRecord(ctx.init.headers) },\n // A plugin that builds a fresh `{ url, init }` must not lose the call's meta.\n meta: ctx.meta ?? previous.meta,\n };\n}\n\n/**\n * A plugin written in JS (or with a cast) that forgets to return would hand\n * `undefined` on — and an error interceptor's would be what the caller\n * catches. Name the plugin slot instead.\n */\nfunction returned<T>(\n value: T | null | undefined,\n who: string,\n what: string,\n cause?: unknown,\n verb = \"returned\",\n): T {\n if (value != null) return value;\n throw new TypeError(\n `arthron: ${who} ${verb} ${value} — return ${what}`,\n cause === undefined ? undefined : { cause },\n );\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 for (const interceptor of request) {\n const next = returned(\n await interceptor(current),\n \"a request interceptor\",\n \"the context (ctx or a changed copy)\",\n );\n current = normalize(next, current);\n }\n let res: Response;\n try {\n res = await send(current);\n } catch (thrown) {\n // Only send's failures are this request's: an ArthronError a plugin\n // throws (its own call failed) is a plugin exception like any other.\n if (!isArthronError(thrown)) throw thrown;\n let err = thrown;\n for (const interceptor of error) {\n err = returned(\n await interceptor(err, current),\n \"an error interceptor\",\n \"the error (err or a changed one)\",\n thrown,\n );\n }\n throw err;\n }\n for (const interceptor of response) {\n res = returned(\n await interceptor(res, current),\n \"a response interceptor\",\n \"the Response (res or a new one)\",\n );\n }\n return res;\n };\n\n const dispatch = async (\n index: number,\n input: RequestContext,\n ): Promise<Response> => {\n const middleware = middlewares[index];\n if (!middleware) return attempt(input);\n // A middleware that builds a fresh `{ url, init }` keeps the call's meta.\n const res = await middleware(input, (next) =>\n dispatch(index + 1, { ...next, meta: next.meta ?? input.meta }),\n );\n return returned(\n res,\n \"a middleware\",\n \"the Response, usually `return next(ctx)`\",\n undefined,\n \"resolved with\",\n );\n };\n return dispatch(0, ctx);\n}\n","import type {\n InferOutput,\n SchemaIssue,\n StandardSchemaV1,\n} from \"../types/response.types\";\n\nconst pathOf = (issue: SchemaIssue): string =>\n (issue.path ?? [])\n .map((part) =>\n String(typeof part === \"object\" && part !== null ? part.key : part),\n )\n .join(\".\");\n\n/**\n * `.json(schema)` got a body that does not match. Not an ArthronError: the\n * request succeeded — the data is what is wrong.\n */\nexport class SchemaError extends Error {\n override readonly name = \"SchemaError\";\n /** Every problem the validator reported, with the path to it. */\n readonly issues: readonly SchemaIssue[];\n\n constructor(issues: readonly SchemaIssue[]) {\n const [first] = issues;\n const where = first ? pathOf(first) : \"\";\n const more = issues.length > 1 ? ` (+${issues.length - 1} more)` : \"\";\n super(\n `arthron: the response body does not match the schema — ${where ? `${where}: ` : \"\"}${first?.message ?? \"invalid\"}${more}. See error.issues`,\n );\n this.issues = issues;\n }\n}\n\n/** Validate `data`; resolve with the schema's output or reject with a SchemaError. */\nexport async function validate<S extends StandardSchemaV1>(\n schema: S,\n data: unknown,\n): Promise<InferOutput<S>> {\n const result = await schema[\"~standard\"].validate(data);\n if (result.issues) throw new SchemaError(result.issues);\n return result.value as InferOutput<S>;\n}\n","import type {\n NdjsonOptions,\n SseEvent,\n SseOptions,\n} from \"../types/response.types\";\n\nconst DEFAULT_LIMIT = 8 * 1024 * 1024;\n\n/**\n * Yield the body line by line, reassembling lines split across chunks. Only\n * the newly arrived text is scanned, so a long line costs linear time; a line\n * past `maxLineLength` stops the stream instead of growing without bound.\n */\nasync function* lines(\n response: Response,\n maxLineLength: number,\n): AsyncGenerator<string> {\n if (!response.body) return;\n const checked = (line: string): string => {\n if (line.length <= maxLineLength) return line;\n throw new RangeError(\n `arthron: a stream line is longer than ${maxLineLength} characters — pass a larger maxLineLength to .sse() / .ndjson() if that is expected`,\n );\n };\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n const breaks = /\\r\\n|\\r|\\n/g;\n let pending = \"\";\n // The previous chunk ended in \"\\r\": a \"\\n\" opening this one belongs to it.\n let afterCR = false;\n try {\n while (true) {\n const { value, done } = await reader.read();\n let text = decoder.decode(value, { stream: !done });\n if (afterCR && text.startsWith(\"\\n\")) {\n text = text.slice(1);\n afterCR = false;\n }\n if (text) afterCR = text.endsWith(\"\\r\");\n let start = 0;\n breaks.lastIndex = 0;\n for (let m = breaks.exec(text); m; m = breaks.exec(text)) {\n yield checked(pending + text.slice(start, m.index));\n pending = \"\";\n start = breaks.lastIndex;\n }\n // Checked before the break arrives too, so a line without one cannot\n // grow without bound.\n pending = checked(pending + text.slice(start));\n if (done) break;\n }\n if (pending) yield pending;\n } finally {\n // Leaving early (break, return, a throw) must close the connection too —\n // releasing the lock alone leaves the body streaming.\n await reader.cancel().catch(() => {});\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` — or one the stream ends inside — is not dispatched.\n * Provider sentinels like\n * `[DONE]` are passed through untouched — they are not part of the spec.\n */\nexport async function* parseSse(\n response: Response,\n {\n maxLineLength = DEFAULT_LIMIT,\n maxEventSize = DEFAULT_LIMIT,\n }: SseOptions = {},\n): AsyncGenerator<SseEvent> {\n let data: string[] = [];\n let size = 0;\n let event: string | undefined;\n let id: string | undefined;\n // Stream state, not block state: neither resets when an event dispatches.\n let lastEventId = \"\";\n let retry: number | undefined;\n\n const flush = (): SseEvent | undefined => {\n const out: SseEvent | undefined =\n data.length === 0 ? undefined : { data: data.join(\"\\n\"), lastEventId };\n if (out && event !== undefined) out.event = event;\n if (out && id !== undefined) out.id = id;\n if (out && retry !== undefined) out.retry = retry;\n data = [];\n size = 0;\n event = id = undefined;\n return out;\n };\n\n for await (const line of lines(response, maxLineLength)) {\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 size += value.length + 1;\n if (size > maxEventSize + 1) {\n throw new RangeError(\n `arthron: an SSE event's data is longer than ${maxEventSize} characters — pass a larger maxEventSize to .sse() if that is expected`,\n );\n }\n data.push(value);\n break;\n case \"event\":\n event = value;\n break;\n case \"id\":\n if (!value.includes(\"\\u0000\")) id = lastEventId = value;\n break;\n case \"retry\":\n if (/^\\d+$/.test(value)) retry = Number(value);\n break;\n default:\n break; // unknown fields are ignored, per spec\n }\n }\n // A block the stream ended in before its blank line is incomplete — the\n // spec discards it rather than dispatch a possibly truncated payload.\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 { maxLineLength = DEFAULT_LIMIT }: NdjsonOptions = {},\n): AsyncGenerator<T> {\n for await (const line of lines(response, maxLineLength)) {\n if (line.trim() === \"\") continue;\n yield JSON.parse(line) as T;\n }\n}\n","import { cancellationError } from \"../error/create-error\";\nimport type {\n ArthronPromise,\n NdjsonOptions,\n SseOptions,\n StandardSchemaV1,\n} from \"../types/response.types\";\nimport { validate } from \"./schema-validation\";\nimport { parseNdjson, parseSse } from \"./stream-parser\";\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 signal: AbortSignal | null | undefined,\n): AsyncIterable<T> {\n // Iteration may start long after the request fails. Mark the rejection\n // handled now; the first `next()` still rethrows it.\n promise.catch(() => {});\n return {\n async *[Symbol.asyncIterator]() {\n try {\n yield* parse(await promise);\n } catch (error) {\n throw cancellationError(error, signal);\n }\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(\n promise: Promise<Response>,\n signal?: AbortSignal | null,\n): ArthronPromise {\n // A body read the caller aborted rejects with their reason, whatever the\n // runtime's fetch reports.\n const read = <T>(body: (res: Response) => Promise<T>): Promise<T> =>\n promise.then(body).catch((error: unknown) => {\n throw cancellationError(error, signal);\n });\n return Object.assign(promise, {\n json: ((schema?: StandardSchemaV1) =>\n read(async (res) => {\n const data: unknown = await res.json();\n return schema ? validate(schema, data) : data;\n })) as ArthronPromise[\"json\"],\n text: () => read((res) => res.text()),\n blob: () => read((res) => res.blob()),\n arrayBuffer: () => read((res) => res.arrayBuffer()),\n formData: () => read((res) => res.formData()),\n sse: (options?: SseOptions) =>\n iterate(promise, (res) => parseSse(res, options), signal),\n ndjson: <T = unknown>(options?: NdjsonOptions) =>\n iterate(promise, (res) => parseNdjson<T>(res, options), signal),\n });\n}\n","import { toHeaderRecord } from \"../request/headers\";\nimport type { Plugin } from \"../types/plugin.types\";\nimport type { ArthronConfig, FetchDefaults, Resolved } from \"./client.types\";\n\n/**\n * Config keys that configure arthron itself; every other key is a fetch\n * default. Typed so that a new config key fails to compile until it is\n * listed here — otherwise it would be sent to fetch.\n */\nconst OWN_KEYS: Record<\n Exclude<keyof ArthronConfig, keyof FetchDefaults>,\n true\n> = {\n baseURL: true,\n allowAbsoluteUrls: true,\n headers: true,\n fetch: true,\n use: true,\n errorDetail: true,\n throwOn: true,\n};\n\n/** Fields that belong to one request, refused as client defaults. */\nconst PER_CALL_ONLY = [\"signal\", \"body\", \"method\"] as const;\n\n/**\n * Add plugins to a list. A named plugin takes the place of the one already\n * there with that name — same position, so a replaced `logging()` stays\n * outermost and a second `retry()` does not multiply attempts. Unnamed\n * plugins append.\n */\nexport function addPlugins(\n base: readonly Plugin[],\n added: readonly Plugin[],\n): Plugin[] {\n const out = [...base];\n for (const plugin of added) {\n const at =\n plugin.name === undefined\n ? -1\n : out.findIndex((p) => p.name === plugin.name);\n if (at === -1) out.push(plugin);\n else out[at] = plugin;\n }\n return out;\n}\n\n/** The fetch defaults in a config: every set key that is not arthron's own. */\nexport function fetchDefaults(config: Resolved): FetchDefaults {\n return Object.fromEntries(\n Object.entries(config).filter(\n ([key, value]) => value !== undefined && !(key in OWN_KEYS),\n ),\n ) as FetchDefaults;\n}\n\n/** Normalize once so `extend` can merge headers and add plugins cheaply. */\nexport function resolveConfig(config: ArthronConfig): Resolved {\n for (const key of PER_CALL_ONLY) {\n if ((config as Record<string, unknown>)[key] !== undefined) {\n throw new TypeError(\n `arthron: ${key} cannot be a client default, it belongs to one request — pass it per call (api.get(path, { signal }), api.post(path, body), api.request(path, { method }))`,\n );\n }\n }\n const { headers, use, ...rest } = config;\n return {\n ...rest,\n headers: toHeaderRecord(headers),\n use: addPlugins([], use ?? []),\n };\n}\n","import {\n cancellationError,\n isCancellation,\n networkError,\n responseError,\n} from \"../error/create-error\";\nimport { isReadableStream, runtimeStreamsRequestBody } from \"../request/body\";\nimport { redactUrl, redactUrlIn } from \"../request/url\";\nimport type { RequestContext } from \"../types/request.types\";\nimport type { Resolved } from \"./client.types\";\n\n/**\n * The global fetch rejects a malformed URL or method with a TypeError that\n * looks just like a network failure (status 0, retried). A bodiless `Request`\n * runs the same checks first, so those become argument errors instead. Only\n * for the global fetch: an injected one owns its URL rules (SvelteKit's\n * server-side `fetch` accepts relative URLs, for one).\n */\nfunction assertSendable(url: string, method: string): void {\n if (typeof Request !== \"function\") return;\n try {\n new Request(url, { method });\n } catch (cause) {\n // fetch's own message repeats the full URL, query included.\n const reason = redactUrlIn(\n cause instanceof Error ? cause.message : String(cause),\n url,\n );\n throw new TypeError(\n `arthron: cannot send ${method} ${redactUrl(url)} (${reason}) — use an absolute URL or a baseURL with a scheme (https://…), and a valid HTTP method`,\n { cause },\n );\n }\n}\n\n/**\n * A ReadableStream body the browser cannot stream is rejected before it is\n * sent: Firefox would send the text \"[object ReadableStream]\", older Safari\n * fails with a bare network error. Only for the global fetch, like\n * assertSendable.\n */\nfunction assertStreamable(url: string, method: string, body: unknown): void {\n if (!isReadableStream(body) || runtimeStreamsRequestBody()) return;\n throw new TypeError(\n `arthron: cannot send ${method} ${redactUrl(url)} with a ReadableStream body — this browser cannot stream a request body and would send it garbled or not at all; pass a Blob or ArrayBuffer instead`,\n );\n}\n\n/**\n * Non-ok responses the caller asked for are not failures: an opaque one\n * (`mode: \"no-cors\"`) and the 3xx of `redirect: \"manual\"`.\n */\nfunction failedByDefault(res: Response, init: RequestContext[\"init\"]): boolean {\n if (res.type === \"opaque\" || res.type === \"opaqueredirect\") return false;\n return !(init.redirect === \"manual\" && res.status >= 300 && res.status < 400);\n}\n\n/** The one place a failure becomes an ArthronError. Cancellation passes through. */\nexport async function send(\n resolved: Resolved,\n ctx: RequestContext,\n throwOn: ((res: Response) => boolean) | undefined,\n): 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 // Here rather than before the pipeline, so it sees the URL the request\n // interceptors produced. A TypeError is not an ArthronError: retry and\n // error interceptors leave it alone.\n if (resolved.fetch === undefined) {\n const method = ctx.init.method ?? \"GET\";\n assertSendable(ctx.url, method);\n assertStreamable(ctx.url, method, ctx.init.body);\n }\n const { signal } = ctx.init;\n let res: Response;\n try {\n res = await fetchImpl(ctx.url, ctx.init);\n } catch (thrown) {\n if (isCancellation(thrown, signal)) throw cancellationError(thrown, signal);\n throw networkError(thrown, ctx);\n }\n // Only a non-ok response can become an ArthronError; throwOn narrows which.\n if (res.ok) return res;\n if (!(throwOn ? throwOn(res) : failedByDefault(res, ctx.init))) return res;\n const error = await responseError(res, ctx, resolved.errorDetail);\n // Aborted while the error body was being read (timeout, caller): that is\n // a cancellation, not this status.\n if (signal?.aborted) throw signal.reason;\n throw error;\n}\n","import { runPipeline } from \"../pipeline/pipeline\";\nimport { isReadableStream, serializeBody } from \"../request/body\";\nimport { mergeHeaders } from \"../request/headers\";\nimport { fillPath, leavesBase, resolveUrl } from \"../request/url\";\nimport { toArthronPromise } from \"../response/arthron-promise\";\nimport type { RequestContext } from \"../types/request.types\";\nimport type { ArthronPromise } from \"../types/response.types\";\nimport type {\n Arthron,\n ArthronConfig,\n RequestInitWithBody,\n RequestOptions,\n} from \"./client.types\";\nimport { addPlugins, fetchDefaults, resolveConfig } from \"./resolve-config\";\nimport { send } from \"./send\";\n\nconst BODYLESS_METHODS = new Set([\"GET\", \"HEAD\"]);\n/** Sent uppercased whatever their case; fetch itself only normalizes six. */\nconst STANDARD_METHODS = new Set([\n \"GET\",\n \"HEAD\",\n \"POST\",\n \"PUT\",\n \"PATCH\",\n \"DELETE\",\n \"OPTIONS\",\n \"QUERY\",\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 initDefaults = fetchDefaults(resolved);\n\n const dispatch = (\n path: string,\n method: string,\n rawBody: unknown,\n {\n params,\n headers: callHeaders,\n meta,\n throwOn = resolved.throwOn,\n path: pathValues,\n ...init\n }: RequestOptions,\n ): ArthronPromise => {\n try {\n const upper = method.toUpperCase();\n const verb = STANDARD_METHODS.has(upper) ? upper : method;\n if (rawBody !== undefined && BODYLESS_METHODS.has(upper)) {\n // fetch rejects these with a TypeError that would otherwise be mistaken for a network failure and retried.\n throw new TypeError(\n `arthron: ${upper} requests cannot carry a body — send it as params (query) or switch to POST`,\n );\n }\n if (pathValues) path = fillPath(path, pathValues);\n const url = resolveUrl(\n resolved.baseURL,\n path,\n params,\n resolved.allowAbsoluteUrls,\n );\n // Config headers (credentials, typically) belong to baseURL's origin.\n // Another origin, reachable only with allowAbsoluteUrls, gets per-call\n // headers alone.\n const defaults = leavesBase(resolved.baseURL, path)\n ? undefined\n : resolved.headers;\n const { body, headers } = serializeBody(\n rawBody,\n mergeHeaders(defaults, callHeaders),\n );\n // Config fetch defaults sit underneath, so a per-call value wins.\n const requestInit: RequestContext[\"init\"] = {\n ...initDefaults,\n ...init,\n method: verb,\n headers,\n };\n if (body !== undefined) requestInit.body = body;\n // Without half-duplex, fetch rejects a streaming upload (Node, Chromium).\n if (isReadableStream(body))\n Object.assign(requestInit, { duplex: \"half\" });\n return toArthronPromise(\n runPipeline(\n resolved.use,\n { url, init: requestInit, meta: { ...meta } },\n (ctx) => send(resolved, ctx, throwOn),\n ),\n init.signal,\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: addPlugins(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 query: (path, body, init = {}) => dispatch(path, \"QUERY\", 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 without: (...names) =>\n arthron({\n ...resolved,\n use: resolved.use.filter(\n (p) => p.name === undefined || !names.includes(p.name),\n ),\n }),\n };\n}\n","import type {\n ErrorInterceptor,\n Middleware,\n Plugin,\n RequestInterceptor,\n ResponseInterceptor,\n} from \"../types/plugin.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 * as plugins from \"./plugins/index\";\n\nexport { arthron } from \"./client/client\";\nexport type {\n Arthron,\n ArthronConfig,\n HttpMethod,\n RequestInitWithBody,\n RequestOptions,\n} from \"./client/client.types\";\nexport { ArthronError, isArthronError } from \"./error/arthron-error\";\nexport {\n defineInterceptor,\n defineMiddleware,\n definePlugin,\n} from \"./pipeline/define-plugin\";\nexport { SchemaError } from \"./response/schema-validation\";\nexport type {\n ErrorInterceptor,\n Middleware,\n Plugin,\n RequestInterceptor,\n ResponseInterceptor,\n} from \"./types/plugin.types\";\nexport type {\n ArthronMeta,\n HeadersOption,\n QueryParams,\n RequestContext,\n RetryOptions,\n} from \"./types/request.types\";\nexport type {\n ArthronPromise,\n ErrorDetailResolver,\n InferOutput,\n NdjsonOptions,\n SchemaIssue,\n SseEvent,\n SseOptions,\n StandardSchemaV1,\n} from \"./types/response.types\";\n\n/*\n * The built-in plugins moved to \"arthron/plugins\". These keep 1.0 imports\n * working until the next major; they are the same functions.\n */\n\n/** @deprecated Import from \"arthron/plugins\"; this export goes in the next major. */\nexport const logging: typeof plugins.logging = plugins.logging;\n/** @deprecated Import from \"arthron/plugins\"; this export goes in the next major. */\nexport const retry: typeof plugins.retry = plugins.retry;\n/** @deprecated Import from \"arthron/plugins\"; this export goes in the next major. */\nexport const timeout: typeof plugins.timeout = plugins.timeout;\n/** @deprecated Import from \"arthron/plugins\"; this export goes in the next major. */\nexport type LogEvent = plugins.LogEvent;\n/** @deprecated Import from \"arthron/plugins\"; this export goes in the next major. */\nexport type LoggingOptions = plugins.LoggingOptions;\n/** @deprecated Import from \"arthron/plugins\"; this export goes in the next major. */\nexport type TimeoutOptions = plugins.TimeoutOptions;\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["isArthronError","cancellationError","redactUrlIn","redactUrl","isReadableStream","runtimeStreamsRequestBody","isCancellation","cancellationError","networkError","responseError","fillPath","resolveUrl","leavesBase","serializeBody","isReadableStream"],"sources":["../src/request/headers.ts","../src/pipeline/pipeline.ts","../src/response/schema-validation.ts","../src/response/stream-parser.ts","../src/response/arthron-promise.ts","../src/client/resolve-config.ts","../src/client/send.ts","../src/client/client.ts","../src/pipeline/define-plugin.ts","../src/index.ts"],"sourcesContent":["import type { HeadersOption } from \"../types/request.types\";\n\nconst isPlainRecord = (\n headers: HeadersOption,\n): headers is Record<string, string | null | undefined> =>\n !Array.isArray(headers) &&\n Object.prototype.toString.call(headers) !== \"[object Headers]\";\n\n/**\n * Layer header sets left to right into one plain record. Names are compared\n * case-insensitively and come back lowercased; a later layer wins, so a\n * caller's `content-type` replaces a default `Content-Type` instead of fetch\n * sending both values joined. `Headers` validates every value it sets.\n */\nexport function mergeHeaders(\n ...layers: (HeadersOption | undefined)[]\n): Record<string, string> {\n const merged = new Headers();\n for (const layer of layers) {\n if (!layer) continue;\n if (isPlainRecord(layer)) {\n for (const [name, value] of Object.entries(layer)) {\n if (value == null) merged.delete(name);\n else merged.set(name, value);\n }\n } else {\n for (const [name, value] of new Headers(layer)) merged.set(name, value);\n }\n }\n return Object.fromEntries(merged);\n}\n\n/** Normalize any header shape into a plain record with lowercased names. */\nexport function toHeaderRecord(\n headers: HeadersOption | undefined,\n): Record<string, string> {\n return mergeHeaders(headers);\n}\n","import { isArthronError } from \"../error/arthron-error\";\nimport { toHeaderRecord } from \"../request/headers\";\nimport type {\n ErrorInterceptor,\n Middleware,\n Plugin,\n RequestInterceptor,\n ResponseInterceptor,\n} from \"../types/plugin.types\";\nimport type { RequestContext } from \"../types/request.types\";\n\n/** The mutable body kinds an interceptor can `append` to; anything else is shared as is. */\nfunction cloneBody(body: BodyInit): BodyInit {\n if (typeof FormData !== \"undefined\" && body instanceof FormData) {\n const copy = new FormData();\n for (const [name, value] of body) copy.append(name, value);\n return copy;\n }\n if (body instanceof URLSearchParams) return new URLSearchParams(body);\n return body;\n}\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 of the headers and of a FormData/URLSearchParams body.\n */\nfunction cloneContext({\n url,\n init,\n meta,\n fetch,\n}: RequestContext): RequestContext {\n const copy: RequestContext[\"init\"] = {\n ...init,\n headers: toHeaderRecord(init.headers),\n };\n if (init.body) copy.body = cloneBody(init.body);\n const out: RequestContext = { url, init: copy, meta: { ...meta } };\n if (fetch) out.fetch = fetch;\n return out;\n}\n\n/**\n * Put headers back into a plain lowercase record. A plugin written in JS (or\n * with a cast) may hand back a `Headers` object, and the next plugin's\n * `{ ...ctx.init.headers }` would otherwise drop every header.\n */\nfunction normalize(\n ctx: RequestContext,\n previous: RequestContext,\n): RequestContext {\n const out: RequestContext = {\n ...ctx,\n init: { ...ctx.init, headers: toHeaderRecord(ctx.init.headers) },\n // A plugin that builds a fresh `{ url, init }` must not lose the call's\n // meta, nor the fetch it is sent with.\n meta: ctx.meta ?? previous.meta,\n };\n const fetch = ctx.fetch ?? previous.fetch;\n if (fetch) out.fetch = fetch;\n return out;\n}\n\n/**\n * A plugin written in JS (or with a cast) that forgets to return would hand\n * `undefined` on — and an error interceptor's would be what the caller\n * catches. Name the plugin slot instead.\n */\nfunction returned<T>(\n value: T | null | undefined,\n who: string,\n what: string,\n cause?: unknown,\n verb = \"returned\",\n): T {\n if (value != null) return value;\n throw new TypeError(\n `arthron: ${who} ${verb} ${value} — return ${what}`,\n cause === undefined ? undefined : { cause },\n );\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 for (const interceptor of request) {\n const next = returned(\n await interceptor(current),\n \"a request interceptor\",\n \"the context (ctx or a changed copy)\",\n );\n current = normalize(next, current);\n }\n let res: Response;\n try {\n res = await send(current);\n } catch (thrown) {\n // Only send's failures are this request's: an ArthronError a plugin\n // throws (its own call failed) is a plugin exception like any other.\n if (!isArthronError(thrown)) throw thrown;\n let err = thrown;\n for (const interceptor of error) {\n err = returned(\n await interceptor(err, current),\n \"an error interceptor\",\n \"the error (err or a changed one)\",\n thrown,\n );\n }\n throw err;\n }\n for (const interceptor of response) {\n res = returned(\n await interceptor(res, current),\n \"a response interceptor\",\n \"the Response (res or a new one)\",\n );\n }\n return res;\n };\n\n const dispatch = async (\n index: number,\n input: RequestContext,\n ): Promise<Response> => {\n const middleware = middlewares[index];\n if (!middleware) return attempt(input);\n // A middleware that builds a fresh `{ url, init }` keeps the call's meta\n // and fetch.\n const res = await middleware(input, (next) => {\n const ctx: RequestContext = { ...next, meta: next.meta ?? input.meta };\n const fetch = next.fetch ?? input.fetch;\n if (fetch) ctx.fetch = fetch;\n return dispatch(index + 1, ctx);\n });\n return returned(\n res,\n \"a middleware\",\n \"the Response, usually `return next(ctx)`\",\n undefined,\n \"resolved with\",\n );\n };\n return dispatch(0, ctx);\n}\n","import type {\n InferOutput,\n SchemaIssue,\n StandardSchemaV1,\n} from \"../types/response.types\";\n\nconst pathOf = (issue: SchemaIssue): string =>\n (issue.path ?? [])\n .map((part) =>\n String(typeof part === \"object\" && part !== null ? part.key : part),\n )\n .join(\".\");\n\n/**\n * `.json(schema)` got a body that does not match. Not an ArthronError: the\n * request succeeded — the data is what is wrong.\n */\nexport class SchemaError extends Error {\n override readonly name = \"SchemaError\";\n /** Every problem the validator reported, with the path to it. */\n readonly issues: readonly SchemaIssue[];\n\n constructor(issues: readonly SchemaIssue[]) {\n const [first] = issues;\n const where = first ? pathOf(first) : \"\";\n const more = issues.length > 1 ? ` (+${issues.length - 1} more)` : \"\";\n super(\n `arthron: the response body does not match the schema — ${where ? `${where}: ` : \"\"}${first?.message ?? \"invalid\"}${more}. See error.issues`,\n );\n this.issues = issues;\n }\n}\n\n/** Validate `data`; resolve with the schema's output or reject with a SchemaError. */\nexport async function validate<S extends StandardSchemaV1>(\n schema: S,\n data: unknown,\n): Promise<InferOutput<S>> {\n const result = await schema[\"~standard\"].validate(data);\n if (result.issues) throw new SchemaError(result.issues);\n return result.value as InferOutput<S>;\n}\n","import type {\n NdjsonOptions,\n SseEvent,\n SseOptions,\n} from \"../types/response.types\";\n\nconst DEFAULT_LIMIT = 8 * 1024 * 1024;\n\n/**\n * Yield the body line by line, reassembling lines split across chunks. Only\n * the newly arrived text is scanned, so a long line costs linear time; a line\n * past `maxLineLength` stops the stream instead of growing without bound.\n */\nasync function* lines(\n response: Response,\n maxLineLength: number,\n): AsyncGenerator<string> {\n if (!response.body) return;\n const checked = (line: string): string => {\n if (line.length <= maxLineLength) return line;\n throw new RangeError(\n `arthron: a stream line is longer than ${maxLineLength} characters — pass a larger maxLineLength to .sse() / .ndjson() if that is expected`,\n );\n };\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n const breaks = /\\r\\n|\\r|\\n/g;\n let pending = \"\";\n // The previous chunk ended in \"\\r\": a \"\\n\" opening this one belongs to it.\n let afterCR = false;\n try {\n while (true) {\n const { value, done } = await reader.read();\n let text = decoder.decode(value, { stream: !done });\n if (afterCR && text.startsWith(\"\\n\")) {\n text = text.slice(1);\n afterCR = false;\n }\n if (text) afterCR = text.endsWith(\"\\r\");\n let start = 0;\n breaks.lastIndex = 0;\n for (let m = breaks.exec(text); m; m = breaks.exec(text)) {\n yield checked(pending + text.slice(start, m.index));\n pending = \"\";\n start = breaks.lastIndex;\n }\n // Checked before the break arrives too, so a line without one cannot\n // grow without bound.\n pending = checked(pending + text.slice(start));\n if (done) break;\n }\n if (pending) yield pending;\n } finally {\n // Leaving early (break, return, a throw) must close the connection too —\n // releasing the lock alone leaves the body streaming.\n await reader.cancel().catch(() => {});\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` — or one the stream ends inside — is not dispatched.\n * Provider sentinels like\n * `[DONE]` are passed through untouched — they are not part of the spec.\n */\nexport async function* parseSse(\n response: Response,\n {\n maxLineLength = DEFAULT_LIMIT,\n maxEventSize = DEFAULT_LIMIT,\n }: SseOptions = {},\n): AsyncGenerator<SseEvent> {\n let data: string[] = [];\n let size = 0;\n let event: string | undefined;\n let id: string | undefined;\n // Stream state, not block state: neither resets when an event dispatches.\n let lastEventId = \"\";\n let retry: number | undefined;\n\n const flush = (): SseEvent | undefined => {\n const out: SseEvent | undefined =\n data.length === 0 ? undefined : { data: data.join(\"\\n\"), lastEventId };\n if (out && event !== undefined) out.event = event;\n if (out && id !== undefined) out.id = id;\n if (out && retry !== undefined) out.retry = retry;\n data = [];\n size = 0;\n event = id = undefined;\n return out;\n };\n\n for await (const line of lines(response, maxLineLength)) {\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 size += value.length + 1;\n if (size > maxEventSize + 1) {\n throw new RangeError(\n `arthron: an SSE event's data is longer than ${maxEventSize} characters — pass a larger maxEventSize to .sse() if that is expected`,\n );\n }\n data.push(value);\n break;\n case \"event\":\n event = value;\n break;\n case \"id\":\n if (!value.includes(\"\\u0000\")) id = lastEventId = value;\n break;\n case \"retry\":\n if (/^\\d+$/.test(value)) retry = Number(value);\n break;\n default:\n break; // unknown fields are ignored, per spec\n }\n }\n // A block the stream ended in before its blank line is incomplete — the\n // spec discards it rather than dispatch a possibly truncated payload.\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 { maxLineLength = DEFAULT_LIMIT }: NdjsonOptions = {},\n): AsyncGenerator<T> {\n for await (const line of lines(response, maxLineLength)) {\n if (line.trim() === \"\") continue;\n yield JSON.parse(line) as T;\n }\n}\n","import { cancellationError } from \"../error/create-error\";\nimport type {\n ArthronPromise,\n NdjsonOptions,\n SseOptions,\n StandardSchemaV1,\n} from \"../types/response.types\";\nimport { validate } from \"./schema-validation\";\nimport { parseNdjson, parseSse } from \"./stream-parser\";\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 signal: AbortSignal | null | undefined,\n): AsyncIterable<T> {\n // Iteration may start long after the request fails. Mark the rejection\n // handled now; the first `next()` still rethrows it.\n promise.catch(() => {});\n return {\n async *[Symbol.asyncIterator]() {\n try {\n yield* parse(await promise);\n } catch (error) {\n throw cancellationError(error, signal);\n }\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(\n promise: Promise<Response>,\n signal?: AbortSignal | null,\n): ArthronPromise {\n // A body read the caller aborted rejects with their reason, whatever the\n // runtime's fetch reports.\n const read = <T>(body: (res: Response) => Promise<T>): Promise<T> =>\n promise.then(body).catch((error: unknown) => {\n throw cancellationError(error, signal);\n });\n return Object.assign(promise, {\n json: ((schema?: StandardSchemaV1) =>\n read(async (res) => {\n const data: unknown = await res.json();\n return schema ? validate(schema, data) : data;\n })) as ArthronPromise[\"json\"],\n text: () => read((res) => res.text()),\n blob: () => read((res) => res.blob()),\n arrayBuffer: () => read((res) => res.arrayBuffer()),\n formData: () => read((res) => res.formData()),\n sse: (options?: SseOptions) =>\n iterate(promise, (res) => parseSse(res, options), signal),\n ndjson: <T = unknown>(options?: NdjsonOptions) =>\n iterate(promise, (res) => parseNdjson<T>(res, options), signal),\n });\n}\n","import { toHeaderRecord } from \"../request/headers\";\nimport type { Plugin } from \"../types/plugin.types\";\nimport type { ArthronConfig, FetchDefaults, Resolved } from \"./client.types\";\n\n/**\n * Config keys that configure arthron itself; every other key is a fetch\n * default. Typed so that a new config key fails to compile until it is\n * listed here — otherwise it would be sent to fetch.\n */\nconst OWN_KEYS: Record<\n Exclude<keyof ArthronConfig, keyof FetchDefaults>,\n true\n> = {\n baseURL: true,\n allowAbsoluteUrls: true,\n headers: true,\n fetch: true,\n use: true,\n errorDetail: true,\n throwOn: true,\n};\n\n/** Fields that belong to one request, refused as client defaults. */\nconst PER_CALL_ONLY = [\"signal\", \"body\", \"method\"] as const;\n\n/**\n * Add plugins to a list. A named plugin takes the place of the one already\n * there with that name — same position, so a replaced `logging()` stays\n * outermost and a second `retry()` does not multiply attempts. Unnamed\n * plugins append.\n */\nexport function addPlugins(\n base: readonly Plugin[],\n added: readonly Plugin[],\n): Plugin[] {\n const out = [...base];\n for (const plugin of added) {\n const at =\n plugin.name === undefined\n ? -1\n : out.findIndex((p) => p.name === plugin.name);\n if (at === -1) out.push(plugin);\n else out[at] = plugin;\n }\n return out;\n}\n\n/** The fetch defaults in a config: every set key that is not arthron's own. */\nexport function fetchDefaults(config: Resolved): FetchDefaults {\n return Object.fromEntries(\n Object.entries(config).filter(\n ([key, value]) => value !== undefined && !(key in OWN_KEYS),\n ),\n ) as FetchDefaults;\n}\n\n/** Normalize once so `extend` can merge headers and add plugins cheaply. */\nexport function resolveConfig(config: ArthronConfig): Resolved {\n for (const key of PER_CALL_ONLY) {\n if ((config as Record<string, unknown>)[key] !== undefined) {\n throw new TypeError(\n `arthron: ${key} cannot be a client default, it belongs to one request — pass it per call (api.get(path, { signal }), api.post(path, body), api.request(path, { method }))`,\n );\n }\n }\n const { headers, use, ...rest } = config;\n return {\n ...rest,\n headers: toHeaderRecord(headers),\n use: addPlugins([], use ?? []),\n };\n}\n","import {\n cancellationError,\n isCancellation,\n networkError,\n responseError,\n} from \"../error/create-error\";\nimport { isReadableStream, runtimeStreamsRequestBody } from \"../request/body\";\nimport { redactUrl, redactUrlIn } from \"../request/url\";\nimport type { RequestContext } from \"../types/request.types\";\nimport type { Resolved } from \"./client.types\";\n\n/**\n * The global fetch rejects a malformed URL or method with a TypeError that\n * looks just like a network failure (status 0, retried). A bodiless `Request`\n * runs the same checks first, so those become argument errors instead. Only\n * for the global fetch: an injected one owns its URL rules (SvelteKit's\n * server-side `fetch` accepts relative URLs, for one).\n */\nfunction assertSendable(url: string, method: string): void {\n if (typeof Request !== \"function\") return;\n try {\n new Request(url, { method });\n } catch (cause) {\n // fetch's own message repeats the full URL, query included.\n const reason = redactUrlIn(\n cause instanceof Error ? cause.message : String(cause),\n url,\n );\n throw new TypeError(\n `arthron: cannot send ${method} ${redactUrl(url)} (${reason}) — use an absolute URL or a baseURL with a scheme (https://…), and a valid HTTP method`,\n { cause },\n );\n }\n}\n\n/**\n * A ReadableStream body the browser cannot stream is rejected before it is\n * sent: Firefox would send the text \"[object ReadableStream]\", older Safari\n * fails with a bare network error. Only for the global fetch, like\n * assertSendable.\n */\nfunction assertStreamable(url: string, method: string, body: unknown): void {\n if (!isReadableStream(body) || runtimeStreamsRequestBody()) return;\n throw new TypeError(\n `arthron: cannot send ${method} ${redactUrl(url)} with a ReadableStream body — this browser cannot stream a request body and would send it garbled or not at all; pass a Blob or ArrayBuffer instead`,\n );\n}\n\n/**\n * Non-ok responses the caller asked for are not failures: an opaque one\n * (`mode: \"no-cors\"`) and the 3xx of `redirect: \"manual\"`.\n */\nfunction failedByDefault(res: Response, init: RequestContext[\"init\"]): boolean {\n if (res.type === \"opaque\" || res.type === \"opaqueredirect\") return false;\n return !(init.redirect === \"manual\" && res.status >= 300 && res.status < 400);\n}\n\n/** The one place a failure becomes an ArthronError. Cancellation passes through. */\nexport async function send(\n resolved: Resolved,\n ctx: RequestContext,\n throwOn: ((res: Response) => boolean) | undefined,\n): Promise<Response> {\n // The attempt's own fetch, which a plugin may have replaced or wrapped.\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 = ctx.fetch ?? resolved.fetch ?? globalThis.fetch;\n // Here rather than before the pipeline, so it sees the URL the request\n // interceptors produced. Whenever the client relies on the runtime's fetch\n // — also when a plugin wrapped or swapped it for this attempt (upload\n // progress) — but not for an injected fetch, which owns its URL rules. A\n // TypeError is not an ArthronError: retry and error interceptors leave it\n // alone.\n if (resolved.fetch === undefined) {\n const method = ctx.init.method ?? \"GET\";\n assertSendable(ctx.url, method);\n assertStreamable(ctx.url, method, ctx.init.body);\n }\n const { signal } = ctx.init;\n let res: Response;\n try {\n res = await fetchImpl(ctx.url, ctx.init);\n } catch (thrown) {\n if (isCancellation(thrown, signal)) throw cancellationError(thrown, signal);\n throw networkError(thrown, ctx);\n }\n // Only a non-ok response can become an ArthronError; throwOn narrows which.\n if (res.ok) return res;\n if (!(throwOn ? throwOn(res) : failedByDefault(res, ctx.init))) return res;\n const error = await responseError(res, ctx, resolved.errorDetail);\n // Aborted while the error body was being read (timeout, caller): that is\n // a cancellation, not this status.\n if (signal?.aborted) throw signal.reason;\n throw error;\n}\n","import { runPipeline } from \"../pipeline/pipeline\";\nimport { isReadableStream, serializeBody } from \"../request/body\";\nimport { mergeHeaders } from \"../request/headers\";\nimport { fillPath, leavesBase, resolveUrl } from \"../request/url\";\nimport { toArthronPromise } from \"../response/arthron-promise\";\nimport type { RequestContext } from \"../types/request.types\";\nimport type { ArthronPromise } from \"../types/response.types\";\nimport type {\n Arthron,\n ArthronConfig,\n RequestInitWithBody,\n RequestOptions,\n} from \"./client.types\";\nimport { addPlugins, fetchDefaults, resolveConfig } from \"./resolve-config\";\nimport { send } from \"./send\";\n\nconst BODYLESS_METHODS = new Set([\"GET\", \"HEAD\"]);\n/** Sent uppercased whatever their case; fetch itself only normalizes six. */\nconst STANDARD_METHODS = new Set([\n \"GET\",\n \"HEAD\",\n \"POST\",\n \"PUT\",\n \"PATCH\",\n \"DELETE\",\n \"OPTIONS\",\n \"QUERY\",\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 initDefaults = fetchDefaults(resolved);\n\n const dispatch = (\n path: string,\n method: string,\n rawBody: unknown,\n {\n params,\n headers: callHeaders,\n meta,\n throwOn = resolved.throwOn,\n path: pathValues,\n ...init\n }: RequestOptions,\n ): ArthronPromise => {\n try {\n const upper = method.toUpperCase();\n const verb = STANDARD_METHODS.has(upper) ? upper : method;\n if (rawBody !== undefined && BODYLESS_METHODS.has(upper)) {\n // fetch rejects these with a TypeError that would otherwise be mistaken for a network failure and retried.\n throw new TypeError(\n `arthron: ${upper} requests cannot carry a body — send it as params (query) or switch to POST`,\n );\n }\n if (pathValues) path = fillPath(path, pathValues);\n const url = resolveUrl(\n resolved.baseURL,\n path,\n params,\n resolved.allowAbsoluteUrls,\n );\n // Config headers (credentials, typically) belong to baseURL's origin.\n // Another origin, reachable only with allowAbsoluteUrls, gets per-call\n // headers alone.\n const defaults = leavesBase(resolved.baseURL, path)\n ? undefined\n : resolved.headers;\n const { body, headers } = serializeBody(\n rawBody,\n mergeHeaders(defaults, callHeaders),\n );\n // Config fetch defaults sit underneath, so a per-call value wins.\n const requestInit: RequestContext[\"init\"] = {\n ...initDefaults,\n ...init,\n method: verb,\n headers,\n };\n if (body !== undefined) requestInit.body = body;\n // Without half-duplex, fetch rejects a streaming upload (Node, Chromium).\n if (isReadableStream(body))\n Object.assign(requestInit, { duplex: \"half\" });\n return toArthronPromise(\n runPipeline(\n resolved.use,\n {\n url,\n init: requestInit,\n meta: { ...meta },\n // Read per call: a global patched after the client was created\n // (msw's `server.listen()`, for one) is still honoured.\n fetch: resolved.fetch ?? globalThis.fetch,\n },\n (ctx) => send(resolved, ctx, throwOn),\n ),\n init.signal,\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: addPlugins(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 query: (path, body, init = {}) => dispatch(path, \"QUERY\", 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 without: (...names) =>\n arthron({\n ...resolved,\n use: resolved.use.filter(\n (p) => p.name === undefined || !names.includes(p.name),\n ),\n }),\n };\n}\n","import type {\n ErrorInterceptor,\n Middleware,\n Plugin,\n RequestInterceptor,\n ResponseInterceptor,\n} from \"../types/plugin.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 * as plugins from \"./plugins/index\";\n\nexport { arthron } from \"./client/client\";\nexport type {\n Arthron,\n ArthronConfig,\n HttpMethod,\n RequestInitWithBody,\n RequestOptions,\n} from \"./client/client.types\";\nexport { ArthronError, isArthronError } from \"./error/arthron-error\";\nexport {\n defineInterceptor,\n defineMiddleware,\n definePlugin,\n} from \"./pipeline/define-plugin\";\nexport { SchemaError } from \"./response/schema-validation\";\nexport type {\n ErrorInterceptor,\n Middleware,\n Plugin,\n RequestInterceptor,\n ResponseInterceptor,\n} from \"./types/plugin.types\";\nexport type {\n ArthronMeta,\n HeadersOption,\n QueryParams,\n RequestContext,\n RetryOptions,\n} from \"./types/request.types\";\nexport type {\n ArthronPromise,\n ErrorDetailResolver,\n InferOutput,\n NdjsonOptions,\n SchemaIssue,\n SseEvent,\n SseOptions,\n StandardSchemaV1,\n} from \"./types/response.types\";\n\n/*\n * The built-in plugins moved to \"arthron/plugins\". These keep 1.0 imports\n * working until the next major; they are the same functions.\n */\n\n/** @deprecated Import from \"arthron/plugins\"; this export goes in the next major. */\nexport const logging: typeof plugins.logging = plugins.logging;\n/** @deprecated Import from \"arthron/plugins\"; this export goes in the next major. */\nexport const retry: typeof plugins.retry = plugins.retry;\n/** @deprecated Import from \"arthron/plugins\"; this export goes in the next major. */\nexport const timeout: typeof plugins.timeout = plugins.timeout;\n/** @deprecated Import from \"arthron/plugins\"; this export goes in the next major. */\nexport type LogEvent = plugins.LogEvent;\n/** @deprecated Import from \"arthron/plugins\"; this export goes in the next major. */\nexport type LoggingOptions = plugins.LoggingOptions;\n/** @deprecated Import from \"arthron/plugins\"; this export goes in the next major. */\nexport type TimeoutOptions = plugins.TimeoutOptions;\n"],"mappings":";;;AAEA,MAAM,iBACJ,YAEA,CAAC,MAAM,QAAQ,OAAO,KACtB,OAAO,UAAU,SAAS,KAAK,OAAO,MAAM;;;;;;;AAQ9C,SAAgB,aACd,GAAG,QACqB;CACxB,MAAM,SAAS,IAAI,QAAQ;CAC3B,KAAK,MAAM,SAAS,QAAQ;EAC1B,IAAI,CAAC,OAAO;EACZ,IAAI,cAAc,KAAK,GACrB,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,KAAK,GAC9C,IAAI,SAAS,MAAM,OAAO,OAAO,IAAI;OAChC,OAAO,IAAI,MAAM,KAAK;OAG7B,KAAK,MAAM,CAAC,MAAM,UAAU,IAAI,QAAQ,KAAK,GAAG,OAAO,IAAI,MAAM,KAAK;CAE1E;CACA,OAAO,OAAO,YAAY,MAAM;AAClC;;AAGA,SAAgB,eACd,SACwB;CACxB,OAAO,aAAa,OAAO;AAC7B;;;;ACzBA,SAAS,UAAU,MAA0B;CAC3C,IAAI,OAAO,aAAa,eAAe,gBAAgB,UAAU;EAC/D,MAAM,OAAO,IAAI,SAAS;EAC1B,KAAK,MAAM,CAAC,MAAM,UAAU,MAAM,KAAK,OAAO,MAAM,KAAK;EACzD,OAAO;CACT;CACA,IAAI,gBAAgB,iBAAiB,OAAO,IAAI,gBAAgB,IAAI;CACpE,OAAO;AACT;;;;;;AAOA,SAAS,aAAa,EACpB,KACA,MACA,MACA,SACiC;CACjC,MAAM,OAA+B;EACnC,GAAG;EACH,SAAS,eAAe,KAAK,OAAO;CACtC;CACA,IAAI,KAAK,MAAM,KAAK,OAAO,UAAU,KAAK,IAAI;CAC9C,MAAM,MAAsB;EAAE;EAAK,MAAM;EAAM,MAAM,EAAE,GAAG,KAAK;CAAE;CACjE,IAAI,OAAO,IAAI,QAAQ;CACvB,OAAO;AACT;;;;;;AAOA,SAAS,UACP,KACA,UACgB;CAChB,MAAM,MAAsB;EAC1B,GAAG;EACH,MAAM;GAAE,GAAG,IAAI;GAAM,SAAS,eAAe,IAAI,KAAK,OAAO;EAAE;EAG/D,MAAM,IAAI,QAAQ,SAAS;CAC7B;CACA,MAAM,QAAQ,IAAI,SAAS,SAAS;CACpC,IAAI,OAAO,IAAI,QAAQ;CACvB,OAAO;AACT;;;;;;AAOA,SAAS,SACP,OACA,KACA,MACA,OACA,OAAO,YACJ;CACH,IAAI,SAAS,MAAM,OAAO;CAC1B,MAAM,IAAI,UACR,YAAY,IAAI,GAAG,KAAK,GAAG,MAAM,YAAY,QAC7C,UAAU,KAAA,IAAY,KAAA,IAAY,EAAE,MAAM,CAC5C;AACF;;;;;;;;;;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,KAAK,MAAM,eAAe,SAMxB,UAAU,UALG,SACX,MAAM,YAAY,OAAO,GACzB,yBACA,qCAEqB,GAAG,OAAO;EAEnC,IAAI;EACJ,IAAI;GACF,MAAM,MAAM,KAAK,OAAO;EAC1B,SAAS,QAAQ;GAGf,IAAI,CAACA,gBAAAA,eAAe,MAAM,GAAG,MAAM;GACnC,IAAI,MAAM;GACV,KAAK,MAAM,eAAe,OACxB,MAAM,SACJ,MAAM,YAAY,KAAK,OAAO,GAC9B,wBACA,oCACA,MACF;GAEF,MAAM;EACR;EACA,KAAK,MAAM,eAAe,UACxB,MAAM,SACJ,MAAM,YAAY,KAAK,OAAO,GAC9B,0BACA,iCACF;EAEF,OAAO;CACT;CAEA,MAAM,WAAW,OACf,OACA,UACsB;EACtB,MAAM,aAAa,YAAY;EAC/B,IAAI,CAAC,YAAY,OAAO,QAAQ,KAAK;EASrC,OAAO,SACL,MAPgB,WAAW,QAAQ,SAAS;GAC5C,MAAM,MAAsB;IAAE,GAAG;IAAM,MAAM,KAAK,QAAQ,MAAM;GAAK;GACrE,MAAM,QAAQ,KAAK,SAAS,MAAM;GAClC,IAAI,OAAO,IAAI,QAAQ;GACvB,OAAO,SAAS,QAAQ,GAAG,GAAG;EAChC,CAAC,GAGC,gBACA,4CACA,KAAA,GACA,eACF;CACF;CACA,OAAO,SAAS,GAAG,GAAG;AACxB;;;ACnKA,MAAM,UAAU,WACb,MAAM,QAAQ,CAAC,EAAA,CACb,KAAK,SACJ,OAAO,OAAO,SAAS,YAAY,SAAS,OAAO,KAAK,MAAM,IAAI,CACpE,CAAC,CACA,KAAK,GAAG;;;;;AAMb,IAAa,cAAb,cAAiC,MAAM;CACrC,OAAyB;;CAEzB;CAEA,YAAY,QAAgC;EAC1C,MAAM,CAAC,SAAS;EAChB,MAAM,QAAQ,QAAQ,OAAO,KAAK,IAAI;EACtC,MAAM,OAAO,OAAO,SAAS,IAAI,MAAM,OAAO,SAAS,EAAE,UAAU;EACnE,MACE,0DAA0D,QAAQ,GAAG,MAAM,MAAM,KAAK,OAAO,WAAW,YAAY,KAAK,mBAC3H;EACA,KAAK,SAAS;CAChB;AACF;;AAGA,eAAsB,SACpB,QACA,MACyB;CACzB,MAAM,SAAS,MAAM,OAAO,YAAY,CAAC,SAAS,IAAI;CACtD,IAAI,OAAO,QAAQ,MAAM,IAAI,YAAY,OAAO,MAAM;CACtD,OAAO,OAAO;AAChB;;;ACnCA,MAAM,gBAAgB;;;;;;AAOtB,gBAAgB,MACd,UACA,eACwB;CACxB,IAAI,CAAC,SAAS,MAAM;CACpB,MAAM,WAAW,SAAyB;EACxC,IAAI,KAAK,UAAU,eAAe,OAAO;EACzC,MAAM,IAAI,WACR,yCAAyC,cAAc,oFACzD;CACF;CACA,MAAM,SAAS,SAAS,KAAK,UAAU;CACvC,MAAM,UAAU,IAAI,YAAY;CAChC,MAAM,SAAS;CACf,IAAI,UAAU;CAEd,IAAI,UAAU;CACd,IAAI;EACF,OAAO,MAAM;GACX,MAAM,EAAE,OAAO,SAAS,MAAM,OAAO,KAAK;GAC1C,IAAI,OAAO,QAAQ,OAAO,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC;GAClD,IAAI,WAAW,KAAK,WAAW,IAAI,GAAG;IACpC,OAAO,KAAK,MAAM,CAAC;IACnB,UAAU;GACZ;GACA,IAAI,MAAM,UAAU,KAAK,SAAS,IAAI;GACtC,IAAI,QAAQ;GACZ,OAAO,YAAY;GACnB,KAAK,IAAI,IAAI,OAAO,KAAK,IAAI,GAAG,GAAG,IAAI,OAAO,KAAK,IAAI,GAAG;IACxD,MAAM,QAAQ,UAAU,KAAK,MAAM,OAAO,EAAE,KAAK,CAAC;IAClD,UAAU;IACV,QAAQ,OAAO;GACjB;GAGA,UAAU,QAAQ,UAAU,KAAK,MAAM,KAAK,CAAC;GAC7C,IAAI,MAAM;EACZ;EACA,IAAI,SAAS,MAAM;CACrB,UAAU;EAGR,MAAM,OAAO,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC;EACpC,OAAO,YAAY;CACrB;AACF;;;;;;;;;AAUA,gBAAuB,SACrB,UACA,EACE,gBAAgB,eAChB,eAAe,kBACD,CAAC,GACS;CAC1B,IAAI,OAAiB,CAAC;CACtB,IAAI,OAAO;CACX,IAAI;CACJ,IAAI;CAEJ,IAAI,cAAc;CAClB,IAAI;CAEJ,MAAM,cAAoC;EACxC,MAAM,MACJ,KAAK,WAAW,IAAI,KAAA,IAAY;GAAE,MAAM,KAAK,KAAK,IAAI;GAAG;EAAY;EACvE,IAAI,OAAO,UAAU,KAAA,GAAW,IAAI,QAAQ;EAC5C,IAAI,OAAO,OAAO,KAAA,GAAW,IAAI,KAAK;EACtC,IAAI,OAAO,UAAU,KAAA,GAAW,IAAI,QAAQ;EAC5C,OAAO,CAAC;EACR,OAAO;EACP,QAAQ,KAAK,KAAA;EACb,OAAO;CACT;CAEA,WAAW,MAAM,QAAQ,MAAM,UAAU,aAAa,GAAG;EACvD,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,QAAQ,MAAM,SAAS;IACvB,IAAI,OAAO,eAAe,GACxB,MAAM,IAAI,WACR,+CAA+C,aAAa,uEAC9D;IAEF,KAAK,KAAK,KAAK;IACf;GACF,KAAK;IACH,QAAQ;IACR;GACF,KAAK;IACH,IAAI,CAAC,MAAM,SAAS,IAAQ,GAAG,KAAK,cAAc;IAClD;GACF,KAAK,SACH,IAAI,QAAQ,KAAK,KAAK,GAAG,QAAQ,OAAO,KAAK;EAIjD;CACF;AAGF;;;;;AAMA,gBAAuB,YACrB,UACA,EAAE,gBAAgB,kBAAiC,CAAC,GACjC;CACnB,WAAW,MAAM,QAAQ,MAAM,UAAU,aAAa,GAAG;EACvD,IAAI,KAAK,KAAK,MAAM,IAAI;EACxB,MAAM,KAAK,MAAM,IAAI;CACvB;AACF;;;;ACtIA,SAAS,QACP,SACA,OACA,QACkB;CAGlB,QAAQ,YAAY,CAAC,CAAC;CACtB,OAAO,EACL,QAAQ,OAAO,iBAAiB;EAC9B,IAAI;GACF,OAAO,MAAM,MAAM,OAAO;EAC5B,SAAS,OAAO;GACd,MAAMC,gBAAAA,kBAAkB,OAAO,MAAM;EACvC;CACF,EACF;AACF;;;;;AAMA,SAAgB,iBACd,SACA,QACgB;CAGhB,MAAM,QAAW,SACf,QAAQ,KAAK,IAAI,CAAC,CAAC,OAAO,UAAmB;EAC3C,MAAMA,gBAAAA,kBAAkB,OAAO,MAAM;CACvC,CAAC;CACH,OAAO,OAAO,OAAO,SAAS;EAC5B,QAAQ,WACN,KAAK,OAAO,QAAQ;GAClB,MAAM,OAAgB,MAAM,IAAI,KAAK;GACrC,OAAO,SAAS,SAAS,QAAQ,IAAI,IAAI;EAC3C,CAAC;EACH,YAAY,MAAM,QAAQ,IAAI,KAAK,CAAC;EACpC,YAAY,MAAM,QAAQ,IAAI,KAAK,CAAC;EACpC,mBAAmB,MAAM,QAAQ,IAAI,YAAY,CAAC;EAClD,gBAAgB,MAAM,QAAQ,IAAI,SAAS,CAAC;EAC5C,MAAM,YACJ,QAAQ,UAAU,QAAQ,SAAS,KAAK,OAAO,GAAG,MAAM;EAC1D,SAAsB,YACpB,QAAQ,UAAU,QAAQ,YAAe,KAAK,OAAO,GAAG,MAAM;CAClE,CAAC;AACH;;;;;;;;AClDA,MAAM,WAGF;CACF,SAAS;CACT,mBAAmB;CACnB,SAAS;CACT,OAAO;CACP,KAAK;CACL,aAAa;CACb,SAAS;AACX;;AAGA,MAAM,gBAAgB;CAAC;CAAU;CAAQ;AAAQ;;;;;;;AAQjD,SAAgB,WACd,MACA,OACU;CACV,MAAM,MAAM,CAAC,GAAG,IAAI;CACpB,KAAK,MAAM,UAAU,OAAO;EAC1B,MAAM,KACJ,OAAO,SAAS,KAAA,IACZ,KACA,IAAI,WAAW,MAAM,EAAE,SAAS,OAAO,IAAI;EACjD,IAAI,OAAO,IAAI,IAAI,KAAK,MAAM;OACzB,IAAI,MAAM;CACjB;CACA,OAAO;AACT;;AAGA,SAAgB,cAAc,QAAiC;CAC7D,OAAO,OAAO,YACZ,OAAO,QAAQ,MAAM,CAAC,CAAC,QACpB,CAAC,KAAK,WAAW,UAAU,KAAA,KAAa,EAAE,OAAO,SACpD,CACF;AACF;;AAGA,SAAgB,cAAc,QAAiC;CAC7D,KAAK,MAAM,OAAO,eAChB,IAAK,OAAmC,SAAS,KAAA,GAC/C,MAAM,IAAI,UACR,YAAY,IAAI,2JAClB;CAGJ,MAAM,EAAE,SAAS,KAAK,GAAG,SAAS;CAClC,OAAO;EACL,GAAG;EACH,SAAS,eAAe,OAAO;EAC/B,KAAK,WAAW,CAAC,GAAG,OAAO,CAAC,CAAC;CAC/B;AACF;;;;;;;;;;ACrDA,SAAS,eAAe,KAAa,QAAsB;CACzD,IAAI,OAAO,YAAY,YAAY;CACnC,IAAI;EACF,IAAI,QAAQ,KAAK,EAAE,OAAO,CAAC;CAC7B,SAAS,OAAO;EAEd,MAAM,SAASC,gBAAAA,YACb,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GACrD,GACF;EACA,MAAM,IAAI,UACR,wBAAwB,OAAO,GAAGC,gBAAAA,UAAU,GAAG,EAAE,IAAI,OAAO,0FAC5D,EAAE,MAAM,CACV;CACF;AACF;;;;;;;AAQA,SAAS,iBAAiB,KAAa,QAAgB,MAAqB;CAC1E,IAAI,CAACC,gBAAAA,iBAAiB,IAAI,KAAKC,gBAAAA,0BAA0B,GAAG;CAC5D,MAAM,IAAI,UACR,wBAAwB,OAAO,GAAGF,gBAAAA,UAAU,GAAG,EAAE,oJACnD;AACF;;;;;AAMA,SAAS,gBAAgB,KAAe,MAAuC;CAC7E,IAAI,IAAI,SAAS,YAAY,IAAI,SAAS,kBAAkB,OAAO;CACnE,OAAO,EAAE,KAAK,aAAa,YAAY,IAAI,UAAU,OAAO,IAAI,SAAS;AAC3E;;AAGA,eAAsB,KACpB,UACA,KACA,SACmB;CAInB,MAAM,YAAY,IAAI,SAAS,SAAS,SAAS,WAAW;CAO5D,IAAI,SAAS,UAAU,KAAA,GAAW;EAChC,MAAM,SAAS,IAAI,KAAK,UAAU;EAClC,eAAe,IAAI,KAAK,MAAM;EAC9B,iBAAiB,IAAI,KAAK,QAAQ,IAAI,KAAK,IAAI;CACjD;CACA,MAAM,EAAE,WAAW,IAAI;CACvB,IAAI;CACJ,IAAI;EACF,MAAM,MAAM,UAAU,IAAI,KAAK,IAAI,IAAI;CACzC,SAAS,QAAQ;EACf,IAAIG,gBAAAA,eAAe,QAAQ,MAAM,GAAG,MAAMC,gBAAAA,kBAAkB,QAAQ,MAAM;EAC1E,MAAMC,gBAAAA,aAAa,QAAQ,GAAG;CAChC;CAEA,IAAI,IAAI,IAAI,OAAO;CACnB,IAAI,EAAE,UAAU,QAAQ,GAAG,IAAI,gBAAgB,KAAK,IAAI,IAAI,IAAI,OAAO;CACvE,MAAM,QAAQ,MAAMC,gBAAAA,cAAc,KAAK,KAAK,SAAS,WAAW;CAGhE,IAAI,QAAQ,SAAS,MAAM,OAAO;CAClC,MAAM;AACR;;;AC9EA,MAAM,mCAAmB,IAAI,IAAI,CAAC,OAAO,MAAM,CAAC;;AAEhD,MAAM,mCAAmB,IAAI,IAAI;CAC/B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;;;;AAMD,SAAgB,QAAQ,SAAwB,CAAC,GAAY;CAC3D,MAAM,WAAW,cAAc,MAAM;CACrC,MAAM,eAAe,cAAc,QAAQ;CAE3C,MAAM,YACJ,MACA,QACA,SACA,EACE,QACA,SAAS,aACT,MACA,UAAU,SAAS,SACnB,MAAM,YACN,GAAG,WAEc;EACnB,IAAI;GACF,MAAM,QAAQ,OAAO,YAAY;GACjC,MAAM,OAAO,iBAAiB,IAAI,KAAK,IAAI,QAAQ;GACnD,IAAI,YAAY,KAAA,KAAa,iBAAiB,IAAI,KAAK,GAErD,MAAM,IAAI,UACR,YAAY,MAAM,4EACpB;GAEF,IAAI,YAAY,OAAOC,gBAAAA,SAAS,MAAM,UAAU;GAChD,MAAM,MAAMC,gBAAAA,WACV,SAAS,SACT,MACA,QACA,SAAS,iBACX;GAIA,MAAM,WAAWC,gBAAAA,WAAW,SAAS,SAAS,IAAI,IAC9C,KAAA,IACA,SAAS;GACb,MAAM,EAAE,MAAM,YAAYC,gBAAAA,cACxB,SACA,aAAa,UAAU,WAAW,CACpC;GAEA,MAAM,cAAsC;IAC1C,GAAG;IACH,GAAG;IACH,QAAQ;IACR;GACF;GACA,IAAI,SAAS,KAAA,GAAW,YAAY,OAAO;GAE3C,IAAIC,gBAAAA,iBAAiB,IAAI,GACvB,OAAO,OAAO,aAAa,EAAE,QAAQ,OAAO,CAAC;GAC/C,OAAO,iBACL,YACE,SAAS,KACT;IACE;IACA,MAAM;IACN,MAAM,EAAE,GAAG,KAAK;IAGhB,OAAO,SAAS,SAAS,WAAW;GACtC,IACC,QAAQ,KAAK,UAAU,KAAK,OAAO,CACtC,GACA,KAAK,MACP;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,WAAW,SAAS,KAAK,KAAK,OAAO,CAAC,CAAC;EAE1B,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,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;EAC7B,UAAU,GAAG,UACX,QAAQ;GACN,GAAG;GACH,KAAK,SAAS,IAAI,QACf,MAAM,EAAE,SAAS,KAAA,KAAa,CAAC,MAAM,SAAS,EAAE,IAAI,CACvD;EACF,CAAC;CACL;AACF;;;;ACvIA,MAAa,oBAAoB,eAC/B;;AAGF,MAAa,oBAAoB;CAC/B,UAAU,gBAAwD;CAClE,WAAW,gBACT;CACF,QAAQ,gBAAoD;AAC9D;;AAGA,MAAa,gBAAgB,WAA2B;;;;ACsBxD,MAAa,UAAA,gBAAA;;AAEb,MAAa,QAAA,gBAAA;;AAEb,MAAa,UAAA,gBAAA"}
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { _ as HeadersOption, a as LogEvent$1, b as RetryOptions, d as Plugin, f as RequestInterceptor, g as ArthronMeta, h as isArthronError, i as logging$1, l as ErrorInterceptor, m as ArthronError, n as TimeoutOptions$1, o as LoggingOptions$1, p as ResponseInterceptor, r as retry$1, t as timeout$1, u as Middleware, v as QueryParams, y as RequestContext } from "./index-Bz1P80JN.cjs";
1
+ import { _ as ArthronMeta, a as logging$1, b as RequestContext, d as Middleware, f as Plugin, g as isArthronError, h as ArthronError, i as retry$1, m as ResponseInterceptor, n as timeout$1, o as LogEvent$1, p as RequestInterceptor, r as TimeoutOptions$1, s as LoggingOptions$1, u as ErrorInterceptor, v as HeadersOption, x as RetryOptions, y as QueryParams } from "./index-C3lylSNd.cjs";
2
2
  //#region src/types/response.types.d.ts
3
3
  interface ArthronPromise extends Promise<Response> {
4
4
  /** The body as JSON. `T` is a cast — nothing checks it. */
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { _ as HeadersOption, a as LogEvent$1, b as RetryOptions, d as Plugin, f as RequestInterceptor, g as ArthronMeta, h as isArthronError, i as logging$1, l as ErrorInterceptor, m as ArthronError, n as TimeoutOptions$1, o as LoggingOptions$1, p as ResponseInterceptor, r as retry$1, t as timeout$1, u as Middleware, v as QueryParams, y as RequestContext } from "./index-Bz1P80JN.mjs";
1
+ import { _ as ArthronMeta, a as logging$1, b as RequestContext, d as Middleware, f as Plugin, g as isArthronError, h as ArthronError, i as retry$1, m as ResponseInterceptor, n as timeout$1, o as LogEvent$1, p as RequestInterceptor, r as TimeoutOptions$1, s as LoggingOptions$1, u as ErrorInterceptor, v as HeadersOption, x as RetryOptions, y as QueryParams } from "./index-C3lylSNd.mjs";
2
2
  //#region src/types/response.types.d.ts
3
3
  interface ArthronPromise extends Promise<Response> {
4
4
  /** The body as JSON. `T` is a cast — nothing checks it. */
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { _ as redactUrlIn, a as isCancellation, d as serializeBody, f as ArthronError, g as redactUrl, h as leavesBase, i as cancellationError, l as isReadableStream, m as fillPath, n as retry$1, o as networkError, p as isArthronError, r as logging$1, s as responseError, t as timeout$1, u as runtimeStreamsRequestBody, v as resolveUrl } from "./plugins-ChNZ2Q8t.mjs";
1
+ import { _ as redactUrl, a as cancellationError, c as responseError, d as runtimeStreamsRequestBody, f as serializeBody, g as leavesBase, h as fillPath, i as logging$1, m as isArthronError, n as timeout$1, o as isCancellation, p as ArthronError, r as retry$1, s as networkError, u as isReadableStream, v as redactUrlIn, y as resolveUrl } from "./plugins-CHCMxtAE.mjs";
2
2
  //#region src/request/headers.ts
3
3
  const isPlainRecord = (headers) => !Array.isArray(headers) && Object.prototype.toString.call(headers) !== "[object Headers]";
4
4
  /**
@@ -38,17 +38,19 @@ function cloneBody(body) {
38
38
  * again (retry) must not see the previous attempt's changes, so every attempt
39
39
  * starts from a fresh copy of the headers and of a FormData/URLSearchParams body.
40
40
  */
41
- function cloneContext({ url, init, meta }) {
41
+ function cloneContext({ url, init, meta, fetch }) {
42
42
  const copy = {
43
43
  ...init,
44
44
  headers: toHeaderRecord(init.headers)
45
45
  };
46
46
  if (init.body) copy.body = cloneBody(init.body);
47
- return {
47
+ const out = {
48
48
  url,
49
49
  init: copy,
50
50
  meta: { ...meta }
51
51
  };
52
+ if (fetch) out.fetch = fetch;
53
+ return out;
52
54
  }
53
55
  /**
54
56
  * Put headers back into a plain lowercase record. A plugin written in JS (or
@@ -56,7 +58,7 @@ function cloneContext({ url, init, meta }) {
56
58
  * `{ ...ctx.init.headers }` would otherwise drop every header.
57
59
  */
58
60
  function normalize(ctx, previous) {
59
- return {
61
+ const out = {
60
62
  ...ctx,
61
63
  init: {
62
64
  ...ctx.init,
@@ -64,6 +66,9 @@ function normalize(ctx, previous) {
64
66
  },
65
67
  meta: ctx.meta ?? previous.meta
66
68
  };
69
+ const fetch = ctx.fetch ?? previous.fetch;
70
+ if (fetch) out.fetch = fetch;
71
+ return out;
67
72
  }
68
73
  /**
69
74
  * A plugin written in JS (or with a cast) that forgets to return would hand
@@ -112,10 +117,15 @@ async function runPipeline(plugins, ctx, send) {
112
117
  const dispatch = async (index, input) => {
113
118
  const middleware = middlewares[index];
114
119
  if (!middleware) return attempt(input);
115
- return returned(await middleware(input, (next) => dispatch(index + 1, {
116
- ...next,
117
- meta: next.meta ?? input.meta
118
- })), "a middleware", "the Response, usually `return next(ctx)`", void 0, "resolved with");
120
+ return returned(await middleware(input, (next) => {
121
+ const ctx = {
122
+ ...next,
123
+ meta: next.meta ?? input.meta
124
+ };
125
+ const fetch = next.fetch ?? input.fetch;
126
+ if (fetch) ctx.fetch = fetch;
127
+ return dispatch(index + 1, ctx);
128
+ }), "a middleware", "the Response, usually `return next(ctx)`", void 0, "resolved with");
119
129
  };
120
130
  return dispatch(0, ctx);
121
131
  }
@@ -376,7 +386,7 @@ function failedByDefault(res, init) {
376
386
  }
377
387
  /** The one place a failure becomes an ArthronError. Cancellation passes through. */
378
388
  async function send(resolved, ctx, throwOn) {
379
- const fetchImpl = resolved.fetch ?? globalThis.fetch;
389
+ const fetchImpl = ctx.fetch ?? resolved.fetch ?? globalThis.fetch;
380
390
  if (resolved.fetch === void 0) {
381
391
  const method = ctx.init.method ?? "GET";
382
392
  assertSendable(ctx.url, method);
@@ -437,7 +447,8 @@ function arthron(config = {}) {
437
447
  return toArthronPromise(runPipeline(resolved.use, {
438
448
  url,
439
449
  init: requestInit,
440
- meta: { ...meta }
450
+ meta: { ...meta },
451
+ fetch: resolved.fetch ?? globalThis.fetch
441
452
  }, (ctx) => send(resolved, ctx, throwOn)), init.signal);
442
453
  } catch (thrown) {
443
454
  return toArthronPromise(Promise.reject(thrown));