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.
Files changed (72) hide show
  1. package/README.md +39 -36
  2. package/dist/lib/event-emitter.cjs +195 -9
  3. package/dist/lib/event-emitter.cjs.map +1 -1
  4. package/dist/lib/event-emitter.js +195 -9
  5. package/dist/lib/event-emitter.js.map +1 -1
  6. package/dist/lib/global-event-target.cjs +217 -0
  7. package/dist/lib/global-event-target.cjs.map +1 -0
  8. package/dist/lib/global-event-target.d.cts +70 -0
  9. package/dist/lib/global-event-target.d.ts +70 -0
  10. package/dist/lib/global-event-target.js +189 -0
  11. package/dist/lib/global-event-target.js.map +1 -0
  12. package/dist/lib/http-client/index.cjs +554 -193
  13. package/dist/lib/http-client/index.cjs.map +1 -1
  14. package/dist/lib/http-client/index.d.cts +9 -3
  15. package/dist/lib/http-client/index.d.ts +9 -3
  16. package/dist/lib/http-client/index.js +554 -193
  17. package/dist/lib/http-client/index.js.map +1 -1
  18. package/dist/lib/http-client-mock/index.cjs +71 -10
  19. package/dist/lib/http-client-mock/index.cjs.map +1 -1
  20. package/dist/lib/http-client-mock/index.d.cts +50 -3
  21. package/dist/lib/http-client-mock/index.d.ts +50 -3
  22. package/dist/lib/http-client-mock/index.js +71 -10
  23. package/dist/lib/http-client-mock/index.js.map +1 -1
  24. package/dist/lib/http-client-node/index.cjs +98 -19
  25. package/dist/lib/http-client-node/index.cjs.map +1 -1
  26. package/dist/lib/http-client-node/index.d.cts +2 -2
  27. package/dist/lib/http-client-node/index.d.ts +2 -2
  28. package/dist/lib/http-client-node/index.js +98 -19
  29. package/dist/lib/http-client-node/index.js.map +1 -1
  30. package/dist/lib/http-client-xhr/index.cjs.map +1 -1
  31. package/dist/lib/http-client-xhr/index.d.cts +2 -2
  32. package/dist/lib/http-client-xhr/index.d.ts +2 -2
  33. package/dist/lib/http-client-xhr/index.js.map +1 -1
  34. package/dist/lib/lifecycle-manager/index.cjs +199 -13
  35. package/dist/lib/lifecycle-manager/index.cjs.map +1 -1
  36. package/dist/lib/lifecycle-manager/index.js +199 -13
  37. package/dist/lib/lifecycle-manager/index.js.map +1 -1
  38. package/dist/lib/logger/index.cjs +203 -21
  39. package/dist/lib/logger/index.cjs.map +1 -1
  40. package/dist/lib/logger/index.d.cts +5 -1
  41. package/dist/lib/logger/index.d.ts +5 -1
  42. package/dist/lib/logger/index.js +203 -21
  43. package/dist/lib/logger/index.js.map +1 -1
  44. package/dist/lib/lru-cache/index.cjs +195 -9
  45. package/dist/lib/lru-cache/index.cjs.map +1 -1
  46. package/dist/lib/lru-cache/index.js +195 -9
  47. package/dist/lib/lru-cache/index.js.map +1 -1
  48. package/dist/lib/process-signal-manager.cjs +199 -13
  49. package/dist/lib/process-signal-manager.cjs.map +1 -1
  50. package/dist/lib/process-signal-manager.js +199 -13
  51. package/dist/lib/process-signal-manager.js.map +1 -1
  52. package/dist/lib/promise-protected-resolver.cjs +195 -9
  53. package/dist/lib/promise-protected-resolver.cjs.map +1 -1
  54. package/dist/lib/promise-protected-resolver.js +195 -9
  55. package/dist/lib/promise-protected-resolver.js.map +1 -1
  56. package/dist/lib/retry-utils/index.cjs +195 -9
  57. package/dist/lib/retry-utils/index.cjs.map +1 -1
  58. package/dist/lib/retry-utils/index.js +195 -9
  59. package/dist/lib/retry-utils/index.js.map +1 -1
  60. package/dist/lib/safe-handle-callback.cjs +196 -18
  61. package/dist/lib/safe-handle-callback.cjs.map +1 -1
  62. package/dist/lib/safe-handle-callback.d.cts +14 -4
  63. package/dist/lib/safe-handle-callback.d.ts +14 -4
  64. package/dist/lib/safe-handle-callback.js +196 -18
  65. package/dist/lib/safe-handle-callback.js.map +1 -1
  66. package/dist/lib/single-event-observer.cjs +195 -9
  67. package/dist/lib/single-event-observer.cjs.map +1 -1
  68. package/dist/lib/single-event-observer.js +195 -9
  69. package/dist/lib/single-event-observer.js.map +1 -1
  70. package/dist/{types-6G59m8U9.d.cts → types-BFvpJPMt.d.cts} +71 -10
  71. package/dist/{types-CG10j6Lc.d.ts → types-BONtIhQ5.d.ts} +71 -10
  72. package/package.json +12 -2
@@ -248,13 +248,33 @@ var MockAdapter = class {
248
248
  if (request.signal?.aborted) {
249
249
  throwAbortError();
250
250
  }
251
- const responseBody = shouldOmitResponseBody(method, mockResponse.status) ? null : serializeResponseBody(mockResponse);
251
+ if (mockResponse.transportError) {
252
+ const options = mockResponse.transportError === true ? {} : mockResponse.transportError;
253
+ return {
254
+ status: options.status ?? 0,
255
+ isTransportError: true,
256
+ ...options.isRetryable !== void 0 ? { isRetryable: options.isRetryable } : {},
257
+ ...options.wasDefinitelyNotSent !== void 0 ? { wasDefinitelyNotSent: options.wasDefinitelyNotSent } : {},
258
+ headers: {},
259
+ body: null,
260
+ errorCause: new Error(
261
+ options.message ?? "Mock transport error (MockResponse.transportError)"
262
+ )
263
+ };
264
+ }
265
+ const streamErrorCode = resolveMockStreamErrorCode(
266
+ mockResponse.streamError
267
+ );
268
+ const intendedBody = shouldOmitResponseBody(method, mockResponse.status) ? null : serializeResponseBody(mockResponse);
269
+ const responseBody = streamErrorCode !== void 0 ? null : intendedBody;
252
270
  request.onUploadProgress?.({ loaded: 1, total: 1, progress: 1 });
253
- request.onDownloadProgress?.({
254
- loaded: responseBody?.length ?? 0,
255
- total: responseBody?.length ?? 0,
256
- progress: 1
257
- });
271
+ if (streamErrorCode === void 0) {
272
+ request.onDownloadProgress?.({
273
+ loaded: responseBody?.length ?? 0,
274
+ total: responseBody?.length ?? 0,
275
+ progress: 1
276
+ });
277
+ }
258
278
  const responseHeaders = {
259
279
  ...mockResponse.headers ?? {}
260
280
  };
@@ -266,7 +286,7 @@ var MockAdapter = class {
266
286
  responseHeaders["set-cookie"] = [...existingArr, ...setCookieEntries];
267
287
  }
268
288
  }
269
- if (!hasHeader(responseHeaders, "content-type") && responseBody !== null) {
289
+ if (!hasHeader(responseHeaders, "content-type") && intendedBody !== null) {
270
290
  const ct = mockResponse.contentType ?? inferContentType(mockResponse.body);
271
291
  if (ct === "json") {
272
292
  responseHeaders["content-type"] = "application/json";
@@ -285,10 +305,23 @@ var MockAdapter = class {
285
305
  wasRedirectDetected: REDIRECT_STATUS_CODES.has(mockResponse.status),
286
306
  ...detectedRedirectURL ? { detectedRedirectURL } : {},
287
307
  headers: normalizedResponseHeaders,
288
- body: responseBody
308
+ body: responseBody,
309
+ ...streamErrorCode !== void 0 ? {
310
+ isStreamError: true,
311
+ streamErrorCode,
312
+ errorCause: new Error(
313
+ "Mock response stream error (MockResponse.streamError)"
314
+ )
315
+ } : {}
289
316
  };
290
317
  }
291
318
  };
319
+ function resolveMockStreamErrorCode(streamError) {
320
+ if (streamError === void 0 || streamError === false) {
321
+ return void 0;
322
+ }
323
+ return streamError === true ? "stream_response_error" : streamError;
324
+ }
292
325
  function buildRoutes(router) {
293
326
  function on(method, path, handler) {
294
327
  try {
@@ -311,7 +344,8 @@ function buildRoutes(router) {
311
344
  };
312
345
  }
313
346
  function isDuplicateRouteRegistrationError(error) {
314
- return error instanceof Error && error.message.includes("already declared for route");
347
+ const message = readObjectMember(error, "message");
348
+ return isErrorValue(error) && typeof message === "string" && message.includes("already declared for route");
315
349
  }
316
350
  function duplicateRouteRegistrationError(method, path, cause) {
317
351
  return new Error(
@@ -483,11 +517,38 @@ function awaitAbortable(value, signal) {
483
517
  },
484
518
  (error) => {
485
519
  signal.removeEventListener("abort", onAbort);
486
- reject(error instanceof Error ? error : new Error(String(error)));
520
+ reject(normalizeError(error));
487
521
  }
488
522
  );
489
523
  });
490
524
  }
525
+ function readObjectMember(source, key) {
526
+ if (source === null || typeof source !== "object" && typeof source !== "function") {
527
+ return void 0;
528
+ }
529
+ try {
530
+ return source[key];
531
+ } catch {
532
+ return void 0;
533
+ }
534
+ }
535
+ function isErrorValue(value) {
536
+ try {
537
+ return value instanceof Error;
538
+ } catch {
539
+ return false;
540
+ }
541
+ }
542
+ function normalizeError(value) {
543
+ if (isErrorValue(value)) {
544
+ return value;
545
+ }
546
+ try {
547
+ return new Error(String(value));
548
+ } catch {
549
+ return new Error("Unknown error");
550
+ }
551
+ }
491
552
  function sleepAbortable(ms, signal) {
492
553
  if (signal.aborted) {
493
554
  throw new InternalMockAbortError();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/lib/http-client-mock/index.ts","../../../src/lib/http-client/adapters/mock-adapter.ts","../../../src/lib/sleep.ts","../../../src/lib/http-client/consts.ts","../../../src/lib/http-client/utils.ts"],"sourcesContent":["export { MockAdapter } from '../http-client/adapters/mock-adapter';\nexport type {\n MockAdapterConfig,\n MockAdapterRoutes,\n MockRequest,\n MockResponse,\n MockRouteHandler,\n MockFormData,\n MockCookieOptions,\n} from '../http-client/adapters/mock-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","import Router from 'find-my-way';\nimport qs from 'qs';\nimport { sleep } from '../../sleep';\nimport { REDIRECT_STATUS_CODES } from '../consts';\nimport {\n isPlainJSONBodyObject,\n normalizeAdapterResponseHeaders,\n parseContentType,\n resolveDetectedRedirectURL,\n} from '../utils';\nimport type {\n HTTPAdapter,\n AdapterRequest,\n AdapterResponse,\n AdapterType,\n ContentType,\n QueryObject,\n} from '../types';\n\nexport interface MockFormData {\n /** String fields from the multipart body */\n fields: Record<string, string>;\n /** File fields from the multipart body */\n files: Record<string, File>;\n}\n\nexport interface MockRequest {\n method: string;\n path: string;\n params: Record<string, string>;\n query: QueryObject;\n headers: Record<string, string>;\n /**\n * Parsed cookies from the `cookie` request header — same data as\n * `headers.cookie`, pre-parsed for convenience.\n */\n cookies: Record<string, string>;\n /**\n * Parsed request body for mock handlers.\n * JSON bodies are parsed, text bodies stay strings, binary bodies stay as\n * `Uint8Array`, and multipart/form-data bodies become `MockFormData`.\n */\n body?: unknown;\n}\n\n/** Cookie attributes for the `MockResponse.cookies` shorthand. */\nexport interface MockCookieOptions {\n value: string;\n /** Seconds until the cookie expires. Use `0` or a negative value to expire it immediately. */\n maxAge?: number;\n httpOnly?: boolean;\n secure?: boolean;\n sameSite?: 'Strict' | 'Lax' | 'None';\n /** Defaults to `'/'` when omitted. */\n path?: string;\n domain?: string;\n}\n\nexport interface MockResponse {\n status: number;\n /**\n * Response entity body. Aligned with outgoing request rules in `serializeBody`\n * / `assertSupportedRequestBody`, except mock responses do not use `FormData`.\n *\n * Supported values (anything else throws when serialized):\n *\n * - `undefined` or omitted — no body\n * - `null` — no body\n * - `string` — UTF-8 bytes\n * - `Uint8Array` — raw bytes unchanged\n * - `ArrayBuffer` — copied to a `Uint8Array`\n * - plain object (`Object.prototype` or `null` prototype) — JSON\n * - array — JSON\n */\n body?: unknown;\n /**\n * Raw response headers. Use `set-cookie: string[]` for multiple cookies,\n * same as real HTTP. Merged with any entries from `cookies`.\n */\n headers?: Record<string, string | string[]>;\n contentType?: ContentType;\n delay?: number;\n /**\n * Shorthand for setting / deleting cookies without writing raw Set-Cookie\n * strings. Merged with any `set-cookie` entries already in `headers`.\n *\n * - `string` → `name=value; Path=/` — session cookie (no expiry)\n * - `null` → `name=; Path=/; Max-Age=0` — deletes that same default\n * root-scoped cookie\n * - `MockCookieOptions` → full control over path, domain, and attributes;\n * when deleting one of these scoped cookies, the delete cookie must use\n * the same identity (name + path + domain) and typically `maxAge: 0`\n */\n cookies?: Record<string, string | MockCookieOptions | null>;\n}\n\nexport type MockRouteHandler = (\n request: MockRequest,\n) => MockResponse | Promise<MockResponse>;\n\nexport interface MockAdapterConfig {\n defaultDelay?: number;\n /**\n * Called when a route handler throws. Return a `MockResponse` to customize\n * the error response — similar to Fastify's `setErrorHandler`. Falls back to\n * the default `{ status: 500, body: { message: 'Internal Server Error' } }`\n * if this handler is not set or if it also throws.\n */\n onError?: (\n req: MockRequest,\n error: unknown,\n ) => MockResponse | Promise<MockResponse>;\n}\n\n/**\n * Routes match on **path only** — the domain/host in the request URL is\n * stripped before matching. `http://api.test/users` and\n * `http://auth.test/users` both match a registered `/users` route.\n *\n * **Multiple domains** — use separate `MockAdapter` instances and assign\n * them to the root client and sub-clients respectively:\n *\n * ```ts\n * const apiMock = new MockAdapter();\n * const authMock = new MockAdapter();\n * const client = new HTTPClient({ adapter: apiMock, baseURL: 'https://api.test' });\n * const authClient = client.createSubClient({ adapter: authMock, baseURL: 'https://auth.test' });\n * ```\n *\n * **Cross-domain redirects** — redirects are followed by the same adapter\n * that initiated the request. Since domain is stripped, a redirect to\n * `https://auth.test/callback` will match a `/callback` route on the\n * originating adapter — no special setup needed for single-adapter setups.\n * For separate-adapter setups, register the redirect target path on the\n * originating adapter as well.\n */\n\nexport interface MockAdapterRoutes {\n get(path: string, handler: MockRouteHandler): void;\n post(path: string, handler: MockRouteHandler): void;\n put(path: string, handler: MockRouteHandler): void;\n patch(path: string, handler: MockRouteHandler): void;\n delete(path: string, handler: MockRouteHandler): void;\n head(path: string, handler: MockRouteHandler): void;\n /** Remove all registered routes. */\n clear(): void;\n}\n\n// find-my-way requires a handler function; the actual MockRouteHandler lives in store.\nconst noop: Router.Handler<Router.HTTPVersion.V1> = () => {};\n\nexport class MockAdapter implements HTTPAdapter {\n public readonly routes: MockAdapterRoutes;\n private readonly router: Router.Instance<Router.HTTPVersion.V1>;\n private readonly config: MockAdapterConfig;\n\n constructor(config?: MockAdapterConfig) {\n this.config = config ?? {};\n\n this.router = Router({\n ignoreTrailingSlash: true,\n ignoreDuplicateSlashes: false,\n maxParamLength: 100,\n });\n\n this.routes = buildRoutes(this.router);\n }\n\n public getType(): AdapterType {\n return 'mock';\n }\n\n public async send(request: AdapterRequest): Promise<AdapterResponse> {\n const { requestURL, method, headers, body } = request;\n const materializedHeaders = materializeMockRequestHeaders(headers);\n\n // --- 1. Pre-flight abort check ---\n // Throw immediately if the signal was already cancelled before we even start.\n if (request.signal?.aborted) {\n throwAbortError();\n }\n\n // Signal 0% upload — upload is instant for mock, but we fire the event so\n // progress listeners see the same shape they would from FetchAdapter.\n request.onUploadProgress?.({ loaded: 0, total: 0, progress: 0 });\n\n // --- 2. Parse URL ---\n // Strip host so routes match on path only — same behavior regardless of\n // whether the client passed an absolute URL (https://api.test/users) or a\n // path-only URL (/users). Falls back to manual splitting for path-only URLs\n // that `new URL()` would reject.\n let path: string;\n let queryString: string;\n\n try {\n const url = new URL(requestURL);\n path = url.pathname;\n queryString = url.search.slice(1);\n } catch {\n const qIdx = requestURL.indexOf('?');\n\n if (qIdx >= 0) {\n path = requestURL.slice(0, qIdx);\n queryString = requestURL.slice(qIdx + 1);\n } else {\n path = requestURL;\n queryString = '';\n }\n }\n\n // --- 3. Route match & build MockRequest ---\n // qs handles nested bracket notation (e.g. ?where[active]=true).\n // cookies are pre-parsed from the `cookie` header for convenience —\n // the raw header is still available on req.headers.cookie.\n const query = qs.parse(queryString) as QueryObject;\n const cookies = parseCookieHeader(materializedHeaders['cookie']);\n const match = this.router.find(method, path);\n const params = (match?.params ?? {}) as Record<string, string>;\n\n const mockRequest: MockRequest = {\n method,\n path,\n params,\n query,\n headers: materializedHeaders,\n cookies,\n body:\n body instanceof FormData\n ? extractFormData(body)\n : parseRequestBody(body, materializedHeaders['content-type']),\n };\n\n // --- 4. Invoke handler ---\n // No registered route → default 404.\n // To customize the 404 body, register a wildcard route:\n // adapter.routes.get('/*', (req) => ({ status: 404, body: { error: '...' } }))\n //\n // While if a handler throws → onError (if set),\n // then falls back to default 500 if onError is unset or also throws.\n\n let mockResponse: MockResponse;\n\n if (match === null) {\n mockResponse = { status: 404, body: { message: 'Not Found' } };\n } else {\n const handler = match.store as MockRouteHandler;\n\n try {\n // Match real network semantics more closely: once the caller aborts,\n // stop waiting on an async mock handler and reject immediately.\n mockResponse = await awaitAbortable(\n handler(mockRequest),\n request.signal,\n );\n } catch (handlerError) {\n if (isInternalAbortError(handlerError)) {\n throwAbortError();\n }\n\n if (this.config.onError) {\n try {\n mockResponse = await awaitAbortable(\n this.config.onError(mockRequest, handlerError),\n request.signal,\n );\n } catch (error) {\n if (isInternalAbortError(error)) {\n throwAbortError();\n }\n\n mockResponse = {\n status: 500,\n body: { message: 'Internal Server Error' },\n };\n }\n } else {\n mockResponse = {\n status: 500,\n body: { message: 'Internal Server Error' },\n };\n }\n }\n }\n\n // --- 5. Delay ---\n // Simulates network latency. Per-response delay takes priority over the\n // adapter default. When a signal is present, the sleep is abort-aware and\n // throws AbortError immediately instead of waiting out the full duration.\n const delay = mockResponse.delay ?? this.config.defaultDelay ?? 0;\n\n if (delay > 0) {\n // Use abort-aware sleep when a signal is present so cancellation throws\n // immediately rather than waiting for the full delay to elapse.\n if (request.signal) {\n await sleepAbortable(delay, request.signal);\n } else {\n await sleep(delay);\n }\n }\n\n // --- 6. Post-delay abort check ---\n // Catches cancellation when there was no delay (or delay was 0).\n if (request.signal?.aborted) {\n throwAbortError();\n }\n\n // HEAD / 204 / 304 never expose a body to consumers, even if the handler\n // returned one. This keeps the mock transport aligned with real HTTP.\n const responseBody = shouldOmitResponseBody(method, mockResponse.status)\n ? null\n : serializeResponseBody(mockResponse);\n\n // Signal upload complete, then report download size based on serialised body.\n request.onUploadProgress?.({ loaded: 1, total: 1, progress: 1 });\n\n request.onDownloadProgress?.({\n loaded: responseBody?.length ?? 0,\n total: responseBody?.length ?? 0,\n progress: 1,\n });\n\n // --- 7. Build response headers ---\n const responseHeaders: AdapterResponse['headers'] = {\n ...(mockResponse.headers ?? {}),\n };\n\n // Merge cookies shorthand into set-cookie headers.\n // Entries from `cookies` are APPENDED after any existing `headers['set-cookie']`\n // entries — they are not overwritten. If both set the same cookie name, the\n // shorthand entry wins because it appears last (last Set-Cookie header for a\n // given name takes precedence in browsers and CookieJar).\n if (mockResponse.cookies) {\n const setCookieEntries = cookiesToSetCookieHeaders(mockResponse.cookies);\n\n if (setCookieEntries.length > 0) {\n const existing = responseHeaders['set-cookie'];\n const existingArr =\n existing === undefined\n ? []\n : Array.isArray(existing)\n ? existing\n : [existing];\n responseHeaders['set-cookie'] = [...existingArr, ...setCookieEntries];\n }\n }\n\n // Auto-set content-type when handler didn't provide one.\n // Binary responses are intentionally skipped — content-type for binary is\n // format-specific (image/png, application/pdf, etc.) so the handler is\n // responsible for setting it via `headers` when it matters.\n if (!hasHeader(responseHeaders, 'content-type') && responseBody !== null) {\n const ct =\n mockResponse.contentType ?? inferContentType(mockResponse.body);\n\n if (ct === 'json') {\n responseHeaders['content-type'] = 'application/json';\n } else if (ct === 'text') {\n responseHeaders['content-type'] = 'text/plain';\n }\n }\n\n const normalizedResponseHeaders =\n normalizeAdapterResponseHeaders(responseHeaders);\n\n const detectedRedirectURL = resolveDetectedRedirectURL(\n request.requestURL,\n mockResponse.status,\n normalizedResponseHeaders,\n );\n\n return {\n status: mockResponse.status,\n wasRedirectDetected: REDIRECT_STATUS_CODES.has(mockResponse.status),\n ...(detectedRedirectURL ? { detectedRedirectURL } : {}),\n headers: normalizedResponseHeaders,\n body: responseBody,\n };\n }\n}\n\n// --- Helpers ---\n\nfunction buildRoutes(\n router: Router.Instance<Router.HTTPVersion.V1>,\n): MockAdapterRoutes {\n function on(\n method: Router.HTTPMethod,\n path: string,\n handler: MockRouteHandler,\n ): void {\n try {\n router.on(method, path, noop, handler);\n } catch (error) {\n if (isDuplicateRouteRegistrationError(error)) {\n throw duplicateRouteRegistrationError(method, path, error);\n }\n\n throw error;\n }\n }\n\n return {\n get: (path, handler) => on('GET', path, handler),\n post: (path, handler) => on('POST', path, handler),\n put: (path, handler) => on('PUT', path, handler),\n patch: (path, handler) => on('PATCH', path, handler),\n delete: (path, handler) => on('DELETE', path, handler),\n head: (path, handler) => on('HEAD', path, handler),\n clear: () => router.reset(),\n };\n}\n\nfunction isDuplicateRouteRegistrationError(error: unknown): error is Error {\n return (\n error instanceof Error &&\n error.message.includes('already declared for route')\n );\n}\n\nfunction duplicateRouteRegistrationError(\n method: Router.HTTPMethod,\n path: string,\n cause: Error,\n): Error {\n return new Error(\n `[MockAdapter] Duplicate route registration for ${method} ${path}. ` +\n 'Routes must be unique per method and normalized path.',\n { cause },\n );\n}\n\nfunction parseCookieHeader(\n cookieHeader: string | undefined,\n): Record<string, string> {\n if (!cookieHeader) {\n return {};\n }\n\n const cookies: Record<string, string> = {};\n\n for (const part of cookieHeader.split(';')) {\n const eqIdx = part.indexOf('=');\n\n if (eqIdx < 0) {\n continue;\n }\n\n const name = part.slice(0, eqIdx).trim();\n const value = part.slice(eqIdx + 1).trim();\n\n if (name) {\n cookies[name] = value;\n }\n }\n\n return cookies;\n}\n\nfunction materializeMockRequestHeaders(\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 result[key] = Array.isArray(value)\n ? key.toLowerCase() === 'cookie'\n ? value.join('; ')\n : value.join(', ')\n : value;\n }\n\n return result;\n}\n\nfunction cookiesToSetCookieHeaders(\n cookies: Record<string, string | MockCookieOptions | null>,\n): string[] {\n return Object.entries(cookies).map(([name, value]) => {\n // null pairs with the string shorthand above: both use Path=/.\n // Scoped deletes need explicit path/domain so the cookie identity matches.\n if (value === null) {\n return `${name}=; Path=/; Max-Age=0`;\n }\n\n // string → session cookie: no expiry, defaults to Path=/.\n if (typeof value === 'string') {\n return `${name}=${value}; Path=/`;\n }\n\n return serializeMockCookieOptions(name, value);\n });\n}\n\nfunction serializeMockCookieOptions(\n name: string,\n cookie: MockCookieOptions,\n): string {\n const parts: string[] = [\n `${name}=${cookie.value}`,\n `Path=${cookie.path ?? '/'}`,\n ];\n\n appendCookieAttribute(parts, 'Max-Age', cookie.maxAge);\n appendCookieAttribute(parts, 'Domain', cookie.domain);\n\n if (cookie.httpOnly) {\n parts.push('HttpOnly');\n }\n\n if (cookie.secure) {\n parts.push('Secure');\n }\n\n appendCookieAttribute(parts, 'SameSite', cookie.sameSite);\n\n return parts.join('; ');\n}\n\nfunction appendCookieAttribute(\n parts: string[],\n name: string,\n value: number | string | undefined,\n): void {\n if (value !== undefined && value !== '') {\n parts.push(`${name}=${value}`);\n }\n}\n\nfunction extractFormData(fd: FormData): MockFormData {\n const fields: Record<string, string> = {};\n const files: Record<string, File> = {};\n\n for (const [key, value] of fd.entries()) {\n if (typeof value === 'string') {\n fields[key] = value;\n } else {\n files[key] = value;\n }\n }\n\n return { fields, files };\n}\n\nfunction parseRequestBody(\n body: string | Uint8Array | null | undefined,\n contentType: string | undefined,\n): unknown {\n if (body === null || body === undefined) {\n return undefined;\n }\n\n const parsedContentType = parseContentType(contentType);\n\n if (body instanceof Uint8Array) {\n // Keep opaque bytes intact so mock handlers can inspect binary payloads\n // like uploads or octet-stream requests without lossy text decoding.\n if (parsedContentType === 'binary') {\n return body;\n } else if (parsedContentType === 'json') {\n // JSON requests arrive as bytes from the adapter layer, so decode first\n // and then match the client response path by attempting JSON.parse().\n const text = new TextDecoder().decode(body);\n\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n } else if (parsedContentType === 'text') {\n // Text-like bodies should be exposed to mock handlers as plain strings.\n return new TextDecoder().decode(body);\n }\n } else if (parsedContentType === 'json') {\n // String bodies can still declare JSON; parse when possible but preserve\n // the original string if the payload is invalid JSON.\n try {\n return JSON.parse(body);\n } catch {\n return body;\n }\n }\n\n // Plain strings with non-JSON content types are already in the desired form.\n return body;\n}\n\nfunction serializeResponseBody(response: MockResponse): Uint8Array | null {\n const { body } = response;\n\n if (body === undefined || body === null) {\n return null;\n } else if (body instanceof Uint8Array) {\n // Avoid JSON.stringify indexing the buffer into {\"0\":…,\"1\":…}.\n return body;\n } else if (body instanceof ArrayBuffer) {\n return new Uint8Array(body);\n } else if (typeof body === 'string') {\n return new TextEncoder().encode(body);\n } else if (Array.isArray(body) || isPlainJSONBodyObject(body)) {\n return new TextEncoder().encode(JSON.stringify(body));\n }\n\n throw new Error(\n 'Unsupported mock response body type. Supported types: string, Uint8Array, ArrayBuffer, plain object, array, null, and undefined.',\n );\n}\n\nfunction inferContentType(body: unknown): ContentType {\n if (typeof body === 'string') {\n return 'text';\n } else if (body instanceof Uint8Array || body instanceof ArrayBuffer) {\n return 'binary';\n } else {\n return 'json';\n }\n}\n\nfunction hasHeader(\n headers: Record<string, string | string[]>,\n name: string,\n): boolean {\n return Object.keys(headers).some(\n (k) => k.toLowerCase() === name.toLowerCase(),\n );\n}\n\nclass InternalMockAbortError extends Error {\n constructor() {\n super('The operation was aborted.');\n this.name = 'AbortError';\n }\n}\n\nfunction isInternalAbortError(error: unknown): error is InternalMockAbortError {\n return error instanceof InternalMockAbortError;\n}\n\nfunction throwAbortError(): never {\n const err = new Error('The operation was aborted.');\n err.name = 'AbortError';\n throw err;\n}\n\nfunction shouldOmitResponseBody(method: string, status: number): boolean {\n return method === 'HEAD' || status === 204 || status === 304;\n}\n\nfunction awaitAbortable<T>(\n value: T | Promise<T>,\n signal: AbortSignal | undefined,\n): Promise<T> {\n if (!signal) {\n return Promise.resolve(value);\n }\n\n if (signal.aborted) {\n throw new InternalMockAbortError();\n }\n\n return new Promise<T>((resolve, reject) => {\n // Cancellation should reject immediately with AbortError, even if the\n // wrapped handler/onError promise is still pending.\n const onAbort = () => {\n signal.removeEventListener('abort', onAbort);\n reject(new InternalMockAbortError());\n };\n\n signal.addEventListener('abort', onAbort, { once: true });\n\n Promise.resolve(value).then(\n (result) => {\n signal.removeEventListener('abort', onAbort);\n resolve(result);\n },\n (error) => {\n signal.removeEventListener('abort', onAbort);\n // Preserve real handler failures, only normalize non-Error rejections\n // so promise rejection values stay lint-safe and predictable.\n reject(error instanceof Error ? error : new Error(String(error)));\n },\n );\n });\n}\n\n/**\n * Like `sleep()` but throws AbortError immediately if the signal fires during\n * the delay rather than waiting for the full duration to elapse. Cleans up\n * both the timer and the abort listener to avoid leaks.\n */\nfunction sleepAbortable(ms: number, signal: AbortSignal): Promise<void> {\n if (signal.aborted) {\n throw new InternalMockAbortError();\n }\n\n return new Promise<void>((resolve, reject) => {\n const onAbort = () => {\n clearTimeout(id);\n reject(new InternalMockAbortError());\n };\n\n const id = setTimeout(() => {\n signal.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n\n signal.addEventListener('abort', onAbort, { once: true });\n });\n}\n","/**\n * Sleeps the function for the specified number of milliseconds\n * Your code will not do it's next step during this time while you await\n *\n * ```typescript\n * await sleep(1000);\n * ```\n */\n\nexport async function sleep(time: number): Promise<void> {\n return new Promise<void>(function (resolve) {\n setTimeout(function () {\n resolve();\n }, time);\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","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":["import_qs","Router","qs"]}
1
+ {"version":3,"sources":["../../../src/lib/http-client-mock/index.ts","../../../src/lib/http-client/adapters/mock-adapter.ts","../../../src/lib/sleep.ts","../../../src/lib/http-client/consts.ts","../../../src/lib/http-client/utils.ts"],"sourcesContent":["export { MockAdapter } from '../http-client/adapters/mock-adapter';\nexport type {\n MockAdapterConfig,\n MockAdapterRoutes,\n MockRequest,\n MockResponse,\n MockRouteHandler,\n MockFormData,\n MockCookieOptions,\n MockTransportErrorOptions,\n} from '../http-client/adapters/mock-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","import Router from 'find-my-way';\nimport qs from 'qs';\nimport { sleep } from '../../sleep';\nimport { REDIRECT_STATUS_CODES } from '../consts';\nimport {\n isPlainJSONBodyObject,\n normalizeAdapterResponseHeaders,\n parseContentType,\n resolveDetectedRedirectURL,\n} from '../utils';\nimport type {\n HTTPAdapter,\n AdapterRequest,\n AdapterResponse,\n AdapterType,\n ContentType,\n QueryObject,\n} from '../types';\n\nexport interface MockFormData {\n /** String fields from the multipart body */\n fields: Record<string, string>;\n /** File fields from the multipart body */\n files: Record<string, File>;\n}\n\nexport interface MockRequest {\n method: string;\n path: string;\n params: Record<string, string>;\n query: QueryObject;\n headers: Record<string, string>;\n /**\n * Parsed cookies from the `cookie` request header — same data as\n * `headers.cookie`, pre-parsed for convenience.\n */\n cookies: Record<string, string>;\n /**\n * Parsed request body for mock handlers.\n * JSON bodies are parsed, text bodies stay strings, binary bodies stay as\n * `Uint8Array`, and multipart/form-data bodies become `MockFormData`.\n */\n body?: unknown;\n}\n\n/** Cookie attributes for the `MockResponse.cookies` shorthand. */\nexport interface MockCookieOptions {\n value: string;\n /** Seconds until the cookie expires. Use `0` or a negative value to expire it immediately. */\n maxAge?: number;\n httpOnly?: boolean;\n secure?: boolean;\n sameSite?: 'Strict' | 'Lax' | 'None';\n /** Defaults to `'/'` when omitted. */\n path?: string;\n domain?: string;\n}\n\nexport interface MockResponse {\n status: number;\n /**\n * Response entity body. Aligned with outgoing request rules in `serializeBody`\n * / `assertSupportedRequestBody`, except mock responses do not use `FormData`.\n *\n * Supported values (anything else throws when serialized):\n *\n * - `undefined` or omitted — no body\n * - `null` — no body\n * - `string` — UTF-8 bytes\n * - `Uint8Array` — raw bytes unchanged\n * - `ArrayBuffer` — copied to a `Uint8Array`\n * - plain object (`Object.prototype` or `null` prototype) — JSON\n * - array — JSON\n */\n body?: unknown;\n /**\n * Raw response headers. Use `set-cookie: string[]` for multiple cookies,\n * same as real HTTP. Merged with any entries from `cookies`.\n */\n headers?: Record<string, string | string[]>;\n contentType?: ContentType;\n delay?: number;\n /**\n * Shorthand for setting / deleting cookies without writing raw Set-Cookie\n * strings. Merged with any `set-cookie` entries already in `headers`.\n *\n * - `string` → `name=value; Path=/` — session cookie (no expiry)\n * - `null` → `name=; Path=/; Max-Age=0` — deletes that same default\n * root-scoped cookie\n * - `MockCookieOptions` → full control over path, domain, and attributes;\n * when deleting one of these scoped cookies, the delete cookie must use\n * the same identity (name + path + domain) and typically `maxAge: 0`\n */\n cookies?: Record<string, string | MockCookieOptions | null>;\n /**\n * Simulate a response-body failure that happens **after** headers arrive, so\n * consumers can test the `isStreamError` branch without a real socket.\n *\n * `status`, `headers` and cookies are reported as given, but `body` resolves as\n * `null` whatever the handler set and the response carries\n * `isStreamError: true`, which `HTTPClient` treats as terminal.\n *\n * Pass `true` for the default `'stream_response_error'`, or name the code.\n */\n streamError?: boolean | 'stream_write_error' | 'stream_response_error';\n /**\n * Simulate a failure that produced **no response at all** — a refused\n * connection, a name that did not resolve, a socket dropped before headers.\n *\n * Nothing from the handler's response is delivered: no body, headers, cookies,\n * or terminal progress. The response carries `isTransportError: true`.\n *\n * Pass `true` for a plain `status: 0` failure, or an object to exercise the\n * replay signals a real adapter would attach:\n *\n * ```ts\n * // Proven undelivered — a POST may be replayed\n * { status: 200, transportError: { wasDefinitelyNotSent: true } }\n *\n * // Delivery unknown — a POST is not replayed\n * { status: 200, transportError: true }\n *\n * // Nothing should retry this, whatever the method (a TLS failure)\n * { status: 200, transportError: { status: 495, isRetryable: false } }\n * ```\n */\n transportError?: boolean | MockTransportErrorOptions;\n}\n\n/** Shape of a simulated transport failure. See {@link MockResponse.transportError}. */\nexport interface MockTransportErrorOptions {\n /**\n * Diagnostic status to preserve, as adapters do for TLS failures (`495`).\n * Defaults to `0`, the ordinary \"no response\" status.\n */\n status?: number;\n /** Proof no request bytes reached the server. Authorizes replaying a write. */\n wasDefinitelyNotSent?: boolean;\n /** Set `false` for a failure no method should retry. */\n isRetryable?: boolean;\n /** Message for the generated `errorCause`. */\n message?: string;\n}\n\nexport type MockRouteHandler = (\n request: MockRequest,\n) => MockResponse | Promise<MockResponse>;\n\nexport interface MockAdapterConfig {\n defaultDelay?: number;\n /**\n * Called when a route handler throws. Return a `MockResponse` to customize\n * the error response — similar to Fastify's `setErrorHandler`. Falls back to\n * the default `{ status: 500, body: { message: 'Internal Server Error' } }`\n * if this handler is not set or if it also throws.\n */\n onError?: (\n req: MockRequest,\n error: unknown,\n ) => MockResponse | Promise<MockResponse>;\n}\n\n/**\n * Routes match on **path only** — the domain/host in the request URL is\n * stripped before matching. `http://api.test/users` and\n * `http://auth.test/users` both match a registered `/users` route.\n *\n * **Multiple domains** — use separate `MockAdapter` instances and assign\n * them to the root client and sub-clients respectively:\n *\n * ```ts\n * const apiMock = new MockAdapter();\n * const authMock = new MockAdapter();\n * const client = new HTTPClient({ adapter: apiMock, baseURL: 'https://api.test' });\n * const authClient = client.createSubClient({ adapter: authMock, baseURL: 'https://auth.test' });\n * ```\n *\n * **Cross-domain redirects** — redirects are followed by the same adapter\n * that initiated the request. Since domain is stripped, a redirect to\n * `https://auth.test/callback` will match a `/callback` route on the\n * originating adapter — no special setup needed for single-adapter setups.\n * For separate-adapter setups, register the redirect target path on the\n * originating adapter as well.\n */\n\nexport interface MockAdapterRoutes {\n get(path: string, handler: MockRouteHandler): void;\n post(path: string, handler: MockRouteHandler): void;\n put(path: string, handler: MockRouteHandler): void;\n patch(path: string, handler: MockRouteHandler): void;\n delete(path: string, handler: MockRouteHandler): void;\n head(path: string, handler: MockRouteHandler): void;\n /** Remove all registered routes. */\n clear(): void;\n}\n\n// find-my-way requires a handler function; the actual MockRouteHandler lives in store.\nconst noop: Router.Handler<Router.HTTPVersion.V1> = () => {};\n\nexport class MockAdapter implements HTTPAdapter {\n public readonly routes: MockAdapterRoutes;\n private readonly router: Router.Instance<Router.HTTPVersion.V1>;\n private readonly config: MockAdapterConfig;\n\n constructor(config?: MockAdapterConfig) {\n this.config = config ?? {};\n\n this.router = Router({\n ignoreTrailingSlash: true,\n ignoreDuplicateSlashes: false,\n maxParamLength: 100,\n });\n\n this.routes = buildRoutes(this.router);\n }\n\n public getType(): AdapterType {\n return 'mock';\n }\n\n public async send(request: AdapterRequest): Promise<AdapterResponse> {\n const { requestURL, method, headers, body } = request;\n const materializedHeaders = materializeMockRequestHeaders(headers);\n\n // --- 1. Pre-flight abort check ---\n // Throw immediately if the signal was already cancelled before we even start.\n if (request.signal?.aborted) {\n throwAbortError();\n }\n\n // Signal 0% upload — upload is instant for mock, but we fire the event so\n // progress listeners see the same shape they would from FetchAdapter.\n request.onUploadProgress?.({ loaded: 0, total: 0, progress: 0 });\n\n // --- 2. Parse URL ---\n // Strip host so routes match on path only — same behavior regardless of\n // whether the client passed an absolute URL (https://api.test/users) or a\n // path-only URL (/users). Falls back to manual splitting for path-only URLs\n // that `new URL()` would reject.\n let path: string;\n let queryString: string;\n\n try {\n const url = new URL(requestURL);\n path = url.pathname;\n queryString = url.search.slice(1);\n } catch {\n const qIdx = requestURL.indexOf('?');\n\n if (qIdx >= 0) {\n path = requestURL.slice(0, qIdx);\n queryString = requestURL.slice(qIdx + 1);\n } else {\n path = requestURL;\n queryString = '';\n }\n }\n\n // --- 3. Route match & build MockRequest ---\n // qs handles nested bracket notation (e.g. ?where[active]=true).\n // cookies are pre-parsed from the `cookie` header for convenience —\n // the raw header is still available on req.headers.cookie.\n const query = qs.parse(queryString) as QueryObject;\n const cookies = parseCookieHeader(materializedHeaders['cookie']);\n const match = this.router.find(method, path);\n const params = (match?.params ?? {}) as Record<string, string>;\n\n const mockRequest: MockRequest = {\n method,\n path,\n params,\n query,\n headers: materializedHeaders,\n cookies,\n body:\n body instanceof FormData\n ? extractFormData(body)\n : parseRequestBody(body, materializedHeaders['content-type']),\n };\n\n // --- 4. Invoke handler ---\n // No registered route → default 404.\n // To customize the 404 body, register a wildcard route:\n // adapter.routes.get('/*', (req) => ({ status: 404, body: { error: '...' } }))\n //\n // While if a handler throws → onError (if set),\n // then falls back to default 500 if onError is unset or also throws.\n\n let mockResponse: MockResponse;\n\n if (match === null) {\n mockResponse = { status: 404, body: { message: 'Not Found' } };\n } else {\n const handler = match.store as MockRouteHandler;\n\n try {\n // Match real network semantics more closely: once the caller aborts,\n // stop waiting on an async mock handler and reject immediately.\n mockResponse = await awaitAbortable(\n handler(mockRequest),\n request.signal,\n );\n } catch (handlerError) {\n if (isInternalAbortError(handlerError)) {\n throwAbortError();\n }\n\n if (this.config.onError) {\n try {\n mockResponse = await awaitAbortable(\n this.config.onError(mockRequest, handlerError),\n request.signal,\n );\n } catch (error) {\n if (isInternalAbortError(error)) {\n throwAbortError();\n }\n\n mockResponse = {\n status: 500,\n body: { message: 'Internal Server Error' },\n };\n }\n } else {\n mockResponse = {\n status: 500,\n body: { message: 'Internal Server Error' },\n };\n }\n }\n }\n\n // --- 5. Delay ---\n // Simulates network latency. Per-response delay takes priority over the\n // adapter default. When a signal is present, the sleep is abort-aware and\n // throws AbortError immediately instead of waiting out the full duration.\n const delay = mockResponse.delay ?? this.config.defaultDelay ?? 0;\n\n if (delay > 0) {\n // Use abort-aware sleep when a signal is present so cancellation throws\n // immediately rather than waiting for the full delay to elapse.\n if (request.signal) {\n await sleepAbortable(delay, request.signal);\n } else {\n await sleep(delay);\n }\n }\n\n // --- 6. Post-delay abort check ---\n // Catches cancellation when there was no delay (or delay was 0).\n if (request.signal?.aborted) {\n throwAbortError();\n }\n\n // A simulated transport failure never produced a response, so it returns\n // before any of the response-shaped work below — no body, no headers, no\n // cookies, and no terminal progress, exactly as a real one delivers none.\n if (mockResponse.transportError) {\n const options: MockTransportErrorOptions =\n mockResponse.transportError === true ? {} : mockResponse.transportError;\n\n return {\n status: options.status ?? 0,\n isTransportError: true,\n ...(options.isRetryable !== undefined\n ? { isRetryable: options.isRetryable }\n : {}),\n ...(options.wasDefinitelyNotSent !== undefined\n ? { wasDefinitelyNotSent: options.wasDefinitelyNotSent }\n : {}),\n headers: {},\n body: null,\n errorCause: new Error(\n options.message ??\n 'Mock transport error (MockResponse.transportError)',\n ),\n };\n }\n\n // A simulated post-header body failure loses the body the same way a real\n // one does, so the handler's body is never serialized.\n const streamErrorCode = resolveMockStreamErrorCode(\n mockResponse.streamError,\n );\n\n // What the server would have put on the wire. HEAD / 204 / 304 never expose\n // a body to consumers, even if the handler returned one. This keeps the mock\n // transport aligned with real HTTP.\n const intendedBody = shouldOmitResponseBody(method, mockResponse.status)\n ? null\n : serializeResponseBody(mockResponse);\n\n // A simulated stream error loses the body, but it strikes after headers\n // arrived, so the headers still stand — Content-Type included. Header\n // inference below reads `intendedBody` for that reason.\n const responseBody = streamErrorCode !== undefined ? null : intendedBody;\n\n // Signal upload complete, then report download size based on serialised body.\n request.onUploadProgress?.({ loaded: 1, total: 1, progress: 1 });\n\n // A simulated stream error reports no terminal download progress: real\n // adapters fail the body read before that point, so `progress: 1` here would\n // signal a completed download for a body that never arrived.\n if (streamErrorCode === undefined) {\n request.onDownloadProgress?.({\n loaded: responseBody?.length ?? 0,\n total: responseBody?.length ?? 0,\n progress: 1,\n });\n }\n\n // --- 7. Build response headers ---\n const responseHeaders: AdapterResponse['headers'] = {\n ...(mockResponse.headers ?? {}),\n };\n\n // Merge cookies shorthand into set-cookie headers.\n // Entries from `cookies` are APPENDED after any existing `headers['set-cookie']`\n // entries — they are not overwritten. If both set the same cookie name, the\n // shorthand entry wins because it appears last (last Set-Cookie header for a\n // given name takes precedence in browsers and CookieJar).\n if (mockResponse.cookies) {\n const setCookieEntries = cookiesToSetCookieHeaders(mockResponse.cookies);\n\n if (setCookieEntries.length > 0) {\n const existing = responseHeaders['set-cookie'];\n const existingArr =\n existing === undefined\n ? []\n : Array.isArray(existing)\n ? existing\n : [existing];\n responseHeaders['set-cookie'] = [...existingArr, ...setCookieEntries];\n }\n }\n\n // Auto-set content-type when handler didn't provide one.\n // Binary responses are intentionally skipped — content-type for binary is\n // format-specific (image/png, application/pdf, etc.) so the handler is\n // responsible for setting it via `headers` when it matters.\n if (!hasHeader(responseHeaders, 'content-type') && intendedBody !== null) {\n const ct =\n mockResponse.contentType ?? inferContentType(mockResponse.body);\n\n if (ct === 'json') {\n responseHeaders['content-type'] = 'application/json';\n } else if (ct === 'text') {\n responseHeaders['content-type'] = 'text/plain';\n }\n }\n\n const normalizedResponseHeaders =\n normalizeAdapterResponseHeaders(responseHeaders);\n\n const detectedRedirectURL = resolveDetectedRedirectURL(\n request.requestURL,\n mockResponse.status,\n normalizedResponseHeaders,\n );\n\n return {\n status: mockResponse.status,\n wasRedirectDetected: REDIRECT_STATUS_CODES.has(mockResponse.status),\n ...(detectedRedirectURL ? { detectedRedirectURL } : {}),\n headers: normalizedResponseHeaders,\n body: responseBody,\n ...(streamErrorCode !== undefined\n ? {\n isStreamError: true,\n streamErrorCode,\n errorCause: new Error(\n 'Mock response stream error (MockResponse.streamError)',\n ),\n }\n : {}),\n };\n }\n}\n\n// --- Helpers ---\n\n/**\n * Normalizes `MockResponse.streamError` to a `streamErrorCode`, or `undefined`\n * when no post-header body failure was requested.\n */\nfunction resolveMockStreamErrorCode(\n streamError: MockResponse['streamError'],\n): AdapterResponse['streamErrorCode'] {\n if (streamError === undefined || streamError === false) {\n return undefined;\n }\n\n return streamError === true ? 'stream_response_error' : streamError;\n}\n\nfunction buildRoutes(\n router: Router.Instance<Router.HTTPVersion.V1>,\n): MockAdapterRoutes {\n function on(\n method: Router.HTTPMethod,\n path: string,\n handler: MockRouteHandler,\n ): void {\n try {\n router.on(method, path, noop, handler);\n } catch (error) {\n if (isDuplicateRouteRegistrationError(error)) {\n throw duplicateRouteRegistrationError(method, path, error);\n }\n\n throw error;\n }\n }\n\n return {\n get: (path, handler) => on('GET', path, handler),\n post: (path, handler) => on('POST', path, handler),\n put: (path, handler) => on('PUT', path, handler),\n patch: (path, handler) => on('PATCH', path, handler),\n delete: (path, handler) => on('DELETE', path, handler),\n head: (path, handler) => on('HEAD', path, handler),\n clear: () => router.reset(),\n };\n}\n\nfunction isDuplicateRouteRegistrationError(error: unknown): error is Error {\n const message = readObjectMember(error, 'message');\n\n return (\n isErrorValue(error) &&\n typeof message === 'string' &&\n message.includes('already declared for route')\n );\n}\n\nfunction duplicateRouteRegistrationError(\n method: Router.HTTPMethod,\n path: string,\n cause: Error,\n): Error {\n return new Error(\n `[MockAdapter] Duplicate route registration for ${method} ${path}. ` +\n 'Routes must be unique per method and normalized path.',\n { cause },\n );\n}\n\nfunction parseCookieHeader(\n cookieHeader: string | undefined,\n): Record<string, string> {\n if (!cookieHeader) {\n return {};\n }\n\n const cookies: Record<string, string> = {};\n\n for (const part of cookieHeader.split(';')) {\n const eqIdx = part.indexOf('=');\n\n if (eqIdx < 0) {\n continue;\n }\n\n const name = part.slice(0, eqIdx).trim();\n const value = part.slice(eqIdx + 1).trim();\n\n if (name) {\n cookies[name] = value;\n }\n }\n\n return cookies;\n}\n\nfunction materializeMockRequestHeaders(\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 result[key] = Array.isArray(value)\n ? key.toLowerCase() === 'cookie'\n ? value.join('; ')\n : value.join(', ')\n : value;\n }\n\n return result;\n}\n\nfunction cookiesToSetCookieHeaders(\n cookies: Record<string, string | MockCookieOptions | null>,\n): string[] {\n return Object.entries(cookies).map(([name, value]) => {\n // null pairs with the string shorthand above: both use Path=/.\n // Scoped deletes need explicit path/domain so the cookie identity matches.\n if (value === null) {\n return `${name}=; Path=/; Max-Age=0`;\n }\n\n // string → session cookie: no expiry, defaults to Path=/.\n if (typeof value === 'string') {\n return `${name}=${value}; Path=/`;\n }\n\n return serializeMockCookieOptions(name, value);\n });\n}\n\nfunction serializeMockCookieOptions(\n name: string,\n cookie: MockCookieOptions,\n): string {\n const parts: string[] = [\n `${name}=${cookie.value}`,\n `Path=${cookie.path ?? '/'}`,\n ];\n\n appendCookieAttribute(parts, 'Max-Age', cookie.maxAge);\n appendCookieAttribute(parts, 'Domain', cookie.domain);\n\n if (cookie.httpOnly) {\n parts.push('HttpOnly');\n }\n\n if (cookie.secure) {\n parts.push('Secure');\n }\n\n appendCookieAttribute(parts, 'SameSite', cookie.sameSite);\n\n return parts.join('; ');\n}\n\nfunction appendCookieAttribute(\n parts: string[],\n name: string,\n value: number | string | undefined,\n): void {\n if (value !== undefined && value !== '') {\n parts.push(`${name}=${value}`);\n }\n}\n\nfunction extractFormData(fd: FormData): MockFormData {\n const fields: Record<string, string> = {};\n const files: Record<string, File> = {};\n\n for (const [key, value] of fd.entries()) {\n if (typeof value === 'string') {\n fields[key] = value;\n } else {\n files[key] = value;\n }\n }\n\n return { fields, files };\n}\n\nfunction parseRequestBody(\n body: string | Uint8Array | null | undefined,\n contentType: string | undefined,\n): unknown {\n if (body === null || body === undefined) {\n return undefined;\n }\n\n const parsedContentType = parseContentType(contentType);\n\n if (body instanceof Uint8Array) {\n // Keep opaque bytes intact so mock handlers can inspect binary payloads\n // like uploads or octet-stream requests without lossy text decoding.\n if (parsedContentType === 'binary') {\n return body;\n } else if (parsedContentType === 'json') {\n // JSON requests arrive as bytes from the adapter layer, so decode first\n // and then match the client response path by attempting JSON.parse().\n const text = new TextDecoder().decode(body);\n\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n } else if (parsedContentType === 'text') {\n // Text-like bodies should be exposed to mock handlers as plain strings.\n return new TextDecoder().decode(body);\n }\n } else if (parsedContentType === 'json') {\n // String bodies can still declare JSON; parse when possible but preserve\n // the original string if the payload is invalid JSON.\n try {\n return JSON.parse(body);\n } catch {\n return body;\n }\n }\n\n // Plain strings with non-JSON content types are already in the desired form.\n return body;\n}\n\nfunction serializeResponseBody(response: MockResponse): Uint8Array | null {\n const { body } = response;\n\n if (body === undefined || body === null) {\n return null;\n } else if (body instanceof Uint8Array) {\n // Avoid JSON.stringify indexing the buffer into {\"0\":…,\"1\":…}.\n return body;\n } else if (body instanceof ArrayBuffer) {\n return new Uint8Array(body);\n } else if (typeof body === 'string') {\n return new TextEncoder().encode(body);\n } else if (Array.isArray(body) || isPlainJSONBodyObject(body)) {\n return new TextEncoder().encode(JSON.stringify(body));\n }\n\n throw new Error(\n 'Unsupported mock response body type. Supported types: string, Uint8Array, ArrayBuffer, plain object, array, null, and undefined.',\n );\n}\n\nfunction inferContentType(body: unknown): ContentType {\n if (typeof body === 'string') {\n return 'text';\n } else if (body instanceof Uint8Array || body instanceof ArrayBuffer) {\n return 'binary';\n } else {\n return 'json';\n }\n}\n\nfunction hasHeader(\n headers: Record<string, string | string[]>,\n name: string,\n): boolean {\n return Object.keys(headers).some(\n (k) => k.toLowerCase() === name.toLowerCase(),\n );\n}\n\nclass InternalMockAbortError extends Error {\n constructor() {\n super('The operation was aborted.');\n this.name = 'AbortError';\n }\n}\n\nfunction isInternalAbortError(error: unknown): error is InternalMockAbortError {\n return error instanceof InternalMockAbortError;\n}\n\nfunction throwAbortError(): never {\n const err = new Error('The operation was aborted.');\n err.name = 'AbortError';\n throw err;\n}\n\nfunction shouldOmitResponseBody(method: string, status: number): boolean {\n return method === 'HEAD' || status === 204 || status === 304;\n}\n\nfunction awaitAbortable<T>(\n value: T | Promise<T>,\n signal: AbortSignal | undefined,\n): Promise<T> {\n if (!signal) {\n return Promise.resolve(value);\n }\n\n if (signal.aborted) {\n throw new InternalMockAbortError();\n }\n\n return new Promise<T>((resolve, reject) => {\n // Cancellation should reject immediately with AbortError, even if the\n // wrapped handler/onError promise is still pending.\n const onAbort = () => {\n signal.removeEventListener('abort', onAbort);\n reject(new InternalMockAbortError());\n };\n\n signal.addEventListener('abort', onAbort, { once: true });\n\n Promise.resolve(value).then(\n (result) => {\n signal.removeEventListener('abort', onAbort);\n resolve(result);\n },\n (error) => {\n signal.removeEventListener('abort', onAbort);\n // Preserve real handler failures, only normalize non-Error rejections\n // so promise rejection values stay lint-safe and predictable.\n reject(normalizeError(error));\n },\n );\n });\n}\n\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\n/**\n * Like `sleep()` but throws AbortError immediately if the signal fires during\n * the delay rather than waiting for the full duration to elapse. Cleans up\n * both the timer and the abort listener to avoid leaks.\n */\nfunction sleepAbortable(ms: number, signal: AbortSignal): Promise<void> {\n if (signal.aborted) {\n throw new InternalMockAbortError();\n }\n\n return new Promise<void>((resolve, reject) => {\n const onAbort = () => {\n clearTimeout(id);\n reject(new InternalMockAbortError());\n };\n\n const id = setTimeout(() => {\n signal.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n\n signal.addEventListener('abort', onAbort, { once: true });\n });\n}\n","/**\n * Sleeps the function for the specified number of milliseconds\n * Your code will not do it's next step during this time while you await\n *\n * ```typescript\n * await sleep(1000);\n * ```\n */\n\nexport async function sleep(time: number): Promise<void> {\n return new Promise<void>(function (resolve) {\n setTimeout(function () {\n resolve();\n }, time);\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","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":["import_qs","Router","qs"]}
@@ -1,5 +1,5 @@
1
- import { H as HTTPAdapter, Q as QueryObject, C as ContentType, A as AdapterType, a as AdapterRequest, b as AdapterResponse } from '../../types-6G59m8U9.cjs';
2
- export { c as AdapterProgressEvent, d as HTTPMethod } from '../../types-6G59m8U9.cjs';
1
+ import { H as HTTPAdapter, Q as QueryObject, C as ContentType, A as AdapterType, a as AdapterRequest, b as AdapterResponse } from '../../types-BFvpJPMt.cjs';
2
+ export { c as AdapterProgressEvent, d as HTTPMethod } from '../../types-BFvpJPMt.cjs';
3
3
  import '../../types-BW0vnSzd.cjs';
4
4
 
5
5
  interface MockFormData {
@@ -74,6 +74,53 @@ interface MockResponse {
74
74
  * the same identity (name + path + domain) and typically `maxAge: 0`
75
75
  */
76
76
  cookies?: Record<string, string | MockCookieOptions | null>;
77
+ /**
78
+ * Simulate a response-body failure that happens **after** headers arrive, so
79
+ * consumers can test the `isStreamError` branch without a real socket.
80
+ *
81
+ * `status`, `headers` and cookies are reported as given, but `body` resolves as
82
+ * `null` whatever the handler set and the response carries
83
+ * `isStreamError: true`, which `HTTPClient` treats as terminal.
84
+ *
85
+ * Pass `true` for the default `'stream_response_error'`, or name the code.
86
+ */
87
+ streamError?: boolean | 'stream_write_error' | 'stream_response_error';
88
+ /**
89
+ * Simulate a failure that produced **no response at all** — a refused
90
+ * connection, a name that did not resolve, a socket dropped before headers.
91
+ *
92
+ * Nothing from the handler's response is delivered: no body, headers, cookies,
93
+ * or terminal progress. The response carries `isTransportError: true`.
94
+ *
95
+ * Pass `true` for a plain `status: 0` failure, or an object to exercise the
96
+ * replay signals a real adapter would attach:
97
+ *
98
+ * ```ts
99
+ * // Proven undelivered — a POST may be replayed
100
+ * { status: 200, transportError: { wasDefinitelyNotSent: true } }
101
+ *
102
+ * // Delivery unknown — a POST is not replayed
103
+ * { status: 200, transportError: true }
104
+ *
105
+ * // Nothing should retry this, whatever the method (a TLS failure)
106
+ * { status: 200, transportError: { status: 495, isRetryable: false } }
107
+ * ```
108
+ */
109
+ transportError?: boolean | MockTransportErrorOptions;
110
+ }
111
+ /** Shape of a simulated transport failure. See {@link MockResponse.transportError}. */
112
+ interface MockTransportErrorOptions {
113
+ /**
114
+ * Diagnostic status to preserve, as adapters do for TLS failures (`495`).
115
+ * Defaults to `0`, the ordinary "no response" status.
116
+ */
117
+ status?: number;
118
+ /** Proof no request bytes reached the server. Authorizes replaying a write. */
119
+ wasDefinitelyNotSent?: boolean;
120
+ /** Set `false` for a failure no method should retry. */
121
+ isRetryable?: boolean;
122
+ /** Message for the generated `errorCause`. */
123
+ message?: string;
77
124
  }
78
125
  type MockRouteHandler = (request: MockRequest) => MockResponse | Promise<MockResponse>;
79
126
  interface MockAdapterConfig {
@@ -127,4 +174,4 @@ declare class MockAdapter implements HTTPAdapter {
127
174
  send(request: AdapterRequest): Promise<AdapterResponse>;
128
175
  }
129
176
 
130
- export { AdapterRequest, AdapterResponse, AdapterType, HTTPAdapter, MockAdapter, type MockAdapterConfig, type MockAdapterRoutes, type MockCookieOptions, type MockFormData, type MockRequest, type MockResponse, type MockRouteHandler };
177
+ export { AdapterRequest, AdapterResponse, AdapterType, HTTPAdapter, MockAdapter, type MockAdapterConfig, type MockAdapterRoutes, type MockCookieOptions, type MockFormData, type MockRequest, type MockResponse, type MockRouteHandler, type MockTransportErrorOptions };
@@ -1,5 +1,5 @@
1
- import { H as HTTPAdapter, Q as QueryObject, C as ContentType, A as AdapterType, a as AdapterRequest, b as AdapterResponse } from '../../types-CG10j6Lc.js';
2
- export { c as AdapterProgressEvent, d as HTTPMethod } from '../../types-CG10j6Lc.js';
1
+ import { H as HTTPAdapter, Q as QueryObject, C as ContentType, A as AdapterType, a as AdapterRequest, b as AdapterResponse } from '../../types-BONtIhQ5.js';
2
+ export { c as AdapterProgressEvent, d as HTTPMethod } from '../../types-BONtIhQ5.js';
3
3
  import '../../types-BW0vnSzd.js';
4
4
 
5
5
  interface MockFormData {
@@ -74,6 +74,53 @@ interface MockResponse {
74
74
  * the same identity (name + path + domain) and typically `maxAge: 0`
75
75
  */
76
76
  cookies?: Record<string, string | MockCookieOptions | null>;
77
+ /**
78
+ * Simulate a response-body failure that happens **after** headers arrive, so
79
+ * consumers can test the `isStreamError` branch without a real socket.
80
+ *
81
+ * `status`, `headers` and cookies are reported as given, but `body` resolves as
82
+ * `null` whatever the handler set and the response carries
83
+ * `isStreamError: true`, which `HTTPClient` treats as terminal.
84
+ *
85
+ * Pass `true` for the default `'stream_response_error'`, or name the code.
86
+ */
87
+ streamError?: boolean | 'stream_write_error' | 'stream_response_error';
88
+ /**
89
+ * Simulate a failure that produced **no response at all** — a refused
90
+ * connection, a name that did not resolve, a socket dropped before headers.
91
+ *
92
+ * Nothing from the handler's response is delivered: no body, headers, cookies,
93
+ * or terminal progress. The response carries `isTransportError: true`.
94
+ *
95
+ * Pass `true` for a plain `status: 0` failure, or an object to exercise the
96
+ * replay signals a real adapter would attach:
97
+ *
98
+ * ```ts
99
+ * // Proven undelivered — a POST may be replayed
100
+ * { status: 200, transportError: { wasDefinitelyNotSent: true } }
101
+ *
102
+ * // Delivery unknown — a POST is not replayed
103
+ * { status: 200, transportError: true }
104
+ *
105
+ * // Nothing should retry this, whatever the method (a TLS failure)
106
+ * { status: 200, transportError: { status: 495, isRetryable: false } }
107
+ * ```
108
+ */
109
+ transportError?: boolean | MockTransportErrorOptions;
110
+ }
111
+ /** Shape of a simulated transport failure. See {@link MockResponse.transportError}. */
112
+ interface MockTransportErrorOptions {
113
+ /**
114
+ * Diagnostic status to preserve, as adapters do for TLS failures (`495`).
115
+ * Defaults to `0`, the ordinary "no response" status.
116
+ */
117
+ status?: number;
118
+ /** Proof no request bytes reached the server. Authorizes replaying a write. */
119
+ wasDefinitelyNotSent?: boolean;
120
+ /** Set `false` for a failure no method should retry. */
121
+ isRetryable?: boolean;
122
+ /** Message for the generated `errorCause`. */
123
+ message?: string;
77
124
  }
78
125
  type MockRouteHandler = (request: MockRequest) => MockResponse | Promise<MockResponse>;
79
126
  interface MockAdapterConfig {
@@ -127,4 +174,4 @@ declare class MockAdapter implements HTTPAdapter {
127
174
  send(request: AdapterRequest): Promise<AdapterResponse>;
128
175
  }
129
176
 
130
- export { AdapterRequest, AdapterResponse, AdapterType, HTTPAdapter, MockAdapter, type MockAdapterConfig, type MockAdapterRoutes, type MockCookieOptions, type MockFormData, type MockRequest, type MockResponse, type MockRouteHandler };
177
+ export { AdapterRequest, AdapterResponse, AdapterType, HTTPAdapter, MockAdapter, type MockAdapterConfig, type MockAdapterRoutes, type MockCookieOptions, type MockFormData, type MockRequest, type MockResponse, type MockRouteHandler, type MockTransportErrorOptions };