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 +27 -2
- package/dist/{index-Bz1P80JN.d.cts → index-C3lylSNd.d.cts} +44 -4
- package/dist/{index-Bz1P80JN.d.mts → index-C3lylSNd.d.mts} +44 -4
- package/dist/index.cjs +21 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +21 -10
- package/dist/index.mjs.map +1 -1
- package/dist/{plugins-ChNZ2Q8t.mjs → plugins-CHCMxtAE.mjs} +419 -10
- package/dist/plugins-CHCMxtAE.mjs.map +1 -0
- package/dist/{plugins-CVrzb8x6.cjs → plugins-Cigqz2SS.cjs} +424 -9
- package/dist/plugins-Cigqz2SS.cjs.map +1 -0
- package/dist/plugins.cjs +2 -1
- package/dist/plugins.d.cts +2 -2
- package/dist/plugins.d.mts +2 -2
- package/dist/plugins.mjs +1 -1
- package/llms.txt +6 -5
- package/package.json +1 -1
- package/dist/plugins-CVrzb8x6.cjs.map +0 -1
- package/dist/plugins-ChNZ2Q8t.mjs.map +0 -1
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["plugins.logging","plugins.retry","plugins.timeout"],"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.mjs","names":["plugins.logging","plugins.retry","plugins.timeout"],"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,CAAC,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,MAAM,kBAAkB,OAAO,MAAM;EACvC;CACF,EACF;AACF;;;;;AAMA,SAAgB,iBACd,SACA,QACgB;CAGhB,MAAM,QAAW,SACf,QAAQ,KAAK,IAAI,CAAC,CAAC,OAAO,UAAmB;EAC3C,MAAM,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,SAAS,YACb,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GACrD,GACF;EACA,MAAM,IAAI,UACR,wBAAwB,OAAO,GAAG,UAAU,GAAG,EAAE,IAAI,OAAO,0FAC5D,EAAE,MAAM,CACV;CACF;AACF;;;;;;;AAQA,SAAS,iBAAiB,KAAa,QAAgB,MAAqB;CAC1E,IAAI,CAAC,iBAAiB,IAAI,KAAK,0BAA0B,GAAG;CAC5D,MAAM,IAAI,UACR,wBAAwB,OAAO,GAAG,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,IAAI,eAAe,QAAQ,MAAM,GAAG,MAAM,kBAAkB,QAAQ,MAAM;EAC1E,MAAM,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,MAAM,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,OAAO,SAAS,MAAM,UAAU;GAChD,MAAM,MAAM,WACV,SAAS,SACT,MACA,QACA,SAAS,iBACX;GAIA,MAAM,WAAW,WAAW,SAAS,SAAS,IAAI,IAC9C,KAAA,IACA,SAAS;GACb,MAAM,EAAE,MAAM,YAAY,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,IAAI,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,UAAkCA;;AAE/C,MAAa,QAA8BC;;AAE3C,MAAa,UAAkCC"}
|
|
@@ -351,20 +351,26 @@ function bearerAuth(options) {
|
|
|
351
351
|
* redirects keep working.
|
|
352
352
|
*/
|
|
353
353
|
function rebuildResponse(original, body) {
|
|
354
|
-
|
|
354
|
+
const rebuilt = new Response(body, {
|
|
355
355
|
status: original.status,
|
|
356
356
|
statusText: original.statusText,
|
|
357
357
|
headers: original.headers
|
|
358
|
-
})
|
|
358
|
+
});
|
|
359
|
+
const { url, redirected, type } = original;
|
|
360
|
+
return withFields(rebuilt, {
|
|
361
|
+
url,
|
|
362
|
+
redirected,
|
|
363
|
+
type
|
|
364
|
+
});
|
|
359
365
|
}
|
|
360
|
-
/**
|
|
361
|
-
function
|
|
366
|
+
/** Set those fields on `target` — and on its `clone()`s as well. */
|
|
367
|
+
function withFields(target, fields) {
|
|
362
368
|
const clone = target.clone.bind(target);
|
|
363
369
|
return Object.defineProperties(target, {
|
|
364
|
-
url: { value:
|
|
365
|
-
redirected: { value:
|
|
366
|
-
type: { value:
|
|
367
|
-
clone: { value: () =>
|
|
370
|
+
url: { value: fields.url },
|
|
371
|
+
redirected: { value: fields.redirected },
|
|
372
|
+
type: { value: fields.type },
|
|
373
|
+
clone: { value: () => withFields(clone(), fields) }
|
|
368
374
|
});
|
|
369
375
|
}
|
|
370
376
|
//#endregion
|
|
@@ -752,6 +758,409 @@ function timeout(first, second = {}) {
|
|
|
752
758
|
};
|
|
753
759
|
}
|
|
754
760
|
//#endregion
|
|
755
|
-
|
|
761
|
+
//#region src/plugins/upload-progress/form-data.ts
|
|
762
|
+
const encoder = new TextEncoder();
|
|
763
|
+
/** Names and filenames inside the quoted Content-Disposition parameters. */
|
|
764
|
+
const escapeName = (name) => name.replace(/\n/g, "%0A").replace(/\r/g, "%0D").replace(/"/g, "%22");
|
|
765
|
+
/** String values go out with CRLF line breaks, as the runtime's encoder sends them. */
|
|
766
|
+
const normalizeBreaks = (value) => value.replace(/\r\n|\r|\n/g, "\r\n");
|
|
767
|
+
function boundary() {
|
|
768
|
+
const bytes = crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(12));
|
|
769
|
+
return `----arthron${Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("")}`;
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* A FormData body as multipart/form-data, with its length known before
|
|
773
|
+
* anything is read: the parts' headers and string values are encoded up
|
|
774
|
+
* front, files are only counted by size and streamed while the body is
|
|
775
|
+
* sent — a large file is not held in memory to learn the length.
|
|
776
|
+
*/
|
|
777
|
+
function encodeFormData(form) {
|
|
778
|
+
const b = boundary();
|
|
779
|
+
const parts = [];
|
|
780
|
+
for (const [name, value] of form) {
|
|
781
|
+
let head = `--${b}\r\nContent-Disposition: form-data; name="${escapeName(name)}"`;
|
|
782
|
+
if (typeof value === "string") {
|
|
783
|
+
parts.push(encoder.encode(`${head}\r\n\r\n`));
|
|
784
|
+
parts.push(encoder.encode(normalizeBreaks(value)));
|
|
785
|
+
} else {
|
|
786
|
+
head += `; filename="${escapeName(value.name)}"\r\nContent-Type: ${value.type || "application/octet-stream"}`;
|
|
787
|
+
parts.push(encoder.encode(`${head}\r\n\r\n`));
|
|
788
|
+
parts.push(value);
|
|
789
|
+
}
|
|
790
|
+
parts.push(encoder.encode("\r\n"));
|
|
791
|
+
}
|
|
792
|
+
parts.push(encoder.encode(`--${b}--\r\n`));
|
|
793
|
+
const total = parts.reduce((sum, part) => sum + (part instanceof Uint8Array ? part.byteLength : part.size), 0);
|
|
794
|
+
let index = 0;
|
|
795
|
+
let reader;
|
|
796
|
+
return {
|
|
797
|
+
stream: new ReadableStream({
|
|
798
|
+
async pull(controller) {
|
|
799
|
+
for (;;) {
|
|
800
|
+
if (reader) {
|
|
801
|
+
const { value, done } = await reader.read();
|
|
802
|
+
if (!done) return controller.enqueue(value);
|
|
803
|
+
reader = void 0;
|
|
804
|
+
}
|
|
805
|
+
const part = parts[index++];
|
|
806
|
+
if (part === void 0) return controller.close();
|
|
807
|
+
if (part instanceof Uint8Array) return controller.enqueue(part);
|
|
808
|
+
reader = part.stream().getReader();
|
|
809
|
+
}
|
|
810
|
+
},
|
|
811
|
+
cancel: (reason) => reader?.cancel(reason)
|
|
812
|
+
}, { highWaterMark: 0 }),
|
|
813
|
+
total,
|
|
814
|
+
type: `multipart/form-data; boundary=${b}`
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
//#endregion
|
|
818
|
+
//#region src/plugins/upload-progress/progress-event.ts
|
|
819
|
+
/** An empty body is complete at once, not `0 / 0`. */
|
|
820
|
+
function progressEvent(loaded, total) {
|
|
821
|
+
return {
|
|
822
|
+
loaded,
|
|
823
|
+
total,
|
|
824
|
+
progress: total === void 0 ? void 0 : total === 0 ? 1 : loaded / total
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
//#endregion
|
|
828
|
+
//#region src/plugins/upload-progress/request-parts.ts
|
|
829
|
+
/**
|
|
830
|
+
* What a transport needs from fetch's arguments, in any shape a plugin
|
|
831
|
+
* wrapping it may pass: arthron sends a URL string and a lowercase header
|
|
832
|
+
* record, a wrapper may pass a Request, a URL or Headers.
|
|
833
|
+
*/
|
|
834
|
+
const urlOf = (input) => input instanceof Request ? input.url : String(input);
|
|
835
|
+
/** A Request's headers, then init's on top, as one lowercase record. */
|
|
836
|
+
function headersOf(input, init) {
|
|
837
|
+
const merged = new Headers(input instanceof Request ? input.headers : void 0);
|
|
838
|
+
for (const [name, value] of new Headers(init.headers)) merged.set(name, value);
|
|
839
|
+
return Object.fromEntries(merged);
|
|
840
|
+
}
|
|
841
|
+
//#endregion
|
|
842
|
+
//#region src/plugins/upload-progress/counting-fetch.ts
|
|
843
|
+
/**
|
|
844
|
+
* The most one progress step covers. Chunks are cut to it whatever their
|
|
845
|
+
* source: Node hands an in-memory Blob over as a single chunk, which would
|
|
846
|
+
* jump from 0 to 100%.
|
|
847
|
+
*/
|
|
848
|
+
const SLICE = 65536;
|
|
849
|
+
const fromBytes = (bytes, type) => ({
|
|
850
|
+
stream: new ReadableStream({ start(controller) {
|
|
851
|
+
controller.enqueue(bytes);
|
|
852
|
+
controller.close();
|
|
853
|
+
} }),
|
|
854
|
+
total: bytes.byteLength,
|
|
855
|
+
type: type ?? void 0
|
|
856
|
+
});
|
|
857
|
+
/** The body as bytes to count, or `undefined` for none (or one fetch should get as is). */
|
|
858
|
+
async function sourceOf(body, headers) {
|
|
859
|
+
if (body === void 0 || body === null) return void 0;
|
|
860
|
+
if (typeof body === "string") return fromBytes(new TextEncoder().encode(body), "text/plain;charset=UTF-8");
|
|
861
|
+
if (ArrayBuffer.isView(body)) return fromBytes(new Uint8Array(body.buffer, body.byteOffset, body.byteLength));
|
|
862
|
+
switch (tagOf(body)) {
|
|
863
|
+
case "Blob":
|
|
864
|
+
case "File": {
|
|
865
|
+
const blob = body;
|
|
866
|
+
return {
|
|
867
|
+
stream: blob.stream(),
|
|
868
|
+
total: blob.size,
|
|
869
|
+
type: blob.type || void 0
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
case "ArrayBuffer": return fromBytes(new Uint8Array(body));
|
|
873
|
+
case "URLSearchParams": return fromBytes(new TextEncoder().encode(String(body)), "application/x-www-form-urlencoded;charset=UTF-8");
|
|
874
|
+
case "FormData": {
|
|
875
|
+
const { stream, total, type } = encodeFormData(body);
|
|
876
|
+
return {
|
|
877
|
+
stream,
|
|
878
|
+
total,
|
|
879
|
+
type
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
case "ReadableStream": {
|
|
883
|
+
const length = Number(headers["content-length"]);
|
|
884
|
+
return {
|
|
885
|
+
stream: body,
|
|
886
|
+
total: Number.isSafeInteger(length) && length >= 0 ? length : void 0,
|
|
887
|
+
type: void 0
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
default: return;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* `base` with the request body counted as it is read. The body is swapped
|
|
895
|
+
* here, inside the call, so the context the plugins see keeps the caller's
|
|
896
|
+
* own. A callback that throws fails the call with its own error, not the
|
|
897
|
+
* "fetch failed" the body error would become.
|
|
898
|
+
*/
|
|
899
|
+
function countingFetch(base, onProgress) {
|
|
900
|
+
return (async (input, init = {}) => {
|
|
901
|
+
const headers = headersOf(input, init);
|
|
902
|
+
const source = await sourceOf(init.body, headers);
|
|
903
|
+
if (!source) return base(input, init);
|
|
904
|
+
const { total, type } = source;
|
|
905
|
+
if (total !== void 0 && !("content-length" in headers)) headers["content-length"] = String(total);
|
|
906
|
+
if (type && !("content-type" in headers)) headers["content-type"] = type;
|
|
907
|
+
const signal = init.signal;
|
|
908
|
+
const reader = source.stream.getReader();
|
|
909
|
+
let loaded = 0;
|
|
910
|
+
let rest;
|
|
911
|
+
let reported = false;
|
|
912
|
+
let answered = false;
|
|
913
|
+
let failure;
|
|
914
|
+
const report = () => {
|
|
915
|
+
if (answered) return;
|
|
916
|
+
reported = true;
|
|
917
|
+
try {
|
|
918
|
+
onProgress(progressEvent(loaded, total));
|
|
919
|
+
} catch (error) {
|
|
920
|
+
failure = { error };
|
|
921
|
+
throw error;
|
|
922
|
+
}
|
|
923
|
+
};
|
|
924
|
+
const body = new ReadableStream({
|
|
925
|
+
async pull(controller) {
|
|
926
|
+
const stopped = async () => {
|
|
927
|
+
if (!signal?.aborted) return false;
|
|
928
|
+
await reader.cancel(signal.reason).catch(() => {});
|
|
929
|
+
controller.error(signal.reason);
|
|
930
|
+
return true;
|
|
931
|
+
};
|
|
932
|
+
if (await stopped()) return;
|
|
933
|
+
let chunk = rest;
|
|
934
|
+
if (!chunk?.byteLength) {
|
|
935
|
+
const { value, done } = await reader.read();
|
|
936
|
+
if (done) {
|
|
937
|
+
if (!reported) report();
|
|
938
|
+
return controller.close();
|
|
939
|
+
}
|
|
940
|
+
chunk = value;
|
|
941
|
+
if (await stopped()) return;
|
|
942
|
+
}
|
|
943
|
+
rest = chunk.subarray(SLICE);
|
|
944
|
+
const piece = chunk.subarray(0, SLICE);
|
|
945
|
+
loaded += piece.byteLength;
|
|
946
|
+
report();
|
|
947
|
+
controller.enqueue(piece);
|
|
948
|
+
},
|
|
949
|
+
cancel: (reason) => reader.cancel(reason)
|
|
950
|
+
}, { highWaterMark: 0 });
|
|
951
|
+
try {
|
|
952
|
+
const res = await base(input, {
|
|
953
|
+
...init,
|
|
954
|
+
body,
|
|
955
|
+
headers,
|
|
956
|
+
duplex: "half"
|
|
957
|
+
});
|
|
958
|
+
answered = true;
|
|
959
|
+
return res;
|
|
960
|
+
} catch (error) {
|
|
961
|
+
throw failure ? failure.error : error;
|
|
962
|
+
}
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
//#endregion
|
|
966
|
+
//#region src/plugins/upload-progress/xhr-fetch.ts
|
|
967
|
+
const NULL_BODY_STATUSES = /* @__PURE__ */ new Set([
|
|
968
|
+
101,
|
|
969
|
+
204,
|
|
970
|
+
205,
|
|
971
|
+
304
|
|
972
|
+
]);
|
|
973
|
+
/** `getAllResponseHeaders()` as `Headers`; lines without a name are skipped. */
|
|
974
|
+
function parseResponseHeaders(raw) {
|
|
975
|
+
const headers = new Headers();
|
|
976
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
977
|
+
const colon = line.indexOf(":");
|
|
978
|
+
if (colon <= 0) continue;
|
|
979
|
+
const name = line.slice(0, colon).trim();
|
|
980
|
+
if (name) headers.append(name, line.slice(colon + 1).trim());
|
|
981
|
+
}
|
|
982
|
+
return headers;
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* What fetch would report for this response. XHR gives only the final URL;
|
|
986
|
+
* the request URL is normalized (case, default port, no fragment) before
|
|
987
|
+
* comparing, so writing it differently is not a redirect.
|
|
988
|
+
*/
|
|
989
|
+
function responseFields(requestUrl, responseURL, pageHref) {
|
|
990
|
+
const requested = new URL(requestUrl, pageHref);
|
|
991
|
+
requested.hash = "";
|
|
992
|
+
const final = new URL(responseURL || requested.href, pageHref);
|
|
993
|
+
const sameOrigin = pageHref !== void 0 && final.origin === new URL(pageHref).origin;
|
|
994
|
+
return {
|
|
995
|
+
url: final.href,
|
|
996
|
+
redirected: final.href !== requested.href,
|
|
997
|
+
type: sameOrigin ? "basic" : "cors"
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
1001
|
+
* A fetch over XMLHttpRequest, for the one thing fetch cannot do in a
|
|
1002
|
+
* browser: report upload progress. It honours what an arthron attempt
|
|
1003
|
+
* carries — method, headers, body, `credentials: "include"`, `signal` — and
|
|
1004
|
+
* fails the way fetch does, so `send` makes the same ArthronErrors: a
|
|
1005
|
+
* network failure rejects with a TypeError, an abort with the signal's
|
|
1006
|
+
* reason. The response arrives whole; XHR does not stream it.
|
|
1007
|
+
*/
|
|
1008
|
+
function xhrFetch(onProgress) {
|
|
1009
|
+
return ((input, init = {}) => new Promise((resolve, reject) => {
|
|
1010
|
+
const url = urlOf(input);
|
|
1011
|
+
const { signal } = init;
|
|
1012
|
+
if (signal?.aborted) return reject(signal.reason);
|
|
1013
|
+
const xhr = new XMLHttpRequest();
|
|
1014
|
+
let settled = false;
|
|
1015
|
+
const settle = (done) => {
|
|
1016
|
+
if (settled) return;
|
|
1017
|
+
settled = true;
|
|
1018
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1019
|
+
done();
|
|
1020
|
+
};
|
|
1021
|
+
const fail = (error) => {
|
|
1022
|
+
settle(() => reject(error));
|
|
1023
|
+
xhr.abort();
|
|
1024
|
+
};
|
|
1025
|
+
const onAbort = () => fail(signal?.reason);
|
|
1026
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1027
|
+
let reported;
|
|
1028
|
+
const report = (loaded, total) => {
|
|
1029
|
+
if (settled || reported?.loaded === loaded && reported.total === total) return;
|
|
1030
|
+
reported = progressEvent(loaded, total);
|
|
1031
|
+
try {
|
|
1032
|
+
onProgress(reported);
|
|
1033
|
+
} catch (error) {
|
|
1034
|
+
fail(error);
|
|
1035
|
+
}
|
|
1036
|
+
};
|
|
1037
|
+
const fromEvent = (e) => report(e.loaded, e.lengthComputable ? e.total : void 0);
|
|
1038
|
+
xhr.upload.onprogress = fromEvent;
|
|
1039
|
+
xhr.upload.onload = fromEvent;
|
|
1040
|
+
xhr.onload = () => {
|
|
1041
|
+
if (!reported) report(0, 0);
|
|
1042
|
+
if (xhr.status === 0) return settle(() => reject(/* @__PURE__ */ new TypeError("Network request failed")));
|
|
1043
|
+
const headers = parseResponseHeaders(xhr.getAllResponseHeaders());
|
|
1044
|
+
const body = NULL_BODY_STATUSES.has(xhr.status) ? null : xhr.response;
|
|
1045
|
+
const res = new Response(body, {
|
|
1046
|
+
status: xhr.status,
|
|
1047
|
+
statusText: xhr.statusText,
|
|
1048
|
+
headers
|
|
1049
|
+
});
|
|
1050
|
+
const page = globalThis.location?.href;
|
|
1051
|
+
settle(() => resolve(withFields(res, responseFields(url, xhr.responseURL, page))));
|
|
1052
|
+
};
|
|
1053
|
+
xhr.onerror = xhr.ontimeout = () => settle(() => reject(/* @__PURE__ */ new TypeError("Network request failed")));
|
|
1054
|
+
xhr.open(init.method ?? "GET", url);
|
|
1055
|
+
xhr.responseType = "arraybuffer";
|
|
1056
|
+
if (init.credentials === "include") xhr.withCredentials = true;
|
|
1057
|
+
for (const [name, value] of Object.entries(headersOf(input, init))) xhr.setRequestHeader(name, value);
|
|
1058
|
+
xhr.send(init.body ?? null);
|
|
1059
|
+
}));
|
|
1060
|
+
}
|
|
1061
|
+
//#endregion
|
|
1062
|
+
//#region src/plugins/upload-progress/upload-progress.ts
|
|
1063
|
+
/**
|
|
1064
|
+
* How a progress call can be sent here. Node first — jsdom on Node has an
|
|
1065
|
+
* XMLHttpRequest too, but Node's fetch is the real transport there. Bun,
|
|
1066
|
+
* Deno, Electron and Cloudflare Workers also expose `process.versions.node`;
|
|
1067
|
+
* only Node was shown to keep content-length on a counted body, and an
|
|
1068
|
+
* Electron renderer has XHR.
|
|
1069
|
+
*/
|
|
1070
|
+
function transportFor(runtime) {
|
|
1071
|
+
const { versions, hasXhr, userAgent, deno } = runtime;
|
|
1072
|
+
if (Boolean(versions?.node) && !versions?.bun && !versions?.electron && !deno && userAgent !== "Cloudflare-Workers") return "count";
|
|
1073
|
+
return hasXhr ? "xhr" : void 0;
|
|
1074
|
+
}
|
|
1075
|
+
function currentRuntime() {
|
|
1076
|
+
const g = globalThis;
|
|
1077
|
+
const runtime = {
|
|
1078
|
+
hasXhr: typeof XMLHttpRequest === "function",
|
|
1079
|
+
deno: "Deno" in globalThis
|
|
1080
|
+
};
|
|
1081
|
+
if (g.process?.versions) runtime.versions = g.process.versions;
|
|
1082
|
+
if (g.navigator?.userAgent) runtime.userAgent = g.navigator.userAgent;
|
|
1083
|
+
return runtime;
|
|
1084
|
+
}
|
|
1085
|
+
/**
|
|
1086
|
+
* What an XHR cannot honour and whose loss would change the call: it always
|
|
1087
|
+
* follows redirects, has no no-cors mode, and always sends cookies to its own
|
|
1088
|
+
* origin. `undefined` when the call can go over XHR.
|
|
1089
|
+
*/
|
|
1090
|
+
function xhrRefusal(init, url, pageHref) {
|
|
1091
|
+
if (init.redirect === "manual" || init.redirect === "error") return `redirect: "${init.redirect}" — XMLHttpRequest always follows redirects`;
|
|
1092
|
+
if (init.mode === "no-cors") return "mode: \"no-cors\" — XMLHttpRequest has no no-cors mode";
|
|
1093
|
+
if (init.credentials === "omit" && pageHref) {
|
|
1094
|
+
const page = new URL(pageHref).origin;
|
|
1095
|
+
if (new URL(url, pageHref).origin === page) return "credentials: \"omit\" on this origin — XMLHttpRequest always sends cookies to its own origin";
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* The attempt with its own signal, joined to the call's. A callback that
|
|
1100
|
+
* throws aborts the attempt with its error: send passes an abort reason on
|
|
1101
|
+
* untouched, so it stays a plugin exception — not a network ArthronError
|
|
1102
|
+
* that error interceptors would see and retry would repeat.
|
|
1103
|
+
*/
|
|
1104
|
+
function withOwnSignal(ctx, onProgress) {
|
|
1105
|
+
const own = new AbortController();
|
|
1106
|
+
const outer = ctx.init.signal;
|
|
1107
|
+
const signal = outer ? AbortSignal.any([outer, own.signal]) : own.signal;
|
|
1108
|
+
const report = (event) => {
|
|
1109
|
+
if (own.signal.aborted) return;
|
|
1110
|
+
try {
|
|
1111
|
+
onProgress(event);
|
|
1112
|
+
} catch (error) {
|
|
1113
|
+
own.abort(error);
|
|
1114
|
+
}
|
|
1115
|
+
};
|
|
1116
|
+
return {
|
|
1117
|
+
ctx: {
|
|
1118
|
+
...ctx,
|
|
1119
|
+
init: {
|
|
1120
|
+
...ctx.init,
|
|
1121
|
+
signal
|
|
1122
|
+
}
|
|
1123
|
+
},
|
|
1124
|
+
report
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
/**
|
|
1128
|
+
* Does nothing by itself — only a call that passes `meta.onUploadProgress`
|
|
1129
|
+
* reports upload progress. On such a call it changes how that one attempt
|
|
1130
|
+
* is sent: over XMLHttpRequest in a browser, with a counted body in Node.
|
|
1131
|
+
* Every other call, on this client too, goes out exactly as before.
|
|
1132
|
+
*
|
|
1133
|
+
* Progress needs the runtime's own fetch: with an injected `fetch`, another
|
|
1134
|
+
* plugin's transport, or a runtime that is neither (Deno, Bun, Cloudflare
|
|
1135
|
+
* Workers, a service worker), the call is sent as usual without progress.
|
|
1136
|
+
* Put it last in `use`: the call's signal carries a throwing callback's
|
|
1137
|
+
* error, and an interceptor after it that replaces the signal would cut it.
|
|
1138
|
+
*/
|
|
1139
|
+
function uploadProgress() {
|
|
1140
|
+
return {
|
|
1141
|
+
name: "uploadProgress",
|
|
1142
|
+
request: (ctx) => {
|
|
1143
|
+
const onProgress = ctx.meta?.onUploadProgress;
|
|
1144
|
+
const { body } = ctx.init;
|
|
1145
|
+
if (!onProgress || body === void 0 || body === null) return ctx;
|
|
1146
|
+
if (ctx.fetch !== globalThis.fetch) return ctx;
|
|
1147
|
+
const transport = transportFor(currentRuntime());
|
|
1148
|
+
if (!transport) return ctx;
|
|
1149
|
+
if (transport === "xhr") {
|
|
1150
|
+
const page = globalThis.location?.href;
|
|
1151
|
+
const refusal = xhrRefusal(ctx.init, ctx.url, page);
|
|
1152
|
+
if (refusal) throw new TypeError(`arthron: onUploadProgress cannot be combined with ${refusal}; drop one of them`);
|
|
1153
|
+
if (isReadableStream(body)) return ctx;
|
|
1154
|
+
}
|
|
1155
|
+
const { ctx: next, report } = withOwnSignal(ctx, onProgress);
|
|
1156
|
+
return {
|
|
1157
|
+
...next,
|
|
1158
|
+
fetch: transport === "xhr" ? xhrFetch(report) : countingFetch(ctx.fetch, report)
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
//#endregion
|
|
1164
|
+
export { redactUrl as _, cancellationError as a, responseError as c, runtimeStreamsRequestBody as d, serializeBody as f, leavesBase as g, fillPath as h, logging as i, bearerAuth as l, isArthronError as m, timeout as n, isCancellation as o, ArthronError as p, retry as r, networkError as s, uploadProgress as t, isReadableStream as u, redactUrlIn as v, resolveUrl as y };
|
|
756
1165
|
|
|
757
|
-
//# sourceMappingURL=plugins-
|
|
1166
|
+
//# sourceMappingURL=plugins-CHCMxtAE.mjs.map
|