lifecycleion 0.0.20 → 0.0.21
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 +39 -36
- package/dist/lib/event-emitter.cjs +195 -9
- package/dist/lib/event-emitter.cjs.map +1 -1
- package/dist/lib/event-emitter.js +195 -9
- package/dist/lib/event-emitter.js.map +1 -1
- package/dist/lib/global-event-target.cjs +217 -0
- package/dist/lib/global-event-target.cjs.map +1 -0
- package/dist/lib/global-event-target.d.cts +70 -0
- package/dist/lib/global-event-target.d.ts +70 -0
- package/dist/lib/global-event-target.js +189 -0
- package/dist/lib/global-event-target.js.map +1 -0
- package/dist/lib/http-client/index.cjs +554 -193
- package/dist/lib/http-client/index.cjs.map +1 -1
- package/dist/lib/http-client/index.d.cts +9 -3
- package/dist/lib/http-client/index.d.ts +9 -3
- package/dist/lib/http-client/index.js +554 -193
- package/dist/lib/http-client/index.js.map +1 -1
- package/dist/lib/http-client-mock/index.cjs +71 -10
- package/dist/lib/http-client-mock/index.cjs.map +1 -1
- package/dist/lib/http-client-mock/index.d.cts +50 -3
- package/dist/lib/http-client-mock/index.d.ts +50 -3
- package/dist/lib/http-client-mock/index.js +71 -10
- package/dist/lib/http-client-mock/index.js.map +1 -1
- package/dist/lib/http-client-node/index.cjs +98 -19
- package/dist/lib/http-client-node/index.cjs.map +1 -1
- package/dist/lib/http-client-node/index.d.cts +2 -2
- package/dist/lib/http-client-node/index.d.ts +2 -2
- package/dist/lib/http-client-node/index.js +98 -19
- package/dist/lib/http-client-node/index.js.map +1 -1
- package/dist/lib/http-client-xhr/index.cjs.map +1 -1
- package/dist/lib/http-client-xhr/index.d.cts +2 -2
- package/dist/lib/http-client-xhr/index.d.ts +2 -2
- package/dist/lib/http-client-xhr/index.js.map +1 -1
- package/dist/lib/lifecycle-manager/index.cjs +199 -13
- package/dist/lib/lifecycle-manager/index.cjs.map +1 -1
- package/dist/lib/lifecycle-manager/index.js +199 -13
- package/dist/lib/lifecycle-manager/index.js.map +1 -1
- package/dist/lib/logger/index.cjs +203 -21
- package/dist/lib/logger/index.cjs.map +1 -1
- package/dist/lib/logger/index.d.cts +5 -1
- package/dist/lib/logger/index.d.ts +5 -1
- package/dist/lib/logger/index.js +203 -21
- package/dist/lib/logger/index.js.map +1 -1
- package/dist/lib/lru-cache/index.cjs +195 -9
- package/dist/lib/lru-cache/index.cjs.map +1 -1
- package/dist/lib/lru-cache/index.js +195 -9
- package/dist/lib/lru-cache/index.js.map +1 -1
- package/dist/lib/process-signal-manager.cjs +199 -13
- package/dist/lib/process-signal-manager.cjs.map +1 -1
- package/dist/lib/process-signal-manager.js +199 -13
- package/dist/lib/process-signal-manager.js.map +1 -1
- package/dist/lib/promise-protected-resolver.cjs +195 -9
- package/dist/lib/promise-protected-resolver.cjs.map +1 -1
- package/dist/lib/promise-protected-resolver.js +195 -9
- package/dist/lib/promise-protected-resolver.js.map +1 -1
- package/dist/lib/retry-utils/index.cjs +195 -9
- package/dist/lib/retry-utils/index.cjs.map +1 -1
- package/dist/lib/retry-utils/index.js +195 -9
- package/dist/lib/retry-utils/index.js.map +1 -1
- package/dist/lib/safe-handle-callback.cjs +196 -18
- package/dist/lib/safe-handle-callback.cjs.map +1 -1
- package/dist/lib/safe-handle-callback.d.cts +14 -4
- package/dist/lib/safe-handle-callback.d.ts +14 -4
- package/dist/lib/safe-handle-callback.js +196 -18
- package/dist/lib/safe-handle-callback.js.map +1 -1
- package/dist/lib/single-event-observer.cjs +195 -9
- package/dist/lib/single-event-observer.cjs.map +1 -1
- package/dist/lib/single-event-observer.js +195 -9
- package/dist/lib/single-event-observer.js.map +1 -1
- package/dist/{types-6G59m8U9.d.cts → types-BFvpJPMt.d.cts} +71 -10
- package/dist/{types-CG10j6Lc.d.ts → types-BONtIhQ5.d.ts} +71 -10
- package/package.json +12 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/http-client-node/index.ts","../../../src/lib/http-client/adapters/node-adapter.ts","../../../src/lib/http-client/consts.ts","../../../src/lib/http-client/internal/multipart.ts","../../../src/lib/http-client/internal/request-body-writer.ts","../../../src/lib/http-client/internal/tls-error-utils.ts","../../../src/lib/http-client/adapters/node-adapter-utils.ts","../../../src/lib/http-client/utils.ts"],"sourcesContent":["export { NodeAdapter } from '../http-client/adapters/node-adapter';\nexport type { NodeAdapterConfig } from '../http-client/adapters/node-adapter';\n\n/**\n * Adapter contract types, re-exported so code driving an adapter directly —\n * building AdapterRequest objects and calling send() rather than going through\n * HTTPClient — does not have to import types from 'lifecycleion/http-client',\n * the module it is specifically not using. Type-only, so nothing is added to\n * the runtime bundle.\n */\nexport type {\n HTTPAdapter,\n AdapterRequest,\n AdapterResponse,\n AdapterType,\n AdapterProgressEvent,\n HTTPMethod,\n} from '../http-client/types';\n\n/**\n * Response-streaming types. NodeAdapter is the only adapter that honours\n * `AdapterRequest.streamResponse`, so these live here rather than on the other\n * adapter barrels.\n */\nexport type {\n StreamResponseFactory,\n StreamResponseInfo,\n StreamResponseContext,\n StreamResponseCancel,\n WritableLike,\n} from '../http-client/types';\n","import * as http from 'node:http';\nimport * as https from 'node:https';\nimport { urlToHttpOptions } from 'node:url';\nimport type {\n HTTPAdapter,\n AdapterRequest,\n AdapterResponse,\n AdapterProgressEvent,\n AdapterType,\n WritableLike,\n StreamResponseCancel,\n} from '../types';\nimport {\n NON_RETRYABLE_HTTP_CLIENT_CALLBACK_ERROR_FLAG,\n REDIRECT_STATUS_CODES,\n RESPONSE_STREAM_ABORT_FLAG,\n STREAM_FACTORY_CANCEL_KEY,\n STREAM_FACTORY_ERROR_FLAG,\n} from '../consts';\nimport {\n generateMultipartBoundary,\n serializeMultipartFormData,\n} from '../internal/multipart';\nimport { writeRequestBodyChunked } from '../internal/request-body-writer';\nimport { isTLSCertificateError } from '../internal/tls-error-utils';\nimport {\n materializeNodeRequestHeaders,\n normalizeNodeRequestHeaders,\n} from './node-adapter-utils';\nimport { resolveDetectedRedirectURL } from '../utils';\n\ntype StreamResponseBodyResult =\n | true\n | {\n code: 'stream_write_error' | 'stream_response_error';\n cause: Error;\n };\n\n// ---------------------------------------------------------------------------\n// Config\n// ---------------------------------------------------------------------------\n\nexport interface NodeAdapterConfig {\n /**\n * Unix domain socket path. When set, the HTTP connection routes through\n * the socket instead of TCP. The URL host in baseURL is ignored for routing\n * but is still used for the HTTP Host header — so a placeholder like\n * 'http://localhost' is required even when all traffic goes through the socket.\n *\n * const client = new HTTPClient({\n * adapter: new NodeAdapter({ socketPath: '/var/run/docker.sock' }),\n * baseURL: 'http://localhost', // host ignored; only path matters\n * });\n */\n socketPath?: string;\n\n /**\n * Mutual TLS credentials. When set on an https: request, the adapter\n * presents the client certificate to the server. Cert errors return\n * status 495 (non-standard but widely understood for client cert failure)\n * rather than throwing. The client treats 495 as a transport-level failure,\n * so it resolves through the failed/error path instead of response observers\n * and is NOT retryable.\n */\n mtls?: {\n cert: string | Buffer;\n key: string | Buffer;\n ca?: string | Buffer | Array<string | Buffer>;\n };\n\n /**\n * Trusted CA certificate(s) for verifying the server's TLS certificate.\n * Use this when connecting to internal services that use a private CA not\n * in the system trust store. Accepts PEM string, Buffer, or an array of\n * either. Does not require a client certificate — use `mtls` for that.\n */\n ca?: string | Buffer | Array<string | Buffer>;\n\n /**\n * TLS server name (SNI) to send in the handshake and verify the server\n * certificate against. Required when dialing by IP address but the server\n * cert's SAN lists a DNS name — without this, TLS verification fails because\n * the IP does not match the DNS SAN.\n *\n * Typical pattern when using a service registry:\n * baseURL: 'https://10.0.0.5:443' ← IP from the registry (where bytes go)\n * servername: 'billing.internal' ← DNS name on the cert (TLS identity)\n *\n * The Host header defaults to the baseURL hostname (the IP). If the backend\n * routes by Host, also set defaultHeaders: { host: 'billing.internal' }.\n */\n servername?: string;\n\n /**\n * Certificate revocation list(s), used to reject a server certificate whose\n * serial has been revoked even though its chain and hostname still check out.\n * A revoked certificate fails the handshake with `CERT_REVOKED`.\n *\n * Accepts whatever your CA tooling produced. A PEM string holding SEVERAL\n * concatenated CRLs — the format Apache's `SSLCARevocationFile`, nginx's\n * `ssl_crl` and HAProxy's `crl-file` all expect, and what `openssl ca\n * -gencrl` output is usually assembled into — is split into the array Node\n * requires. A single CRL, a DER `Buffer`, or an array you built yourself is\n * passed through untouched.\n *\n * The split exists because Node reads only the FIRST CRL of a concatenated\n * string and silently ignores the rest, unlike `ca`, which reads every\n * certificate in a bundle. See docs/http-client.md.\n *\n * Two things to know before using this:\n *\n * - **Every certificate in the chain needs a covering CRL.** Node enables\n * `X509_V_FLAG_CRL_CHECK_ALL`, so supplying a CRL for one root while\n * connecting through another fails with `UNABLE_TO_GET_CRL` even when\n * nothing was revoked. Cover every root you talk to, or scope the client.\n * - **CRLs expire.** Past `nextUpdate` the handshake fails with\n * `CRL_HAS_EXPIRED`, including for certificates that were never revoked.\n * Re-pass a fresh CRL before then; the refresh cadence is yours to own.\n *\n * Both failures are fail-closed, so a stale CRL never silently stops\n * enforcing — but both can take out healthy connections.\n */\n crl?: string | Buffer | Array<string | Buffer>;\n\n /**\n * Set to false to accept self-signed certificates in dev/test environments.\n * Defaults to true (Node.js default — rejects invalid certs).\n */\n rejectUnauthorized?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// NodeAdapter\n// ---------------------------------------------------------------------------\n\nexport class NodeAdapter implements HTTPAdapter {\n private _config: NodeAdapterConfig;\n\n constructor(config: NodeAdapterConfig = {}) {\n this._config = config;\n }\n\n public getType(): AdapterType {\n return 'node';\n }\n\n public async send(request: AdapterRequest): Promise<AdapterResponse> {\n const parsedURL = new URL(request.requestURL);\n const urlOptions = urlToHttpOptions(parsedURL);\n const isHTTPS = parsedURL.protocol === 'https:';\n const httpModule = isHTTPS ? https : http;\n\n const options: http.RequestOptions = {\n method: request.method,\n headers: materializeNodeRequestHeaders(request.headers),\n // Timeout is managed by the client via abort signal — the adapter does\n // not impose its own timeout so the client retains full control.\n };\n\n if (urlOptions.auth) {\n options.auth = urlOptions.auth;\n }\n\n if (this._config.socketPath) {\n // Unix socket: the TCP connection goes to the socket path, not the host.\n // We still need options.path so the HTTP request line has the right path.\n // Preserve the URL host/port too so Node generates the correct Host\n // header for virtual-hosted services behind the socket.\n options.socketPath = this._config.socketPath;\n options.hostname = urlOptions.hostname;\n options.port = urlOptions.port;\n options.path = urlOptions.path;\n } else {\n options.hostname = urlOptions.hostname;\n options.port = urlOptions.port ?? (isHTTPS ? 443 : 80);\n options.path = urlOptions.path;\n }\n\n if (isHTTPS) {\n const httpsOptions = options as https.RequestOptions;\n\n // custom CA trust store\n if (this._config.ca) {\n httpsOptions.ca = this._config.ca;\n }\n\n // SNI hostname — required when dialing by IP with a DNS-named cert\n if (this._config.servername) {\n httpsOptions.servername = this._config.servername;\n }\n\n if (this._config.mtls) {\n // mTLS: present client cert. rejectUnauthorized stays true so the\n // server cert is still validated even though we're sending our own.\n httpsOptions.cert = this._config.mtls.cert;\n httpsOptions.key = this._config.mtls.key;\n\n if (this._config.mtls.ca) {\n httpsOptions.ca = this._config.mtls.ca;\n }\n\n httpsOptions.rejectUnauthorized = true;\n }\n\n if (this._config.crl !== undefined) {\n httpsOptions.crl = normalizeCRL(this._config.crl);\n }\n\n if (this._config.rejectUnauthorized === false) {\n // Dev-only: accept self-signed certs. Explicit false required — we do\n // not default to insecure, this must be an intentional opt-in.\n httpsOptions.rejectUnauthorized = false;\n }\n }\n\n return new Promise<AdapterResponse>((resolve, reject) => {\n let activeResponseStream:\n | {\n status: number;\n headers: Record<string, string | string[]>;\n writable: WritableLike;\n }\n | undefined;\n let activeBufferedResponse:\n | {\n status: number;\n headers: Record<string, string | string[]>;\n }\n | undefined;\n let isStreamFactoryPending = false;\n let uploadedBodyBytes = 0;\n\n // Deduplication guard — Node's upload path can reach 100% from multiple\n // sources (final drain callback and the upload-complete signal). Once\n // 100% is reported any further calls are dropped.\n let didFireUpload100 = false;\n\n const reportUploadProgress = (event: AdapterProgressEvent): void => {\n if (didFireUpload100) {\n return;\n }\n\n if (event.progress === 1) {\n didFireUpload100 = true;\n }\n\n uploadedBodyBytes = Math.max(uploadedBodyBytes, event.loaded);\n request.onUploadProgress?.(event);\n };\n\n // 0% upload progress before any bytes leave the process\n reportUploadProgress({ loaded: 0, total: 0, progress: 0 });\n\n // The http callback is typed as (res: IncomingMessage) => void, so we\n // cannot make it async directly. We use a void IIFE that routes any\n // unhandled rejections back to the outer promise's reject.\n const req = httpModule.request(options, (res) => {\n void (async () => {\n const status = res.statusCode ?? 0;\n const headers = normalizeResponseHeaders(res.headers);\n\n // --- Response streaming (NodeAdapter-only feature) ---\n //\n // Only offered on HTTP 200 responses. All other statuses bypass the\n // factory and return a normal buffered response. This prevents:\n // 1. Accidentally streaming error bodies (you'd lose the error detail)\n // 2. The mistake of buffering a large 200 into memory when you forgot\n // to check the status — factory null = cancel, not buffer\n if (request.streamResponse && status === 200) {\n // Per-attempt abort controller for the stream context signal. This is\n // separate from the top-level request signal so we can fire it on local\n // write failures (disk full, etc.) without aborting the request itself —\n // the factory's cleanup listener fires, and we resolve with isStreamError.\n const streamAbort = new AbortController();\n\n // Propagate external cancellation (user abort, timeout) into the\n // factory's signal so cleanup listeners fire in all terminal cases.\n if (request.signal) {\n if (request.signal.aborted) {\n streamAbort.abort();\n }\n\n request.signal.addEventListener(\n 'abort',\n () => {\n streamAbort.abort();\n },\n { once: true },\n );\n }\n\n let writable: WritableLike | null | StreamResponseCancel;\n\n try {\n isStreamFactoryPending = true;\n writable = await request.streamResponse(\n {\n status: 200,\n headers,\n url: request.requestURL,\n attempt: request.attemptNumber ?? 1,\n requestID: request.requestID ?? '',\n },\n { signal: streamAbort.signal },\n );\n } catch (error) {\n isStreamFactoryPending = false;\n // Factory threw — non-retryable setup error, equivalent to an\n // interceptor throw. Abort the stream signal so any partial cleanup\n // listeners run, destroy the request, and propagate as a setup failure.\n streamAbort.abort();\n req.destroy();\n reject(markStreamFactoryError(error, req, request.headers));\n return;\n }\n isStreamFactoryPending = false;\n\n // The request may have been cancelled or timed out while an async\n // factory was still setting up its sink. In that case the outer\n // promise has already settled through the abort listener; make a\n // best effort to close the newly created writable and stop here.\n if (streamAbort.signal.aborted) {\n if (writable && !isStreamResponseCancel(writable)) {\n writable.destroy();\n }\n\n return;\n }\n\n if (writable === null || isStreamResponseCancel(writable)) {\n // Factory declined to stream — user-initiated cancel. Fire the stream\n // signal so any cleanup listeners wired in the factory run, then throw\n // AbortError so the client's cancel path takes over (isCancelled: true).\n const cancelReason =\n writable !== null ? writable.reason : undefined;\n\n streamAbort.abort();\n req.destroy();\n const abortErr = new Error(\n 'Request cancelled by streamResponse factory',\n );\n abortErr.name = 'AbortError';\n Object.assign(abortErr, {\n [STREAM_FACTORY_CANCEL_KEY]: cancelReason ?? true,\n });\n reject(abortErr);\n return;\n }\n\n activeResponseStream = {\n status,\n headers,\n writable,\n };\n\n const totalBytes =\n parseInt(String(headers['content-length'] ?? '0'), 10) || 0;\n\n // Pipe the response into the caller's writable. Extracted to a\n // module-level function to keep callback nesting within ESLint's\n // max-nested-callbacks limit. Returns true on success, or the\n // writable error when the local sink fails (disk full, etc.).\n const streamResult = await streamResponseBody(\n res,\n writable,\n totalBytes,\n request.onDownloadProgress,\n );\n\n if (streamResult === true) {\n activeResponseStream = undefined;\n resolveAdapterResponse(\n resolve,\n req,\n request.requestURL,\n request.headers,\n {\n status,\n headers,\n body: null,\n isStreamed: true,\n },\n );\n } else {\n activeResponseStream = undefined;\n // Body streaming failure after headers (disk full, writable\n // destroyed, upstream socket reset, etc.)\n //\n // The server already returned a real 200 response, so retries are no\n // longer safe: the caller's sink may already contain partial bytes.\n // We therefore preserve the real HTTP status and resolve with\n // isStreamError rather than throwing/retrying.\n // - The stream signal fires so factory cleanup listeners run\n // - Non-retryable once streaming has started\n streamAbort.abort();\n destroyWritableQuietly(writable);\n req.destroy();\n resolveAdapterResponse(\n resolve,\n req,\n request.requestURL,\n request.headers,\n {\n status,\n headers,\n body: null,\n isStreamError: true,\n streamErrorCode: streamResult.code,\n errorCause: streamResult.cause,\n },\n );\n }\n\n return;\n }\n\n // --- Normal buffered response ---\n activeBufferedResponse = {\n status,\n headers,\n };\n const chunks: Buffer[] = [];\n let loadedBytes = 0;\n\n // Deduplication guard — when Content-Length is known and the last\n // `data` chunk fills the body exactly, progress: 1 fires there.\n // The `end` event fires unconditionally afterward, so skip the\n // completion event if the last chunk already reported 100%.\n let didFireDownload100 = false;\n\n const totalBytes =\n parseInt(String(headers['content-length'] ?? '0'), 10) || 0;\n\n res.on('data', (chunk: Buffer) => {\n chunks.push(chunk);\n loadedBytes += chunk.length;\n\n // progress: -1 when Content-Length is absent (chunked transfer,\n // compressed response, etc.) — callers treat -1 as \"length unknown\".\n const progress = totalBytes > 0 ? loadedBytes / totalBytes : -1;\n\n // Track whether the final chunk already closed out 100% so the\n // `end` handler can skip a duplicate event.\n if (progress === 1) {\n didFireDownload100 = true;\n }\n\n request.onDownloadProgress?.({\n loaded: loadedBytes,\n // When total is unknown fall back to loaded so the event always\n // has a sensible non-zero total.\n total: totalBytes > 0 ? totalBytes : loadedBytes,\n progress,\n });\n });\n\n res.on('end', () => {\n activeBufferedResponse = undefined;\n\n // Final 100% download event — skipped when the last `data` chunk\n // already reported it (Content-Length known, body filled exactly).\n if (!didFireDownload100) {\n request.onDownloadProgress?.({\n loaded: loadedBytes,\n total: loadedBytes,\n progress: 1,\n });\n }\n\n const body =\n chunks.length > 0 ? new Uint8Array(Buffer.concat(chunks)) : null;\n\n resolveAdapterResponse(\n resolve,\n req,\n request.requestURL,\n request.headers,\n {\n status,\n headers,\n body,\n },\n );\n });\n\n res.on('error', (err: Error) => {\n if (!activeBufferedResponse) {\n return;\n }\n\n activeBufferedResponse = undefined;\n resolveAdapterResponse(\n resolve,\n req,\n request.requestURL,\n request.headers,\n {\n status,\n headers,\n body: null,\n isStreamError: true,\n streamErrorCode: 'stream_response_error',\n errorCause: makeResponseStreamError(\n 'Response stream error',\n err,\n ),\n },\n );\n });\n\n res.on('aborted', () => {\n if (!activeBufferedResponse) {\n return;\n }\n\n activeBufferedResponse = undefined;\n resolveAdapterResponse(\n resolve,\n req,\n request.requestURL,\n request.headers,\n {\n status,\n headers,\n body: null,\n isStreamError: true,\n streamErrorCode: 'stream_response_error',\n errorCause: makeResponseStreamError('Response stream aborted'),\n },\n );\n });\n\n res.on('close', () => {\n if (!activeBufferedResponse) {\n return;\n }\n\n activeBufferedResponse = undefined;\n resolveAdapterResponse(\n resolve,\n req,\n request.requestURL,\n request.headers,\n {\n status,\n headers,\n body: null,\n isStreamError: true,\n streamErrorCode: 'stream_response_error',\n errorCause: makeResponseStreamError(\n 'Response stream closed before completion',\n ),\n },\n );\n });\n })().catch((error: unknown) => {\n reject(error instanceof Error ? error : new Error(String(error)));\n });\n });\n\n // Network / transport errors (DNS failure, connection refused, cert errors)\n req.on('error', (error) => {\n // Abort signal fired before network error — priorities the abort path\n if (request.signal?.aborted) {\n const abortErr = new Error('Request aborted');\n abortErr.name = 'AbortError';\n reject(abortErr);\n return;\n }\n\n // TLS certificate errors → 495. This is non-standard but widely\n // understood for client cert / server cert validation failures. We\n // preserve the diagnostic 495 status, but still flag it as a transport\n // failure so the client routes it through the failed/error path and\n // never retries it.\n if (isTLSCertificateError(error)) {\n resolveAdapterResponse(\n resolve,\n req,\n request.requestURL,\n request.headers,\n {\n status: 495,\n isTransportError: true,\n isRetryable: false,\n headers: {},\n body: null,\n errorCause: error,\n },\n );\n return;\n }\n\n const isRetryableTransportError = uploadedBodyBytes === 0;\n\n // All other transport errors (ECONNREFUSED, ENOTFOUND, etc.) → status 0\n resolveAdapterResponse(\n resolve,\n req,\n request.requestURL,\n request.headers,\n {\n status: 0,\n isTransportError: true,\n isRetryable: isRetryableTransportError,\n headers: {},\n body: null,\n errorCause: error,\n },\n );\n });\n\n // Wire abort signal — destroy the underlying socket when fired.\n // Reject immediately rather than waiting for the 'error' event; some\n // runtimes (e.g. Bun) do not emit 'error' on req.destroy(), so waiting\n // leaves the promise unsettled. Promise resolution is idempotent, so any\n // subsequent error event is a safe no-op.\n if (request.signal) {\n if (request.signal.aborted) {\n // Signal already aborted before we even started (e.g., pre-cancelled builder)\n req.destroy();\n const abortErr = new Error('Request aborted');\n abortErr.name = 'AbortError';\n reject(abortErr);\n return;\n }\n\n request.signal.addEventListener(\n 'abort',\n () => {\n if (activeResponseStream) {\n const { status, headers, writable } = activeResponseStream;\n activeResponseStream = undefined;\n destroyWritableQuietly(writable);\n req.destroy();\n\n const error = new Error(\n 'Request aborted during response streaming',\n );\n error.name = 'AbortError';\n reject(\n markResponseStreamAbortError(\n error,\n req,\n request.headers,\n status,\n headers,\n ),\n );\n return;\n }\n\n if (activeBufferedResponse) {\n const { status, headers } = activeBufferedResponse;\n activeBufferedResponse = undefined;\n req.destroy();\n\n const error = new Error(\n 'Request aborted during response streaming',\n );\n error.name = 'AbortError';\n reject(\n markResponseStreamAbortError(\n error,\n req,\n request.headers,\n status,\n headers,\n ),\n );\n return;\n }\n\n if (isStreamFactoryPending) {\n req.destroy();\n const abortErr = new Error(\n 'Request aborted during streamResponse setup',\n );\n abortErr.name = 'AbortError';\n reject(markStreamFactoryError(abortErr, req, request.headers));\n return;\n }\n\n req.destroy();\n const abortErr = new Error('Request aborted');\n abortErr.name = 'AbortError';\n reject(abortErr);\n },\n { once: true },\n );\n }\n\n // Write request body\n if (request.body instanceof FormData) {\n // FormData → multipart/form-data with exact Content-Length so upload\n // progress is length-computable (not chunked-transfer guesswork).\n const boundary = generateMultipartBoundary();\n\n serializeMultipartFormData(\n request.body,\n req,\n boundary,\n reportUploadProgress,\n )\n .then(() => {\n req.end();\n })\n .catch((error: unknown) => {\n req.destroy();\n resolveAdapterResponse(\n resolve,\n req,\n request.requestURL,\n request.headers,\n {\n status: 0,\n isTransportError: true,\n isRetryable: false,\n headers: {},\n body: null,\n errorCause:\n error instanceof Error ? error : new Error(String(error)),\n },\n );\n });\n } else if (\n typeof request.body === 'string' ||\n request.body instanceof Uint8Array\n ) {\n // String or Uint8Array body — write in chunks so upload progress fires\n // at meaningful granularity rather than one giant 100% event at the end.\n const bytes =\n typeof request.body === 'string'\n ? Buffer.from(request.body, 'utf8')\n : Buffer.from(request.body);\n\n req.setHeader('Content-Length', bytes.length.toString());\n\n writeRequestBodyChunked(bytes, req, reportUploadProgress)\n .then(() => {\n req.end();\n })\n .catch((error: unknown) => {\n req.destroy();\n resolveAdapterResponse(\n resolve,\n req,\n request.requestURL,\n request.headers,\n {\n status: 0,\n isTransportError: true,\n isRetryable: false,\n headers: {},\n body: null,\n errorCause:\n error instanceof Error ? error : new Error(String(error)),\n },\n );\n });\n } else {\n // No body — fire 100% upload immediately and end the request\n reportUploadProgress({ loaded: 0, total: 0, progress: 1 });\n req.end();\n }\n });\n }\n}\n\n// ---------------------------------------------------------------------------\n// Streaming pipe helper\n// ---------------------------------------------------------------------------\n\n// Extracted to module scope so its internal callback nesting starts from 1,\n// keeping each level within ESLint's max-nested-callbacks limit of 3.\n//\n// Returns true when the response body was fully written to the writable, or the\n// underlying streaming failure when delivery fails after headers (disk full,\n// writable destroyed, upstream response stream error, etc.). The caller maps\n// that error into isStreamError rather than throwing, so the real HTTP status\n// is preserved and the failure stays non-retryable once streaming has started.\nasync function streamResponseBody(\n res: http.IncomingMessage,\n writable: WritableLike,\n totalBytes: number,\n onProgress?: (e: AdapterProgressEvent) => void,\n): Promise<StreamResponseBodyResult> {\n return new Promise((resolve) => {\n let loadedBytes = 0;\n\n // Deduplication guard — same as buffered download: when Content-Length is\n // known and the last write callback fills the body exactly, progress: 1\n // fires there. The `end` → writable.end callback fires unconditionally\n // afterward, so skip the completion event if the write already reported 100%.\n let didFireDownload100 = false;\n let isPaused = false;\n let isSettled = false;\n let didReceiveEnd = false;\n\n const cleanup = (): void => {\n removeWritableListener(writable, 'drain', onWritableDrain);\n removeWritableListener(writable, 'error', onWritableError);\n res.off('data', onResponseData);\n res.off('end', onResponseEnd);\n res.off('error', onResponseError);\n res.off('aborted', onResponseAborted);\n res.off('close', onResponseClose);\n };\n\n const settle = (result: StreamResponseBodyResult): void => {\n if (isSettled) {\n return;\n }\n\n isSettled = true;\n cleanup();\n resolve(result);\n };\n\n // Register the drain handler once at this level (depth 2) rather than\n // inside the data callback (depth 3+). Uses a flag instead of a one-shot\n // .once() to handle spurious drains gracefully.\n const onWritableDrain = (): void => {\n if (isPaused) {\n isPaused = false;\n res.resume();\n }\n };\n\n // Writable write failure (disk full, closed stream, etc.) resolves the\n // original error instead of rejecting so the caller can return the real\n // HTTP status code in an isStreamError response rather than surfacing a\n // thrown error.\n const onWritableError = (error: Error): void => {\n settle({ code: 'stream_write_error', cause: error });\n };\n\n const onResponseData = (chunk: Buffer): void => {\n if (isSettled) {\n return;\n }\n\n let canContinue: boolean;\n\n try {\n canContinue = writable.write(chunk, (error) => {\n if (error || isSettled) {\n return;\n }\n\n loadedBytes += chunk.length;\n\n // progress: -1 when Content-Length is absent — callers treat -1 as\n // \"length unknown\". Track 100% to avoid a duplicate from onResponseEnd.\n const progress = totalBytes > 0 ? loadedBytes / totalBytes : -1;\n\n if (progress === 1) {\n didFireDownload100 = true;\n }\n\n onProgress?.({\n loaded: loadedBytes,\n // Fall back to loaded when total is unknown so the event always\n // has a sensible non-zero total.\n total: totalBytes > 0 ? totalBytes : loadedBytes,\n progress,\n });\n });\n } catch (error) {\n settle({\n code: 'stream_write_error',\n cause: error instanceof Error ? error : new Error(String(error)),\n });\n return;\n }\n\n if (!canContinue) {\n // Writable signalled backpressure — pause the readable until the\n // drain event fires (handled above) to keep memory bounded.\n isPaused = true;\n res.pause();\n }\n };\n\n const onResponseEnd = (): void => {\n if (isSettled) {\n return;\n }\n\n didReceiveEnd = true;\n\n try {\n writable.end(() => {\n if (isSettled) {\n return;\n }\n\n // Fire 100% download progress on successful completion, unless a\n // data chunk already reported exactly 100% (Content-Length known and\n // last chunk completed the body).\n if (!didFireDownload100) {\n onProgress?.({\n loaded: loadedBytes,\n total: loadedBytes,\n progress: 1,\n });\n }\n\n settle(true);\n });\n } catch (error) {\n settle({\n code: 'stream_write_error',\n cause: error instanceof Error ? error : new Error(String(error)),\n });\n }\n };\n\n const onResponseError = (err: Error): void => {\n // Response stream error after headers is terminal for streamed 200\n // responses too: bytes may already have been written to the caller's\n // sink, so retrying would risk duplicate/corrupt output.\n const streamError = new Error('Response stream error');\n streamError.cause = err;\n settle({ code: 'stream_response_error', cause: streamError });\n };\n\n const onResponseAborted = (): void => {\n const streamError = new Error('Response stream aborted');\n settle({ code: 'stream_response_error', cause: streamError });\n };\n\n const onResponseClose = (): void => {\n if (didReceiveEnd || isSettled) {\n return;\n }\n\n const streamError = new Error('Response stream closed before completion');\n settle({ code: 'stream_response_error', cause: streamError });\n };\n\n writable.on('drain', onWritableDrain);\n writable.on('error', onWritableError);\n res.on('data', onResponseData);\n res.on('end', onResponseEnd);\n res.on('error', onResponseError);\n res.on('aborted', onResponseAborted);\n res.on('close', onResponseClose);\n });\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n// Matches one PEM-armoured CRL. The armour is fixed by RFC 7468, so this is a\n// constant rather than an option — there is exactly one correct value for it.\nconst PEM_CRL_HEADER = '-----BEGIN X509 CRL-----';\n\nconst PEM_CRL_BLOCK = /-----BEGIN X509 CRL-----[\\s\\S]*?-----END X509 CRL-----/g;\n\n// Anything at all left over once every COMPLETE block has been removed.\n//\n// The rule is exact rather than a heuristic, and that is the point. Earlier\n// versions tried to tell a damaged CRL apart from a human-readable annotation\n// — first by searching for well-formed `-----BEGIN X509 CRL-----`, which\n// missed `----BEGIN` and a clipped tail; then by any triple hyphen, which\n// refused a Markdown rule; then by a hyphen run fused to text, which refused\n// an issuer CN of `ACME---Production`. Each narrowing traded a false negative\n// for a false positive, and a false negative here is a CRL dropped in silence.\n//\n// There is no line to draw, because a truncated CRL and a line of prose are\n// the same thing to a parser: bytes that are not a PEM block. So require that\n// there be none. Everything outside a complete block must be whitespace, which\n// makes the accepted input exactly \"PEM blocks, in any order, separated by\n// whitespace\" — what every producer of a CRL bundle emits by default.\n//\n// The cost is `openssl ca -gencrl -text` output, whose decoded header is not\n// whitespace. That is refused with an error saying so, which is a better\n// outcome than a rule that accepts it and, in the same breath, accepts a CRL\n// that was cut in half.\nconst RESIDUE_OUTSIDE_PEM_BLOCKS = /\\S/;\n\n/**\n * Normalizes whatever the caller supplied as `crl` into the shape Node needs.\n *\n * Node reads only the FIRST CRL of a PEM string and silently ignores the rest\n * — unlike `ca`, which reads every certificate in a bundle. The asymmetry is\n * easy to miss because the failure is `UNABLE_TO_GET_CRL`, which reads as \"no\n * CRL supplied\" rather than \"your bundle was truncated\", and because a bundle\n * whose first entry happens to cover the root in use appears to work until the\n * roster or the export order changes.\n *\n * The bundle is the standard interchange format — Apache documents\n * `SSLCARevocationFile` as \"the concatenation of the various PEM-encoded CRL\n * files\", nginx and HAProxy agree, and `openssl verify -CRLfile` reads a whole\n * bundle — so callers reasonably hand one over. Splitting it here means they\n * do not have to know about Node's divergence from the library it links.\n *\n * The truncation applies per STRING, not per argument, so an array is walked\n * and each string element split in turn: `[bundleOfTwo, oneCrl]` would\n * otherwise contribute one CRL where it should contribute three. A string\n * inside an array is treated exactly like a string passed on its own.\n *\n * Anything that is not a multi-CRL string is returned untouched: a Buffer may\n * be DER, and a string with zero or one match is passed through so Node\n * reports its own error rather than having one masked here.\n */\nfunction normalizeCRL(\n crl: string | Buffer | Array<string | Buffer>,\n): string | Buffer | Array<string | Buffer> {\n if (Array.isArray(crl)) {\n return crl.flatMap((entry) => normalizeCRLEntry(entry));\n }\n\n return normalizeCRLEntry(crl);\n}\n\n/**\n * One `crl` value — or one element of an array of them.\n *\n * Buffers cannot simply be passed through. `fs.readFileSync('bundle.pem')`\n * without an encoding returns a Buffer, which is the DEFAULT way to load a\n * file, and its contents are PEM text like any other bundle — so Node reads\n * only its first CRL exactly as it would from a string. Measured: a stale CRL\n * followed by one revoking the server's certificate accepted the connection as\n * a Buffer, and rejected it once split.\n *\n * So the question is not \"string or Buffer\" but \"PEM or DER\". A Buffer holding\n * PEM goes through the same splitting and validation as a string; a Buffer\n * holding DER — which has no armour to look for — is passed through untouched,\n * since DER encodes exactly one CRL and there is nothing to split.\n */\nfunction normalizeCRLEntry(entry: string | Buffer): string | Buffer | string[] {\n if (typeof entry === 'string') {\n return splitCRLString(entry);\n }\n\n // latin1 is a byte-for-byte mapping, so PEM (which is ASCII) survives\n // exactly and DER cannot be corrupted by the round trip — it is only being\n // inspected, and is returned as the original Buffer either way.\n const text = entry.toString('latin1');\n\n if (!text.includes(PEM_CRL_HEADER)) {\n return entry;\n }\n\n const split = splitCRLString(text);\n\n // A single block stays a Buffer: it needed no splitting, so hand back what\n // the caller gave rather than a re-encoded copy of it.\n return Array.isArray(split) ? split : entry;\n}\n\nfunction splitCRLString(crl: string): string | string[] {\n const blocks = crl.match(PEM_CRL_BLOCK) ?? [];\n\n // Returning only the complete blocks would DISCARD whatever did not match,\n // so a truncated or corrupted CRL in the bundle would vanish silently and\n // the caller would enforce a set they did not supply. With the rest of the\n // bundle still present, a stale-but-complete CRL for the same issuer stays\n // in force while the newer, broken one is dropped.\n //\n // Passing the original string through instead does not help: Node reads its\n // first CRL and ignores the remainder, so a bundle whose first entry is\n // intact still swallows the damage without an error.\n //\n // So refuse anything that is not a complete block or whitespace.\n const residue = crl.replace(PEM_CRL_BLOCK, '');\n\n if (RESIDUE_OUTSIDE_PEM_BLOCKS.test(residue)) {\n throw new Error(\n 'NodeAdapter: the `crl` value contains content outside any complete ' +\n '-----BEGIN/END X509 CRL----- block. Only PEM CRL blocks separated ' +\n 'by whitespace are accepted, because a truncated CRL and a line of ' +\n 'commentary are indistinguishable — accepting the second would mean ' +\n 'silently dropping the first and enforcing an incomplete revocation ' +\n 'set. Common causes: a partial download, a clipped or mistyped ' +\n 'delimiter, or decoded text from `openssl ... -text`, which must be ' +\n 'stripped before the bundle is passed here.',\n );\n }\n\n return blocks.length > 1 ? blocks : crl;\n}\n\nfunction normalizeResponseHeaders(\n headers: http.IncomingHttpHeaders,\n): Record<string, string | string[]> {\n const result: Record<string, string | string[]> = {};\n\n for (const [key, value] of Object.entries(headers)) {\n if (value === undefined) {\n continue;\n }\n // Keys are already lowercase from Node's http parser\n result[key] = Array.isArray(value) ? value : String(value);\n }\n\n return result;\n}\n\nfunction snapshotEffectiveRequestHeaders(\n req: http.ClientRequest,\n fallbackHeaders: Record<string, string | string[]>,\n): Record<string, string | string[]> {\n return normalizeNodeRequestHeaders({\n // Start with the client-level attempt headers, then overlay any adapter-side\n // mutations (for example multipart Content-Type/Content-Length).\n ...fallbackHeaders,\n ...req.getHeaders(),\n });\n}\n\nfunction resolveAdapterResponse(\n resolve: (response: AdapterResponse | PromiseLike<AdapterResponse>) => void,\n req: http.ClientRequest,\n requestURL: string,\n fallbackHeaders: Record<string, string | string[]>,\n response: Omit<AdapterResponse, 'effectiveRequestHeaders'>,\n): void {\n const detectedRedirectURL = resolveDetectedRedirectURL(\n requestURL,\n response.status,\n response.headers,\n );\n\n resolve({\n ...response,\n // Flag redirect responses so HTTPClient can surface wasRedirectDetected on\n // the final HTTPResponse consistently across all adapters. The actual\n // follow-or-disable decision is still made by HTTPClient's redirect loop.\n wasRedirectDetected: REDIRECT_STATUS_CODES.has(response.status),\n ...(detectedRedirectURL ? { detectedRedirectURL } : {}),\n effectiveRequestHeaders: snapshotEffectiveRequestHeaders(\n req,\n fallbackHeaders,\n ),\n });\n}\n\nfunction destroyWritableQuietly(writable: WritableLike): void {\n try {\n writable.destroy();\n } catch {\n // Best-effort cleanup only. Preserve the original stream failure.\n }\n}\n\nfunction removeWritableListener(\n writable: WritableLike,\n event: 'drain' | 'error',\n listener: (() => void) | ((error: Error) => void),\n): void {\n const removable = writable as WritableLike & {\n off?: (\n event: 'drain' | 'error',\n listener: (() => void) | ((error: Error) => void),\n ) => WritableLike;\n };\n\n removable.off?.(event, listener);\n}\n\nfunction makeResponseStreamError(message: string, cause?: Error): Error {\n const error = new Error(message);\n\n if (cause) {\n error.cause = cause;\n }\n\n return error;\n}\n\nfunction markStreamFactoryError(\n error: unknown,\n req: http.ClientRequest,\n fallbackHeaders: Record<string, string | string[]>,\n): Error {\n const normalized = error instanceof Error ? error : new Error(String(error));\n const tagged = normalized as Error &\n Partial<\n Record<typeof NON_RETRYABLE_HTTP_CLIENT_CALLBACK_ERROR_FLAG, boolean>\n >;\n\n tagged[NON_RETRYABLE_HTTP_CLIENT_CALLBACK_ERROR_FLAG] = true;\n Object.assign(normalized, { [STREAM_FACTORY_ERROR_FLAG]: true });\n Object.assign(normalized, {\n effectiveRequestHeaders: snapshotEffectiveRequestHeaders(\n req,\n fallbackHeaders,\n ),\n });\n\n return normalized;\n}\n\nfunction markResponseStreamAbortError(\n error: Error,\n req: http.ClientRequest,\n fallbackHeaders: Record<string, string | string[]>,\n status: number,\n headers: Record<string, string | string[]>,\n): Error {\n const tagged = error as Error &\n Partial<Record<typeof RESPONSE_STREAM_ABORT_FLAG, boolean>> & {\n effectiveRequestHeaders?: Record<string, string | string[]>;\n streamAbortStatus?: number;\n streamAbortHeaders?: Record<string, string | string[]>;\n };\n\n // Keep the flag sourced from consts while avoiding eslint's false-positive\n // on direct computed assignment into an Error-typed value.\n Object.assign(tagged, { [RESPONSE_STREAM_ABORT_FLAG]: true });\n tagged.effectiveRequestHeaders = snapshotEffectiveRequestHeaders(\n req,\n fallbackHeaders,\n );\n tagged.streamAbortStatus = status;\n tagged.streamAbortHeaders = headers;\n\n return error;\n}\n\nfunction isStreamResponseCancel(value: unknown): value is StreamResponseCancel {\n return (\n value !== null &&\n typeof value === 'object' &&\n (value as StreamResponseCancel).cancel === true\n );\n}\n","import type { HTTPMethod } from './types';\n\n/**\n * HTTP responses that are plausibly transient and worth retrying when a retry\n * policy is explicitly enabled.\n *\n * `status === 0` is included on purpose because browser/XHR-style adapters can\n * surface \"no real HTTP response\" that way when the network is unavailable or\n * the request otherwise fails before a normal status code is received.\n */\nexport const RETRYABLE_STATUS_CODES: ReadonlySet<number> = new Set([\n // 0: Browser/XHR-style \"no response\" status.\n 0,\n\n // 408 Request Timeout\n 408,\n // 429 Too Many Requests\n 429,\n\n // 500 Internal Server Error\n 500,\n // 502 Bad Gateway\n 502,\n // 503 Service Unavailable\n 503,\n // 504 Gateway Timeout\n 504,\n\n // 507 Insufficient Storage\n 507,\n // 509 Bandwidth Limit Exceeded (non-standard)\n 509,\n // 520 Unknown Error (Cloudflare)\n 520,\n // 521 Web Server Is Down (Cloudflare)\n 521,\n // 522 Connection Timed Out (Cloudflare)\n 522,\n // 523 Origin Is Unreachable (Cloudflare)\n 523,\n // 524 A Timeout Occurred (Cloudflare)\n 524,\n // 598 Network Read Timeout Error (non-standard)\n 598,\n // 599 Network Connect Timeout Error (non-standard)\n 599,\n]);\n\nexport const DEFAULT_TIMEOUT_MS = 30_000;\n\nexport const DEFAULT_REQUEST_ID_HEADER = 'x-local-client-request-id';\n\nexport const DEFAULT_REQUEST_ATTEMPT_HEADER = 'x-local-client-request-attempt';\n\nexport const DEFAULT_USER_AGENT = 'lifecycleion-http-client';\n\nexport const NON_RETRYABLE_HTTP_CLIENT_CALLBACK_ERROR_FLAG =\n '_lifecycleion_non_retryable_http_client_callback_error';\n\nexport const STREAM_FACTORY_ERROR_FLAG = '_lifecycleion_stream_factory_error';\n\n/**\n * Attached to the AbortError thrown when a StreamResponseFactory returns null\n * or `{ cancel: true, reason? }`. The value is the reason string if provided,\n * or `true` if the factory cancelled without a reason. Lets HTTPClient surface\n * the reason on HTTPClientError.cancelReason.\n */\nexport const STREAM_FACTORY_CANCEL_KEY =\n '_lifecycleion_stream_factory_cancel_reason';\n\nexport const RESPONSE_STREAM_ABORT_FLAG = '_lifecycleion_response_stream_abort';\n\n/**\n * Set on the AbortError thrown by XHRAdapter's defensive `timeout` event\n * listener. Lets HTTPClient classify the error as a timeout (retryable) rather\n * than an unexpected abort (non-retryable cancel).\n */\nexport const XHR_BROWSER_TIMEOUT_FLAG = '_lifecycleion_xhr_browser_timeout';\n\nexport const HTTP_METHODS: ReadonlyArray<HTTPMethod> = [\n 'GET',\n 'POST',\n 'PUT',\n 'PATCH',\n 'DELETE',\n 'HEAD',\n];\n\n/**\n * Exact-match request headers that browsers either forbid outright or do not\n * let this client set reliably via plain Fetch/XHR headers.\n *\n * Prefix-based rules like `proxy-*` and `sec-*` are handled in `header-utils.ts`.\n */\nexport const BROWSER_RESTRICTED_HEADERS: ReadonlySet<string> = new Set([\n // Encoding / CORS negotiation headers controlled by the browser.\n 'accept-charset',\n 'accept-encoding',\n 'access-control-request-headers',\n 'access-control-request-method',\n 'access-control-request-private-network',\n\n // Connection-level transport headers.\n 'connection',\n 'content-length',\n 'date',\n 'expect',\n 'host',\n 'keep-alive',\n 'te',\n 'trailer',\n 'transfer-encoding',\n 'upgrade',\n 'via',\n\n // Browser-managed request context / privacy headers.\n 'cookie',\n 'dnt',\n 'origin',\n 'referer',\n 'set-cookie',\n 'user-agent',\n]);\n\nexport const BROWSER_RESTRICTED_HEADER_PREFIXES: ReadonlyArray<string> = [\n 'proxy-',\n 'sec-',\n];\n\n/**\n * Headers that can tunnel the real method through POST. Browsers block these\n * when they try to smuggle forbidden transport methods.\n */\nexport const BROWSER_METHOD_OVERRIDE_HEADER_NAMES: ReadonlySet<string> =\n new Set(['x-http-method', 'x-http-method-override', 'x-method-override']);\n\n/**\n * Methods that browsers do not allow request headers to tunnel via the\n * override headers above.\n */\nexport const BROWSER_FORBIDDEN_METHOD_OVERRIDE_VALUES: ReadonlySet<string> =\n new Set(['connect', 'trace', 'track']);\n\nexport const DEFAULT_MAX_REDIRECTS = 5;\n\n/**\n * Redirect responses that carry a follow-up `Location` hop. `300` and `304`\n * are excluded because they do not represent an automatic redirect here.\n */\nexport const REDIRECT_STATUS_CODES: ReadonlySet<number> = new Set([\n // 301 Moved Permanently\n 301,\n // 302 Found\n 302,\n\n // 303 See Other\n 303,\n\n // 307 Temporary Redirect\n 307,\n // 308 Permanent Redirect\n 308,\n]);\n","// cspell:ignore WHATWG\n/**\n * Multipart/form-data serialization for Node.js HTTP requests.\n *\n * RFC 7578 defines the multipart/form-data wire format. Each field in the\n * FormData becomes a \"part\" in the body, separated by a boundary string:\n *\n * --<boundary>\\r\\n\n * Content-Disposition: form-data; name=\"username\"\\r\\n\n * \\r\\n\n * alice\\r\\n\n * --<boundary>\\r\\n\n * Content-Disposition: form-data; name=\"avatar\"; filename=\"photo.jpg\"\\r\\n\n * Content-Type: image/jpeg\\r\\n\n * \\r\\n\n * <binary file bytes>\\r\\n\n * --<boundary>--\\r\\n ← trailing \"--\" marks end of body\n *\n * The boundary is a random string that must not appear in any field value or\n * file content. Each part has its own mini-headers (Content-Disposition,\n * optionally Content-Type), followed by a blank line, then the value.\n *\n * This module uses a two-pass approach:\n * Pass 1 — calculateMultipartFormDataSize: walk all fields, count exact byte length.\n * Pass 2 — serializeMultipartFormData: write boundary + headers + value for each field.\n *\n * Knowing the total size upfront lets us set an exact Content-Length header,\n * which makes upload progress length-computable (a real 0–100%) rather than\n * indeterminate. Without Content-Length the browser/server can't tell the\n * client how far along it is.\n *\n * File/blob parts are read chunk-by-chunk via the Web Streams API reader (a\n * WHATWG standard — available in browsers, Node 18+, and Bun) so large files\n * are never fully buffered in memory. String fields are already in memory so\n * they are written in one shot.\n *\n * The functions accept a RequestBodyWritable instead of a concrete\n * http.ClientRequest so they can be tested and reused without a live socket.\n * http.ClientRequest satisfies the interface structurally.\n */\n\nimport type { AdapterProgressEvent } from '../types';\nimport type { RequestBodyWritable } from './request-body-writable';\n\n/**\n * Formats the filename parameter for a multipart Content-Disposition header.\n *\n * Every filename includes a quoted `filename=\"<fallback>\"` parameter so older\n * multipart parsers still see a usable name.\n *\n * When the original name is not already safe printable ASCII, we also emit an\n * RFC 5987 `filename*=` parameter carrying the exact UTF-8 filename:\n *\n * filename*=UTF-8''<percent-encoded-name>\n *\n * Including both forms maximizes compatibility: RFC 5987-aware parsers use\n * `filename*=` to recover the exact original filename, while older or lenient\n * parsers can still fall back to the sanitized ASCII `filename=` value.\n * Servers that normalize, strip, or rename uploads on their side will use\n * whichever form they support.\n *\n * The quoted `filename=` fallback is intentionally ASCII-safe and lossy when\n * needed. That avoids parser-dependent handling of quoted-string escaping while\n * still preserving a broadly compatible fallback for runtimes that ignore\n * `filename*=`.\n *\n * The RFC 5987 encoding here is `encodeURIComponent(...)` plus escaping for\n * `'`, `(`, `)`, and `*`, which must also be percent-encoded.\n */\nfunction formatFilename(filename: string): string {\n const fallback = toSafeASCIIQuotedFilenameFallback(filename);\n const base = isSafeASCIIQuotedFilename(filename)\n ? `filename=\"${filename}\"`\n : `filename=\"${fallback}\"`;\n\n if (isSafeASCIIQuotedFilename(filename)) {\n return base;\n }\n\n const encoded = encodeURIComponent(filename)\n .replace(/'/g, '%27')\n .replace(/\\(/g, '%28')\n .replace(/\\)/g, '%29')\n .replace(/\\*/g, '%2A');\n\n return `${base}; filename*=UTF-8''${encoded}`;\n}\n\nfunction isSafeASCIIQuotedFilename(value: string): boolean {\n return /^[\\u0020-\\u007E]+$/.test(value) && !/[\"\\\\]/.test(value);\n}\n\nfunction toSafeASCIIQuotedFilenameFallback(value: string): string {\n const normalized = value.normalize('NFKD');\n let result = '';\n let wasUnderscore = false;\n\n for (const char of normalized) {\n const code = char.charCodeAt(0);\n\n // Drop combining marks introduced by NFKD decomposition.\n if (code >= 0x0300 && code <= 0x036f) {\n continue;\n }\n\n // Allow a narrow filename-safe ASCII subset in the quoted fallback.\n // Everything else collapses to `_` so older parsers still get a stable,\n // unambiguous name without needing quoted-string escaping.\n const isSafeASCII =\n // 0-9\n (code >= 0x30 && code <= 0x39) ||\n // A-Z\n (code >= 0x41 && code <= 0x5a) ||\n // a-z\n (code >= 0x61 && code <= 0x7a) ||\n char === '.' ||\n char === '-' ||\n char === '_';\n\n if (isSafeASCII) {\n result += char;\n wasUnderscore = false;\n } else if (!wasUnderscore) {\n result += '_';\n wasUnderscore = true;\n }\n }\n\n result = result.replace(/^_+|_+$/g, '');\n\n // Avoid an empty fallback if every character was replaced.\n return result.length > 0 ? result : 'file';\n}\n\n/**\n * Formats a multipart field name for the quoted `name=\"...\"` parameter.\n *\n * Field names are not lossy-sanitized like filenames because servers typically\n * expect the original key. We preserve the name while making it safe for a\n * quoted-string parameter by:\n * 1. Collapsing CRLF / CR / LF sequences to a single space so a malicious\n * key cannot inject new headers\n * 2. Stripping remaining control characters (0x00–0x08, 0x0B, 0x0C,\n * 0x0E–0x1F, 0x7F) that are invalid inside a quoted-string per\n * RFC 7230 §3.2.6\n * 3. Escaping `\\` and `\"` per quoted-string rules\n */\nfunction formatFieldName(name: string): string {\n return (\n name\n .replace(/\\r\\n|\\r|\\n/g, ' ')\n // eslint-disable-next-line no-control-regex\n .replace(/[\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]/g, '')\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"')\n );\n}\n\n/**\n * Sanitizes a MIME type string for safe use in a multipart Content-Type header.\n *\n * Browser and Node.js `File.type` values are normally clean, but a\n * programmatically constructed File could carry CR/LF sequences that would\n * inject extra headers into the multipart part. Stripping line breaks\n * eliminates that risk while still forwarding the intended media type.\n */\nfunction sanitizeContentType(raw: string): string {\n return raw.replace(/\\r\\n|\\r|\\n/g, '');\n}\n\n/**\n * Generates a random boundary string for use as the multipart delimiter.\n *\n * The boundary must not appear anywhere in the body content (RFC 7578 §4.1).\n * The \"----\" prefix and random suffix make accidental collision essentially\n * impossible for normal payloads. For adversarial inputs (a file that happens\n * to contain the boundary string) you would need boundary detection — we don't\n * do that here, matching browser FormData behavior.\n */\nexport function generateMultipartBoundary(): string {\n return `----NodeAdapterFormBoundary${Math.random().toString(36).slice(2)}`;\n}\n\n/**\n * Returns the exact byte length of the multipart body for a given FormData\n * and boundary — without writing anything.\n *\n * This mirrors the exact structure that serializeMultipartFormData will\n * produce, so the two functions must stay in sync.\n *\n * Why this exists: Node can stream the body without precomputing it, but then\n * upload progress would be indeterminate. By counting the exact bytes first,\n * we can set Content-Length up front and report real 0–100% progress during\n * serialization instead of guesswork.\n */\nexport function calculateMultipartFormDataSize(\n formData: FormData,\n boundary: string,\n): number {\n let size = 0;\n\n for (const [name, value] of formData.entries()) {\n const fieldName = formatFieldName(name);\n\n // Every part starts with its own opening boundary line.\n size += Buffer.byteLength(`--${boundary}\\r\\n`);\n\n if (typeof value === 'string') {\n // String part wire format:\n // Content-Disposition header\n // blank line\n // raw string value\n // trailing CRLF before the next boundary\n size += Buffer.byteLength(\n `Content-Disposition: form-data; name=\"${fieldName}\"\\r\\n\\r\\n${value}\\r\\n`,\n );\n } else {\n const filename = (value as File).name || 'blob';\n const contentType =\n sanitizeContentType((value as File).type) || 'application/octet-stream';\n\n // File/blob part wire format:\n // Content-Disposition header (with filename)\n // Content-Type header\n // blank line\n // raw file bytes\n // trailing CRLF before the next boundary\n size += Buffer.byteLength(\n `Content-Disposition: form-data; name=\"${fieldName}\"; ${formatFilename(filename)}\\r\\nContent-Type: ${contentType}\\r\\n\\r\\n`,\n );\n // Blob.size is already the exact byte length of the binary payload.\n size += (value as Blob).size;\n size += Buffer.byteLength('\\r\\n');\n }\n }\n\n // Final terminating boundary line — note the extra trailing `--`.\n size += Buffer.byteLength(`--${boundary}--\\r\\n`);\n return size;\n}\n\n/**\n * Writes a FormData body into a RequestBodyWritable following the\n * multipart/form-data wire format (RFC 7578), setting Content-Type and\n * Content-Length headers first.\n *\n * This does not build one giant multipart buffer in memory. It writes the\n * envelope (boundaries + headers) directly and streams file/blob payloads\n * chunk-by-chunk from Blob.stream().\n */\nexport async function serializeMultipartFormData(\n formData: FormData,\n req: RequestBodyWritable,\n boundary: string,\n onProgress?: (e: AdapterProgressEvent) => void,\n): Promise<void> {\n const totalSize = calculateMultipartFormDataSize(formData, boundary);\n\n // Servers need the boundary to parse the multipart body. We set an exact\n // Content-Length too, so upload progress is based on known total bytes\n // rather than HTTP chunked-transfer behavior.\n req.setHeader('Content-Type', `multipart/form-data; boundary=${boundary}`);\n req.setHeader('Content-Length', totalSize.toString());\n\n let uploadedBytes = 0;\n\n const write = (data: string | Buffer | Uint8Array): Promise<void> =>\n new Promise<void>((resolve, reject) => {\n let hasWriteReturned = false;\n let isWriteCallbackDone = false;\n let isDrainDone = true;\n\n const cleanup = (): void => {\n req.off('drain', onDrain);\n req.off('close', onClose);\n req.off('error', onError);\n };\n\n const maybeResolve = (): void => {\n if (isWriteCallbackDone && isDrainDone) {\n cleanup();\n uploadedBytes += Buffer.byteLength(data);\n\n onProgress?.({\n loaded: uploadedBytes,\n total: totalSize,\n progress: uploadedBytes / totalSize,\n });\n resolve();\n }\n };\n\n const onDrain = (): void => {\n isDrainDone = true;\n maybeResolve();\n };\n\n const onClose = (): void => {\n cleanup();\n resolve();\n };\n\n const onError = (error: Error): void => {\n cleanup();\n reject(error);\n };\n\n const canContinue = req.write(data, (error: Error | null | undefined) => {\n if (error) {\n cleanup();\n reject(error);\n return;\n }\n\n isWriteCallbackDone = true;\n if (hasWriteReturned) {\n maybeResolve();\n }\n });\n hasWriteReturned = true;\n\n if (!canContinue) {\n isDrainDone = false;\n req.once('drain', onDrain);\n req.once('close', onClose);\n req.once('error', onError);\n\n if (req.destroyed) {\n onClose();\n }\n }\n\n maybeResolve();\n });\n\n for (const [name, value] of formData.entries()) {\n const fieldName = formatFieldName(name);\n\n if (req.destroyed) {\n break;\n }\n\n // Start this part with its boundary delimiter.\n await write(`--${boundary}\\r\\n`);\n\n if (typeof value === 'string') {\n // Simple field: one header, blank line, then the field value.\n await write(\n `Content-Disposition: form-data; name=\"${fieldName}\"\\r\\n\\r\\n${value}\\r\\n`,\n );\n } else {\n const filename = (value as File).name || 'blob';\n const contentType =\n sanitizeContentType((value as File).type) || 'application/octet-stream';\n\n // File/blob field: multipart headers first, then the binary payload.\n await write(\n `Content-Disposition: form-data; name=\"${fieldName}\"; ${formatFilename(filename)}\\r\\nContent-Type: ${contentType}\\r\\n\\r\\n`,\n );\n\n // Blob.stream() lets us forward large files piece-by-piece instead of\n // concatenating the whole payload into a single upload buffer.\n const reader = (value as Blob)\n .stream()\n .getReader() as ReadableStreamDefaultReader<Uint8Array>;\n\n try {\n while (true) {\n if (req.destroyed) {\n await cancelReaderQuietly(reader);\n break;\n }\n\n const { done: isDone, value: chunk } = await reader.read();\n\n if (isDone) {\n break;\n }\n\n if (req.destroyed) {\n await cancelReaderQuietly(reader);\n break;\n }\n\n if (chunk) {\n // Each chunk contributes to upload progress immediately after write.\n await write(chunk);\n }\n }\n } finally {\n if (req.destroyed) {\n await cancelReaderQuietly(reader);\n }\n }\n\n if (!req.destroyed) {\n // Multipart parts are separated by CRLF after the value bytes too.\n await write('\\r\\n');\n }\n }\n }\n\n if (!req.destroyed) {\n // Closing delimiter that tells the server there are no more parts.\n await write(`--${boundary}--\\r\\n`);\n }\n}\n\nasync function cancelReaderQuietly(\n reader: ReadableStreamDefaultReader<Uint8Array>,\n): Promise<void> {\n try {\n await reader.cancel();\n } catch {\n // Best-effort cancellation only. Preserve the original request outcome.\n }\n}\n","import type { AdapterProgressEvent } from '../types';\nimport type { RequestBodyWritable } from './request-body-writable';\n\n/**\n * 16 KB chunks: meaningful upload progress granularity (a 1 MB body fires ~64\n * events) without excessive syscall overhead from tiny writes.\n */\nexport const REQUEST_BODY_CHUNK_SIZE = 16 * 1024;\n\n/**\n * Writes a pre-serialized request body buffer (string or Uint8Array body, not\n * FormData) into a RequestBodyWritable in fixed-size chunks.\n *\n * Each chunk awaits its write callback before the next chunk is sent. This\n * serves two purposes:\n * 1. Progress accuracy — each event reflects bytes actually handed off to the\n * OS socket buffer, not just bytes queued in the JS write buffer.\n * 2. Backpressure — we don't race ahead of the socket; if the OS buffer is\n * full the await naturally yields until there's room.\n *\n * This is intentionally separate from multipart handling. Multipart uploads\n * need boundaries/part headers and may stream Blob chunks directly; this\n * helper is only for bodies that are already serialized into one Buffer.\n */\nexport async function writeRequestBodyChunked(\n data: Buffer,\n req: RequestBodyWritable,\n onProgress?: (e: AdapterProgressEvent) => void,\n): Promise<void> {\n const totalSize = data.length;\n\n if (totalSize === 0) {\n // Empty explicit bodies still need a terminal completion event so progress\n // consumers do not wait forever for a 100% upload signal.\n onProgress?.({ loaded: 0, total: 0, progress: 1 });\n return;\n }\n\n let uploadedBytes = 0;\n\n while (uploadedBytes < totalSize && !req.destroyed) {\n // Slice the next fixed-size window from the already-serialized payload.\n const chunk = data.subarray(\n uploadedBytes,\n uploadedBytes + REQUEST_BODY_CHUNK_SIZE,\n );\n\n // Wait for both the write callback and any required drain signal before\n // moving on so large uploads do not outrun socket backpressure.\n await writeChunkWithBackpressure(req, chunk, () => {\n // Only count bytes after the writable confirms the chunk was accepted.\n uploadedBytes += chunk.length;\n onProgress?.({\n loaded: uploadedBytes,\n total: totalSize,\n progress: uploadedBytes / totalSize,\n });\n });\n }\n}\n\nfunction writeChunkWithBackpressure(\n req: RequestBodyWritable,\n chunk: Buffer,\n onAccepted?: () => void,\n): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n let hasWriteReturned = false;\n let isWriteCallbackDone = false;\n let isDrainDone = true;\n let isSettled = false;\n\n const cleanup = (): void => {\n req.off('drain', onDrain);\n req.off('close', onClose);\n req.off('error', onError);\n };\n\n const maybeResolve = (): void => {\n if (isSettled || !isWriteCallbackDone || !isDrainDone) {\n return;\n }\n\n isSettled = true;\n cleanup();\n onAccepted?.();\n resolve();\n };\n\n const onDrain = (): void => {\n isDrainDone = true;\n maybeResolve();\n };\n\n const onClose = (): void => {\n if (isSettled) {\n return;\n }\n\n isSettled = true;\n cleanup();\n resolve();\n };\n\n const onError = (error: Error): void => {\n if (isSettled) {\n return;\n }\n\n isSettled = true;\n cleanup();\n reject(error);\n };\n\n const canContinue = req.write(\n chunk,\n (error: Error | null | undefined): void => {\n if (error) {\n cleanup();\n reject(error);\n return;\n }\n\n isWriteCallbackDone = true;\n if (hasWriteReturned) {\n maybeResolve();\n }\n },\n );\n hasWriteReturned = true;\n\n if (!canContinue) {\n isDrainDone = false;\n req.once('drain', onDrain);\n req.once('close', onClose);\n req.once('error', onError);\n\n if (req.destroyed) {\n onClose();\n }\n }\n\n maybeResolve();\n });\n}\n","const CERT_ERROR_CODES = new Set([\n 'UNABLE_TO_VERIFY_LEAF_SIGNATURE',\n 'CERT_HAS_EXPIRED',\n 'DEPTH_ZERO_SELF_SIGNED_CERT',\n 'ERR_TLS_CERT_ALTNAME_INVALID',\n 'ERR_SSL_TLSV13_ALERT_CERTIFICATE_REQUIRED',\n 'ERR_SSL_PEER_DID_NOT_RETURN_A_CERTIFICATE',\n 'SELF_SIGNED_CERT_IN_CHAIN',\n 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY',\n 'CERT_REVOKED',\n]);\n\n/**\n * Best-effort classification for TLS certificate failures that should be\n * treated as non-retryable transport errors by NodeAdapter.\n */\nexport function isTLSCertificateError(error: Error): boolean {\n const code = (error as NodeJS.ErrnoException).code ?? '';\n\n if (CERT_ERROR_CODES.has(code)) {\n return true;\n }\n\n // Every CRL-related verification code, by property rather than by name.\n // OpenSSL exposes twelve of them (UNABLE_TO_GET_CRL,\n // UNABLE_TO_DECRYPT_CRL_SIGNATURE, CRL_HAS_EXPIRED, KEYUSAGE_NO_CRL_SIGN,\n // ERROR_IN_CRL_NEXT_UPDATE_FIELD, DIFFERENT_CRL_SCOPE, …) and an earlier\n // version of this listed six by hand and missed the other six. They are all\n // revocation-check failures, and enumerating them means being wrong again\n // the next time OpenSSL adds one — so ask whether the code concerns a CRL\n // instead. The message fallback below does not catch them: 'CRL has expired'\n // and \"format error in CRL's lastUpdate field\" contain none of the words it\n // looks for, so without this they resolve as status 0 rather than 495.\n if (code.includes('CRL')) {\n return true;\n }\n\n if (\n (code.startsWith('ERR_TLS_') || code.startsWith('ERR_SSL_')) &&\n code.includes('CERT')\n ) {\n return true;\n }\n\n return /certificate|self signed|unable to verify|altname/i.test(\n error.message,\n );\n}\n","export function normalizeNodeRequestHeaders(\n headers: Record<string, string | string[] | number | undefined>,\n): Record<string, string | string[]> {\n const result: Record<string, string | string[]> = {};\n\n for (const [key, value] of Object.entries(headers)) {\n if (value === undefined) {\n continue;\n }\n\n result[key.toLowerCase()] = Array.isArray(value)\n ? value.map((item) => String(item))\n : String(value);\n }\n\n return result;\n}\n\nexport function materializeNodeRequestHeaders(\n headers: Record<string, string | string[]>,\n): Record<string, string> {\n const result: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(headers)) {\n if (!Array.isArray(value)) {\n result[key] = value;\n continue;\n }\n\n result[key] =\n key.toLowerCase() === 'cookie'\n ? // RFC 6265 cookie-pair delimiter for a single Cookie header field.\n value.join('; ')\n : value.join(', ');\n }\n\n return result;\n}\n","import qs from 'qs';\nimport {\n matchesWildcardDomain,\n normalizeDomain,\n} from '../domain-utils/domain-utils';\nimport type {\n ContentType,\n RequestPhaseName,\n HTTPClientConfig,\n AdapterType,\n} from './types';\n\n/**\n * If `path` is an absolute HTTP(S) URL, returns its canonical `href` (normalized\n * scheme/host casing). Otherwise `null`. Protocol-relative `//host` is handled\n * separately in `buildURL`.\n *\n * Rules differ from `resolveAbsoluteURL` (which accepts any absolute scheme).\n * One parse here per request is negligible next to network I/O.\n */\nfunction tryAbsoluteWebHref(path: string): string | null {\n if (!path || path.startsWith('//')) {\n return null;\n }\n\n try {\n const u = new URL(path);\n if (u.protocol === 'http:' || u.protocol === 'https:') {\n return u.href;\n }\n } catch {\n // not parseable as absolute\n }\n\n return null;\n}\n\n/**\n * Builds a request URL string from `baseURL`, `path`, and optional query params\n * (`qs` — nested objects and arrays supported).\n *\n * **Relative paths (usual case)** — When `baseURL` is set and `path` is not\n * absolute, `path` is joined to `baseURL` (leading slash normalized). Example:\n * `baseURL: https://api.test`, `path: /v1/users` → `https://api.test/v1/users`.\n *\n * **Absolute / protocol-relative `path` (escape hatch)** — If `path` is a full\n * `http:` or `https:` URL, it is **not** prefixed with `baseURL` (after\n * normalization via `URL#href`). The same applies to protocol-relative URLs\n * (`//cdn.example/x`): they are left for {@link resolveAbsoluteURL} to resolve\n * using the client `baseURL`’s scheme. Use this for one-off cross-origin calls,\n * CDN assets, or URLs returned by APIs; for strict per-origin clients, prefer\n * relative paths and a dedicated client or `HTTPClient.createSubClient()` per\n * origin.\n */\nexport function buildURL(\n baseURL: string | undefined,\n path: string,\n params?: Record<string, unknown>,\n): string {\n let url: string;\n\n const absoluteHref = tryAbsoluteWebHref(path);\n\n if (baseURL && absoluteHref === null && !path.startsWith('//')) {\n // Avoid double slashes when joining base + path\n const base = baseURL.endsWith('/') ? baseURL.slice(0, -1) : baseURL;\n const p = path.startsWith('/') ? path : `/${path}`;\n url = `${base}${p}`;\n } else if (absoluteHref !== null) {\n url = absoluteHref;\n } else {\n url = path;\n }\n\n if (params && Object.keys(params).length > 0) {\n const [urlWithoutHash, hash = ''] = url.split('#', 2);\n const queryStartIndex = urlWithoutHash.indexOf('?');\n\n if (queryStartIndex === -1) {\n const queryString = qs.stringify(params, { addQueryPrefix: true });\n url = `${urlWithoutHash}${queryString}${hash ? `#${hash}` : ''}`;\n } else {\n const basePath = urlWithoutHash.slice(0, queryStartIndex);\n const existingQuery = urlWithoutHash.slice(queryStartIndex + 1);\n // Fragments are preserved only as part of the caller's URL string.\n // They are not transmitted in HTTP requests, but keeping them intact\n // makes buildURL safer as a general-purpose URL composition helper.\n const mergedParams = {\n ...qs.parse(existingQuery),\n ...params,\n };\n\n const queryString = qs.stringify(mergedParams, { addQueryPrefix: true });\n url = `${basePath}${queryString}${hash ? `#${hash}` : ''}`;\n }\n }\n\n return url;\n}\n\n/**\n * Best-effort absolute URL for logging, redirects, and hop metadata.\n *\n * - If `url` parses as an absolute URL (has a scheme), returns normalized `href`.\n * - Otherwise, when `baseURL` is set, resolves `url` against it (path-relative,\n * same-host relative, protocol-relative `//host`, query-only, etc.).\n * - If neither works, returns `url` unchanged (callers without `baseURL` may still\n * see path-only strings).\n */\nexport function resolveAbsoluteURL(url: string, baseURL?: string): string {\n if (!url) {\n return url;\n }\n\n try {\n return new URL(url).href;\n } catch {\n // Not a standalone absolute URL\n }\n\n if (baseURL) {\n try {\n const base = baseURL.endsWith('/') ? baseURL : `${baseURL}/`;\n return new URL(url, base).href;\n } catch {\n // fall through\n }\n }\n\n return url;\n}\n\n/**\n * Browser-aware absolute URL resolution used by HTTPClient before interceptors\n * and adapter dispatch. Starts with normal baseURL resolution, then falls back\n * to the current page/worker location when running in a browser-like runtime.\n */\nexport function resolveAbsoluteURLForRuntime(\n url: string,\n baseURL: string | undefined,\n isBrowserRuntime: boolean,\n): string {\n const resolved = resolveAbsoluteURL(url, baseURL);\n\n if (\n !isBrowserRuntime ||\n resolved.startsWith('http://') ||\n resolved.startsWith('https://')\n ) {\n return resolved;\n }\n\n const browserBase = getBrowserResolutionBase();\n\n if (!browserBase) {\n return resolved;\n }\n\n return resolveAbsoluteURL(resolved, browserBase);\n}\n\nfunction getBrowserResolutionBase(): string | undefined {\n if (\n typeof document !== 'undefined' &&\n typeof document.baseURI === 'string' &&\n document.baseURI\n ) {\n return document.baseURI;\n }\n\n if (\n typeof window !== 'undefined' &&\n window.location &&\n typeof window.location.href === 'string' &&\n window.location.href\n ) {\n return window.location.href;\n }\n\n const globalLocation = (globalThis as { location?: { href?: unknown } })\n .location;\n\n if (\n globalLocation &&\n typeof globalLocation.href === 'string' &&\n globalLocation.href\n ) {\n return globalLocation.href;\n }\n\n const selfLocation = (\n globalThis as { self?: { location?: { href?: unknown } } }\n ).self?.location;\n\n if (\n selfLocation &&\n typeof selfLocation.href === 'string' &&\n selfLocation.href\n ) {\n return selfLocation.href;\n }\n\n return undefined;\n}\n\n/**\n * Normalizes header keys to lowercase.\n */\nexport function normalizeHeaders(\n headers: Record<string, string>,\n): Record<string, string> {\n const result: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(headers)) {\n result[key.toLowerCase()] = value;\n }\n\n return result;\n}\n\n/**\n * Merges multiple request-header objects, normalizing keys to lowercase.\n * Later objects win on conflict. Array values replace earlier scalars/arrays\n * wholesale, and single-item arrays are collapsed back to a plain string.\n */\nexport function mergeHeaders(\n ...headerSets: Array<Record<string, string | string[]> | undefined>\n): Record<string, string | string[]> {\n const result: Record<string, string | string[]> = {};\n for (const headers of headerSets) {\n if (!headers) {\n continue;\n }\n\n for (const [key, value] of Object.entries(headers)) {\n result[key.toLowerCase()] = Array.isArray(value)\n ? normalizeMergedHeaderArray(value)\n : String(value);\n }\n }\n\n return result;\n}\n\nfunction normalizeMergedHeaderArray(value: string[]): string | string[] {\n const normalized = value.map((item) => String(item));\n return normalized.length === 1 ? normalized[0] : normalized;\n}\n\nexport function mergeObservedHeaders(\n ...headerSets: Array<Record<string, string | string[]> | undefined>\n): Record<string, string | string[]> {\n const result: Record<string, string | string[]> = {};\n\n for (const headers of headerSets) {\n if (!headers) {\n continue;\n }\n\n for (const [key, value] of Object.entries(headers)) {\n result[key.toLowerCase()] = Array.isArray(value)\n ? value.map((item) => String(item))\n : String(value);\n }\n }\n\n return result;\n}\n\n/**\n * Parses the Content-Type header into a ContentType enum value.\n */\nexport function parseContentType(\n contentTypeHeader: string | undefined,\n): ContentType {\n if (!contentTypeHeader) {\n return 'binary';\n } else {\n const lower = contentTypeHeader.trim().toLowerCase();\n\n if (lower.includes('application/json') || lower.includes('+json')) {\n return 'json';\n } else if (lower.startsWith('text/')) {\n return 'text';\n } else if (lower.includes('application/x-www-form-urlencoded')) {\n return 'text';\n } else {\n return 'binary';\n }\n }\n}\n\n/**\n * Validates adapter/runtime combinations and redirect config before a client\n * is constructed, so unsupported browser-only/server-only options fail fast\n * with clear errors instead of surfacing later during request dispatch.\n */\nexport function assertSupportedAdapterRuntimeAndConfig(\n config: HTTPClientConfig,\n adapterType: AdapterType,\n isBrowserRuntime: boolean,\n): void {\n if (\n config.baseURL !== undefined &&\n requiresAbsoluteBaseURL(adapterType, isBrowserRuntime)\n ) {\n assertValidBaseURL(config.baseURL);\n }\n\n if (config.maxRedirects !== undefined && config.followRedirects !== true) {\n throw new Error('HTTPClient maxRedirects requires followRedirects: true.');\n }\n\n if (\n config.followRedirects === true &&\n config.maxRedirects !== undefined &&\n config.maxRedirects < 1\n ) {\n throw new Error(\n 'HTTPClient maxRedirects must be greater than or equal to 1 when followRedirects is true.',\n );\n }\n\n if (adapterType === 'xhr' && config.followRedirects === true) {\n throw new Error(\n 'HTTPClient redirect handling is not supported with XHR adapter. Set followRedirects: false or use a different adapter/runtime.',\n );\n }\n\n if (adapterType === 'xhr' && !hasXMLHttpRequestGlobal()) {\n throw new Error(\n 'HTTPClient XHR adapter is not supported when XMLHttpRequest is unavailable. Use a browser runtime, install a test shim, or switch to the FetchAdapter/NodeAdapter.',\n );\n }\n\n if (!isBrowserRuntime) {\n return;\n }\n\n if (adapterType === 'node') {\n throw new Error(\n 'HTTPClient Node adapter is not supported in browser environments.',\n );\n }\n\n // MockAdapter is intentionally allowed in browser runtimes: it is an\n // in-memory test adapter, so cookie jars and redirect following are local\n // simulation features rather than forbidden browser networking controls.\n if ((adapterType === 'fetch' || adapterType === 'xhr') && config.cookieJar) {\n throw new Error(\n `HTTPClient cookieJar is not supported with ${adapterType === 'fetch' ? 'FetchAdapter' : 'XHR adapter'} in browser environments. Browsers manage cookies automatically.`,\n );\n }\n\n if ((adapterType === 'fetch' || adapterType === 'xhr') && config.userAgent) {\n throw new Error(\n `HTTPClient userAgent is not supported with ${adapterType === 'fetch' ? 'FetchAdapter' : 'XHR adapter'} in browser environments. Browsers do not allow overriding the User-Agent header.`,\n );\n }\n\n if (adapterType === 'fetch' && config.followRedirects === true) {\n throw new Error(\n 'HTTPClient redirect handling is not supported with FetchAdapter in browser environments. Set followRedirects: false or use a server runtime.',\n );\n }\n}\n\nfunction requiresAbsoluteBaseURL(\n adapterType: AdapterType,\n isBrowserRuntime: boolean,\n): boolean {\n if (adapterType === 'node' || adapterType === 'mock') {\n return true;\n }\n\n if (adapterType === 'fetch' && !isBrowserRuntime) {\n return true;\n }\n\n return false;\n}\n\nfunction hasXMLHttpRequestGlobal(): boolean {\n return (\n typeof globalThis !== 'undefined' &&\n typeof (globalThis as { XMLHttpRequest?: unknown }).XMLHttpRequest ===\n 'function'\n );\n}\n\n/**\n * Converts a Headers object (from fetch) into AdapterResponse headers.\n * `set-cookie` is extracted as `string[]` via `getSetCookie()` — the Fetch API\n * would otherwise incorrectly comma-join multiple Set-Cookie values.\n * All other headers are extracted as plain strings.\n */\nexport function extractFetchHeaders(\n headers: Headers,\n): Record<string, string | string[]> {\n const result: Record<string, string | string[]> = {};\n\n for (const [key, value] of headers.entries()) {\n const lower = key.toLowerCase();\n\n if (lower !== 'set-cookie') {\n result[lower] = value;\n }\n }\n\n // Use getSetCookie() when available (Bun, Node 18.14+, modern browsers)\n if (typeof headers.getSetCookie === 'function') {\n const setCookies = headers.getSetCookie();\n\n if (setCookies.length > 0) {\n result['set-cookie'] = setCookies;\n }\n } else {\n // Fallback: headers.get() comma-joins — split on ', ' is unreliable for\n // cookies but better than nothing on older runtimes\n const raw = headers.get('set-cookie');\n\n if (raw) {\n result['set-cookie'] = [raw];\n }\n }\n\n return result;\n}\n\n/**\n * Lowercases all keys on adapter/response header objects. `HTTPClient` runs\n * this on each adapter response before {@link CookieJar.processResponseHeaders}.\n * The jar also normalizes so the same shapes work when feeding headers directly.\n *\n * - Non–`set-cookie` values: if an array appears (unexpected), the first\n * element is kept when read via {@link scalarHeader}.\n * - `set-cookie`: stored as `string[]` — each array entry is one full\n * `Set-Cookie` header line (one cookie). A single string value becomes a\n * one-element array. If the same header appears under keys that differ only\n * by case, those lines are appended in the order they appear on the input\n * object.\n */\nexport function normalizeAdapterResponseHeaders(\n headers: Record<string, string | string[]>,\n): Record<string, string | string[]> {\n const result: Record<string, string | string[]> = {};\n\n for (const [key, value] of Object.entries(headers)) {\n const lower = key.toLowerCase();\n\n if (lower === 'set-cookie') {\n const chunk = Array.isArray(value) ? value : [value];\n const existing = result[lower];\n\n if (existing === undefined) {\n result[lower] = chunk;\n } else {\n const existingLines = Array.isArray(existing) ? existing : [existing];\n result[lower] = [...existingLines, ...chunk];\n }\n } else {\n result[lower] = Array.isArray(value) ? (value[0] ?? '') : value;\n }\n }\n\n return result;\n}\n\n/**\n * Reads a single-valued header when keys are already lowercase (e.g. after\n * {@link mergeHeaders} on requests or {@link normalizeAdapterResponseHeaders}\n * on responses). If the stored value is `string[]`, returns the first entry.\n */\nexport function scalarHeader(\n headers: Record<string, string | string[]>,\n lowercaseName: string,\n): string | undefined {\n const v = headers[lowercaseName];\n\n if (v === undefined) {\n return undefined;\n }\n\n return Array.isArray(v) ? v[0] : v;\n}\n\n/**\n * Resolves a redirect target from response headers when the adapter can\n * observe a redirect response but the client may not follow it itself.\n */\nexport function resolveDetectedRedirectURL(\n requestURL: string,\n status: number,\n headers: Record<string, string | string[]>,\n baseURL?: string,\n): string | undefined {\n if (![301, 302, 303, 307, 308].includes(status)) {\n return undefined;\n }\n\n const location = scalarHeader(headers, 'location');\n\n if (!location) {\n return undefined;\n }\n\n try {\n const absoluteRequestURL = resolveAbsoluteURL(requestURL, baseURL);\n return new URL(location, absoluteRequestURL).toString();\n } catch {\n return location;\n }\n}\n\nexport function assertValidBaseURL(\n baseURL: string,\n fieldName = 'baseURL',\n): void {\n try {\n const url = new URL(baseURL);\n\n if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n throw new Error('unsupported protocol');\n }\n } catch {\n throw new Error(\n `HTTPClient ${fieldName} must be an absolute http(s) URL (for example \"https://api.example.com\").`,\n );\n }\n}\n\n/**\n * Detects whether the current runtime looks like a browser environment.\n */\nexport function isBrowserEnvironment(): boolean {\n if (typeof globalThis === 'undefined') {\n return false;\n }\n\n if ('window' in globalThis && 'document' in globalThis) {\n return true;\n }\n\n const workerGlobalScope = (\n globalThis as {\n WorkerGlobalScope?: abstract new (...args: never[]) => unknown;\n }\n ).WorkerGlobalScope;\n\n if (\n typeof workerGlobalScope === 'function' &&\n (globalThis as { self?: unknown }).self instanceof workerGlobalScope\n ) {\n return true;\n }\n\n const constructorName = globalThis.constructor?.name;\n\n return (\n !('window' in globalThis) &&\n !('document' in globalThis) &&\n typeof constructorName === 'string' &&\n constructorName.endsWith('WorkerGlobalScope')\n );\n}\n\n/**\n * Serializes the request body and returns the body + inferred content-type.\n * If `formData` is provided, it takes precedence over `body`.\n */\nexport function serializeBody(body: unknown): {\n body: string | Uint8Array | FormData | null;\n contentType: string | null;\n} {\n assertSupportedRequestBody(body);\n\n if (body instanceof FormData) {\n return { body, contentType: null }; // browser/runtime sets multipart boundary automatically\n } else if (body === undefined || body === null) {\n return { body: null, contentType: null };\n } else if (typeof body === 'string') {\n return { body, contentType: 'text/plain; charset=utf-8' };\n } else if (body instanceof Uint8Array) {\n return { body, contentType: 'application/octet-stream' };\n } else if (Array.isArray(body) || isPlainJSONBodyObject(body)) {\n return {\n body: JSON.stringify(body),\n contentType: 'application/json; charset=utf-8',\n };\n } else {\n throw new Error(\n 'Unsupported request body type. Supported types: string, Uint8Array, FormData, plain object, array, null, and undefined.',\n );\n }\n}\n\nexport function assertSupportedRequestBody(body: unknown): void {\n if (\n body === undefined ||\n body === null ||\n typeof body === 'string' ||\n body instanceof Uint8Array ||\n body instanceof FormData ||\n Array.isArray(body) ||\n isPlainJSONBodyObject(body)\n ) {\n return;\n }\n\n throw new Error(\n 'Unsupported request body type. Supported types: string, Uint8Array, FormData, plain object, array, null, and undefined.',\n );\n}\n\nexport function isPlainJSONBodyObject(\n value: unknown,\n): value is Record<string, unknown> {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) {\n return false;\n }\n\n const prototype = Reflect.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n}\n\n/**\n * Extracts the hostname from a URL string. Returns empty string on failure.\n */\nexport function extractHostname(url: string): string {\n try {\n return new URL(url).hostname;\n } catch {\n return '';\n }\n}\n\n/**\n * Wildcard hostname matching backed by {@link matchesWildcardDomain}.\n *\n * - `*` — global wildcard, matches any valid hostname including apex domains\n * - `*.example.com` — matches exactly one subdomain label (`api.example.com`) but not deeper levels or the apex\n * - `**.example.com` — matches one or more subdomain labels (`api.example.com`, `a.b.example.com`) but not the apex\n * - Exact patterns (no `*`) — normalized comparison, case-insensitive\n * - PSL tail guard active: `*.com`, `**.co.uk`, etc. never match\n * - Pseudo-TLD suffix wildcards are rejected just like PSL tails (`*.localhost`, `*.local`, etc.)\n */\nexport function matchesHostPattern(hostname: string, pattern: string): boolean {\n if (pattern.includes('*')) {\n return matchesWildcardDomain(hostname, pattern);\n }\n\n const normalizedHostname = normalizeDomain(hostname);\n const normalizedPattern = normalizeDomain(pattern);\n return normalizedHostname !== '' && normalizedHostname === normalizedPattern;\n}\n\n/**\n * Checks whether a dot-path key exists in a nested object.\n * Arrays are not traversed — only plain objects at each segment.\n */\nfunction hasNestedKey(obj: Record<string, unknown>, path: string): boolean {\n const parts = path.split('.');\n let current: unknown = obj;\n\n for (const part of parts) {\n if (!current || typeof current !== 'object' || Array.isArray(current)) {\n return false;\n }\n\n if (!(part in (current as Record<string, unknown>))) {\n return false;\n }\n\n current = (current as Record<string, unknown>)[part];\n }\n\n return true;\n}\n\nfunction normalizeMimeType(value: string): string {\n return value.split(';', 1)[0].trim().toLowerCase();\n}\n\nfunction matchesContentTypePattern(\n actualHeader: string,\n pattern: string,\n): boolean {\n const actual = normalizeMimeType(actualHeader);\n const expected = normalizeMimeType(pattern);\n\n if (!actual || !expected) {\n return false;\n }\n\n if (expected.endsWith('/*')) {\n const expectedType = expected.slice(0, -2);\n const slashIndex = actual.indexOf('/');\n\n if (slashIndex === -1) {\n return false;\n }\n\n return actual.slice(0, slashIndex) === expectedType;\n }\n\n return actual === expected;\n}\n\n/**\n * Tests whether a request context matches an interceptor/observer filter.\n *\n * Each filter field is optional — omitting it skips that check entirely.\n * All specified fields must match for the function to return true.\n * Within each field, values are matched with OR logic (any one match is sufficient).\n *\n * - `phases`: **OR** allowlist on `phaseType` ({@link RequestPhaseName}). Skipped when\n * `filter.phases` is omitted or empty.\n * - `statusCodes`: skipped if `context.status` is absent.\n * - `methods`: skipped if `context.method` is absent.\n * - `hosts`: supports exact hostnames and wildcard patterns. `*.example.com` matches\n * exactly one subdomain label; `**.example.com` matches any depth. Neither matches the\n * apex — list it explicitly. PSL tail guard prevents `*.com`-style patterns. `*` is a\n * global wildcard that matches any valid hostname. Skipped if `context.requestURL` is absent.\n * - `schemes`: `'http'` or `'https'`. `requestURL` is absolute whenever the\n * request could be resolved before dispatch. For `MockAdapter`, path-only\n * requests without a client `baseURL` are materialized as `http://localhost/...`;\n * browser adapters fall back to `window.location`, and the Node adapter requires\n * absolute URLs. Skipped only when `requestURL` is absent.\n * - `bodyContainsKeys`: supports dot paths (e.g. `data.results`). Each segment in\n * the path must resolve to a plain object for traversal to continue — the final\n * value can be anything (array, string, null, etc). Array indexing is not supported.\n * Skipped when `kind` is `'error'`.\n */\nexport function matchesFilter(\n filter: {\n statusCodes?: number[];\n methods?: string[];\n bodyContainsKeys?: string[];\n hosts?: string[];\n schemes?: ('http' | 'https')[];\n phases?: RequestPhaseName[];\n contentTypes?: ContentType[];\n contentTypeHeaders?: string[];\n },\n context: {\n status?: number;\n method?: string;\n body?: unknown;\n requestURL?: string;\n contentType?: ContentType;\n contentTypeHeader?: string;\n },\n phaseType: RequestPhaseName,\n kind: 'request' | 'response' | 'error',\n): boolean {\n if (\n filter.phases &&\n filter.phases.length > 0 &&\n !filter.phases.includes(phaseType)\n ) {\n return false;\n }\n\n if (filter.statusCodes && context.status !== undefined) {\n if (!filter.statusCodes.includes(context.status)) {\n return false;\n }\n }\n\n if (filter.methods && context.method) {\n if (!filter.methods.includes(context.method)) {\n return false;\n }\n }\n\n if (filter.contentTypes && filter.contentTypes.length > 0) {\n if (\n !context.contentType ||\n !filter.contentTypes.includes(context.contentType)\n ) {\n return false;\n }\n }\n\n if (filter.contentTypeHeaders && filter.contentTypeHeaders.length > 0) {\n if (\n !context.contentTypeHeader ||\n !filter.contentTypeHeaders.some((pattern) =>\n matchesContentTypePattern(context.contentTypeHeader as string, pattern),\n )\n ) {\n return false;\n }\n }\n\n if (\n kind !== 'error' &&\n filter.bodyContainsKeys &&\n filter.bodyContainsKeys.length > 0\n ) {\n if (\n !context.body ||\n typeof context.body !== 'object' ||\n Array.isArray(context.body)\n ) {\n return false;\n }\n\n const body = context.body as Record<string, unknown>;\n\n if (!filter.bodyContainsKeys.some((k) => hasNestedKey(body, k))) {\n return false;\n }\n }\n\n if (filter.hosts && context.requestURL) {\n const hostname = extractHostname(context.requestURL);\n\n if (\n !filter.hosts.some((pattern: string) =>\n matchesHostPattern(hostname, pattern),\n )\n ) {\n return false;\n }\n }\n\n if (filter.schemes && filter.schemes.length > 0 && context.requestURL) {\n let scheme: 'http' | 'https' | null = null;\n\n try {\n const parsedScheme = new URL(context.requestURL).protocol.replace(\n ':',\n '',\n );\n\n if (parsedScheme === 'http' || parsedScheme === 'https') {\n scheme = parsedScheme;\n }\n } catch {\n scheme = null;\n }\n\n if (!scheme || !filter.schemes.includes(scheme)) {\n return false;\n }\n }\n\n return true;\n}\n"],"mappings":"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","names":["totalBytes","abortErr"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/http-client-node/index.ts","../../../src/lib/http-client/adapters/node-adapter.ts","../../../src/lib/http-client/consts.ts","../../../src/lib/http-client/internal/multipart.ts","../../../src/lib/http-client/internal/request-body-writer.ts","../../../src/lib/http-client/internal/tls-error-utils.ts","../../../src/lib/http-client/adapters/node-adapter-utils.ts","../../../src/lib/http-client/utils.ts"],"sourcesContent":["export { NodeAdapter } from '../http-client/adapters/node-adapter';\nexport type { NodeAdapterConfig } from '../http-client/adapters/node-adapter';\n\n/**\n * Adapter contract types, re-exported so code driving an adapter directly —\n * building AdapterRequest objects and calling send() rather than going through\n * HTTPClient — does not have to import types from 'lifecycleion/http-client',\n * the module it is specifically not using. Type-only, so nothing is added to\n * the runtime bundle.\n */\nexport type {\n HTTPAdapter,\n AdapterRequest,\n AdapterResponse,\n AdapterType,\n AdapterProgressEvent,\n HTTPMethod,\n} from '../http-client/types';\n\n/**\n * Response-streaming types. NodeAdapter is the only adapter that honours\n * `AdapterRequest.streamResponse`, so these live here rather than on the other\n * adapter barrels.\n */\nexport type {\n StreamResponseFactory,\n StreamResponseInfo,\n StreamResponseContext,\n StreamResponseCancel,\n WritableLike,\n} from '../http-client/types';\n","import * as http from 'node:http';\nimport * as https from 'node:https';\nimport { urlToHttpOptions } from 'node:url';\nimport type {\n HTTPAdapter,\n AdapterRequest,\n AdapterResponse,\n AdapterProgressEvent,\n AdapterType,\n WritableLike,\n StreamResponseCancel,\n} from '../types';\nimport {\n NON_RETRYABLE_HTTP_CLIENT_CALLBACK_ERROR_FLAG,\n REDIRECT_STATUS_CODES,\n RESPONSE_STREAM_ABORT_FLAG,\n STREAM_FACTORY_CANCEL_KEY,\n STREAM_FACTORY_ERROR_FLAG,\n} from '../consts';\nimport {\n generateMultipartBoundary,\n serializeMultipartFormData,\n} from '../internal/multipart';\nimport { writeRequestBodyChunked } from '../internal/request-body-writer';\nimport { isTLSCertificateError } from '../internal/tls-error-utils';\nimport {\n materializeNodeRequestHeaders,\n normalizeNodeRequestHeaders,\n} from './node-adapter-utils';\nimport { resolveDetectedRedirectURL } from '../utils';\n\ntype StreamResponseBodyResult =\n | true\n | {\n code: 'stream_write_error' | 'stream_response_error';\n cause: Error;\n };\n\n// ---------------------------------------------------------------------------\n// Config\n// ---------------------------------------------------------------------------\n\nexport interface NodeAdapterConfig {\n /**\n * Unix domain socket path. When set, the HTTP connection routes through\n * the socket instead of TCP. The URL host in baseURL is ignored for routing\n * but is still used for the HTTP Host header — so a placeholder like\n * 'http://localhost' is required even when all traffic goes through the socket.\n *\n * const client = new HTTPClient({\n * adapter: new NodeAdapter({ socketPath: '/var/run/docker.sock' }),\n * baseURL: 'http://localhost', // host ignored; only path matters\n * });\n */\n socketPath?: string;\n\n /**\n * Mutual TLS credentials. When set on an https: request, the adapter\n * presents the client certificate to the server. Cert errors return\n * status 495 (non-standard but widely understood for client cert failure)\n * rather than throwing. The client treats 495 as a transport-level failure,\n * so it resolves through the failed/error path instead of response observers\n * and is NOT retryable.\n */\n mtls?: {\n cert: string | Buffer;\n key: string | Buffer;\n ca?: string | Buffer | Array<string | Buffer>;\n };\n\n /**\n * Trusted CA certificate(s) for verifying the server's TLS certificate.\n * Use this when connecting to internal services that use a private CA not\n * in the system trust store. Accepts PEM string, Buffer, or an array of\n * either. Does not require a client certificate — use `mtls` for that.\n */\n ca?: string | Buffer | Array<string | Buffer>;\n\n /**\n * TLS server name (SNI) to send in the handshake and verify the server\n * certificate against. Required when dialing by IP address but the server\n * cert's SAN lists a DNS name — without this, TLS verification fails because\n * the IP does not match the DNS SAN.\n *\n * Typical pattern when using a service registry:\n * baseURL: 'https://10.0.0.5:443' ← IP from the registry (where bytes go)\n * servername: 'billing.internal' ← DNS name on the cert (TLS identity)\n *\n * The Host header defaults to the baseURL hostname (the IP). If the backend\n * routes by Host, also set defaultHeaders: { host: 'billing.internal' }.\n */\n servername?: string;\n\n /**\n * Certificate revocation list(s), used to reject a server certificate whose\n * serial has been revoked even though its chain and hostname still check out.\n * A revoked certificate fails the handshake with `CERT_REVOKED`.\n *\n * Accepts whatever your CA tooling produced. A PEM string holding SEVERAL\n * concatenated CRLs — the format Apache's `SSLCARevocationFile`, nginx's\n * `ssl_crl` and HAProxy's `crl-file` all expect, and what `openssl ca\n * -gencrl` output is usually assembled into — is split into the array Node\n * requires. A single CRL, a DER `Buffer`, or an array you built yourself is\n * passed through untouched.\n *\n * The split exists because Node reads only the FIRST CRL of a concatenated\n * string and silently ignores the rest, unlike `ca`, which reads every\n * certificate in a bundle. See docs/http-client.md.\n *\n * Two things to know before using this:\n *\n * - **Every certificate in the chain needs a covering CRL.** Node enables\n * `X509_V_FLAG_CRL_CHECK_ALL`, so supplying a CRL for one root while\n * connecting through another fails with `UNABLE_TO_GET_CRL` even when\n * nothing was revoked. Cover every root you talk to, or scope the client.\n * - **CRLs expire.** Past `nextUpdate` the handshake fails with\n * `CRL_HAS_EXPIRED`, including for certificates that were never revoked.\n * Re-pass a fresh CRL before then; the refresh cadence is yours to own.\n *\n * Both failures are fail-closed, so a stale CRL never silently stops\n * enforcing — but both can take out healthy connections.\n */\n crl?: string | Buffer | Array<string | Buffer>;\n\n /**\n * Set to false to accept self-signed certificates in dev/test environments.\n * Defaults to true (Node.js default — rejects invalid certs).\n */\n rejectUnauthorized?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// NodeAdapter\n// ---------------------------------------------------------------------------\n\nexport class NodeAdapter implements HTTPAdapter {\n private _config: NodeAdapterConfig;\n\n constructor(config: NodeAdapterConfig = {}) {\n this._config = config;\n }\n\n public getType(): AdapterType {\n return 'node';\n }\n\n public async send(request: AdapterRequest): Promise<AdapterResponse> {\n const parsedURL = new URL(request.requestURL);\n const urlOptions = urlToHttpOptions(parsedURL);\n const isHTTPS = parsedURL.protocol === 'https:';\n const httpModule = isHTTPS ? https : http;\n\n const options: http.RequestOptions = {\n method: request.method,\n headers: materializeNodeRequestHeaders(request.headers),\n // Timeout is managed by the client via abort signal — the adapter does\n // not impose its own timeout so the client retains full control.\n };\n\n if (urlOptions.auth) {\n options.auth = urlOptions.auth;\n }\n\n if (this._config.socketPath) {\n // Unix socket: the TCP connection goes to the socket path, not the host.\n // We still need options.path so the HTTP request line has the right path.\n // Preserve the URL host/port too so Node generates the correct Host\n // header for virtual-hosted services behind the socket.\n options.socketPath = this._config.socketPath;\n options.hostname = urlOptions.hostname;\n options.port = urlOptions.port;\n options.path = urlOptions.path;\n } else {\n options.hostname = urlOptions.hostname;\n options.port = urlOptions.port ?? (isHTTPS ? 443 : 80);\n options.path = urlOptions.path;\n }\n\n if (isHTTPS) {\n const httpsOptions = options as https.RequestOptions;\n\n // custom CA trust store\n if (this._config.ca) {\n httpsOptions.ca = this._config.ca;\n }\n\n // SNI hostname — required when dialing by IP with a DNS-named cert\n if (this._config.servername) {\n httpsOptions.servername = this._config.servername;\n }\n\n if (this._config.mtls) {\n // mTLS: present client cert. rejectUnauthorized stays true so the\n // server cert is still validated even though we're sending our own.\n httpsOptions.cert = this._config.mtls.cert;\n httpsOptions.key = this._config.mtls.key;\n\n if (this._config.mtls.ca) {\n httpsOptions.ca = this._config.mtls.ca;\n }\n\n httpsOptions.rejectUnauthorized = true;\n }\n\n if (this._config.crl !== undefined) {\n httpsOptions.crl = normalizeCRL(this._config.crl);\n }\n\n if (this._config.rejectUnauthorized === false) {\n // Dev-only: accept self-signed certs. Explicit false required — we do\n // not default to insecure, this must be an intentional opt-in.\n httpsOptions.rejectUnauthorized = false;\n }\n }\n\n return new Promise<AdapterResponse>((resolve, reject) => {\n let activeResponseStream:\n | {\n status: number;\n headers: Record<string, string | string[]>;\n writable: WritableLike;\n }\n | undefined;\n let activeBufferedResponse:\n | {\n status: number;\n headers: Record<string, string | string[]>;\n }\n | undefined;\n let isStreamFactoryPending = false;\n\n // Deduplication guard — Node's upload path can reach 100% from multiple\n // sources (final drain callback and the upload-complete signal). Once\n // 100% is reported any further calls are dropped.\n let didFireUpload100 = false;\n\n const reportUploadProgress = (event: AdapterProgressEvent): void => {\n if (didFireUpload100) {\n return;\n }\n\n if (event.progress === 1) {\n didFireUpload100 = true;\n }\n\n request.onUploadProgress?.(event);\n };\n\n // 0% upload progress before any bytes leave the process\n reportUploadProgress({ loaded: 0, total: 0, progress: 0 });\n\n // The http callback is typed as (res: IncomingMessage) => void, so we\n // cannot make it async directly. We use a void IIFE that routes any\n // unhandled rejections back to the outer promise's reject.\n const req = httpModule.request(options, (res) => {\n void (async () => {\n const status = res.statusCode ?? 0;\n const headers = normalizeResponseHeaders(res.headers);\n\n // --- Response streaming (NodeAdapter-only feature) ---\n //\n // Only offered on HTTP 200 responses. All other statuses bypass the\n // factory and return a normal buffered response. This prevents:\n // 1. Accidentally streaming error bodies (you'd lose the error detail)\n // 2. The mistake of buffering a large 200 into memory when you forgot\n // to check the status — factory null = cancel, not buffer\n if (request.streamResponse && status === 200) {\n // Per-attempt abort controller for the stream context signal. This is\n // separate from the top-level request signal so we can fire it on local\n // write failures (disk full, etc.) without aborting the request itself —\n // the factory's cleanup listener fires, and we resolve with isStreamError.\n const streamAbort = new AbortController();\n\n // Propagate external cancellation (user abort, timeout) into the\n // factory's signal so cleanup listeners fire in all terminal cases.\n if (request.signal) {\n if (request.signal.aborted) {\n streamAbort.abort();\n }\n\n request.signal.addEventListener(\n 'abort',\n () => {\n streamAbort.abort();\n },\n { once: true },\n );\n }\n\n let writable: WritableLike | null | StreamResponseCancel;\n\n try {\n isStreamFactoryPending = true;\n writable = await request.streamResponse(\n {\n status: 200,\n headers,\n url: request.requestURL,\n attempt: request.attemptNumber ?? 1,\n requestID: request.requestID ?? '',\n },\n { signal: streamAbort.signal },\n );\n } catch (error) {\n isStreamFactoryPending = false;\n // Factory threw — non-retryable setup error, equivalent to an\n // interceptor throw. Abort the stream signal so any partial cleanup\n // listeners run, destroy the request, and propagate as a setup failure.\n streamAbort.abort();\n req.destroy();\n reject(markStreamFactoryError(error, req, request.headers));\n return;\n }\n isStreamFactoryPending = false;\n\n // The request may have been cancelled or timed out while an async\n // factory was still setting up its sink. In that case the outer\n // promise has already settled through the abort listener; make a\n // best effort to close the newly created writable and stop here.\n if (streamAbort.signal.aborted) {\n if (writable && !isStreamResponseCancel(writable)) {\n writable.destroy();\n }\n\n return;\n }\n\n if (writable === null || isStreamResponseCancel(writable)) {\n // Factory declined to stream — user-initiated cancel. Fire the stream\n // signal so any cleanup listeners wired in the factory run, then throw\n // AbortError so the client's cancel path takes over (isCancelled: true).\n const cancelReason =\n writable !== null\n ? readObjectMember(writable, 'reason')\n : undefined;\n\n streamAbort.abort();\n req.destroy();\n const abortErr = new Error(\n 'Request cancelled by streamResponse factory',\n );\n abortErr.name = 'AbortError';\n Object.assign(abortErr, {\n [STREAM_FACTORY_CANCEL_KEY]: cancelReason ?? true,\n });\n reject(abortErr);\n return;\n }\n\n activeResponseStream = {\n status,\n headers,\n writable,\n };\n\n const totalBytes =\n parseInt(String(headers['content-length'] ?? '0'), 10) || 0;\n\n // Pipe the response into the caller's writable. Extracted to a\n // module-level function to keep callback nesting within ESLint's\n // max-nested-callbacks limit. Returns true on success, or the\n // writable error when the local sink fails (disk full, etc.).\n const streamResult = await streamResponseBody(\n res,\n writable,\n totalBytes,\n request.onDownloadProgress,\n );\n\n if (streamResult === true) {\n activeResponseStream = undefined;\n resolveAdapterResponse(\n resolve,\n req,\n request.requestURL,\n request.headers,\n {\n status,\n headers,\n body: null,\n isStreamed: true,\n },\n );\n } else {\n activeResponseStream = undefined;\n // Body streaming failure after headers (disk full, writable\n // destroyed, upstream socket reset, etc.)\n //\n // The server already returned a real 200 response, so retries are no\n // longer safe: the caller's sink may already contain partial bytes.\n // We therefore preserve the real HTTP status and resolve with\n // isStreamError rather than throwing/retrying.\n // - The stream signal fires so factory cleanup listeners run\n // - Non-retryable once streaming has started\n streamAbort.abort();\n destroyWritableQuietly(writable);\n req.destroy();\n resolveAdapterResponse(\n resolve,\n req,\n request.requestURL,\n request.headers,\n {\n status,\n headers,\n body: null,\n isStreamError: true,\n streamErrorCode: streamResult.code,\n errorCause: streamResult.cause,\n },\n );\n }\n\n return;\n }\n\n // --- Normal buffered response ---\n activeBufferedResponse = {\n status,\n headers,\n };\n const chunks: Buffer[] = [];\n let loadedBytes = 0;\n\n // Deduplication guard — when Content-Length is known and the last\n // `data` chunk fills the body exactly, progress: 1 fires there.\n // The `end` event fires unconditionally afterward, so skip the\n // completion event if the last chunk already reported 100%.\n let didFireDownload100 = false;\n\n const totalBytes =\n parseInt(String(headers['content-length'] ?? '0'), 10) || 0;\n\n res.on('data', (chunk: Buffer) => {\n chunks.push(chunk);\n loadedBytes += chunk.length;\n\n // progress: -1 when Content-Length is absent (chunked transfer,\n // compressed response, etc.) — callers treat -1 as \"length unknown\".\n const progress = totalBytes > 0 ? loadedBytes / totalBytes : -1;\n\n // Track whether the final chunk already closed out 100% so the\n // `end` handler can skip a duplicate event.\n if (progress === 1) {\n didFireDownload100 = true;\n }\n\n request.onDownloadProgress?.({\n loaded: loadedBytes,\n // When total is unknown fall back to loaded so the event always\n // has a sensible non-zero total.\n total: totalBytes > 0 ? totalBytes : loadedBytes,\n progress,\n });\n });\n\n res.on('end', () => {\n activeBufferedResponse = undefined;\n\n // Final 100% download event — skipped when the last `data` chunk\n // already reported it (Content-Length known, body filled exactly).\n if (!didFireDownload100) {\n request.onDownloadProgress?.({\n loaded: loadedBytes,\n total: loadedBytes,\n progress: 1,\n });\n }\n\n const body =\n chunks.length > 0 ? new Uint8Array(Buffer.concat(chunks)) : null;\n\n resolveAdapterResponse(\n resolve,\n req,\n request.requestURL,\n request.headers,\n {\n status,\n headers,\n body,\n },\n );\n });\n\n res.on('error', (err: Error) => {\n if (!activeBufferedResponse) {\n return;\n }\n\n activeBufferedResponse = undefined;\n resolveAdapterResponse(\n resolve,\n req,\n request.requestURL,\n request.headers,\n {\n status,\n headers,\n body: null,\n isStreamError: true,\n streamErrorCode: 'stream_response_error',\n errorCause: makeResponseStreamError(\n 'Response stream error',\n err,\n ),\n },\n );\n });\n\n res.on('aborted', () => {\n if (!activeBufferedResponse) {\n return;\n }\n\n activeBufferedResponse = undefined;\n resolveAdapterResponse(\n resolve,\n req,\n request.requestURL,\n request.headers,\n {\n status,\n headers,\n body: null,\n isStreamError: true,\n streamErrorCode: 'stream_response_error',\n errorCause: makeResponseStreamError('Response stream aborted'),\n },\n );\n });\n\n res.on('close', () => {\n if (!activeBufferedResponse) {\n return;\n }\n\n activeBufferedResponse = undefined;\n resolveAdapterResponse(\n resolve,\n req,\n request.requestURL,\n request.headers,\n {\n status,\n headers,\n body: null,\n isStreamError: true,\n streamErrorCode: 'stream_response_error',\n errorCause: makeResponseStreamError(\n 'Response stream closed before completion',\n ),\n },\n );\n });\n })().catch((error: unknown) => {\n reject(normalizeError(error));\n });\n });\n\n // Network / transport errors (DNS failure, connection refused, cert errors)\n req.on('error', (error) => {\n // Abort signal fired before network error — priorities the abort path\n if (request.signal?.aborted) {\n const abortErr = new Error('Request aborted');\n abortErr.name = 'AbortError';\n reject(abortErr);\n return;\n }\n\n // TLS certificate errors → 495. This is non-standard but widely\n // understood for client cert / server cert validation failures. We\n // preserve the diagnostic 495 status, but still flag it as a transport\n // failure so the client routes it through the failed/error path and\n // never retries it.\n if (isTLSCertificateError(error)) {\n resolveAdapterResponse(\n resolve,\n req,\n request.requestURL,\n request.headers,\n {\n status: 495,\n isTransportError: true,\n isRetryable: false,\n headers: {},\n body: null,\n errorCause: error,\n },\n );\n return;\n }\n\n // Only `wasDefinitelyNotSent` is claimed here, and only the transport\n // error code can supply it. No `isRetryable: false` alongside: that flag\n // blocks every method, so it would stop retrying an idempotent PUT after\n // an ordinary socket error and override retryNonIdempotentMethods.\n // Whether a partly-sent request may be replayed is the client's method\n // rule to answer, and withholding the proof is what answers it.\n //\n // A body-byte count cannot supply it in either direction: an empty-body\n // POST writes headers and nothing else, and the counter tracks bytes\n // handed to the stream rather than bytes on the wire. The error code is\n // also the only signal that behaves the same on Node and Bun, where\n // `socket.bytesWritten` and `socket.connecting` are unreliable.\n //\n // Set only when proven, never as `false` — absence means \"not known\", so\n // an explicit `false` would read as proof of delivery.\n const wasDefinitelyNotSent = isPreConnectionError(error);\n\n // All other transport errors (ECONNREFUSED, ENOTFOUND, etc.) → status 0\n resolveAdapterResponse(\n resolve,\n req,\n request.requestURL,\n request.headers,\n {\n status: 0,\n isTransportError: true,\n ...(wasDefinitelyNotSent ? { wasDefinitelyNotSent: true } : {}),\n headers: {},\n body: null,\n errorCause: error,\n },\n );\n });\n\n // Wire abort signal — destroy the underlying socket when fired.\n // Reject immediately rather than waiting for the 'error' event; some\n // runtimes (e.g. Bun) do not emit 'error' on req.destroy(), so waiting\n // leaves the promise unsettled. Promise resolution is idempotent, so any\n // subsequent error event is a safe no-op.\n if (request.signal) {\n if (request.signal.aborted) {\n // Signal already aborted before we even started (e.g., pre-cancelled builder)\n req.destroy();\n const abortErr = new Error('Request aborted');\n abortErr.name = 'AbortError';\n reject(abortErr);\n return;\n }\n\n request.signal.addEventListener(\n 'abort',\n () => {\n if (activeResponseStream) {\n const { status, headers, writable } = activeResponseStream;\n activeResponseStream = undefined;\n destroyWritableQuietly(writable);\n req.destroy();\n\n const error = new Error(\n 'Request aborted during response streaming',\n );\n error.name = 'AbortError';\n reject(\n markResponseStreamAbortError(\n error,\n req,\n request.headers,\n status,\n headers,\n ),\n );\n return;\n }\n\n if (activeBufferedResponse) {\n const { status, headers } = activeBufferedResponse;\n activeBufferedResponse = undefined;\n req.destroy();\n\n const error = new Error(\n 'Request aborted during response streaming',\n );\n error.name = 'AbortError';\n reject(\n markResponseStreamAbortError(\n error,\n req,\n request.headers,\n status,\n headers,\n ),\n );\n return;\n }\n\n if (isStreamFactoryPending) {\n req.destroy();\n const abortErr = new Error(\n 'Request aborted during streamResponse setup',\n );\n abortErr.name = 'AbortError';\n reject(markStreamFactoryError(abortErr, req, request.headers));\n return;\n }\n\n req.destroy();\n const abortErr = new Error('Request aborted');\n abortErr.name = 'AbortError';\n reject(abortErr);\n },\n { once: true },\n );\n }\n\n // Write request body\n if (request.body instanceof FormData) {\n // FormData → multipart/form-data with exact Content-Length so upload\n // progress is length-computable (not chunked-transfer guesswork).\n const boundary = generateMultipartBoundary();\n\n serializeMultipartFormData(\n request.body,\n req,\n boundary,\n reportUploadProgress,\n )\n .then(() => {\n req.end();\n })\n .catch((error: unknown) => {\n req.destroy();\n resolveAdapterResponse(\n resolve,\n req,\n request.requestURL,\n request.headers,\n {\n // No isRetryable veto: that would stop retrying an idempotent\n // PUT or DELETE. Delivery is unproven rather than disproven, so\n // nothing is claimed and the client's method rule decides.\n status: 0,\n isTransportError: true,\n headers: {},\n body: null,\n errorCause: normalizeError(error),\n },\n );\n });\n } else if (\n typeof request.body === 'string' ||\n request.body instanceof Uint8Array\n ) {\n // String or Uint8Array body — write in chunks so upload progress fires\n // at meaningful granularity rather than one giant 100% event at the end.\n const bytes =\n typeof request.body === 'string'\n ? Buffer.from(request.body, 'utf8')\n : Buffer.from(request.body);\n\n req.setHeader('Content-Length', bytes.length.toString());\n\n writeRequestBodyChunked(bytes, req, reportUploadProgress)\n .then(() => {\n req.end();\n })\n .catch((error: unknown) => {\n req.destroy();\n resolveAdapterResponse(\n resolve,\n req,\n request.requestURL,\n request.headers,\n {\n // No isRetryable veto: that would stop retrying an idempotent\n // PUT or DELETE. Delivery is unproven rather than disproven, so\n // nothing is claimed and the client's method rule decides.\n status: 0,\n isTransportError: true,\n headers: {},\n body: null,\n errorCause: normalizeError(error),\n },\n );\n });\n } else {\n // No body — fire 100% upload immediately and end the request\n reportUploadProgress({ loaded: 0, total: 0, progress: 1 });\n req.end();\n }\n });\n }\n}\n\n// ---------------------------------------------------------------------------\n// Streaming pipe helper\n// ---------------------------------------------------------------------------\n\n// Extracted to module scope so its internal callback nesting starts from 1,\n// keeping each level within ESLint's max-nested-callbacks limit of 3.\n//\n// Returns true when the response body was fully written to the writable, or the\n// underlying streaming failure when delivery fails after headers (disk full,\n// writable destroyed, upstream response stream error, etc.). The caller maps\n// that error into isStreamError rather than throwing, so the real HTTP status\n// is preserved and the failure stays non-retryable once streaming has started.\nasync function streamResponseBody(\n res: http.IncomingMessage,\n writable: WritableLike,\n totalBytes: number,\n onProgress?: (e: AdapterProgressEvent) => void,\n): Promise<StreamResponseBodyResult> {\n return new Promise((resolve) => {\n let loadedBytes = 0;\n\n // Deduplication guard — same as buffered download: when Content-Length is\n // known and the last write callback fills the body exactly, progress: 1\n // fires there. The `end` → writable.end callback fires unconditionally\n // afterward, so skip the completion event if the write already reported 100%.\n let didFireDownload100 = false;\n let isPaused = false;\n let isSettled = false;\n let didReceiveEnd = false;\n\n const cleanup = (): void => {\n removeWritableListener(writable, 'drain', onWritableDrain);\n removeWritableListener(writable, 'error', onWritableError);\n res.off('data', onResponseData);\n res.off('end', onResponseEnd);\n res.off('error', onResponseError);\n res.off('aborted', onResponseAborted);\n res.off('close', onResponseClose);\n };\n\n const settle = (result: StreamResponseBodyResult): void => {\n if (isSettled) {\n return;\n }\n\n isSettled = true;\n cleanup();\n resolve(result);\n };\n\n // Register the drain handler once at this level (depth 2) rather than\n // inside the data callback (depth 3+). Uses a flag instead of a one-shot\n // .once() to handle spurious drains gracefully.\n const onWritableDrain = (): void => {\n if (isPaused) {\n isPaused = false;\n res.resume();\n }\n };\n\n // Writable write failure (disk full, closed stream, etc.) resolves the\n // original error instead of rejecting so the caller can return the real\n // HTTP status code in an isStreamError response rather than surfacing a\n // thrown error.\n const onWritableError = (error: Error): void => {\n settle({ code: 'stream_write_error', cause: error });\n };\n\n const onResponseData = (chunk: Buffer): void => {\n if (isSettled) {\n return;\n }\n\n let canContinue: boolean;\n\n try {\n canContinue = writable.write(chunk, (error) => {\n if (error || isSettled) {\n return;\n }\n\n loadedBytes += chunk.length;\n\n // progress: -1 when Content-Length is absent — callers treat -1 as\n // \"length unknown\". Track 100% to avoid a duplicate from onResponseEnd.\n const progress = totalBytes > 0 ? loadedBytes / totalBytes : -1;\n\n if (progress === 1) {\n didFireDownload100 = true;\n }\n\n onProgress?.({\n loaded: loadedBytes,\n // Fall back to loaded when total is unknown so the event always\n // has a sensible non-zero total.\n total: totalBytes > 0 ? totalBytes : loadedBytes,\n progress,\n });\n });\n } catch (error) {\n settle({\n code: 'stream_write_error',\n cause: normalizeError(error),\n });\n return;\n }\n\n if (!canContinue) {\n // Writable signalled backpressure — pause the readable until the\n // drain event fires (handled above) to keep memory bounded.\n isPaused = true;\n res.pause();\n }\n };\n\n const onResponseEnd = (): void => {\n if (isSettled) {\n return;\n }\n\n didReceiveEnd = true;\n\n try {\n writable.end(() => {\n if (isSettled) {\n return;\n }\n\n // Fire 100% download progress on successful completion, unless a\n // data chunk already reported exactly 100% (Content-Length known and\n // last chunk completed the body).\n if (!didFireDownload100) {\n onProgress?.({\n loaded: loadedBytes,\n total: loadedBytes,\n progress: 1,\n });\n }\n\n settle(true);\n });\n } catch (error) {\n settle({\n code: 'stream_write_error',\n cause: normalizeError(error),\n });\n }\n };\n\n const onResponseError = (err: Error): void => {\n // Response stream error after headers is terminal for streamed 200\n // responses too: bytes may already have been written to the caller's\n // sink, so retrying would risk duplicate/corrupt output.\n const streamError = new Error('Response stream error');\n streamError.cause = err;\n settle({ code: 'stream_response_error', cause: streamError });\n };\n\n const onResponseAborted = (): void => {\n const streamError = new Error('Response stream aborted');\n settle({ code: 'stream_response_error', cause: streamError });\n };\n\n const onResponseClose = (): void => {\n if (didReceiveEnd || isSettled) {\n return;\n }\n\n const streamError = new Error('Response stream closed before completion');\n settle({ code: 'stream_response_error', cause: streamError });\n };\n\n writable.on('drain', onWritableDrain);\n writable.on('error', onWritableError);\n res.on('data', onResponseData);\n res.on('end', onResponseEnd);\n res.on('error', onResponseError);\n res.on('aborted', onResponseAborted);\n res.on('close', onResponseClose);\n });\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n// Matches one PEM-armoured CRL. The armour is fixed by RFC 7468, so this is a\n// constant rather than an option — there is exactly one correct value for it.\nconst PEM_CRL_HEADER = '-----BEGIN X509 CRL-----';\n\nconst PEM_CRL_BLOCK = /-----BEGIN X509 CRL-----[\\s\\S]*?-----END X509 CRL-----/g;\n\n// Anything at all left over once every COMPLETE block has been removed.\n//\n// The rule is exact rather than a heuristic, and that is the point. Earlier\n// versions tried to tell a damaged CRL apart from a human-readable annotation\n// — first by searching for well-formed `-----BEGIN X509 CRL-----`, which\n// missed `----BEGIN` and a clipped tail; then by any triple hyphen, which\n// refused a Markdown rule; then by a hyphen run fused to text, which refused\n// an issuer CN of `ACME---Production`. Each narrowing traded a false negative\n// for a false positive, and a false negative here is a CRL dropped in silence.\n//\n// There is no line to draw, because a truncated CRL and a line of prose are\n// the same thing to a parser: bytes that are not a PEM block. So require that\n// there be none. Everything outside a complete block must be whitespace, which\n// makes the accepted input exactly \"PEM blocks, in any order, separated by\n// whitespace\" — what every producer of a CRL bundle emits by default.\n//\n// The cost is `openssl ca -gencrl -text` output, whose decoded header is not\n// whitespace. That is refused with an error saying so, which is a better\n// outcome than a rule that accepts it and, in the same breath, accepts a CRL\n// that was cut in half.\nconst RESIDUE_OUTSIDE_PEM_BLOCKS = /\\S/;\n\n/**\n * Normalizes whatever the caller supplied as `crl` into the shape Node needs.\n *\n * Node reads only the FIRST CRL of a PEM string and silently ignores the rest\n * — unlike `ca`, which reads every certificate in a bundle. The asymmetry is\n * easy to miss because the failure is `UNABLE_TO_GET_CRL`, which reads as \"no\n * CRL supplied\" rather than \"your bundle was truncated\", and because a bundle\n * whose first entry happens to cover the root in use appears to work until the\n * roster or the export order changes.\n *\n * The bundle is the standard interchange format — Apache documents\n * `SSLCARevocationFile` as \"the concatenation of the various PEM-encoded CRL\n * files\", nginx and HAProxy agree, and `openssl verify -CRLfile` reads a whole\n * bundle — so callers reasonably hand one over. Splitting it here means they\n * do not have to know about Node's divergence from the library it links.\n *\n * The truncation applies per STRING, not per argument, so an array is walked\n * and each string element split in turn: `[bundleOfTwo, oneCrl]` would\n * otherwise contribute one CRL where it should contribute three. A string\n * inside an array is treated exactly like a string passed on its own.\n *\n * Anything that is not a multi-CRL string is returned untouched: a Buffer may\n * be DER, and a string with exactly one match is passed through so Node\n * reports its own error rather than having one masked here.\n *\n * A string with NO complete block does not reach that pass-through: unless it\n * is entirely whitespace, the residue check below throws. That is the case a\n * caller hits when a download truncated to nothing or a path pointed at a file\n * with no CRL in it, and the explicit error names the problem where Node would\n * only report `UNABLE_TO_GET_CRL` at handshake time.\n */\nfunction normalizeCRL(\n crl: string | Buffer | Array<string | Buffer>,\n): string | Buffer | Array<string | Buffer> {\n if (Array.isArray(crl)) {\n return crl.flatMap((entry) => normalizeCRLEntry(entry));\n }\n\n return normalizeCRLEntry(crl);\n}\n\n/**\n * One `crl` value — or one element of an array of them.\n *\n * Buffers cannot simply be passed through. `fs.readFileSync('bundle.pem')`\n * without an encoding returns a Buffer, which is the DEFAULT way to load a\n * file, and its contents are PEM text like any other bundle — so Node reads\n * only its first CRL exactly as it would from a string. Measured: a stale CRL\n * followed by one revoking the server's certificate accepted the connection as\n * a Buffer, and rejected it once split.\n *\n * So the question is not \"string or Buffer\" but \"PEM or DER\". A Buffer holding\n * PEM goes through the same splitting and validation as a string; a Buffer\n * holding DER — which has no armour to look for — is passed through untouched,\n * since DER encodes exactly one CRL and there is nothing to split.\n */\nfunction normalizeCRLEntry(entry: string | Buffer): string | Buffer | string[] {\n if (typeof entry === 'string') {\n return splitCRLString(entry);\n }\n\n // latin1 is a byte-for-byte mapping, so PEM (which is ASCII) survives\n // exactly and DER cannot be corrupted by the round trip — it is only being\n // inspected, and is returned as the original Buffer either way.\n const text = entry.toString('latin1');\n\n if (!text.includes(PEM_CRL_HEADER)) {\n return entry;\n }\n\n const split = splitCRLString(text);\n\n // A single block stays a Buffer: it needed no splitting, so hand back what\n // the caller gave rather than a re-encoded copy of it.\n return Array.isArray(split) ? split : entry;\n}\n\nfunction splitCRLString(crl: string): string | string[] {\n const blocks = crl.match(PEM_CRL_BLOCK) ?? [];\n\n // Returning only the complete blocks would DISCARD whatever did not match,\n // so a truncated or corrupted CRL in the bundle would vanish silently and\n // the caller would enforce a set they did not supply. With the rest of the\n // bundle still present, a stale-but-complete CRL for the same issuer stays\n // in force while the newer, broken one is dropped.\n //\n // Passing the original string through instead does not help: Node reads its\n // first CRL and ignores the remainder, so a bundle whose first entry is\n // intact still swallows the damage without an error.\n //\n // So refuse anything that is not a complete block or whitespace.\n const residue = crl.replace(PEM_CRL_BLOCK, '');\n\n if (RESIDUE_OUTSIDE_PEM_BLOCKS.test(residue)) {\n throw new Error(\n 'NodeAdapter: the `crl` value contains content outside any complete ' +\n '-----BEGIN/END X509 CRL----- block. Only PEM CRL blocks separated ' +\n 'by whitespace are accepted, because a truncated CRL and a line of ' +\n 'commentary are indistinguishable — accepting the second would mean ' +\n 'silently dropping the first and enforcing an incomplete revocation ' +\n 'set. Common causes: a partial download, a clipped or mistyped ' +\n 'delimiter, or decoded text from `openssl ... -text`, which must be ' +\n 'stripped before the bundle is passed here.',\n );\n }\n\n return blocks.length > 1 ? blocks : crl;\n}\n\nfunction normalizeResponseHeaders(\n headers: http.IncomingHttpHeaders,\n): Record<string, string | string[]> {\n const result: Record<string, string | string[]> = {};\n\n for (const [key, value] of Object.entries(headers)) {\n if (value === undefined) {\n continue;\n }\n // Keys are already lowercase from Node's http parser\n result[key] = Array.isArray(value) ? value : String(value);\n }\n\n return result;\n}\n\nfunction snapshotEffectiveRequestHeaders(\n req: http.ClientRequest,\n fallbackHeaders: Record<string, string | string[]>,\n): Record<string, string | string[]> {\n return normalizeNodeRequestHeaders({\n // Start with the client-level attempt headers, then overlay any adapter-side\n // mutations (for example multipart Content-Type/Content-Length).\n ...fallbackHeaders,\n ...req.getHeaders(),\n });\n}\n\nfunction resolveAdapterResponse(\n resolve: (response: AdapterResponse | PromiseLike<AdapterResponse>) => void,\n req: http.ClientRequest,\n requestURL: string,\n fallbackHeaders: Record<string, string | string[]>,\n response: Omit<AdapterResponse, 'effectiveRequestHeaders'>,\n): void {\n const detectedRedirectURL = resolveDetectedRedirectURL(\n requestURL,\n response.status,\n response.headers,\n );\n\n resolve({\n ...response,\n // Flag redirect responses so HTTPClient can surface wasRedirectDetected on\n // the final HTTPResponse consistently across all adapters. The actual\n // follow-or-disable decision is still made by HTTPClient's redirect loop.\n wasRedirectDetected: REDIRECT_STATUS_CODES.has(response.status),\n ...(detectedRedirectURL ? { detectedRedirectURL } : {}),\n effectiveRequestHeaders: snapshotEffectiveRequestHeaders(\n req,\n fallbackHeaders,\n ),\n });\n}\n\nfunction destroyWritableQuietly(writable: WritableLike): void {\n try {\n writable.destroy();\n } catch {\n // Best-effort cleanup only. Preserve the original stream failure.\n }\n}\n\nfunction removeWritableListener(\n writable: WritableLike,\n event: 'drain' | 'error',\n listener: (() => void) | ((error: Error) => void),\n): void {\n const removable = writable as WritableLike & {\n off?: (\n event: 'drain' | 'error',\n listener: (() => void) | ((error: Error) => void),\n ) => WritableLike;\n };\n\n removable.off?.(event, listener);\n}\n\n/**\n * Transport error codes that mean no connection was ever established, so no\n * request bytes can have reached the server. Anything else — `ECONNRESET`,\n * `EPIPE`, `ETIMEDOUT`, a bare socket hang up — is treated as possible delivery,\n * since an unrecognized code must fall on the side of \"may already have been\n * applied\".\n *\n * `EHOSTUNREACH` and `ENETUNREACH` are absent despite reading like connect-time\n * failures: an ICMP unreachable for an established connection is reported on that\n * connection with the same code. Verified to agree on both Node and Bun.\n */\nconst PRE_CONNECTION_ERROR_CODES: ReadonlySet<string> = new Set([\n 'ECONNREFUSED',\n 'ENOTFOUND',\n 'EAI_AGAIN',\n 'EADDRNOTAVAIL',\n]);\n\n/** Whether an error proves the request never left for a connected peer. */\nfunction isPreConnectionError(error: unknown): boolean {\n const code = readObjectMember(error, 'code');\n\n return typeof code === 'string' && PRE_CONNECTION_ERROR_CODES.has(code);\n}\n\nfunction makeResponseStreamError(message: string, cause?: Error): Error {\n const error = new Error(message);\n\n if (cause) {\n error.cause = cause;\n }\n\n return error;\n}\n\nfunction markStreamFactoryError(\n error: unknown,\n req: http.ClientRequest,\n fallbackHeaders: Record<string, string | string[]>,\n): Error {\n const normalized = normalizeError(error);\n const tagged = normalized as Error &\n Partial<\n Record<typeof NON_RETRYABLE_HTTP_CLIENT_CALLBACK_ERROR_FLAG, boolean>\n >;\n\n tagged[NON_RETRYABLE_HTTP_CLIENT_CALLBACK_ERROR_FLAG] = true;\n Object.assign(normalized, { [STREAM_FACTORY_ERROR_FLAG]: true });\n Object.assign(normalized, {\n effectiveRequestHeaders: snapshotEffectiveRequestHeaders(\n req,\n fallbackHeaders,\n ),\n });\n\n return normalized;\n}\n\nfunction markResponseStreamAbortError(\n error: Error,\n req: http.ClientRequest,\n fallbackHeaders: Record<string, string | string[]>,\n status: number,\n headers: Record<string, string | string[]>,\n): Error {\n const tagged = error as Error &\n Partial<Record<typeof RESPONSE_STREAM_ABORT_FLAG, boolean>> & {\n effectiveRequestHeaders?: Record<string, string | string[]>;\n streamAbortStatus?: number;\n streamAbortHeaders?: Record<string, string | string[]>;\n };\n\n // Keep the flag sourced from consts while avoiding eslint's false-positive\n // on direct computed assignment into an Error-typed value.\n Object.assign(tagged, { [RESPONSE_STREAM_ABORT_FLAG]: true });\n tagged.effectiveRequestHeaders = snapshotEffectiveRequestHeaders(\n req,\n fallbackHeaders,\n );\n tagged.streamAbortStatus = status;\n tagged.streamAbortHeaders = headers;\n\n return error;\n}\n\nfunction isStreamResponseCancel(value: unknown): value is StreamResponseCancel {\n return readObjectMember(value, 'cancel') === true;\n}\n\n/** Read replaceable/runtime-owned metadata without letting a getter escape. */\nfunction readObjectMember(source: unknown, key: string): unknown {\n if (\n source === null ||\n (typeof source !== 'object' && typeof source !== 'function')\n ) {\n return undefined;\n }\n\n try {\n return (source as Record<string, unknown>)[key];\n } catch {\n return undefined;\n }\n}\n\nfunction isErrorValue(value: unknown): value is Error {\n try {\n return value instanceof Error;\n } catch {\n return false;\n }\n}\n\nfunction normalizeError(value: unknown): Error {\n if (isErrorValue(value)) {\n return value;\n }\n\n try {\n return new Error(String(value));\n } catch {\n return new Error('Unknown error');\n }\n}\n","import type { HTTPMethod } from './types';\n\n/**\n * HTTP responses that are plausibly transient and worth retrying when a retry\n * policy is explicitly enabled.\n *\n * `status === 0` is included on purpose because browser/XHR-style adapters can\n * surface \"no real HTTP response\" that way when the network is unavailable or\n * the request otherwise fails before a normal status code is received.\n */\nexport const RETRYABLE_STATUS_CODES: ReadonlySet<number> = new Set([\n // 0: Browser/XHR-style \"no response\" status.\n 0,\n\n // 408 Request Timeout\n 408,\n // 429 Too Many Requests\n 429,\n\n // 500 Internal Server Error\n 500,\n // 502 Bad Gateway\n 502,\n // 503 Service Unavailable\n 503,\n // 504 Gateway Timeout\n 504,\n\n // 507 Insufficient Storage\n 507,\n // 509 Bandwidth Limit Exceeded (non-standard)\n 509,\n // 520 Unknown Error (Cloudflare)\n 520,\n // 521 Web Server Is Down (Cloudflare)\n 521,\n // 522 Connection Timed Out (Cloudflare)\n 522,\n // 523 Origin Is Unreachable (Cloudflare)\n 523,\n // 524 A Timeout Occurred (Cloudflare)\n 524,\n // 598 Network Read Timeout Error (non-standard)\n 598,\n // 599 Network Connect Timeout Error (non-standard)\n 599,\n]);\n\n/**\n * Methods RFC 9110 does not define as idempotent, so replaying one may apply\n * the same change twice.\n *\n * `PUT` and `DELETE` are absent on purpose: both are idempotent by definition,\n * even though they mutate. Repeating them lands the resource in the same state\n * as doing it once, which is exactly what makes a replay safe.\n */\nexport const NON_IDEMPOTENT_METHODS: ReadonlySet<HTTPMethod> =\n new Set<HTTPMethod>(['POST', 'PATCH']);\n\nexport const DEFAULT_TIMEOUT_MS = 30_000;\n\nexport const DEFAULT_REQUEST_ID_HEADER = 'x-local-client-request-id';\n\nexport const DEFAULT_REQUEST_ATTEMPT_HEADER = 'x-local-client-request-attempt';\n\nexport const DEFAULT_USER_AGENT = 'lifecycleion-http-client';\n\nexport const NON_RETRYABLE_HTTP_CLIENT_CALLBACK_ERROR_FLAG =\n '_lifecycleion_non_retryable_http_client_callback_error';\n\nexport const STREAM_FACTORY_ERROR_FLAG = '_lifecycleion_stream_factory_error';\n\n/**\n * Attached to the AbortError thrown when a StreamResponseFactory returns null\n * or `{ cancel: true, reason? }`. The value is the reason string if provided,\n * or `true` if the factory cancelled without a reason. Lets HTTPClient surface\n * the reason on HTTPClientError.cancelReason.\n */\nexport const STREAM_FACTORY_CANCEL_KEY =\n '_lifecycleion_stream_factory_cancel_reason';\n\nexport const RESPONSE_STREAM_ABORT_FLAG = '_lifecycleion_response_stream_abort';\n\n/**\n * Set on the AbortError thrown by XHRAdapter's defensive `timeout` event\n * listener. Lets HTTPClient classify the error as a timeout (retryable) rather\n * than an unexpected abort (non-retryable cancel).\n */\nexport const XHR_BROWSER_TIMEOUT_FLAG = '_lifecycleion_xhr_browser_timeout';\n\nexport const HTTP_METHODS: ReadonlyArray<HTTPMethod> = [\n 'GET',\n 'POST',\n 'PUT',\n 'PATCH',\n 'DELETE',\n 'HEAD',\n];\n\n/**\n * Exact-match request headers that browsers either forbid outright or do not\n * let this client set reliably via plain Fetch/XHR headers.\n *\n * Prefix-based rules like `proxy-*` and `sec-*` are handled in `header-utils.ts`.\n */\nexport const BROWSER_RESTRICTED_HEADERS: ReadonlySet<string> = new Set([\n // Encoding / CORS negotiation headers controlled by the browser.\n 'accept-charset',\n 'accept-encoding',\n 'access-control-request-headers',\n 'access-control-request-method',\n 'access-control-request-private-network',\n\n // Connection-level transport headers.\n 'connection',\n 'content-length',\n 'date',\n 'expect',\n 'host',\n 'keep-alive',\n 'te',\n 'trailer',\n 'transfer-encoding',\n 'upgrade',\n 'via',\n\n // Browser-managed request context / privacy headers.\n 'cookie',\n 'dnt',\n 'origin',\n 'referer',\n 'set-cookie',\n 'user-agent',\n]);\n\nexport const BROWSER_RESTRICTED_HEADER_PREFIXES: ReadonlyArray<string> = [\n 'proxy-',\n 'sec-',\n];\n\n/**\n * Headers that can tunnel the real method through POST. Browsers block these\n * when they try to smuggle forbidden transport methods.\n */\nexport const BROWSER_METHOD_OVERRIDE_HEADER_NAMES: ReadonlySet<string> =\n new Set(['x-http-method', 'x-http-method-override', 'x-method-override']);\n\n/**\n * Methods that browsers do not allow request headers to tunnel via the\n * override headers above.\n */\nexport const BROWSER_FORBIDDEN_METHOD_OVERRIDE_VALUES: ReadonlySet<string> =\n new Set(['connect', 'trace', 'track']);\n\nexport const DEFAULT_MAX_REDIRECTS = 5;\n\n/**\n * Redirect responses that carry a follow-up `Location` hop. `300` and `304`\n * are excluded because they do not represent an automatic redirect here.\n */\nexport const REDIRECT_STATUS_CODES: ReadonlySet<number> = new Set([\n // 301 Moved Permanently\n 301,\n // 302 Found\n 302,\n\n // 303 See Other\n 303,\n\n // 307 Temporary Redirect\n 307,\n // 308 Permanent Redirect\n 308,\n]);\n","// cspell:ignore WHATWG\n/**\n * Multipart/form-data serialization for Node.js HTTP requests.\n *\n * RFC 7578 defines the multipart/form-data wire format. Each field in the\n * FormData becomes a \"part\" in the body, separated by a boundary string:\n *\n * --<boundary>\\r\\n\n * Content-Disposition: form-data; name=\"username\"\\r\\n\n * \\r\\n\n * alice\\r\\n\n * --<boundary>\\r\\n\n * Content-Disposition: form-data; name=\"avatar\"; filename=\"photo.jpg\"\\r\\n\n * Content-Type: image/jpeg\\r\\n\n * \\r\\n\n * <binary file bytes>\\r\\n\n * --<boundary>--\\r\\n ← trailing \"--\" marks end of body\n *\n * The boundary is a random string that must not appear in any field value or\n * file content. Each part has its own mini-headers (Content-Disposition,\n * optionally Content-Type), followed by a blank line, then the value.\n *\n * This module uses a two-pass approach:\n * Pass 1 — calculateMultipartFormDataSize: walk all fields, count exact byte length.\n * Pass 2 — serializeMultipartFormData: write boundary + headers + value for each field.\n *\n * Knowing the total size upfront lets us set an exact Content-Length header,\n * which makes upload progress length-computable (a real 0–100%) rather than\n * indeterminate. Without Content-Length the browser/server can't tell the\n * client how far along it is.\n *\n * File/blob parts are read chunk-by-chunk via the Web Streams API reader (a\n * WHATWG standard — available in browsers, Node 18+, and Bun) so large files\n * are never fully buffered in memory. String fields are already in memory so\n * they are written in one shot.\n *\n * The functions accept a RequestBodyWritable instead of a concrete\n * http.ClientRequest so they can be tested and reused without a live socket.\n * http.ClientRequest satisfies the interface structurally.\n */\n\nimport type { AdapterProgressEvent } from '../types';\nimport type { RequestBodyWritable } from './request-body-writable';\n\n/**\n * Formats the filename parameter for a multipart Content-Disposition header.\n *\n * Every filename includes a quoted `filename=\"<fallback>\"` parameter so older\n * multipart parsers still see a usable name.\n *\n * When the original name is not already safe printable ASCII, we also emit an\n * RFC 5987 `filename*=` parameter carrying the exact UTF-8 filename:\n *\n * filename*=UTF-8''<percent-encoded-name>\n *\n * Including both forms maximizes compatibility: RFC 5987-aware parsers use\n * `filename*=` to recover the exact original filename, while older or lenient\n * parsers can still fall back to the sanitized ASCII `filename=` value.\n * Servers that normalize, strip, or rename uploads on their side will use\n * whichever form they support.\n *\n * The quoted `filename=` fallback is intentionally ASCII-safe and lossy when\n * needed. That avoids parser-dependent handling of quoted-string escaping while\n * still preserving a broadly compatible fallback for runtimes that ignore\n * `filename*=`.\n *\n * The RFC 5987 encoding here is `encodeURIComponent(...)` plus escaping for\n * `'`, `(`, `)`, and `*`, which must also be percent-encoded.\n */\nfunction formatFilename(filename: string): string {\n const fallback = toSafeASCIIQuotedFilenameFallback(filename);\n const base = isSafeASCIIQuotedFilename(filename)\n ? `filename=\"${filename}\"`\n : `filename=\"${fallback}\"`;\n\n if (isSafeASCIIQuotedFilename(filename)) {\n return base;\n }\n\n const encoded = encodeURIComponent(filename)\n .replace(/'/g, '%27')\n .replace(/\\(/g, '%28')\n .replace(/\\)/g, '%29')\n .replace(/\\*/g, '%2A');\n\n return `${base}; filename*=UTF-8''${encoded}`;\n}\n\nfunction isSafeASCIIQuotedFilename(value: string): boolean {\n return /^[\\u0020-\\u007E]+$/.test(value) && !/[\"\\\\]/.test(value);\n}\n\nfunction toSafeASCIIQuotedFilenameFallback(value: string): string {\n const normalized = value.normalize('NFKD');\n let result = '';\n let wasUnderscore = false;\n\n for (const char of normalized) {\n const code = char.charCodeAt(0);\n\n // Drop combining marks introduced by NFKD decomposition.\n if (code >= 0x0300 && code <= 0x036f) {\n continue;\n }\n\n // Allow a narrow filename-safe ASCII subset in the quoted fallback.\n // Everything else collapses to `_` so older parsers still get a stable,\n // unambiguous name without needing quoted-string escaping.\n const isSafeASCII =\n // 0-9\n (code >= 0x30 && code <= 0x39) ||\n // A-Z\n (code >= 0x41 && code <= 0x5a) ||\n // a-z\n (code >= 0x61 && code <= 0x7a) ||\n char === '.' ||\n char === '-' ||\n char === '_';\n\n if (isSafeASCII) {\n result += char;\n wasUnderscore = false;\n } else if (!wasUnderscore) {\n result += '_';\n wasUnderscore = true;\n }\n }\n\n result = result.replace(/^_+|_+$/g, '');\n\n // Avoid an empty fallback if every character was replaced.\n return result.length > 0 ? result : 'file';\n}\n\n/**\n * Formats a multipart field name for the quoted `name=\"...\"` parameter.\n *\n * Field names are not lossy-sanitized like filenames because servers typically\n * expect the original key. We preserve the name while making it safe for a\n * quoted-string parameter by:\n * 1. Collapsing CRLF / CR / LF sequences to a single space so a malicious\n * key cannot inject new headers\n * 2. Stripping remaining control characters (0x00–0x08, 0x0B, 0x0C,\n * 0x0E–0x1F, 0x7F) that are invalid inside a quoted-string per\n * RFC 7230 §3.2.6\n * 3. Escaping `\\` and `\"` per quoted-string rules\n */\nfunction formatFieldName(name: string): string {\n return (\n name\n .replace(/\\r\\n|\\r|\\n/g, ' ')\n // eslint-disable-next-line no-control-regex\n .replace(/[\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]/g, '')\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"')\n );\n}\n\n/**\n * Sanitizes a MIME type string for safe use in a multipart Content-Type header.\n *\n * Browser and Node.js `File.type` values are normally clean, but a\n * programmatically constructed File could carry CR/LF sequences that would\n * inject extra headers into the multipart part. Stripping line breaks\n * eliminates that risk while still forwarding the intended media type.\n */\nfunction sanitizeContentType(raw: string): string {\n return raw.replace(/\\r\\n|\\r|\\n/g, '');\n}\n\n/**\n * Generates a random boundary string for use as the multipart delimiter.\n *\n * The boundary must not appear anywhere in the body content (RFC 7578 §4.1).\n * The \"----\" prefix and random suffix make accidental collision essentially\n * impossible for normal payloads. For adversarial inputs (a file that happens\n * to contain the boundary string) you would need boundary detection — we don't\n * do that here, matching browser FormData behavior.\n */\nexport function generateMultipartBoundary(): string {\n return `----NodeAdapterFormBoundary${Math.random().toString(36).slice(2)}`;\n}\n\n/**\n * Returns the exact byte length of the multipart body for a given FormData\n * and boundary — without writing anything.\n *\n * This mirrors the exact structure that serializeMultipartFormData will\n * produce, so the two functions must stay in sync.\n *\n * Why this exists: Node can stream the body without precomputing it, but then\n * upload progress would be indeterminate. By counting the exact bytes first,\n * we can set Content-Length up front and report real 0–100% progress during\n * serialization instead of guesswork.\n */\nexport function calculateMultipartFormDataSize(\n formData: FormData,\n boundary: string,\n): number {\n let size = 0;\n\n for (const [name, value] of formData.entries()) {\n const fieldName = formatFieldName(name);\n\n // Every part starts with its own opening boundary line.\n size += Buffer.byteLength(`--${boundary}\\r\\n`);\n\n if (typeof value === 'string') {\n // String part wire format:\n // Content-Disposition header\n // blank line\n // raw string value\n // trailing CRLF before the next boundary\n size += Buffer.byteLength(\n `Content-Disposition: form-data; name=\"${fieldName}\"\\r\\n\\r\\n${value}\\r\\n`,\n );\n } else {\n const filename = (value as File).name || 'blob';\n const contentType =\n sanitizeContentType((value as File).type) || 'application/octet-stream';\n\n // File/blob part wire format:\n // Content-Disposition header (with filename)\n // Content-Type header\n // blank line\n // raw file bytes\n // trailing CRLF before the next boundary\n size += Buffer.byteLength(\n `Content-Disposition: form-data; name=\"${fieldName}\"; ${formatFilename(filename)}\\r\\nContent-Type: ${contentType}\\r\\n\\r\\n`,\n );\n // Blob.size is already the exact byte length of the binary payload.\n size += (value as Blob).size;\n size += Buffer.byteLength('\\r\\n');\n }\n }\n\n // Final terminating boundary line — note the extra trailing `--`.\n size += Buffer.byteLength(`--${boundary}--\\r\\n`);\n return size;\n}\n\n/**\n * Writes a FormData body into a RequestBodyWritable following the\n * multipart/form-data wire format (RFC 7578), setting Content-Type and\n * Content-Length headers first.\n *\n * This does not build one giant multipart buffer in memory. It writes the\n * envelope (boundaries + headers) directly and streams file/blob payloads\n * chunk-by-chunk from Blob.stream().\n */\nexport async function serializeMultipartFormData(\n formData: FormData,\n req: RequestBodyWritable,\n boundary: string,\n onProgress?: (e: AdapterProgressEvent) => void,\n): Promise<void> {\n const totalSize = calculateMultipartFormDataSize(formData, boundary);\n\n // Servers need the boundary to parse the multipart body. We set an exact\n // Content-Length too, so upload progress is based on known total bytes\n // rather than HTTP chunked-transfer behavior.\n req.setHeader('Content-Type', `multipart/form-data; boundary=${boundary}`);\n req.setHeader('Content-Length', totalSize.toString());\n\n let uploadedBytes = 0;\n\n const write = (data: string | Buffer | Uint8Array): Promise<void> =>\n new Promise<void>((resolve, reject) => {\n let hasWriteReturned = false;\n let isWriteCallbackDone = false;\n let isDrainDone = true;\n\n const cleanup = (): void => {\n req.off('drain', onDrain);\n req.off('close', onClose);\n req.off('error', onError);\n };\n\n const maybeResolve = (): void => {\n if (isWriteCallbackDone && isDrainDone) {\n cleanup();\n uploadedBytes += Buffer.byteLength(data);\n\n onProgress?.({\n loaded: uploadedBytes,\n total: totalSize,\n progress: uploadedBytes / totalSize,\n });\n resolve();\n }\n };\n\n const onDrain = (): void => {\n isDrainDone = true;\n maybeResolve();\n };\n\n const onClose = (): void => {\n cleanup();\n resolve();\n };\n\n const onError = (error: Error): void => {\n cleanup();\n reject(error);\n };\n\n const canContinue = req.write(data, (error: Error | null | undefined) => {\n if (error) {\n cleanup();\n reject(error);\n return;\n }\n\n isWriteCallbackDone = true;\n if (hasWriteReturned) {\n maybeResolve();\n }\n });\n hasWriteReturned = true;\n\n if (!canContinue) {\n isDrainDone = false;\n req.once('drain', onDrain);\n req.once('close', onClose);\n req.once('error', onError);\n\n if (req.destroyed) {\n onClose();\n }\n }\n\n maybeResolve();\n });\n\n for (const [name, value] of formData.entries()) {\n const fieldName = formatFieldName(name);\n\n if (req.destroyed) {\n break;\n }\n\n // Start this part with its boundary delimiter.\n await write(`--${boundary}\\r\\n`);\n\n if (typeof value === 'string') {\n // Simple field: one header, blank line, then the field value.\n await write(\n `Content-Disposition: form-data; name=\"${fieldName}\"\\r\\n\\r\\n${value}\\r\\n`,\n );\n } else {\n const filename = (value as File).name || 'blob';\n const contentType =\n sanitizeContentType((value as File).type) || 'application/octet-stream';\n\n // File/blob field: multipart headers first, then the binary payload.\n await write(\n `Content-Disposition: form-data; name=\"${fieldName}\"; ${formatFilename(filename)}\\r\\nContent-Type: ${contentType}\\r\\n\\r\\n`,\n );\n\n // Blob.stream() lets us forward large files piece-by-piece instead of\n // concatenating the whole payload into a single upload buffer.\n const reader = (value as Blob)\n .stream()\n .getReader() as ReadableStreamDefaultReader<Uint8Array>;\n\n try {\n while (true) {\n if (req.destroyed) {\n await cancelReaderQuietly(reader);\n break;\n }\n\n const { done: isDone, value: chunk } = await reader.read();\n\n if (isDone) {\n break;\n }\n\n if (req.destroyed) {\n await cancelReaderQuietly(reader);\n break;\n }\n\n if (chunk) {\n // Each chunk contributes to upload progress immediately after write.\n await write(chunk);\n }\n }\n } finally {\n if (req.destroyed) {\n await cancelReaderQuietly(reader);\n }\n }\n\n if (!req.destroyed) {\n // Multipart parts are separated by CRLF after the value bytes too.\n await write('\\r\\n');\n }\n }\n }\n\n if (!req.destroyed) {\n // Closing delimiter that tells the server there are no more parts.\n await write(`--${boundary}--\\r\\n`);\n }\n}\n\nasync function cancelReaderQuietly(\n reader: ReadableStreamDefaultReader<Uint8Array>,\n): Promise<void> {\n try {\n await reader.cancel();\n } catch {\n // Best-effort cancellation only. Preserve the original request outcome.\n }\n}\n","import type { AdapterProgressEvent } from '../types';\nimport type { RequestBodyWritable } from './request-body-writable';\n\n/**\n * 16 KB chunks: meaningful upload progress granularity (a 1 MB body fires ~64\n * events) without excessive syscall overhead from tiny writes.\n */\nexport const REQUEST_BODY_CHUNK_SIZE = 16 * 1024;\n\n/**\n * Writes a pre-serialized request body buffer (string or Uint8Array body, not\n * FormData) into a RequestBodyWritable in fixed-size chunks.\n *\n * Each chunk awaits its write callback before the next chunk is sent. This\n * serves two purposes:\n * 1. Progress accuracy — each event reflects bytes actually handed off to the\n * OS socket buffer, not just bytes queued in the JS write buffer.\n * 2. Backpressure — we don't race ahead of the socket; if the OS buffer is\n * full the await naturally yields until there's room.\n *\n * This is intentionally separate from multipart handling. Multipart uploads\n * need boundaries/part headers and may stream Blob chunks directly; this\n * helper is only for bodies that are already serialized into one Buffer.\n */\nexport async function writeRequestBodyChunked(\n data: Buffer,\n req: RequestBodyWritable,\n onProgress?: (e: AdapterProgressEvent) => void,\n): Promise<void> {\n const totalSize = data.length;\n\n if (totalSize === 0) {\n // Empty explicit bodies still need a terminal completion event so progress\n // consumers do not wait forever for a 100% upload signal.\n onProgress?.({ loaded: 0, total: 0, progress: 1 });\n return;\n }\n\n let uploadedBytes = 0;\n\n while (uploadedBytes < totalSize && !req.destroyed) {\n // Slice the next fixed-size window from the already-serialized payload.\n const chunk = data.subarray(\n uploadedBytes,\n uploadedBytes + REQUEST_BODY_CHUNK_SIZE,\n );\n\n // Wait for both the write callback and any required drain signal before\n // moving on so large uploads do not outrun socket backpressure.\n await writeChunkWithBackpressure(req, chunk, () => {\n // Only count bytes after the writable confirms the chunk was accepted.\n uploadedBytes += chunk.length;\n onProgress?.({\n loaded: uploadedBytes,\n total: totalSize,\n progress: uploadedBytes / totalSize,\n });\n });\n }\n}\n\nfunction writeChunkWithBackpressure(\n req: RequestBodyWritable,\n chunk: Buffer,\n onAccepted?: () => void,\n): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n let hasWriteReturned = false;\n let isWriteCallbackDone = false;\n let isDrainDone = true;\n let isSettled = false;\n\n const cleanup = (): void => {\n req.off('drain', onDrain);\n req.off('close', onClose);\n req.off('error', onError);\n };\n\n const maybeResolve = (): void => {\n if (isSettled || !isWriteCallbackDone || !isDrainDone) {\n return;\n }\n\n isSettled = true;\n cleanup();\n onAccepted?.();\n resolve();\n };\n\n const onDrain = (): void => {\n isDrainDone = true;\n maybeResolve();\n };\n\n const onClose = (): void => {\n if (isSettled) {\n return;\n }\n\n isSettled = true;\n cleanup();\n resolve();\n };\n\n const onError = (error: Error): void => {\n if (isSettled) {\n return;\n }\n\n isSettled = true;\n cleanup();\n reject(error);\n };\n\n const canContinue = req.write(\n chunk,\n (error: Error | null | undefined): void => {\n if (error) {\n cleanup();\n reject(error);\n return;\n }\n\n isWriteCallbackDone = true;\n if (hasWriteReturned) {\n maybeResolve();\n }\n },\n );\n hasWriteReturned = true;\n\n if (!canContinue) {\n isDrainDone = false;\n req.once('drain', onDrain);\n req.once('close', onClose);\n req.once('error', onError);\n\n if (req.destroyed) {\n onClose();\n }\n }\n\n maybeResolve();\n });\n}\n","const CERT_ERROR_CODES = new Set([\n 'UNABLE_TO_VERIFY_LEAF_SIGNATURE',\n 'CERT_HAS_EXPIRED',\n 'DEPTH_ZERO_SELF_SIGNED_CERT',\n 'ERR_TLS_CERT_ALTNAME_INVALID',\n 'ERR_SSL_TLSV13_ALERT_CERTIFICATE_REQUIRED',\n 'ERR_SSL_PEER_DID_NOT_RETURN_A_CERTIFICATE',\n 'SELF_SIGNED_CERT_IN_CHAIN',\n 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY',\n 'CERT_REVOKED',\n]);\n\n/** `CRL` or `CRLS` as an underscore-delimited token, anywhere in a code. */\nconst CRL_CODE_PATTERN = /(?:^|_)CRLS?(?:_|$)/;\n\n/**\n * Node system error codes that name a DNS or socket failure, so whatever the\n * message says, the connection is what failed. Consulted only to suppress the\n * message fallback.\n *\n * Deliberately not NodeAdapter's `PRE_CONNECTION_ERROR_CODES`, which answers a\n * different question (did any bytes reach the server?) and must stay\n * conservative in the opposite direction.\n */\nconst TRANSPORT_ERROR_CODES = new Set([\n 'ENOTFOUND',\n 'EAI_AGAIN',\n 'ECONNREFUSED',\n 'ECONNRESET',\n 'ECONNABORTED',\n 'ETIMEDOUT',\n 'EPIPE',\n 'EHOSTUNREACH',\n 'EHOSTDOWN',\n 'ENETUNREACH',\n 'ENETDOWN',\n 'EADDRNOTAVAIL',\n]);\n\n/**\n * Node's built-in `fetch` is backed by Undici, which uses its own `UND_ERR_*`\n * namespace for transport and protocol failures instead of the system codes\n * above. Keep the namespaces separate so this does not imply NodeAdapter —\n * which uses `node:http` / `node:https` — emits Undici errors too.\n */\nconst UNDICI_TRANSPORT_ERROR_PREFIX = 'UND_ERR_';\n\n/**\n * Best-effort classification for TLS certificate failures that should be\n * treated as non-retryable transport errors by NodeAdapter.\n */\nexport function isTLSCertificateError(error: Error): boolean {\n if (isTLSCertificateErrorSelf(error)) {\n return true;\n }\n\n // Bun puts the OpenSSL code on the rejected error itself; Node wraps it as\n // `TypeError: fetch failed` and hangs the real error off `cause`. Checking the\n // error plus exactly one cause link covers both.\n //\n // One link, not a walk: `cause` is library-controlled and may point back at its\n // own error, and nothing in either runtime nests deeper. A browser has nothing\n // to follow, so this stays a server-side classification.\n const cause = readMember(error, 'cause');\n\n return isErrorValue(cause) ? isTLSCertificateErrorSelf(cause) : false;\n}\n\n/** Check Error identity without trusting a Proxy's prototype trap. */\nfunction isErrorValue(value: unknown): value is Error {\n try {\n return value instanceof Error;\n } catch {\n return false;\n }\n}\n\n/**\n * Classify a single error, without consulting its `cause`. Every field is read\n * through {@link readMember}, since the error is whatever a runtime, a library,\n * or a caller's mocked `fetch` rejected with.\n */\nfunction isTLSCertificateErrorSelf(error: Error): boolean {\n const rawCode = readMember(error, 'code');\n const code = typeof rawCode === 'string' ? rawCode : '';\n\n if (CERT_ERROR_CODES.has(code)) {\n return true;\n }\n\n // Every CRL-related verification code, by property rather than by name.\n // OpenSSL exposes twelve of them (UNABLE_TO_GET_CRL,\n // UNABLE_TO_DECRYPT_CRL_SIGNATURE, CRL_HAS_EXPIRED, KEYUSAGE_NO_CRL_SIGN,\n // ERROR_IN_CRL_NEXT_UPDATE_FIELD, DIFFERENT_CRL_SCOPE, …) and an earlier\n // version of this listed six by hand and missed the other six. They are all\n // revocation-check failures, and enumerating them means being wrong again\n // the next time OpenSSL adds one — so ask whether the code concerns a CRL\n // instead. The message fallback below does not catch them: 'CRL has expired'\n // and \"format error in CRL's lastUpdate field\" contain none of the words it\n // looks for, so without this they resolve as status 0 rather than 495.\n //\n // Matched as an underscore-delimited token, not a substring, which closes the\n // one word that shares the prefix by accident: CRLF. No code uses it today, but\n // header and framing errors are where one would be minted, and those are\n // connection problems. The plural is allowed for NO_CRLS_INCLUDED.\n if (CRL_CODE_PATTERN.test(code)) {\n return true;\n }\n\n if (\n (code.startsWith('ERR_TLS_') || code.startsWith('ERR_SSL_')) &&\n code.includes('CERT')\n ) {\n return true;\n }\n\n // A code that names a transport failure is authoritative, so the message is\n // never consulted for one. The message test below runs against a runtime-built\n // string with the hostname interpolated in, so `getaddrinfo EAI_AGAIN\n // certificates.internal.corp` matches /certificate/ on the host name alone —\n // and any deployment calling a `certificates.*` host would have had every DNS\n // blip resolve as a permanent 495 with `isRetryable: false`.\n //\n // Only the fallback is suppressed; the code checks above still run first.\n if (\n TRANSPORT_ERROR_CODES.has(code) ||\n code.startsWith(UNDICI_TRANSPORT_ERROR_PREFIX)\n ) {\n return false;\n }\n\n // Reached with no code, or a code the checks above do not recognize — which is\n // why the fallback cannot be dropped for any error carrying one. OpenSSL has\n // many more permanent verification failures (CERT_UNTRUSTED, HOSTNAME_MISMATCH,\n // UNABLE_TO_GET_ISSUER_CERT, INVALID_CA, …) that match no code rule above.\n const message = readMember(error, 'message');\n\n return typeof message === 'string'\n ? /certificate|self signed|unable to verify|altname/i.test(message)\n : false;\n}\n\n/**\n * Read a field off an error without trusting it.\n *\n * This classifier runs inside adapter error handling, where a throwing getter\n * would replace a normalized `status: 0` transport response with the getter's own\n * error. Unreadable is treated as absent, on every field rather than only on\n * `cause` — the outer error is no more this module's own than the nested one.\n */\nfunction readMember(error: object, key: string): unknown {\n try {\n return (error as Record<string, unknown>)[key];\n } catch {\n return undefined;\n }\n}\n","export function normalizeNodeRequestHeaders(\n headers: Record<string, string | string[] | number | undefined>,\n): Record<string, string | string[]> {\n const result: Record<string, string | string[]> = {};\n\n for (const [key, value] of Object.entries(headers)) {\n if (value === undefined) {\n continue;\n }\n\n result[key.toLowerCase()] = Array.isArray(value)\n ? value.map((item) => String(item))\n : String(value);\n }\n\n return result;\n}\n\nexport function materializeNodeRequestHeaders(\n headers: Record<string, string | string[]>,\n): Record<string, string> {\n const result: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(headers)) {\n if (!Array.isArray(value)) {\n result[key] = value;\n continue;\n }\n\n result[key] =\n key.toLowerCase() === 'cookie'\n ? // RFC 6265 cookie-pair delimiter for a single Cookie header field.\n value.join('; ')\n : value.join(', ');\n }\n\n return result;\n}\n","import qs from 'qs';\nimport {\n matchesWildcardDomain,\n normalizeDomain,\n} from '../domain-utils/domain-utils';\nimport type {\n ContentType,\n RequestPhaseName,\n HTTPClientConfig,\n AdapterType,\n} from './types';\n\n/**\n * If `path` is an absolute HTTP(S) URL, returns its canonical `href` (normalized\n * scheme/host casing). Otherwise `null`. Protocol-relative `//host` is handled\n * separately in `buildURL`.\n *\n * Rules differ from `resolveAbsoluteURL` (which accepts any absolute scheme).\n * One parse here per request is negligible next to network I/O.\n */\nfunction tryAbsoluteWebHref(path: string): string | null {\n if (!path || path.startsWith('//')) {\n return null;\n }\n\n try {\n const u = new URL(path);\n if (u.protocol === 'http:' || u.protocol === 'https:') {\n return u.href;\n }\n } catch {\n // not parseable as absolute\n }\n\n return null;\n}\n\n/**\n * Builds a request URL string from `baseURL`, `path`, and optional query params\n * (`qs` — nested objects and arrays supported).\n *\n * **Relative paths (usual case)** — When `baseURL` is set and `path` is not\n * absolute, `path` is joined to `baseURL` (leading slash normalized). Example:\n * `baseURL: https://api.test`, `path: /v1/users` → `https://api.test/v1/users`.\n *\n * **Absolute / protocol-relative `path` (escape hatch)** — If `path` is a full\n * `http:` or `https:` URL, it is **not** prefixed with `baseURL` (after\n * normalization via `URL#href`). The same applies to protocol-relative URLs\n * (`//cdn.example/x`): they are left for {@link resolveAbsoluteURL} to resolve\n * using the client `baseURL`’s scheme. Use this for one-off cross-origin calls,\n * CDN assets, or URLs returned by APIs; for strict per-origin clients, prefer\n * relative paths and a dedicated client or `HTTPClient.createSubClient()` per\n * origin.\n */\nexport function buildURL(\n baseURL: string | undefined,\n path: string,\n params?: Record<string, unknown>,\n): string {\n let url: string;\n\n const absoluteHref = tryAbsoluteWebHref(path);\n\n if (baseURL && absoluteHref === null && !path.startsWith('//')) {\n // Avoid double slashes when joining base + path\n const base = baseURL.endsWith('/') ? baseURL.slice(0, -1) : baseURL;\n const p = path.startsWith('/') ? path : `/${path}`;\n url = `${base}${p}`;\n } else if (absoluteHref !== null) {\n url = absoluteHref;\n } else {\n url = path;\n }\n\n if (params && Object.keys(params).length > 0) {\n const [urlWithoutHash, hash = ''] = url.split('#', 2);\n const queryStartIndex = urlWithoutHash.indexOf('?');\n\n if (queryStartIndex === -1) {\n const queryString = qs.stringify(params, { addQueryPrefix: true });\n url = `${urlWithoutHash}${queryString}${hash ? `#${hash}` : ''}`;\n } else {\n const basePath = urlWithoutHash.slice(0, queryStartIndex);\n const existingQuery = urlWithoutHash.slice(queryStartIndex + 1);\n // Fragments are preserved only as part of the caller's URL string.\n // They are not transmitted in HTTP requests, but keeping them intact\n // makes buildURL safer as a general-purpose URL composition helper.\n const mergedParams = {\n ...qs.parse(existingQuery),\n ...params,\n };\n\n const queryString = qs.stringify(mergedParams, { addQueryPrefix: true });\n url = `${basePath}${queryString}${hash ? `#${hash}` : ''}`;\n }\n }\n\n return url;\n}\n\n/**\n * Best-effort absolute URL for logging, redirects, and hop metadata.\n *\n * - If `url` parses as an absolute URL (has a scheme), returns normalized `href`.\n * - Otherwise, when `baseURL` is set, resolves `url` against it (path-relative,\n * same-host relative, protocol-relative `//host`, query-only, etc.).\n * - If neither works, returns `url` unchanged (callers without `baseURL` may still\n * see path-only strings).\n */\nexport function resolveAbsoluteURL(url: string, baseURL?: string): string {\n if (!url) {\n return url;\n }\n\n try {\n return new URL(url).href;\n } catch {\n // Not a standalone absolute URL\n }\n\n if (baseURL) {\n try {\n const base = baseURL.endsWith('/') ? baseURL : `${baseURL}/`;\n return new URL(url, base).href;\n } catch {\n // fall through\n }\n }\n\n return url;\n}\n\n/**\n * Browser-aware absolute URL resolution used by HTTPClient before interceptors\n * and adapter dispatch. Starts with normal baseURL resolution, then falls back\n * to the current page/worker location when running in a browser-like runtime.\n */\nexport function resolveAbsoluteURLForRuntime(\n url: string,\n baseURL: string | undefined,\n isBrowserRuntime: boolean,\n): string {\n const resolved = resolveAbsoluteURL(url, baseURL);\n\n if (\n !isBrowserRuntime ||\n resolved.startsWith('http://') ||\n resolved.startsWith('https://')\n ) {\n return resolved;\n }\n\n const browserBase = getBrowserResolutionBase();\n\n if (!browserBase) {\n return resolved;\n }\n\n return resolveAbsoluteURL(resolved, browserBase);\n}\n\nfunction getBrowserResolutionBase(): string | undefined {\n if (\n typeof document !== 'undefined' &&\n typeof document.baseURI === 'string' &&\n document.baseURI\n ) {\n return document.baseURI;\n }\n\n if (\n typeof window !== 'undefined' &&\n window.location &&\n typeof window.location.href === 'string' &&\n window.location.href\n ) {\n return window.location.href;\n }\n\n const globalLocation = (globalThis as { location?: { href?: unknown } })\n .location;\n\n if (\n globalLocation &&\n typeof globalLocation.href === 'string' &&\n globalLocation.href\n ) {\n return globalLocation.href;\n }\n\n const selfLocation = (\n globalThis as { self?: { location?: { href?: unknown } } }\n ).self?.location;\n\n if (\n selfLocation &&\n typeof selfLocation.href === 'string' &&\n selfLocation.href\n ) {\n return selfLocation.href;\n }\n\n return undefined;\n}\n\n/**\n * Normalizes header keys to lowercase.\n */\nexport function normalizeHeaders(\n headers: Record<string, string>,\n): Record<string, string> {\n const result: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(headers)) {\n result[key.toLowerCase()] = value;\n }\n\n return result;\n}\n\n/**\n * Merges multiple request-header objects, normalizing keys to lowercase.\n * Later objects win on conflict. Array values replace earlier scalars/arrays\n * wholesale, and single-item arrays are collapsed back to a plain string.\n */\nexport function mergeHeaders(\n ...headerSets: Array<Record<string, string | string[]> | undefined>\n): Record<string, string | string[]> {\n const result: Record<string, string | string[]> = {};\n for (const headers of headerSets) {\n if (!headers) {\n continue;\n }\n\n for (const [key, value] of Object.entries(headers)) {\n result[key.toLowerCase()] = Array.isArray(value)\n ? normalizeMergedHeaderArray(value)\n : String(value);\n }\n }\n\n return result;\n}\n\nfunction normalizeMergedHeaderArray(value: string[]): string | string[] {\n const normalized = value.map((item) => String(item));\n return normalized.length === 1 ? normalized[0] : normalized;\n}\n\nexport function mergeObservedHeaders(\n ...headerSets: Array<Record<string, string | string[]> | undefined>\n): Record<string, string | string[]> {\n const result: Record<string, string | string[]> = {};\n\n for (const headers of headerSets) {\n if (!headers) {\n continue;\n }\n\n for (const [key, value] of Object.entries(headers)) {\n result[key.toLowerCase()] = Array.isArray(value)\n ? value.map((item) => String(item))\n : String(value);\n }\n }\n\n return result;\n}\n\n/**\n * Parses the Content-Type header into a ContentType enum value.\n */\nexport function parseContentType(\n contentTypeHeader: string | undefined,\n): ContentType {\n if (!contentTypeHeader) {\n return 'binary';\n } else {\n const lower = contentTypeHeader.trim().toLowerCase();\n\n if (lower.includes('application/json') || lower.includes('+json')) {\n return 'json';\n } else if (lower.startsWith('text/')) {\n return 'text';\n } else if (lower.includes('application/x-www-form-urlencoded')) {\n return 'text';\n } else {\n return 'binary';\n }\n }\n}\n\n/**\n * Validates adapter/runtime combinations and redirect config before a client\n * is constructed, so unsupported browser-only/server-only options fail fast\n * with clear errors instead of surfacing later during request dispatch.\n */\nexport function assertSupportedAdapterRuntimeAndConfig(\n config: HTTPClientConfig,\n adapterType: AdapterType,\n isBrowserRuntime: boolean,\n): void {\n if (\n config.baseURL !== undefined &&\n requiresAbsoluteBaseURL(adapterType, isBrowserRuntime)\n ) {\n assertValidBaseURL(config.baseURL);\n }\n\n if (config.maxRedirects !== undefined && config.followRedirects !== true) {\n throw new Error('HTTPClient maxRedirects requires followRedirects: true.');\n }\n\n if (\n config.followRedirects === true &&\n config.maxRedirects !== undefined &&\n config.maxRedirects < 1\n ) {\n throw new Error(\n 'HTTPClient maxRedirects must be greater than or equal to 1 when followRedirects is true.',\n );\n }\n\n if (adapterType === 'xhr' && config.followRedirects === true) {\n throw new Error(\n 'HTTPClient redirect handling is not supported with XHR adapter. Set followRedirects: false or use a different adapter/runtime.',\n );\n }\n\n if (adapterType === 'xhr' && !hasXMLHttpRequestGlobal()) {\n throw new Error(\n 'HTTPClient XHR adapter is not supported when XMLHttpRequest is unavailable. Use a browser runtime, install a test shim, or switch to the FetchAdapter/NodeAdapter.',\n );\n }\n\n if (!isBrowserRuntime) {\n return;\n }\n\n if (adapterType === 'node') {\n throw new Error(\n 'HTTPClient Node adapter is not supported in browser environments.',\n );\n }\n\n // MockAdapter is intentionally allowed in browser runtimes: it is an\n // in-memory test adapter, so cookie jars and redirect following are local\n // simulation features rather than forbidden browser networking controls.\n if ((adapterType === 'fetch' || adapterType === 'xhr') && config.cookieJar) {\n throw new Error(\n `HTTPClient cookieJar is not supported with ${adapterType === 'fetch' ? 'FetchAdapter' : 'XHR adapter'} in browser environments. Browsers manage cookies automatically.`,\n );\n }\n\n if ((adapterType === 'fetch' || adapterType === 'xhr') && config.userAgent) {\n throw new Error(\n `HTTPClient userAgent is not supported with ${adapterType === 'fetch' ? 'FetchAdapter' : 'XHR adapter'} in browser environments. Browsers do not allow overriding the User-Agent header.`,\n );\n }\n\n if (adapterType === 'fetch' && config.followRedirects === true) {\n throw new Error(\n 'HTTPClient redirect handling is not supported with FetchAdapter in browser environments. Set followRedirects: false or use a server runtime.',\n );\n }\n}\n\nfunction requiresAbsoluteBaseURL(\n adapterType: AdapterType,\n isBrowserRuntime: boolean,\n): boolean {\n if (adapterType === 'node' || adapterType === 'mock') {\n return true;\n }\n\n if (adapterType === 'fetch' && !isBrowserRuntime) {\n return true;\n }\n\n return false;\n}\n\nfunction hasXMLHttpRequestGlobal(): boolean {\n return (\n typeof globalThis !== 'undefined' &&\n typeof (globalThis as { XMLHttpRequest?: unknown }).XMLHttpRequest ===\n 'function'\n );\n}\n\n/**\n * Converts a Headers object (from fetch) into AdapterResponse headers.\n * `set-cookie` is extracted as `string[]` via `getSetCookie()` — the Fetch API\n * would otherwise incorrectly comma-join multiple Set-Cookie values.\n * All other headers are extracted as plain strings.\n */\nexport function extractFetchHeaders(\n headers: Headers,\n): Record<string, string | string[]> {\n const result: Record<string, string | string[]> = {};\n\n for (const [key, value] of headers.entries()) {\n const lower = key.toLowerCase();\n\n if (lower !== 'set-cookie') {\n result[lower] = value;\n }\n }\n\n // Use getSetCookie() when available (Bun, Node 18.14+, modern browsers)\n if (typeof headers.getSetCookie === 'function') {\n const setCookies = headers.getSetCookie();\n\n if (setCookies.length > 0) {\n result['set-cookie'] = setCookies;\n }\n } else {\n // Fallback: headers.get() comma-joins — split on ', ' is unreliable for\n // cookies but better than nothing on older runtimes\n const raw = headers.get('set-cookie');\n\n if (raw) {\n result['set-cookie'] = [raw];\n }\n }\n\n return result;\n}\n\n/**\n * Lowercases all keys on adapter/response header objects. `HTTPClient` runs\n * this on each adapter response before {@link CookieJar.processResponseHeaders}.\n * The jar also normalizes so the same shapes work when feeding headers directly.\n *\n * - Non–`set-cookie` values: if an array appears (unexpected), the first\n * element is kept when read via {@link scalarHeader}.\n * - `set-cookie`: stored as `string[]` — each array entry is one full\n * `Set-Cookie` header line (one cookie). A single string value becomes a\n * one-element array. If the same header appears under keys that differ only\n * by case, those lines are appended in the order they appear on the input\n * object.\n */\nexport function normalizeAdapterResponseHeaders(\n headers: Record<string, string | string[]>,\n): Record<string, string | string[]> {\n const result: Record<string, string | string[]> = {};\n\n for (const [key, value] of Object.entries(headers)) {\n const lower = key.toLowerCase();\n\n if (lower === 'set-cookie') {\n const chunk = Array.isArray(value) ? value : [value];\n const existing = result[lower];\n\n if (existing === undefined) {\n result[lower] = chunk;\n } else {\n const existingLines = Array.isArray(existing) ? existing : [existing];\n result[lower] = [...existingLines, ...chunk];\n }\n } else {\n result[lower] = Array.isArray(value) ? (value[0] ?? '') : value;\n }\n }\n\n return result;\n}\n\n/**\n * Reads a single-valued header when keys are already lowercase (e.g. after\n * {@link mergeHeaders} on requests or {@link normalizeAdapterResponseHeaders}\n * on responses). If the stored value is `string[]`, returns the first entry.\n */\nexport function scalarHeader(\n headers: Record<string, string | string[]>,\n lowercaseName: string,\n): string | undefined {\n const v = headers[lowercaseName];\n\n if (v === undefined) {\n return undefined;\n }\n\n return Array.isArray(v) ? v[0] : v;\n}\n\n/**\n * Resolves a redirect target from response headers when the adapter can\n * observe a redirect response but the client may not follow it itself.\n */\nexport function resolveDetectedRedirectURL(\n requestURL: string,\n status: number,\n headers: Record<string, string | string[]>,\n baseURL?: string,\n): string | undefined {\n if (![301, 302, 303, 307, 308].includes(status)) {\n return undefined;\n }\n\n const location = scalarHeader(headers, 'location');\n\n if (!location) {\n return undefined;\n }\n\n try {\n const absoluteRequestURL = resolveAbsoluteURL(requestURL, baseURL);\n return new URL(location, absoluteRequestURL).toString();\n } catch {\n return location;\n }\n}\n\nexport function assertValidBaseURL(\n baseURL: string,\n fieldName = 'baseURL',\n): void {\n try {\n const url = new URL(baseURL);\n\n if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n throw new Error('unsupported protocol');\n }\n } catch {\n throw new Error(\n `HTTPClient ${fieldName} must be an absolute http(s) URL (for example \"https://api.example.com\").`,\n );\n }\n}\n\n/**\n * Detects whether the current runtime looks like a browser environment.\n */\nexport function isBrowserEnvironment(): boolean {\n if (typeof globalThis === 'undefined') {\n return false;\n }\n\n if ('window' in globalThis && 'document' in globalThis) {\n return true;\n }\n\n const workerGlobalScope = (\n globalThis as {\n WorkerGlobalScope?: abstract new (...args: never[]) => unknown;\n }\n ).WorkerGlobalScope;\n\n if (\n typeof workerGlobalScope === 'function' &&\n (globalThis as { self?: unknown }).self instanceof workerGlobalScope\n ) {\n return true;\n }\n\n const constructorName = globalThis.constructor?.name;\n\n return (\n !('window' in globalThis) &&\n !('document' in globalThis) &&\n typeof constructorName === 'string' &&\n constructorName.endsWith('WorkerGlobalScope')\n );\n}\n\n/**\n * Serializes the request body and returns the body + inferred content-type.\n * If `formData` is provided, it takes precedence over `body`.\n */\nexport function serializeBody(body: unknown): {\n body: string | Uint8Array | FormData | null;\n contentType: string | null;\n} {\n assertSupportedRequestBody(body);\n\n if (body instanceof FormData) {\n return { body, contentType: null }; // browser/runtime sets multipart boundary automatically\n } else if (body === undefined || body === null) {\n return { body: null, contentType: null };\n } else if (typeof body === 'string') {\n return { body, contentType: 'text/plain; charset=utf-8' };\n } else if (body instanceof Uint8Array) {\n return { body, contentType: 'application/octet-stream' };\n } else if (Array.isArray(body) || isPlainJSONBodyObject(body)) {\n return {\n body: JSON.stringify(body),\n contentType: 'application/json; charset=utf-8',\n };\n } else {\n throw new Error(\n 'Unsupported request body type. Supported types: string, Uint8Array, FormData, plain object, array, null, and undefined.',\n );\n }\n}\n\nexport function assertSupportedRequestBody(body: unknown): void {\n if (\n body === undefined ||\n body === null ||\n typeof body === 'string' ||\n body instanceof Uint8Array ||\n body instanceof FormData ||\n Array.isArray(body) ||\n isPlainJSONBodyObject(body)\n ) {\n return;\n }\n\n throw new Error(\n 'Unsupported request body type. Supported types: string, Uint8Array, FormData, plain object, array, null, and undefined.',\n );\n}\n\nexport function isPlainJSONBodyObject(\n value: unknown,\n): value is Record<string, unknown> {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) {\n return false;\n }\n\n const prototype = Reflect.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n}\n\n/**\n * Extracts the hostname from a URL string. Returns empty string on failure.\n */\nexport function extractHostname(url: string): string {\n try {\n return new URL(url).hostname;\n } catch {\n return '';\n }\n}\n\n/**\n * Wildcard hostname matching backed by {@link matchesWildcardDomain}.\n *\n * - `*` — global wildcard, matches any valid hostname including apex domains\n * - `*.example.com` — matches exactly one subdomain label (`api.example.com`) but not deeper levels or the apex\n * - `**.example.com` — matches one or more subdomain labels (`api.example.com`, `a.b.example.com`) but not the apex\n * - Exact patterns (no `*`) — normalized comparison, case-insensitive\n * - PSL tail guard active: `*.com`, `**.co.uk`, etc. never match\n * - Pseudo-TLD suffix wildcards are rejected just like PSL tails (`*.localhost`, `*.local`, etc.)\n */\nexport function matchesHostPattern(hostname: string, pattern: string): boolean {\n if (pattern.includes('*')) {\n return matchesWildcardDomain(hostname, pattern);\n }\n\n const normalizedHostname = normalizeDomain(hostname);\n const normalizedPattern = normalizeDomain(pattern);\n return normalizedHostname !== '' && normalizedHostname === normalizedPattern;\n}\n\n/**\n * Checks whether a dot-path key exists in a nested object.\n * Arrays are not traversed — only plain objects at each segment.\n */\nfunction hasNestedKey(obj: Record<string, unknown>, path: string): boolean {\n const parts = path.split('.');\n let current: unknown = obj;\n\n for (const part of parts) {\n if (!current || typeof current !== 'object' || Array.isArray(current)) {\n return false;\n }\n\n if (!(part in (current as Record<string, unknown>))) {\n return false;\n }\n\n current = (current as Record<string, unknown>)[part];\n }\n\n return true;\n}\n\nfunction normalizeMimeType(value: string): string {\n return value.split(';', 1)[0].trim().toLowerCase();\n}\n\nfunction matchesContentTypePattern(\n actualHeader: string,\n pattern: string,\n): boolean {\n const actual = normalizeMimeType(actualHeader);\n const expected = normalizeMimeType(pattern);\n\n if (!actual || !expected) {\n return false;\n }\n\n if (expected.endsWith('/*')) {\n const expectedType = expected.slice(0, -2);\n const slashIndex = actual.indexOf('/');\n\n if (slashIndex === -1) {\n return false;\n }\n\n return actual.slice(0, slashIndex) === expectedType;\n }\n\n return actual === expected;\n}\n\n/**\n * Tests whether a request context matches an interceptor/observer filter.\n *\n * Each filter field is optional — omitting it skips that check entirely.\n * All specified fields must match for the function to return true.\n * Within each field, values are matched with OR logic (any one match is sufficient).\n *\n * - `phases`: **OR** allowlist on `phaseType` ({@link RequestPhaseName}). Skipped when\n * `filter.phases` is omitted or empty.\n * - `statusCodes`: skipped if `context.status` is absent.\n * - `methods`: skipped if `context.method` is absent.\n * - `hosts`: supports exact hostnames and wildcard patterns. `*.example.com` matches\n * exactly one subdomain label; `**.example.com` matches any depth. Neither matches the\n * apex — list it explicitly. PSL tail guard prevents `*.com`-style patterns. `*` is a\n * global wildcard that matches any valid hostname. Skipped if `context.requestURL` is absent.\n * - `schemes`: `'http'` or `'https'`. `requestURL` is absolute whenever the\n * request could be resolved before dispatch. For `MockAdapter`, path-only\n * requests without a client `baseURL` are materialized as `http://localhost/...`;\n * browser adapters fall back to `window.location`, and the Node adapter requires\n * absolute URLs. Skipped only when `requestURL` is absent.\n * - `bodyContainsKeys`: supports dot paths (e.g. `data.results`). Each segment in\n * the path must resolve to a plain object for traversal to continue — the final\n * value can be anything (array, string, null, etc). Array indexing is not supported.\n * Skipped when `kind` is `'error'`.\n */\nexport function matchesFilter(\n filter: {\n statusCodes?: number[];\n methods?: string[];\n bodyContainsKeys?: string[];\n hosts?: string[];\n schemes?: ('http' | 'https')[];\n phases?: RequestPhaseName[];\n contentTypes?: ContentType[];\n contentTypeHeaders?: string[];\n },\n context: {\n status?: number;\n method?: string;\n body?: unknown;\n requestURL?: string;\n contentType?: ContentType;\n contentTypeHeader?: string;\n },\n phaseType: RequestPhaseName,\n kind: 'request' | 'response' | 'error',\n): boolean {\n if (\n filter.phases &&\n filter.phases.length > 0 &&\n !filter.phases.includes(phaseType)\n ) {\n return false;\n }\n\n if (filter.statusCodes && context.status !== undefined) {\n if (!filter.statusCodes.includes(context.status)) {\n return false;\n }\n }\n\n if (filter.methods && context.method) {\n if (!filter.methods.includes(context.method)) {\n return false;\n }\n }\n\n if (filter.contentTypes && filter.contentTypes.length > 0) {\n if (\n !context.contentType ||\n !filter.contentTypes.includes(context.contentType)\n ) {\n return false;\n }\n }\n\n if (filter.contentTypeHeaders && filter.contentTypeHeaders.length > 0) {\n if (\n !context.contentTypeHeader ||\n !filter.contentTypeHeaders.some((pattern) =>\n matchesContentTypePattern(context.contentTypeHeader as string, pattern),\n )\n ) {\n return false;\n }\n }\n\n if (\n kind !== 'error' &&\n filter.bodyContainsKeys &&\n filter.bodyContainsKeys.length > 0\n ) {\n if (\n !context.body ||\n typeof context.body !== 'object' ||\n Array.isArray(context.body)\n ) {\n return false;\n }\n\n const body = context.body as Record<string, unknown>;\n\n if (!filter.bodyContainsKeys.some((k) => hasNestedKey(body, k))) {\n return false;\n }\n }\n\n if (filter.hosts && context.requestURL) {\n const hostname = extractHostname(context.requestURL);\n\n if (\n !filter.hosts.some((pattern: string) =>\n matchesHostPattern(hostname, pattern),\n )\n ) {\n return false;\n }\n }\n\n if (filter.schemes && filter.schemes.length > 0 && context.requestURL) {\n let scheme: 'http' | 'https' | null = null;\n\n try {\n const parsedScheme = new URL(context.requestURL).protocol.replace(\n ':',\n '',\n );\n\n if (parsedScheme === 'http' || parsedScheme === 'https') {\n scheme = parsedScheme;\n }\n } catch {\n scheme = null;\n }\n\n if (!scheme || !filter.schemes.includes(scheme)) {\n return false;\n }\n }\n\n return true;\n}\n"],"mappings":"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","names":["totalBytes","abortErr","isErrorValue"]}
|