@ahoo-wang/fetcher 0.11.1 → 0.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.es.js CHANGED
@@ -901,3 +901,4 @@ export {
901
901
  _ as timeoutFetch,
902
902
  d as toSorted
903
903
  };
904
+ //# sourceMappingURL=index.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.es.js","sources":["../src/urls.ts","../src/urlBuilder.ts","../src/fetcherError.ts","../src/timeout.ts","../src/urlResolveInterceptor.ts","../src/fetchRequest.ts","../src/requestBodyInterceptor.ts","../src/fetchInterceptor.ts","../src/orderedCapable.ts","../src/interceptor.ts","../src/validateStatusInterceptor.ts","../src/interceptorManager.ts","../src/fetchExchange.ts","../src/utils.ts","../src/fetcher.ts","../src/fetcherRegistrar.ts","../src/mergeRequest.ts","../src/namedFetcher.ts"],"sourcesContent":["/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Checks if the given URL is an absolute URL\n *\n * @param url - URL string to check\n * @returns boolean - Returns true if it's an absolute URL, false otherwise\n *\n * @example\n * ```typescript\n * isAbsoluteURL('https://api.example.com/users'); // true\n * isAbsoluteURL('/users'); // false\n * isAbsoluteURL('users'); // false\n * ```\n */\nexport function isAbsoluteURL(url: string) {\n return /^([a-z][a-z\\d+\\-.]*:)?\\/\\//i.test(url);\n}\n\n/**\n * Combines a base URL and a relative URL into a complete URL\n *\n * @param baseURL - Base URL\n * @param relativeURL - Relative URL\n * @returns string - Combined complete URL\n *\n * @remarks\n * If the relative URL is already an absolute URL, it will be returned as-is.\n * Otherwise, the base URL and relative URL will be combined with proper path separator handling.\n *\n * @example\n * ```typescript\n * combineURLs('https://api.example.com', '/users'); // https://api.example.com/users\n * combineURLs('https://api.example.com/', 'users'); // https://api.example.com/users\n * combineURLs('https://api.example.com', 'https://other.com/users'); // https://other.com/users\n * ```\n */\nexport function combineURLs(baseURL: string, relativeURL: string) {\n if (isAbsoluteURL(relativeURL)) {\n return relativeURL;\n }\n // If relative URL exists, combine base URL and relative URL, otherwise return base URL\n return relativeURL\n ? baseURL.replace(/\\/?\\/$/, '') + '/' + relativeURL.replace(/^\\/+/, '')\n : baseURL;\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { combineURLs } from './urls';\nimport { BaseURLCapable, FetchRequest } from './fetchRequest';\n\n/**\n * Container for URL parameters including path and query parameters.\n *\n * Used to define dynamic parts of a URL including path placeholders and query string parameters.\n */\nexport interface UrlParams {\n /**\n * Path parameter object used to replace placeholders in the URL (e.g., {id}).\n *\n * These parameters are used to substitute named placeholders in the URL path.\n *\n * @example\n * ```typescript\n * // For URL template '/users/{id}/posts/{postId}'\n * const path = { id: 123, postId: 456 };\n * ```\n */\n path?: Record<string, any>;\n\n /**\n * Query parameter object to be added to the URL query string.\n *\n * These parameters are appended to the URL as a query string.\n *\n * @example\n * ```typescript\n * const query = { filter: 'active', page: 1, limit: 10 };\n * // Results in query string: ?filter=active&page=1&limit=10\n * ```\n */\n query?: Record<string, any>;\n}\n\n/**\n * Utility class for constructing complete URLs with path parameters and query parameters.\n *\n * Handles URL composition, path parameter interpolation, and query string generation.\n * Combines a base URL with a path, replaces path placeholders with actual values, and appends\n * query parameters to create a complete URL.\n *\n * @example\n * ```typescript\n * const urlBuilder = new UrlBuilder('https://api.example.com');\n * const url = urlBuilder.build('/users/{id}', {\n * path: { id: 123 },\n * query: { filter: 'active' }\n * });\n * // Result: https://api.example.com/users/123?filter=active\n * ```\n */\nexport class UrlBuilder implements BaseURLCapable {\n /**\n * Base URL that all constructed URLs will be based on.\n *\n * This is typically the root of your API endpoint (e.g., 'https://api.example.com').\n */\n readonly baseURL: string;\n\n /**\n * Initializes a new UrlBuilder instance.\n *\n * @param baseURL - Base URL that all constructed URLs will be based on\n *\n * @example\n * ```typescript\n * const urlBuilder = new UrlBuilder('https://api.example.com');\n * ```\n */\n constructor(baseURL: string) {\n this.baseURL = baseURL;\n }\n\n /**\n * Builds a complete URL, including path parameter replacement and query parameter addition.\n *\n * @param url - URL path to build (e.g., '/users/{id}/posts')\n * @param params - URL parameters including path and query parameters\n * @returns Complete URL string with base URL, path parameters interpolated, and query string appended\n * @throws Error when required path parameters are missing\n *\n * @example\n * ```typescript\n * const urlBuilder = new UrlBuilder('https://api.example.com');\n * const url = urlBuilder.build('/users/{id}/posts/{postId}', {\n * path: { id: 123, postId: 456 },\n * query: { filter: 'active', limit: 10 }\n * });\n * // Result: https://api.example.com/users/123/posts/456?filter=active&limit=10\n * ```\n */\n build(url: string, params?: UrlParams): string {\n const path = params?.path;\n const query = params?.query;\n const combinedURL = combineURLs(this.baseURL, url);\n let finalUrl = this.interpolateUrl(combinedURL, path);\n if (query) {\n const queryString = new URLSearchParams(query).toString();\n if (queryString) {\n finalUrl += '?' + queryString;\n }\n }\n return finalUrl;\n }\n\n /**\n * Resolves a complete URL from a FetchRequest.\n *\n * Used internally by the Fetcher to build the final URL for a request\n * by combining the request URL with its URL parameters using this UrlBuilder.\n *\n * @param request - The FetchRequest containing URL and URL parameters\n * @returns Complete resolved URL string\n */\n resolveRequestUrl(request: FetchRequest): string {\n return this.build(request.url, request.urlParams);\n }\n\n /**\n * Replaces placeholders in the URL with path parameters.\n *\n * @param url - Path string containing placeholders, e.g., \"http://localhost/users/{id}/posts/{postId}\"\n * @param path - Path parameter object used to replace placeholders in the URL\n * @returns Path string with placeholders replaced\n * @throws Error when required path parameters are missing\n *\n * @example\n * ```typescript\n * const urlBuilder = new UrlBuilder('https://api.example.com');\n * const result = urlBuilder.interpolateUrl('/users/{id}/posts/{postId}', {\n * path: { id: 123, postId: 456 }\n * });\n * // Result: https://api.example.com/users/123/posts/456\n * ```\n *\n * @example\n * ```typescript\n * // Missing required parameter throws an error\n * try {\n * urlBuilder.interpolateUrl('/users/{id}', { name: 'John' });\n * } catch (error) {\n * console.error(error.message); // \"Missing required path parameter: id\"\n * }\n * ```\n */\n interpolateUrl(url: string, path?: Record<string, any> | null): string {\n if (!path) return url;\n return url.replace(/{([^}]+)}/g, (_, key) => {\n const value = path[key];\n // If path parameter is undefined, throw an error instead of preserving the placeholder\n if (value === undefined) {\n throw new Error(`Missing required path parameter: ${key}`);\n }\n return String(value);\n });\n }\n}\n\n/**\n * Interface for objects that have a UrlBuilder capability.\n *\n * Indicates that an object has a UrlBuilder instance for URL construction.\n */\nexport interface UrlBuilderCapable {\n /**\n * The UrlBuilder instance.\n */\n urlBuilder: UrlBuilder;\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Base error class for all Fetcher-related errors.\n *\n * This class extends the native Error class and provides a foundation for\n * all custom errors thrown by the Fetcher library. It includes support for\n * error chaining through the cause property.\n *\n * @example\n * ```typescript\n * try {\n * await fetcher.get('/api/users');\n * } catch (error) {\n * if (error instanceof FetcherError) {\n * console.log('Fetcher error:', error.message);\n * if (error.cause) {\n * console.log('Caused by:', error.cause);\n * }\n * }\n * }\n * ```\n */\nexport class FetcherError extends Error {\n /**\n * Creates a new FetcherError instance.\n *\n * @param errorMsg - Optional error message. If not provided, will use the cause's message or a default message.\n * @param cause - Optional underlying error that caused this error.\n */\n constructor(\n errorMsg?: string,\n public readonly cause?: Error | any,\n ) {\n const errorMessage =\n errorMsg || cause?.message || 'An error occurred in the fetcher';\n super(errorMessage);\n this.name = 'FetcherError';\n\n // Copy stack trace from cause if available\n if (cause?.stack) {\n this.stack = cause.stack;\n }\n\n // Set prototype for instanceof checks to work correctly\n Object.setPrototypeOf(this, FetcherError.prototype);\n }\n}\n\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { FetchRequest } from './fetchRequest';\nimport { FetcherError } from './fetcherError';\n\n\n/**\n * Exception class thrown when an HTTP request times out.\n *\n * This error is thrown by the timeoutFetch function when a request exceeds its timeout limit.\n *\n * @example\n * ```typescript\n * try {\n * const response = await timeoutFetch('https://api.example.com/users', {}, 1000);\n * } catch (error) {\n * if (error instanceof FetchTimeoutError) {\n * console.log(`Request timed out after ${error.timeout}ms`);\n * }\n * }\n * ```\n */\nexport class FetchTimeoutError extends FetcherError {\n /**\n * The request options that timed out.\n */\n request: FetchRequest;\n\n /**\n * Creates a new FetchTimeoutError instance.\n *\n * @param request - The request options that timed out\n */\n constructor(request: FetchRequest) {\n const method = request.method || 'GET';\n const message = `Request timeout of ${request.timeout}ms exceeded for ${method} ${request.url}`;\n super(message);\n this.name = 'FetchTimeoutError';\n this.request = request;\n // Fix prototype chain\n Object.setPrototypeOf(this, FetchTimeoutError.prototype);\n }\n}\n\n/**\n * Interface that defines timeout capability for HTTP requests.\n *\n * Objects implementing this interface can specify timeout values for HTTP requests.\n */\nexport interface TimeoutCapable {\n /**\n * Request timeout in milliseconds.\n *\n * When the value is 0, it indicates no timeout should be set.\n * The default value is undefined.\n */\n timeout?: number;\n}\n\n/**\n * Resolves request timeout settings, prioritizing request-level timeout settings.\n *\n * @param requestTimeout - Request-level timeout setting\n * @param optionsTimeout - Configuration-level timeout setting\n * @returns Resolved timeout setting\n *\n * @remarks\n * If requestTimeout is defined, it takes precedence over optionsTimeout.\n * Otherwise, optionsTimeout is returned. If both are undefined, undefined is returned.\n */\nexport function resolveTimeout(\n requestTimeout?: number,\n optionsTimeout?: number,\n): number | undefined {\n if (typeof requestTimeout !== 'undefined') {\n return requestTimeout;\n }\n return optionsTimeout;\n}\n\n/**\n * HTTP request method with timeout control.\n *\n * Uses Promise.race to implement timeout control, initiating both\n * fetch request and timeout Promise simultaneously. When either Promise completes,\n * it returns the result or throws an exception.\n *\n * @param request - The request initialization options\n * @returns Promise<Response> HTTP response Promise\n * @throws FetchTimeoutError Thrown when the request times out\n *\n * @example\n * ```typescript\n * // With timeout\n * try {\n * const response = await timeoutFetch('https://api.example.com/users', { method: 'GET' }, 5000);\n * console.log('Request completed successfully');\n * } catch (error) {\n * if (error instanceof FetchTimeoutError) {\n * console.log(`Request timed out after ${error.timeout}ms`);\n * }\n * }\n *\n * // Without timeout (delegates to regular fetch)\n * const response = await timeoutFetch('https://api.example.com/users', { method: 'GET' });\n * ```\n */\nexport async function timeoutFetch(request: FetchRequest): Promise<Response> {\n const url = request.url;\n const timeout = request.timeout;\n const requestInit = request as RequestInit;\n // Extract timeout from request\n if (!timeout) {\n return fetch(url, requestInit);\n }\n\n // Create AbortController for fetch request cancellation\n const controller = new AbortController();\n // Create a new request object to avoid modifying the original request object\n const fetchRequest: RequestInit = {\n ...requestInit,\n signal: controller.signal,\n };\n\n // Timer resource management\n let timerId: ReturnType<typeof setTimeout> | null = null;\n // Create timeout Promise that rejects after specified time\n const timeoutPromise = new Promise<Response>((_, reject) => {\n timerId = setTimeout(() => {\n // Clean up timer resources and handle timeout error\n if (timerId) {\n clearTimeout(timerId);\n }\n const error = new FetchTimeoutError(request);\n controller.abort(error);\n reject(error);\n }, timeout);\n });\n\n try {\n // Race between fetch request and timeout Promise\n return await Promise.race([fetch(url, fetchRequest), timeoutPromise]);\n } finally {\n // Clean up timer resources\n if (timerId) {\n clearTimeout(timerId);\n }\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { RequestInterceptor } from './interceptor';\nimport { FetchExchange } from './fetchExchange';\n\n/**\n * The name of the UrlResolveInterceptor.\n */\nexport const URL_RESOLVE_INTERCEPTOR_NAME = 'UrlResolveInterceptor';\n\n/**\n * The order of the UrlResolveInterceptor.\n * Set to Number.MIN_SAFE_INTEGER + 1000 to ensure it runs earliest among request interceptors.\n */\nexport const URL_RESOLVE_INTERCEPTOR_ORDER = Number.MIN_SAFE_INTEGER + 1000;\n\n/**\n * Interceptor responsible for resolving the final URL for a request.\n *\n * This interceptor combines the base URL, path parameters, and query parameters\n * to create the final URL for a request. It should be executed earliest in\n * the interceptor chain to ensure the URL is properly resolved before other interceptors\n * process the request.\n *\n * @remarks\n * This interceptor runs at the very beginning of the request interceptor chain to ensure\n * URL resolution happens before any other request processing. The order is set to\n * URL_RESOLVE_INTERCEPTOR_ORDER to ensure it executes before all other request interceptors,\n * establishing the foundation for subsequent processing.\n *\n * @example\n * // With baseURL: 'https://api.example.com'\n * // Request URL: '/users/{id}'\n * // Path params: { id: 123 }\n * // Query params: { filter: 'active' }\n * // Final URL: 'https://api.example.com/users/123?filter=active'\n */\nexport class UrlResolveInterceptor implements RequestInterceptor {\n /**\n * The name of this interceptor.\n */\n readonly name = URL_RESOLVE_INTERCEPTOR_NAME;\n\n /**\n * The order of this interceptor (executed earliest).\n *\n * This interceptor should run at the very beginning of the request interceptor chain to ensure\n * URL resolution happens before any other request processing. The order is set to\n * URL_RESOLVE_INTERCEPTOR_ORDER to ensure it executes before all other request interceptors,\n * establishing the foundation for subsequent processing.\n */\n readonly order = URL_RESOLVE_INTERCEPTOR_ORDER;\n\n /**\n * Resolves the final URL by combining the base URL, path parameters, and query parameters.\n *\n * @param exchange - The fetch exchange containing the request information\n */\n intercept(exchange: FetchExchange) {\n const request = exchange.request;\n request.url = exchange.fetcher.urlBuilder.resolveRequestUrl(request);\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { TimeoutCapable } from './timeout';\nimport { UrlParams } from './urlBuilder';\n\n/**\n * Interface for objects that can have a base URL\n *\n * This interface defines a baseURL property that can be used to set a base URL\n * for HTTP requests. When the baseURL is empty, it means no base URL is set.\n */\nexport interface BaseURLCapable {\n /**\n * The base URL for requests\n * When empty, indicates no base URL is set. Default is undefined.\n */\n baseURL: string;\n}\n\n/**\n * HTTP method enumeration constants\n *\n * Defines the standard HTTP methods that can be used for requests.\n * Each method is represented as a string literal type.\n */\nexport enum HttpMethod {\n GET = 'GET',\n POST = 'POST',\n PUT = 'PUT',\n DELETE = 'DELETE',\n PATCH = 'PATCH',\n HEAD = 'HEAD',\n OPTIONS = 'OPTIONS',\n}\n\nexport const ContentTypeHeader = 'Content-Type';\n\nexport enum ContentTypeValues {\n APPLICATION_JSON = 'application/json',\n TEXT_EVENT_STREAM = 'text/event-stream',\n}\n\n/**\n * Request headers interface\n *\n * Defines common HTTP headers that can be sent with requests.\n * Allows for additional custom headers through index signature.\n */\nexport interface RequestHeaders {\n 'Content-Type'?: string;\n Accept?: string;\n Authorization?: string;\n\n [key: string]: string | undefined;\n}\n\n/**\n * Interface for objects that can have request headers\n *\n * This interface defines an optional headers property for HTTP requests.\n */\nexport interface RequestHeadersCapable {\n /**\n * Request headers\n */\n headers?: RequestHeaders;\n}\n\n/**\n * Fetcher request configuration interface\n *\n * This interface defines all the configuration options available for making HTTP requests\n * with the Fetcher client. It extends the standard RequestInit interface while adding\n * Fetcher-specific features like path parameters, query parameters, and timeout control.\n *\n * @example\n * ```typescript\n * const request: FetchRequestInit = {\n * method: 'GET',\n * urlParams: {\n * path: { id: 123 },\n * query: { include: 'profile' }\n * },\n * headers: { 'Authorization': 'Bearer token' },\n * timeout: 5000\n * };\n *\n * const response = await fetcher.fetch('/users/{id}', request);\n * ```\n */\nexport interface FetchRequestInit\n extends TimeoutCapable,\n RequestHeadersCapable,\n Omit<RequestInit, 'body' | 'headers'> {\n urlParams?: UrlParams;\n\n /**\n * Request body\n *\n * The body of the request. Can be a string, Blob, ArrayBuffer, FormData,\n * URLSearchParams, or a plain object. Plain objects are automatically\n * converted to JSON and the appropriate Content-Type header is set.\n *\n * @example\n * ```typescript\n * // Plain object (automatically converted to JSON)\n * const request = {\n * method: 'POST',\n * body: { name: 'John', email: 'john@example.com' }\n * };\n *\n * // FormData\n * const formData = new FormData();\n * formData.append('name', 'John');\n * const request = {\n * method: 'POST',\n * body: formData\n * };\n * ```\n */\n body?: BodyInit | Record<string, any> | string | null;\n}\n\n/**\n * Fetcher request interface\n *\n * Extends FetchRequestInit with a required URL property.\n * Represents a complete request configuration ready to be executed.\n */\nexport interface FetchRequest extends FetchRequestInit {\n /**\n * The URL for this request\n */\n url: string;\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { RequestInterceptor } from './interceptor';\nimport { FetchExchange } from './fetchExchange';\nimport { ContentTypeValues } from './fetchRequest';\nimport { URL_RESOLVE_INTERCEPTOR_ORDER } from './urlResolveInterceptor';\n\n/**\n * The name of the RequestBodyInterceptor.\n */\nexport const REQUEST_BODY_INTERCEPTOR_NAME = 'RequestBodyInterceptor';\n\n/**\n * The order of the RequestBodyInterceptor.\n * Set to URL_RESOLVE_INTERCEPTOR_ORDER + 1000 to ensure it runs early among request interceptors.\n */\nexport const REQUEST_BODY_INTERCEPTOR_ORDER =\n URL_RESOLVE_INTERCEPTOR_ORDER + 1000;\n\n/**\n * Interceptor responsible for converting plain objects to JSON strings for HTTP request bodies.\n *\n * This interceptor ensures that object request bodies are properly serialized and that\n * the appropriate Content-Type header is set. It runs early in the request processing chain\n * to ensure request bodies are properly formatted before other interceptors process them.\n *\n * @remarks\n * This interceptor runs after URL resolution (UrlResolveInterceptor) but before\n * the actual HTTP request is made (FetchInterceptor). The order is set to\n * REQUEST_BODY_INTERCEPTOR_ORDER to ensure it executes in the correct position\n * in the interceptor chain, allowing for other interceptors to run between URL resolution\n * and request body processing. This positioning ensures that URL parameters are resolved\n * first, then request bodies are properly formatted, and finally the HTTP request is executed.\n */\nexport class RequestBodyInterceptor implements RequestInterceptor {\n /**\n * Interceptor name, used for identification and management.\n */\n readonly name = REQUEST_BODY_INTERCEPTOR_NAME;\n\n /**\n * Interceptor execution order, set to run after UrlResolveInterceptor but before FetchInterceptor.\n *\n * This interceptor should run after URL resolution (UrlResolveInterceptor) but before\n * the actual HTTP request is made (FetchInterceptor). The order is set to\n * REQUEST_BODY_INTERCEPTOR_ORDER to ensure it executes in the correct position\n * in the interceptor chain, allowing for other interceptors to run between URL resolution\n * and request body processing. This positioning ensures that URL parameters are resolved\n * first, then request bodies are properly formatted, and finally the HTTP request is executed.\n */\n readonly order = REQUEST_BODY_INTERCEPTOR_ORDER;\n\n /**\n * Attempts to convert request body to a valid fetch API body type.\n *\n * According to the Fetch API specification, body can be multiple types, but for\n * plain objects, they need to be converted to JSON strings.\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#setting_a_body}\n *\n * Supported types:\n * - a string\n * - ArrayBuffer\n * - TypedArray\n * - DataView\n * - Blob\n * - File\n * - URLSearchParams\n * - FormData\n * - ReadableStream\n *\n * For unsupported object types (like plain objects), they will be automatically\n * converted to JSON strings.\n *\n * @param exchange - The exchange object containing the request to process\n *\n * @example\n * // Plain object body will be converted to JSON\n * const fetcher = new Fetcher();\n * const exchange = new FetchExchange(\n * fetcher,\n * {\n * body: { name: 'John', age: 30 }\n * }\n * );\n * interceptor.intercept(exchange);\n * // exchange.request.body will be '{\"name\":\"John\",\"age\":30}'\n * // exchange.request.headers will include 'Content-Type: application/json'\n */\n intercept(exchange: FetchExchange) {\n const request = exchange.request;\n // If there's no request body, return unchanged\n if (request.body === undefined || request.body === null) {\n return;\n }\n\n // If request body is not an object, return unchanged\n if (typeof request.body !== 'object') {\n return;\n }\n\n // Check if it's a supported type\n if (\n request.body instanceof ArrayBuffer ||\n ArrayBuffer.isView(request.body) || // Includes TypedArray and DataView\n request.body instanceof Blob ||\n request.body instanceof File ||\n request.body instanceof URLSearchParams ||\n request.body instanceof FormData ||\n request.body instanceof ReadableStream\n ) {\n return;\n }\n\n // For plain objects, convert to JSON string\n // Also ensure Content-Type header is set to application/json\n const modifiedRequest = { ...request };\n modifiedRequest.body = JSON.stringify(request.body);\n\n // Set Content-Type header\n if (!modifiedRequest.headers) {\n modifiedRequest.headers = {};\n }\n\n // Only set default Content-Type if not explicitly set\n const headers = modifiedRequest.headers;\n if (!headers['Content-Type']) {\n headers['Content-Type'] = ContentTypeValues.APPLICATION_JSON;\n }\n exchange.request = modifiedRequest;\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { RequestInterceptor } from './interceptor';\nimport { timeoutFetch } from './timeout';\nimport { FetchExchange } from './fetchExchange';\n\n/**\n * The name of the FetchInterceptor.\n */\nexport const FETCH_INTERCEPTOR_NAME = 'FetchInterceptor';\n\n/**\n * The order of the FetchInterceptor.\n * Set to Number.MAX_SAFE_INTEGER - 1000 to ensure it runs latest among request interceptors.\n */\nexport const FETCH_INTERCEPTOR_ORDER = Number.MAX_SAFE_INTEGER - 1000;\n\n/**\n * Interceptor implementation responsible for executing actual HTTP requests.\n *\n * This is an interceptor implementation responsible for executing actual HTTP requests\n * and handling timeout control. It is the latest interceptor in the Fetcher request\n * processing chain, ensuring that the actual network request is executed after all\n * previous interceptors have completed processing.\n *\n * @remarks\n * This interceptor runs at the very end of the request interceptor chain to ensure\n * that the actual HTTP request is executed after all other request processing is complete.\n * The order is set to FETCH_INTERCEPTOR_ORDER to ensure it executes after all other\n * request interceptors, completing the request processing pipeline before the network\n * request is made. This positioning ensures that all request preprocessing is\n * completed before the actual network request is made.\n *\n * @example\n * // Usually not created manually as Fetcher uses it automatically\n * const fetcher = new Fetcher();\n * // FetchInterceptor is automatically registered in fetcher.interceptors.request\n */\nexport class FetchInterceptor implements RequestInterceptor {\n /**\n * Interceptor name, used to identify and manage interceptor instances.\n *\n * Each interceptor must have a unique name for identification and manipulation\n * within the interceptor manager.\n */\n readonly name = FETCH_INTERCEPTOR_NAME;\n\n /**\n * Interceptor execution order, set to near maximum safe integer to ensure latest execution.\n *\n * Since this is the interceptor that actually executes HTTP requests, it should\n * execute after all other request interceptors, so its order is set to\n * FETCH_INTERCEPTOR_ORDER. This ensures that all request preprocessing is\n * completed before the actual network request is made, while still allowing\n * other interceptors to run after it if needed. The positioning at the end\n * of the request chain ensures that all transformations and validations are\n * completed before the network request is executed.\n */\n readonly order = FETCH_INTERCEPTOR_ORDER;\n\n /**\n * Intercept and process HTTP requests.\n *\n * Executes the actual HTTP request and applies timeout control. This is the final\n * step in the request processing chain, responsible for calling the timeoutFetch\n * function to send the network request.\n *\n * @param exchange - Exchange object containing request information\n *\n * @throws {FetchTimeoutError} Throws timeout exception when request times out\n *\n * @example\n * // Usually called internally by Fetcher\n * const fetcher = new Fetcher();\n * const exchange = new FetchExchange(\n * fetcher,\n * {\n * url: 'https://api.example.com/users',\n * method: 'GET',\n * timeout: 5000\n * }\n * );\n * await fetchInterceptor.intercept(exchange);\n * console.log(exchange.response); // HTTP response object\n */\n async intercept(exchange: FetchExchange) {\n exchange.response = await timeoutFetch(exchange.request);\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * OrderedCapable Interface\n *\n * Interface that provides ordering capability for types that implement it.\n * Implementing types must provide an order property to determine execution order.\n * Lower numerical values have higher priority, and elements with the same value\n * maintain their relative order.\n */\nexport interface OrderedCapable {\n /**\n * Order value\n *\n * Lower numerical values have higher priority. Negative numbers, zero, and\n * positive numbers are all supported.\n * When multiple elements have the same order value, their relative order\n * will remain unchanged (stable sort).\n */\n order: number;\n}\n\n/**\n * Sorts an array of elements that implement the OrderedCapable interface\n *\n * This function creates and returns a new sorted array without modifying the\n * original array. It supports an optional filter function to select elements\n * that should participate in sorting.\n *\n * @template T - Array element type that must implement the OrderedCapable interface\n * @param array - The array to be sorted\n * @param filter - Optional filter function to select elements that should be sorted\n * @returns A new array sorted in ascending order by the order property\n *\n * @example\n * ```typescript\n * const items: OrderedCapable[] = [\n * { order: 10 },\n * { order: 5 },\n * { order: 1 },\n * ];\n *\n * const sortedItems = toSorted(items);\n * // Result: [{ order: 1 }, { order: 5 }, { order: 10 }]\n *\n * // Using filter function\n * const filteredAndSorted = toSorted(items, item => item.order > 3);\n * // Result: [{ order: 5 }, { order: 10 }]\n * ```\n */\nexport function toSorted<T extends OrderedCapable>(\n array: T[],\n filter?: (item: T) => boolean,\n): T[] {\n if (filter) {\n return array.filter(filter).sort((a, b) => a.order - b.order);\n }\n return [...array].sort((a, b) => a.order - b.order);\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { NamedCapable } from './types';\nimport { OrderedCapable, toSorted } from './orderedCapable';\nimport { FetchExchange } from './fetchExchange';\n\n/**\n * Interface for HTTP interceptors in the fetcher pipeline.\n *\n * Interceptors are middleware components that can modify requests, responses, or handle errors\n * at different stages of the HTTP request lifecycle. They follow the Chain of Responsibility\n * pattern, where each interceptor can process the exchange object and pass it to the next.\n *\n * @example\n * // Example of a custom request interceptor\n * const customRequestInterceptor: Interceptor = {\n * name: 'CustomRequestInterceptor',\n * order: 100,\n * async intercept(exchange: FetchExchange): Promise<void> {\n * // Modify request headers\n * exchange.request.headers = {\n * ...exchange.request.headers,\n * 'X-Custom-Header': 'custom-value'\n * };\n * }\n * };\n */\nexport interface Interceptor extends NamedCapable, OrderedCapable {\n /**\n * Unique identifier for the interceptor.\n *\n * Used by InterceptorRegistry to manage interceptors, including adding, removing,\n * and preventing duplicates. Each interceptor must have a unique name.\n */\n readonly name: string;\n\n /**\n * Interceptor method that modifies the request or response.\n *\n * @param exchange - The current exchange object, which contains the request and response.\n * @returns A promise that resolves to the modified exchange object.\n */\n readonly order: number;\n\n /**\n * Process the exchange object in the interceptor pipeline.\n *\n * This method is called by InterceptorRegistry to process the exchange object.\n * Interceptors can modify request, response, or error properties directly.\n *\n * @param exchange - The exchange object containing request, response, and error information\n *\n * @remarks\n * Interceptors should modify the exchange object directly rather than returning it.\n * They can also throw errors or transform errors into responses.\n */\n intercept(exchange: FetchExchange): void | Promise<void>;\n}\n\n/**\n * Interface for request interceptors.\n *\n * Request interceptors are executed before the HTTP request is sent.\n * They can modify the request configuration, add headers, or perform\n * other preprocessing tasks.\n *\n * @example\n * // Example of a request interceptor that adds an authorization header\n * const authInterceptor: RequestInterceptor = {\n * name: 'AuthorizationInterceptor',\n * order: 100,\n * async intercept(exchange: FetchExchange): Promise<void> {\n * const token = getAuthToken();\n * if (token) {\n * exchange.request.headers = {\n * ...exchange.request.headers,\n * 'Authorization': `Bearer ${token}`\n * };\n * }\n * }\n * };\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface RequestInterceptor extends Interceptor {\n}\n\n/**\n * Interface for response interceptors.\n *\n * Response interceptors are executed after the HTTP response is received\n * but before it's processed by the application. They can modify the response,\n * transform data, or handle response-specific logic.\n *\n * @example\n * // Example of a response interceptor that parses JSON data\n * const jsonInterceptor: ResponseInterceptor = {\n * name: 'JsonResponseInterceptor',\n * order: 100,\n * async intercept(exchange: FetchExchange): Promise<void> {\n * if (exchange.response && exchange.response.headers.get('content-type')?.includes('application/json')) {\n * const data = await exchange.response.json();\n * // Attach parsed data to a custom property\n * (exchange.response as any).jsonData = data;\n * }\n * }\n * };\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface ResponseInterceptor extends Interceptor {\n}\n\n/**\n * Interface for error interceptors.\n *\n * Error interceptors are executed when an HTTP request fails.\n * They can handle errors, transform them, or implement retry logic.\n *\n * @example\n * // Example of an error interceptor that retries failed requests\n * const retryInterceptor: ErrorInterceptor = {\n * name: 'RetryInterceptor',\n * order: 100,\n * async intercept(exchange: FetchExchange): Promise<void> {\n * if (exchange.error && isRetryableError(exchange.error)) {\n * // Implement retry logic\n * const retryCount = (exchange.request as any).retryCount || 0;\n * if (retryCount < 3) {\n * (exchange.request as any).retryCount = retryCount + 1;\n * // Retry the request\n * exchange.response = await fetch(exchange.request);\n * // Clear the error since we've recovered\n * exchange.error = undefined;\n * }\n * }\n * }\n * };\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface ErrorInterceptor extends Interceptor {\n}\n\n/**\n * Registry for a collection of interceptors of the same type.\n *\n * Handles adding, removing, and executing interceptors in the correct order.\n * Each InterceptorRegistry instance manages one type of interceptor (request, response, or error).\n *\n * @remarks\n * Interceptors are executed in ascending order of their `order` property.\n * Interceptors with the same order value are executed in the order they were added.\n *\n * @example\n * // Create an interceptor registry with initial interceptors\n * const requestRegistry = new InterceptorRegistry([interceptor1, interceptor2]);\n *\n * // Add a new interceptor\n * requestRegistry.use(newInterceptor);\n *\n * // Remove an interceptor by name\n * requestRegistry.eject('InterceptorName');\n *\n * // Process an exchange through all interceptors\n * const result = await requestRegistry.intercept(exchange);\n */\nexport class InterceptorRegistry implements Interceptor {\n /**\n * Gets the name of this interceptor registry.\n *\n * @returns The constructor name of this class\n */\n get name(): string {\n return this.constructor.name;\n }\n\n /**\n * Gets the order of this interceptor registry.\n *\n * @returns Number.MIN_SAFE_INTEGER, indicating this registry should execute early\n */\n get order(): number {\n return Number.MIN_SAFE_INTEGER;\n }\n\n /**\n * Array of interceptors managed by this registry, sorted by their order property.\n */\n private sortedInterceptors: Interceptor[] = [];\n\n /**\n * Initializes a new InterceptorRegistry instance.\n *\n * @param interceptors - Initial array of interceptors to manage\n *\n * @remarks\n * The provided interceptors will be sorted by their order property immediately\n * upon construction.\n */\n constructor(interceptors: Interceptor[] = []) {\n this.sortedInterceptors = toSorted(interceptors);\n }\n\n /**\n * Returns an array of all interceptors in the registry.\n */\n get interceptors(): Interceptor[] {\n return [...this.sortedInterceptors];\n }\n\n /**\n * Adds an interceptor to this registry.\n *\n * @param interceptor - The interceptor to add\n * @returns True if the interceptor was added, false if an interceptor with the\n * same name already exists\n *\n * @remarks\n * Interceptors are uniquely identified by their name property. Attempting to add\n * an interceptor with a name that already exists in the registry will fail.\n *\n * After adding, interceptors are automatically sorted by their order property.\n */\n use(interceptor: Interceptor): boolean {\n if (this.sortedInterceptors.some(item => item.name === interceptor.name)) {\n return false;\n }\n this.sortedInterceptors = toSorted([\n ...this.sortedInterceptors,\n interceptor,\n ]);\n return true;\n }\n\n /**\n * Removes an interceptor by name.\n *\n * @param name - The name of the interceptor to remove\n * @returns True if an interceptor was removed, false if no interceptor with the\n * given name was found\n */\n eject(name: string): boolean {\n const original = this.sortedInterceptors;\n this.sortedInterceptors = toSorted(\n original,\n interceptor => interceptor.name !== name,\n );\n return original.length !== this.sortedInterceptors.length;\n }\n\n /**\n * Removes all interceptors from this registry.\n */\n clear(): void {\n this.sortedInterceptors = [];\n }\n\n\n /**\n * Executes all managed interceptors on the given exchange object.\n *\n * @param exchange - The exchange object to process\n * @returns A promise that resolves when all interceptors have been executed\n *\n * @remarks\n * Interceptors are executed in order, with each interceptor receiving the result\n * of the previous interceptor. The first interceptor receives the original\n * exchange object.\n *\n * If any interceptor throws an error, the execution chain is broken and the error\n * is propagated to the caller.\n */\n async intercept(exchange: FetchExchange): Promise<void> {\n for (const interceptor of this.sortedInterceptors) {\n // Each interceptor processes the output of the previous interceptor\n await interceptor.intercept(exchange);\n }\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ResponseInterceptor } from './interceptor';\nimport { FetchExchange } from './fetchExchange';\nimport { FetcherError } from './fetcherError';\n\n/**\n * Error thrown when response status validation fails.\n *\n * This error is thrown by ValidateStatusInterceptor when the response status\n * does not pass the validation defined by the validateStatus function.\n */\nexport class HttpStatusValidationError extends FetcherError {\n constructor(public readonly exchange: FetchExchange) {\n super(\n `Request failed with status code ${exchange.response?.status} for ${exchange.request.url}`,\n );\n this.name = 'HttpStatusValidationError';\n Object.setPrototypeOf(this, HttpStatusValidationError.prototype);\n }\n}\n\n/**\n * Defines whether to resolve or reject the promise for a given HTTP response status code.\n * If `validateStatus` returns `true` (or is set to `null` or `undefined`), the promise will be resolved;\n * otherwise, the promise will be rejected.\n *\n * @param status - HTTP response status code\n * @returns true to resolve the promise, false to reject it\n *\n * @example\n * ```typescript\n * // Default behavior (2xx status codes are resolved)\n * const fetcher = new Fetcher();\n *\n * // Custom behavior (only 200 status code is resolved)\n * const fetcher = new Fetcher({\n * validateStatus: (status) => status === 200\n * });\n *\n * // Always resolve (never reject based on status)\n * const fetcher = new Fetcher({\n * validateStatus: (status) => true\n * });\n * ```\n */\ntype ValidateStatus = (status: number) => boolean;\n\nconst DEFAULT_VALIDATE_STATUS: ValidateStatus = (status: number) =>\n status >= 200 && status < 300;\n\n/**\n * The name of the ValidateStatusInterceptor.\n */\nexport const VALIDATE_STATUS_INTERCEPTOR_NAME = 'ValidateStatusInterceptor';\n\n/**\n * The order of the ValidateStatusInterceptor.\n * Set to Number.MAX_SAFE_INTEGER - 1000 to ensure it runs latest among response interceptors,\n * but still allows other interceptors to run after it if needed.\n */\nexport const VALIDATE_STATUS_INTERCEPTOR_ORDER = Number.MAX_SAFE_INTEGER - 1000;\n\n/**\n * Response interceptor that validates HTTP status codes.\n *\n * This interceptor implements behavior similar to axios's validateStatus option.\n * It checks the response status code against a validation function and throws\n * an error if the status is not valid.\n *\n * @remarks\n * This interceptor runs at the very beginning of the response interceptor chain to ensure\n * status validation happens before any other response processing. The order is set to\n * VALIDATE_STATUS_INTERCEPTOR_ORDER to ensure it executes early in the response chain,\n * allowing for other response interceptors to run after it if needed. This positioning\n * ensures that invalid responses are caught and handled early in the response processing\n * pipeline, before other response handlers attempt to process them.\n *\n * @example\n * ```typescript\n * // Default behavior (2xx status codes are valid)\n * const interceptor = new ValidateStatusInterceptor();\n *\n * // Custom validation (only 200 status code is valid)\n * const interceptor = new ValidateStatusInterceptor((status) => status === 200);\n *\n * // Always valid (never throws based on status)\n * const interceptor = new ValidateStatusInterceptor((status) => true);\n * ```\n */\nexport class ValidateStatusInterceptor implements ResponseInterceptor {\n /**\n * Gets the name of this interceptor.\n *\n * @returns The name of this interceptor\n */\n get name(): string {\n return VALIDATE_STATUS_INTERCEPTOR_NAME;\n }\n\n /**\n * Gets the order of this interceptor.\n *\n * @returns VALIDATE_STATUS_INTERCEPTOR_ORDER, indicating this interceptor should execute early\n */\n get order(): number {\n return VALIDATE_STATUS_INTERCEPTOR_ORDER;\n }\n\n /**\n * Creates a new ValidateStatusInterceptor instance.\n *\n * @param validateStatus - Function that determines if a status code is valid\n */\n constructor(\n private readonly validateStatus: ValidateStatus = DEFAULT_VALIDATE_STATUS,\n ) {\n }\n\n /**\n * Validates the response status code.\n *\n * @param exchange - The exchange containing the response to validate\n * @throws HttpStatusValidationError if the status code is not valid\n *\n * @remarks\n * This method runs at the beginning of the response interceptor chain to ensure\n * status validation happens before any other response processing. Invalid responses\n * are caught and converted to HttpStatusValidationError early in the pipeline,\n * preventing other response handlers from attempting to process them. Valid responses\n * proceed through the rest of the response interceptor chain normally.\n */\n intercept(exchange: FetchExchange) {\n // Only validate if there's a response\n if (!exchange.response) {\n return;\n }\n\n const status = exchange.response.status;\n // If status is valid, do nothing\n if (this.validateStatus(status)) {\n return;\n }\n throw new HttpStatusValidationError(exchange);\n }\n}\n","import { UrlResolveInterceptor } from './urlResolveInterceptor';\nimport { RequestBodyInterceptor } from './requestBodyInterceptor';\nimport { FetchInterceptor } from './fetchInterceptor';\nimport { FetchExchange } from './fetchExchange';\nimport { FetcherError } from './fetcherError';\nimport { InterceptorRegistry } from './interceptor';\nimport { ValidateStatusInterceptor } from './validateStatusInterceptor';\n\n/**\n * Custom error class for FetchExchange related errors.\n *\n * This error is thrown when there are issues with the HTTP exchange process,\n * such as when a request fails and no response is generated. It provides\n * comprehensive information about the failed request through the exchange object.\n *\n * @example\n * ```typescript\n * try {\n * await fetcher.get('/api/users');\n * } catch (error) {\n * if (error instanceof ExchangeError) {\n * console.log('Request URL:', error.exchange.request.url);\n * console.log('Request method:', error.exchange.request.method);\n * if (error.exchange.error) {\n * console.log('Underlying error:', error.exchange.error);\n * }\n * }\n * }\n * ```\n */\nexport class ExchangeError extends FetcherError {\n /**\n * Creates a new ExchangeError instance.\n *\n * @param exchange - The FetchExchange object containing request/response/error information.\n * @param errorMsg - An optional error message.\n */\n constructor(public readonly exchange: FetchExchange, errorMsg?: string) {\n const errorMessage = errorMsg ||\n exchange.error?.message ||\n exchange.response?.statusText ||\n `Request to ${exchange.request.url} failed during exchange`;\n super(errorMessage, exchange.error);\n this.name = 'ExchangeError';\n Object.setPrototypeOf(this, ExchangeError.prototype);\n }\n}\n\n/**\n * Collection of interceptor managers for the Fetcher client.\n *\n * Manages three types of interceptors:\n * 1. Request interceptors - Process requests before sending HTTP requests\n * 2. Response interceptors - Process responses after receiving HTTP responses\n * 3. Error interceptors - Handle errors when they occur during the request process\n *\n * Each type of interceptor is managed by an InterceptorRegistry instance, supporting\n * adding, removing, and executing interceptors.\n *\n * @example\n * // Create a custom interceptor\n * const customRequestInterceptor: Interceptor = {\n * name: 'CustomRequestInterceptor',\n * order: 100,\n * async intercept(exchange: FetchExchange): Promise<FetchExchange> {\n * // Modify request headers\n * exchange.request.headers = {\n * ...exchange.request.headers,\n * 'X-Custom-Header': 'custom-value'\n * };\n * return exchange;\n * }\n * };\n *\n * // Add interceptor to Fetcher\n * const fetcher = new Fetcher();\n * fetcher.interceptors.request.use(customRequestInterceptor);\n *\n * @remarks\n * By default, the request interceptor registry has three built-in interceptors registered:\n * 1. UrlResolveInterceptor - Resolves the final URL with parameters\n * 2. RequestBodyInterceptor - Automatically converts object-type request bodies to JSON strings\n * 3. FetchInterceptor - Executes actual HTTP requests and handles timeouts\n */\nexport class InterceptorManager {\n /**\n * Registry for request-phase interceptors.\n *\n * Executed before HTTP requests are sent. Contains three built-in interceptors by default:\n * UrlResolveInterceptor, RequestBodyInterceptor, and FetchInterceptor.\n *\n * @remarks\n * Request interceptors are executed in ascending order of their order values, with smaller\n * values having higher priority. The default interceptors are:\n * 1. UrlResolveInterceptor (order: Number.MIN_SAFE_INTEGER) - Resolves the final URL\n * 2. RequestBodyInterceptor (order: 0) - Converts object bodies to JSON\n * 3. FetchInterceptor (order: Number.MAX_SAFE_INTEGER) - Executes the actual HTTP request\n */\n readonly request: InterceptorRegistry = new InterceptorRegistry([\n new UrlResolveInterceptor(),\n new RequestBodyInterceptor(),\n new FetchInterceptor(),\n ]);\n\n /**\n * Manager for response-phase interceptors.\n *\n * Executed after HTTP responses are received. Contains ValidateStatusInterceptor by default\n * which validates HTTP status codes and throws errors for invalid statuses.\n *\n * @remarks\n * Response interceptors are executed in ascending order of their order values, with smaller\n * values having higher priority.\n *\n * By default, the response interceptor registry has one built-in interceptor registered:\n * 1. ValidateStatusInterceptor - Validates HTTP status codes and throws HttpStatusValidationError for invalid statuses\n */\n readonly response: InterceptorRegistry = new InterceptorRegistry([\n new ValidateStatusInterceptor(),\n ]);\n\n /**\n * Manager for error-handling phase interceptors.\n *\n * Executed when errors occur during HTTP requests. Empty by default, custom error handling\n * logic can be added as needed.\n *\n * @remarks\n * Error interceptors are executed in ascending order of their order values, with smaller\n * values having higher priority. Error interceptors can transform errors into normal responses,\n * avoiding thrown exceptions.\n */\n readonly error: InterceptorRegistry = new InterceptorRegistry();\n\n /**\n * Processes a FetchExchange through the interceptor pipeline.\n *\n * This method is the core of the Fetcher's interceptor system. It executes the three\n * phases of interceptors in sequence:\n * 1. Request interceptors - Process the request before sending\n * 2. Response interceptors - Process the response after receiving\n * 3. Error interceptors - Handle any errors that occurred during the process\n *\n * The interceptor pipeline follows the Chain of Responsibility pattern, where each\n * interceptor can modify the exchange object and decide whether to continue or\n * terminate the chain.\n *\n * @param fetchExchange - The exchange object containing request, response, and error information\n * @returns Promise that resolves to the processed FetchExchange\n * @throws ExchangeError if an unhandled error occurs during processing\n *\n * @remarks\n * The method handles three distinct phases:\n *\n * 1. Request Phase: Executes request interceptors which can modify headers, URL, body, etc.\n * Built-in interceptors handle URL resolution, body serialization, and actual HTTP execution.\n *\n * 2. Response Phase: Executes response interceptors which can transform or validate responses.\n * These interceptors only run if the request phase completed without throwing.\n *\n * 3. Error Phase: Executes error interceptors when any phase throws an error. Error interceptors\n * can handle errors by clearing the error property. If error interceptors clear the error,\n * the exchange is returned successfully.\n *\n * Error Handling:\n * - If any interceptor throws an error, the error phase is triggered\n * - Error interceptors can \"fix\" errors by clearing the error property on the exchange\n * - If errors remain after error interceptors run, they are wrapped in ExchangeError\n *\n * Order of Execution:\n * 1. Request interceptors (sorted by order property, ascending)\n * 2. Response interceptors (sorted by order property, ascending) - only if no error in request phase\n * 3. Error interceptors (sorted by order property, ascending) - only if an error occurred\n *\n * @example\n * ```typescript\n * // Create a fetcher with custom interceptors\n * const fetcher = new Fetcher();\n *\n * // Add a request interceptor\n * fetcher.interceptors.request.use({\n * name: 'AuthInterceptor',\n * order: 100,\n * async intercept(exchange: FetchExchange) {\n * exchange.request.headers = {\n * ...exchange.request.headers,\n * 'Authorization': 'Bearer ' + getToken()\n * };\n * }\n * });\n *\n * // Add a response interceptor\n * fetcher.interceptors.response.use({\n * name: 'ResponseLogger',\n * order: 100,\n * async intercept(exchange: FetchExchange) {\n * console.log(`Response status: ${exchange.response?.status}`);\n * }\n * });\n *\n * // Add an error interceptor\n * fetcher.interceptors.error.use({\n * name: 'ErrorLogger',\n * order: 100,\n * async intercept(exchange: FetchExchange) {\n * console.error(`Request to ${exchange.request.url} failed:`, exchange.error);\n * // Clear the error to indicate it's been handled\n * exchange.error = undefined;\n * }\n * });\n *\n * // Create and process an exchange\n * const request: FetchRequest = {\n * url: '/api/users',\n * method: HttpMethod.GET\n * };\n * const exchange = new FetchExchange(fetcher, request);\n * const result = await fetcher.exchange(exchange);\n * ```\n */\n async exchange(fetchExchange: FetchExchange): Promise<FetchExchange> {\n try {\n // Apply request interceptors\n await this.request.intercept(fetchExchange);\n // Apply response interceptors\n await this.response.intercept(fetchExchange);\n return fetchExchange;\n } catch (error: any) {\n // Apply error interceptors\n fetchExchange.error = error;\n await this.error.intercept(fetchExchange);\n\n // If error interceptors cleared the error (indicating it's been handled/fixed), return the exchange\n if (!fetchExchange.hasError()) {\n return fetchExchange;\n }\n\n // Otherwise, wrap the error in ExchangeError\n throw new ExchangeError(fetchExchange);\n }\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Fetcher } from './fetcher';\nimport { FetchRequest } from './fetchRequest';\nimport { ExchangeError } from './interceptorManager';\n\n/**\n * Container for HTTP request/response data that flows through the interceptor chain.\n *\n * Represents the complete exchange object that flows through the interceptor chain.\n * This object contains all the information about a request, response, and any errors\n * that occur during the HTTP request lifecycle. It also provides a mechanism for\n * sharing data between interceptors through the attributes property.\n *\n * FetchExchange instances are unique within a single request scope, meaning each HTTP\n * request creates its own FetchExchange instance that is passed through the interceptor\n * chain for that specific request.\n *\n * @example\n * ```typescript\n * // In a request interceptor\n * const requestInterceptor: Interceptor = {\n * name: 'RequestInterceptor',\n * order: 0,\n * intercept(exchange: FetchExchange) {\n * // Add custom data to share with other interceptors\n * exchange.attributes = exchange.attributes || {};\n * exchange.attributes.startTime = Date.now();\n * exchange.attributes.customHeader = 'my-value';\n * }\n * };\n *\n * // In a response interceptor\n * const responseInterceptor: Interceptor = {\n * name: 'ResponseInterceptor',\n * order: 0,\n * intercept(exchange: FetchExchange) {\n * // Access data shared by previous interceptors\n * if (exchange.attributes && exchange.attributes.startTime) {\n * const startTime = exchange.attributes.startTime;\n * const duration = Date.now() - startTime;\n * console.log(`Request took ${duration}ms`);\n * }\n * }\n * };\n * ```\n */\nexport class FetchExchange {\n /**\n * The Fetcher instance that initiated this exchange.\n */\n fetcher: Fetcher;\n\n /**\n * The request configuration including url, method, headers, body, etc.\n */\n request: FetchRequest;\n\n /**\n * The response object, undefined until the request completes successfully.\n */\n response: Response | undefined;\n\n /**\n * Any error that occurred during the request processing, undefined if no error occurred.\n */\n error: Error | any | undefined;\n\n /**\n * Shared attributes for passing data between interceptors.\n *\n * This property allows interceptors to share arbitrary data with each other.\n * Interceptors can read from and write to this object to pass information\n * along the interceptor chain.\n *\n * @remarks\n * - This property is optional and may be undefined initially\n * - Interceptors should initialize this property if they need to use it\n * - Use string keys to avoid conflicts between different interceptors\n * - Consider namespacing your keys (e.g., 'mylib.retryCount' instead of 'retryCount')\n * - Be mindful of memory usage when storing large objects\n */\n attributes: Record<string, any> = {};\n\n constructor(\n fetcher: Fetcher,\n request: FetchRequest,\n response?: Response,\n error?: Error | any,\n ) {\n this.fetcher = fetcher;\n this.request = request;\n this.response = response;\n this.error = error;\n }\n\n /**\n * Checks if the exchange has an error.\n *\n * @returns true if an error is present, false otherwise\n */\n hasError(): boolean {\n return !!this.error;\n }\n\n /**\n * Checks if the exchange has a response.\n *\n * @returns true if a response is present, false otherwise\n */\n hasResponse(): boolean {\n return !!this.response;\n }\n\n /**\n * Gets the required response object, throwing an error if no response is available.\n *\n * This getter ensures that a response object is available, and throws an ExchangeError\n * with details about the request if no response was received. This is useful for\n * guaranteeing that downstream code always has a valid Response object to work with.\n *\n * @throws {ExchangeError} If no response is available for the current exchange\n * @returns The Response object for this exchange\n */\n get requiredResponse(): Response {\n if (!this.response) {\n throw new ExchangeError(\n this,\n `Request to ${this.request.url} failed with no response`,\n );\n }\n return this.response;\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Merges two record objects, giving precedence to the second record for overlapping keys.\n *\n * This utility function is used to merge configuration objects where the second object\n * takes precedence over the first when there are conflicting keys.\n *\n * @template V - The type of values in the records\n * @param first - The first record to merge (lower precedence)\n * @param second - The second record to merge (higher precedence)\n * @returns A new merged record, or undefined if both inputs are undefined\n *\n * @example\n * ```typescript\n * // Merge two objects\n * const defaults = { timeout: 5000, retries: 3 };\n * const overrides = { timeout: 10000 };\n * const result = mergeRecords(defaults, overrides);\n * // Result: { timeout: 10000, retries: 3 }\n *\n * // Handle undefined values\n * const result2 = mergeRecords(undefined, { timeout: 5000 });\n * // Result: { timeout: 5000 }\n *\n * // Return undefined when both are undefined\n * const result3 = mergeRecords(undefined, undefined);\n * // Result: undefined\n * ```\n */\nexport function mergeRecords<V>(\n first?: Record<string, V>,\n second?: Record<string, V>,\n): Record<string, V> | undefined {\n // If both records are undefined, return undefined\n if (first === undefined && second === undefined) {\n return undefined;\n }\n\n // If second record is undefined, return first record\n if (second === undefined) {\n return first;\n }\n\n // If first record is undefined, return second record\n if (first === undefined) {\n return second;\n }\n\n // Merge both records, with second taking precedence\n return { ...first, ...second };\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { UrlBuilder, UrlBuilderCapable } from './urlBuilder';\nimport { resolveTimeout, TimeoutCapable } from './timeout';\nimport { FetchExchange } from './fetchExchange';\nimport {\n BaseURLCapable,\n ContentTypeValues,\n FetchRequest,\n FetchRequestInit,\n HttpMethod,\n RequestHeaders,\n RequestHeadersCapable,\n} from './fetchRequest';\nimport { mergeRecords } from './utils';\nimport { InterceptorManager } from './interceptorManager';\n\n/**\n * Configuration options for the Fetcher client.\n *\n * Defines the customizable aspects of a Fetcher instance including base URL,\n * default headers, timeout settings, and interceptors.\n *\n * @example\n * ```typescript\n * const options: FetcherOptions = {\n * baseURL: 'https://api.example.com',\n * headers: { 'Content-Type': 'application/json' },\n * timeout: 5000,\n * interceptors: new InterceptorManager()\n * };\n * ```\n */\nexport interface FetcherOptions\n extends BaseURLCapable,\n RequestHeadersCapable,\n TimeoutCapable {\n interceptors?: InterceptorManager;\n}\n\nconst DEFAULT_HEADERS: RequestHeaders = {\n 'Content-Type': ContentTypeValues.APPLICATION_JSON,\n};\n\nexport const DEFAULT_OPTIONS: FetcherOptions = {\n baseURL: '',\n headers: DEFAULT_HEADERS,\n};\n\n/**\n * HTTP client with support for interceptors, URL building, and timeout control.\n *\n * The Fetcher class provides a flexible and extensible HTTP client implementation\n * that follows the interceptor pattern. It supports URL parameter interpolation,\n * request/response transformation, and timeout handling.\n *\n * @example\n * ```typescript\n * const fetcher = new Fetcher({ baseURL: 'https://api.example.com' });\n * const response = await fetcher.fetch('/users/{id}', {\n * urlParams: {\n * path: { id: 123 },\n * query: { filter: 'active' }\n * },\n * timeout: 5000\n * });\n * ```\n */\nexport class Fetcher\n implements UrlBuilderCapable, RequestHeadersCapable, TimeoutCapable {\n readonly urlBuilder: UrlBuilder;\n readonly headers?: RequestHeaders = DEFAULT_HEADERS;\n readonly timeout?: number;\n readonly interceptors: InterceptorManager;\n\n /**\n * Initializes a new Fetcher instance with optional configuration.\n *\n * Creates a Fetcher with default settings that can be overridden through the options parameter.\n * If no interceptors are provided, a default set of interceptors will be used.\n *\n * @param options - Configuration options for the Fetcher instance\n */\n constructor(options: FetcherOptions = DEFAULT_OPTIONS) {\n this.urlBuilder = new UrlBuilder(options.baseURL);\n this.headers = options.headers ?? DEFAULT_HEADERS;\n this.timeout = options.timeout;\n this.interceptors = options.interceptors ?? new InterceptorManager();\n }\n\n /**\n * Executes an HTTP request with the specified URL and options.\n *\n * This is the primary method for making HTTP requests. It processes the request\n * through the interceptor chain and returns the resulting Response.\n *\n * @param url - The URL path for the request (relative to baseURL if set)\n * @param request - Request configuration including headers, body, parameters, etc.\n * @returns Promise that resolves to the HTTP response\n * @throws FetchError if the request fails and no response is generated\n */\n async fetch(url: string, request: FetchRequestInit = {}): Promise<Response> {\n const fetchRequest = request as FetchRequest;\n fetchRequest.url = url;\n const exchange = await this.request(fetchRequest);\n return exchange.requiredResponse;\n }\n\n /**\n * Processes an HTTP request through the Fetcher's internal workflow.\n *\n * This method prepares the request by merging headers and timeout settings,\n * creates a FetchExchange object, and passes it through the exchange method\n * for interceptor processing.\n *\n * @param request - Complete request configuration object\n * @returns Promise that resolves to a FetchExchange containing request/response data\n * @throws Error if an unhandled error occurs during request processing\n */\n async request(request: FetchRequest): Promise<FetchExchange> {\n // Merge default headers and request-level headers\n const mergedHeaders = mergeRecords(request.headers, this.headers);\n // Merge request options\n const fetchRequest: FetchRequest = {\n ...request,\n headers: mergedHeaders,\n timeout: resolveTimeout(request.timeout, this.timeout),\n };\n const exchange: FetchExchange = new FetchExchange(this, fetchRequest);\n return this.interceptors.exchange(exchange);\n }\n\n /**\n * Internal helper method for making HTTP requests with a specific method.\n *\n * This private method is used by the public HTTP method methods (get, post, etc.)\n * to execute requests with the appropriate HTTP verb.\n *\n * @param method - The HTTP method to use for the request\n * @param url - The URL path for the request\n * @param request - Additional request options\n * @returns Promise that resolves to the HTTP response\n */\n private async methodFetch(\n method: HttpMethod,\n url: string,\n request: FetchRequestInit = {},\n ): Promise<Response> {\n return this.fetch(url, {\n ...request,\n method,\n });\n }\n\n /**\n * Makes a GET HTTP request.\n *\n * Convenience method for making GET requests. The request body is omitted\n * as GET requests should not contain a body according to HTTP specification.\n *\n * @param url - The URL path for the request\n * @param request - Request options excluding method and body\n * @returns Promise that resolves to the HTTP response\n */\n async get(\n url: string,\n request: Omit<FetchRequestInit, 'method' | 'body'> = {},\n ): Promise<Response> {\n return this.methodFetch(HttpMethod.GET, url, request);\n }\n\n /**\n * Makes a POST HTTP request.\n *\n * Convenience method for making POST requests, commonly used for creating resources.\n *\n * @param url - The URL path for the request\n * @param request - Request options including body and other parameters\n * @returns Promise that resolves to the HTTP response\n */\n async post(\n url: string,\n request: Omit<FetchRequestInit, 'method'> = {},\n ): Promise<Response> {\n return this.methodFetch(HttpMethod.POST, url, request);\n }\n\n /**\n * Makes a PUT HTTP request.\n *\n * Convenience method for making PUT requests, commonly used for updating resources.\n *\n * @param url - The URL path for the request\n * @param request - Request options including body and other parameters\n * @returns Promise that resolves to the HTTP response\n */\n async put(\n url: string,\n request: Omit<FetchRequestInit, 'method'> = {},\n ): Promise<Response> {\n return this.methodFetch(HttpMethod.PUT, url, request);\n }\n\n /**\n * Makes a DELETE HTTP request.\n *\n * Convenience method for making DELETE requests, commonly used for deleting resources.\n *\n * @param url - The URL path for the request\n * @param request - Request options excluding method and body\n * @returns Promise that resolves to the HTTP response\n */\n async delete(\n url: string,\n request: Omit<FetchRequestInit, 'method'> = {},\n ): Promise<Response> {\n return this.methodFetch(HttpMethod.DELETE, url, request);\n }\n\n /**\n * Makes a PATCH HTTP request.\n *\n * Convenience method for making PATCH requests, commonly used for partial updates.\n *\n * @param url - The URL path for the request\n * @param request - Request options including body and other parameters\n * @returns Promise that resolves to the HTTP response\n */\n async patch(\n url: string,\n request: Omit<FetchRequestInit, 'method'> = {},\n ): Promise<Response> {\n return this.methodFetch(HttpMethod.PATCH, url, request);\n }\n\n /**\n * Makes a HEAD HTTP request.\n *\n * Convenience method for making HEAD requests, which retrieve headers only.\n * The request body is omitted as HEAD requests should not contain a body.\n *\n * @param url - The URL path for the request\n * @param request - Request options excluding method and body\n * @returns Promise that resolves to the HTTP response\n */\n async head(\n url: string,\n request: Omit<FetchRequestInit, 'method' | 'body'> = {},\n ): Promise<Response> {\n return this.methodFetch(HttpMethod.HEAD, url, request);\n }\n\n /**\n * Makes an OPTIONS HTTP request.\n *\n * Convenience method for making OPTIONS requests, commonly used for CORS preflight.\n * The request body is omitted as OPTIONS requests typically don't contain a body.\n *\n * @param url - The URL path for the request\n * @param request - Request options excluding method and body\n * @returns Promise that resolves to the HTTP response\n */\n async options(\n url: string,\n request: Omit<FetchRequestInit, 'method' | 'body'> = {},\n ): Promise<Response> {\n return this.methodFetch(HttpMethod.OPTIONS, url, request);\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Fetcher } from './fetcher';\n\n/**\n * Default fetcher name used when no name is specified\n */\nexport const DEFAULT_FETCHER_NAME = 'default';\n\n/**\n * FetcherRegistrar is a registry for managing multiple Fetcher instances.\n * It allows registering, retrieving, and unregistering Fetcher instances by name.\n * This is useful for applications that need to manage multiple HTTP clients\n * with different configurations.\n *\n * @example\n * // Register a fetcher\n * const fetcher = new Fetcher({ baseURL: 'https://api.example.com' });\n * fetcherRegistrar.register('api', fetcher);\n *\n * // Retrieve a fetcher\n * const apiFetcher = fetcherRegistrar.get('api');\n *\n * // Use the default fetcher\n * const defaultFetcher = fetcherRegistrar.default;\n *\n * // Unregister a fetcher\n * fetcherRegistrar.unregister('api');\n */\nexport class FetcherRegistrar {\n /**\n * Internal map for storing registered fetchers\n * @private\n */\n private registrar: Map<string, Fetcher> = new Map();\n\n /**\n * Register a Fetcher instance with a given name\n *\n * @param name - The name to register the fetcher under\n * @param fetcher - The Fetcher instance to register\n * @example\n * const fetcher = new Fetcher({ baseURL: 'https://api.example.com' });\n * fetcherRegistrar.register('api', fetcher);\n */\n register(name: string, fetcher: Fetcher): void {\n this.registrar.set(name, fetcher);\n }\n\n /**\n * Unregister a Fetcher instance by name\n *\n * @param name - The name of the fetcher to unregister\n * @returns boolean - True if the fetcher was successfully unregistered, false otherwise\n * @example\n * const success = fetcherRegistrar.unregister('api');\n * if (success) {\n * console.log('Fetcher unregistered successfully');\n * }\n */\n unregister(name: string): boolean {\n return this.registrar.delete(name);\n }\n\n /**\n * Get a Fetcher instance by name\n *\n * @param name - The name of the fetcher to retrieve\n * @returns Fetcher | undefined - The Fetcher instance if found, undefined otherwise\n * @example\n * const fetcher = fetcherRegistrar.get('api');\n * if (fetcher) {\n * // Use the fetcher\n * }\n */\n get(name: string): Fetcher | undefined {\n return this.registrar.get(name);\n }\n\n /**\n * Get a Fetcher instance by name, throwing an error if not found\n *\n * @param name - The name of the fetcher to retrieve\n * @returns Fetcher - The Fetcher instance\n * @throws Error - If no fetcher is registered with the given name\n * @example\n * try {\n * const fetcher = fetcherRegistrar.requiredGet('api');\n * // Use the fetcher\n * } catch (error) {\n * console.error('Fetcher not found:', error.message);\n * }\n */\n requiredGet(name: string): Fetcher {\n const fetcher = this.get(name);\n if (!fetcher) {\n throw new Error(`Fetcher ${name} not found`);\n }\n return fetcher;\n }\n\n /**\n * Get the default Fetcher instance\n *\n * @returns Fetcher - The default Fetcher instance\n * @throws Error - If no default fetcher is registered\n * @example\n * const defaultFetcher = fetcherRegistrar.default;\n */\n get default(): Fetcher {\n return this.requiredGet(DEFAULT_FETCHER_NAME);\n }\n\n /**\n * Set the default Fetcher instance\n *\n * @param fetcher - The Fetcher instance to set as default\n * @example\n * const fetcher = new Fetcher({ baseURL: 'https://api.example.com' });\n * fetcherRegistrar.default = fetcher;\n */\n set default(fetcher: Fetcher) {\n this.register(DEFAULT_FETCHER_NAME, fetcher);\n }\n\n /**\n * Get a copy of all registered fetchers\n *\n * @returns Map<string, Fetcher> - A copy of the internal registrar map\n * @example\n * const allFetchers = fetcherRegistrar.fetchers;\n * for (const [name, fetcher] of allFetchers) {\n * console.log(`Fetcher ${name}:`, fetcher);\n * }\n */\n get fetchers(): Map<string, Fetcher> {\n return new Map(this.registrar);\n }\n}\n\n/**\n * Global instance of FetcherRegistrar\n * This is the default registrar used throughout the application\n *\n * @example\n * import { fetcherRegistrar } from '@ahoo-wang/fetcher';\n *\n * // Register a fetcher\n * const fetcher = new Fetcher({ baseURL: 'https://api.example.com' });\n * fetcherRegistrar.register('api', fetcher);\n *\n * // Retrieve a fetcher\n * const apiFetcher = fetcherRegistrar.get('api');\n */\nexport const fetcherRegistrar = new FetcherRegistrar();\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { FetchRequestInit } from './fetchRequest';\nimport { UrlParams } from './urlBuilder';\nimport { mergeRecords } from './utils';\n\n/**\n * Merges two FetcherRequest objects into one.\n *\n * This function combines two FetcherRequest objects, with the second object's properties\n * taking precedence over the first object's properties. Special handling is applied\n * to nested objects like path, query, and headers which are merged recursively.\n * For primitive values, the second object's values override the first's.\n *\n * @param first - The first request object (lower priority)\n * @param second - The second request object (higher priority)\n * @returns A new FetcherRequest object with merged properties\n *\n * @example\n * ```typescript\n * const request1 = {\n * method: 'GET',\n * urlParams: {\n * path: { id: 1 }\n * },\n * headers: { 'Content-Type': 'application/json' }\n * };\n *\n * const request2 = {\n * method: 'POST',\n * urlParams: {\n * query: { filter: 'active' }\n * },\n * headers: { 'Authorization': 'Bearer token' }\n * };\n *\n * const merged = mergeRequest(request1, request2);\n * // Result: {\n * // method: 'POST',\n * // urlParams: {\n * // path: { id: 1 },\n * // query: { filter: 'active' }\n * // },\n * // headers: {\n * // 'Content-Type': 'application/json',\n * // 'Authorization': 'Bearer token'\n * // }\n * // }\n * ```\n */\nexport function mergeRequest(\n first: FetchRequestInit,\n second: FetchRequestInit,\n): FetchRequestInit {\n // If first request is empty, return second request\n if (Object.keys(first).length === 0) {\n return second;\n }\n\n // If second request is empty, return first request\n if (Object.keys(second).length === 0) {\n return first;\n }\n\n // Merge nested objects\n const urlParams: UrlParams = {\n path: mergeRecords(first.urlParams?.path, second.urlParams?.path),\n query: mergeRecords(first.urlParams?.query, second.urlParams?.query),\n };\n\n const headers = {\n ...first.headers,\n ...second.headers,\n };\n\n // For primitive values, second takes precedence\n const method = second.method ?? first.method;\n const body = second.body ?? first.body;\n const timeout = second.timeout ?? first.timeout;\n const signal = second.signal ?? first.signal;\n\n // Return merged request with second object's top-level properties taking precedence\n return {\n ...first,\n ...second,\n method,\n urlParams,\n headers,\n body,\n timeout,\n signal,\n };\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { NamedCapable } from './types';\nimport { DEFAULT_OPTIONS, Fetcher, FetcherOptions } from './fetcher';\nimport { DEFAULT_FETCHER_NAME, fetcherRegistrar } from './fetcherRegistrar';\n\n/**\n * NamedFetcher is an extension of the Fetcher class that automatically registers\n * itself with the global fetcherRegistrar using a provided name.\n * This allows for easy management and retrieval of multiple fetcher instances\n * throughout an application by name.\n *\n * @example\n * // Create a named fetcher that automatically registers itself\n * const apiFetcher = new NamedFetcher('api', {\n * baseURL: 'https://api.example.com',\n * timeout: 5000\n * });\n *\n * // Retrieve the fetcher later by name\n * const sameFetcher = fetcherRegistrar.get('api');\n * console.log(apiFetcher === sameFetcher); // true\n *\n * // Use the fetcher normally\n * const response = await apiFetcher.get('/users');\n */\nexport class NamedFetcher extends Fetcher implements NamedCapable {\n /**\n * The name of this fetcher instance, used for registration and retrieval\n */\n name: string;\n\n /**\n * Create a NamedFetcher instance and automatically register it with the global fetcherRegistrar\n *\n * @param name - The name to register this fetcher under\n * @param options - Fetcher configuration options (same as Fetcher constructor)\n *\n * @example\n * // Create with default options\n * const fetcher1 = new NamedFetcher('default');\n *\n * // Create with custom options\n * const fetcher2 = new NamedFetcher('api', {\n * baseURL: 'https://api.example.com',\n * timeout: 5000,\n * headers: { 'Authorization': 'Bearer token' }\n * });\n */\n constructor(name: string, options: FetcherOptions = DEFAULT_OPTIONS) {\n super(options);\n this.name = name;\n fetcherRegistrar.register(name, this);\n }\n}\n\n/**\n * Default named fetcher instance registered with the name 'default'.\n * This provides a convenient way to use a pre-configured fetcher instance\n * without having to create and register one manually.\n *\n * @example\n * // Use the default fetcher directly\n * import { fetcher } from '@ahoo-wang/fetcher';\n *\n * fetcher.get('/users')\n * .then(response => response.json())\n * .then(data => console.log(data));\n *\n * // Or retrieve it from the registrar\n * import { fetcherRegistrar } from '@ahoo-wang/fetcher';\n *\n * const defaultFetcher = fetcherRegistrar.default;\n * defaultFetcher.get('/users')\n * .then(response => response.json())\n * .then(data => console.log(data));\n */\nexport const fetcher = new NamedFetcher(DEFAULT_FETCHER_NAME);\n"],"names":["isAbsoluteURL","url","combineURLs","baseURL","relativeURL","UrlBuilder","params","path","query","combinedURL","finalUrl","queryString","request","_","key","value","FetcherError","errorMsg","cause","errorMessage","FetchTimeoutError","method","message","resolveTimeout","requestTimeout","optionsTimeout","timeoutFetch","timeout","requestInit","controller","fetchRequest","timerId","timeoutPromise","reject","error","URL_RESOLVE_INTERCEPTOR_NAME","URL_RESOLVE_INTERCEPTOR_ORDER","UrlResolveInterceptor","exchange","HttpMethod","ContentTypeHeader","ContentTypeValues","REQUEST_BODY_INTERCEPTOR_NAME","REQUEST_BODY_INTERCEPTOR_ORDER","RequestBodyInterceptor","modifiedRequest","headers","FETCH_INTERCEPTOR_NAME","FETCH_INTERCEPTOR_ORDER","FetchInterceptor","toSorted","array","filter","a","b","InterceptorRegistry","interceptors","interceptor","item","name","original","HttpStatusValidationError","DEFAULT_VALIDATE_STATUS","status","VALIDATE_STATUS_INTERCEPTOR_NAME","VALIDATE_STATUS_INTERCEPTOR_ORDER","ValidateStatusInterceptor","validateStatus","ExchangeError","InterceptorManager","fetchExchange","FetchExchange","fetcher","response","mergeRecords","first","second","DEFAULT_HEADERS","DEFAULT_OPTIONS","Fetcher","options","mergedHeaders","DEFAULT_FETCHER_NAME","FetcherRegistrar","fetcherRegistrar","mergeRequest","urlParams","body","signal","NamedFetcher"],"mappings":"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"}
package/dist/index.umd.js CHANGED
@@ -1 +1,2 @@
1
1
  (function(s,a){typeof exports=="object"&&typeof module<"u"?a(exports):typeof define=="function"&&define.amd?define(["exports"],a):(s=typeof globalThis<"u"?globalThis:s||self,a(s.Fetcher={}))})(this,(function(s){"use strict";function a(r){return/^([a-z][a-z\d+\-.]*:)?\/\//i.test(r)}function A(r,e){return a(e)?e:e?r.replace(/\/?\/$/,"")+"/"+e.replace(/^\/+/,""):r}class S{constructor(e){this.baseURL=e}build(e,t){const n=t?.path,o=t?.query,u=A(this.baseURL,e);let i=this.interpolateUrl(u,n);if(o){const E=new URLSearchParams(o).toString();E&&(i+="?"+E)}return i}resolveRequestUrl(e){return this.build(e.url,e.urlParams)}interpolateUrl(e,t){return t?e.replace(/{([^}]+)}/g,(n,o)=>{const u=t[o];if(u===void 0)throw new Error(`Missing required path parameter: ${o}`);return String(u)}):e}}class h extends Error{constructor(e,t){const n=e||t?.message||"An error occurred in the fetcher";super(n),this.cause=t,this.name="FetcherError",t?.stack&&(this.stack=t.stack),Object.setPrototypeOf(this,h.prototype)}}class T extends h{constructor(e){const t=e.method||"GET",n=`Request timeout of ${e.timeout}ms exceeded for ${t} ${e.url}`;super(n),this.name="FetchTimeoutError",this.request=e,Object.setPrototypeOf(this,T.prototype)}}function g(r,e){return typeof r<"u"?r:e}async function P(r){const e=r.url,t=r.timeout,n=r;if(!t)return fetch(e,n);const o=new AbortController,u={...n,signal:o.signal};let i=null;const E=new Promise((K,z)=>{i=setTimeout(()=>{i&&clearTimeout(i);const k=new T(r);o.abort(k),z(k)},t)});try{return await Promise.race([fetch(e,u),E])}finally{i&&clearTimeout(i)}}const N="UrlResolveInterceptor",_=Number.MIN_SAFE_INTEGER+1e3;class F{constructor(){this.name=N,this.order=_}intercept(e){const t=e.request;t.url=e.fetcher.urlBuilder.resolveRequestUrl(t)}}var c=(r=>(r.GET="GET",r.POST="POST",r.PUT="PUT",r.DELETE="DELETE",r.PATCH="PATCH",r.HEAD="HEAD",r.OPTIONS="OPTIONS",r))(c||{});const J="Content-Type";var l=(r=>(r.APPLICATION_JSON="application/json",r.TEXT_EVENT_STREAM="text/event-stream",r))(l||{});const b="RequestBodyInterceptor",U=_+1e3;class w{constructor(){this.name=b,this.order=U}intercept(e){const t=e.request;if(t.body===void 0||t.body===null||typeof t.body!="object"||t.body instanceof ArrayBuffer||ArrayBuffer.isView(t.body)||t.body instanceof Blob||t.body instanceof File||t.body instanceof URLSearchParams||t.body instanceof FormData||t.body instanceof ReadableStream)return;const n={...t};n.body=JSON.stringify(t.body),n.headers||(n.headers={});const o=n.headers;o["Content-Type"]||(o["Content-Type"]=l.APPLICATION_JSON),e.request=n}}const C="FetchInterceptor",q=Number.MAX_SAFE_INTEGER-1e3;class L{constructor(){this.name=C,this.order=q}async intercept(e){e.response=await P(e.request)}}function R(r,e){return e?r.filter(e).sort((t,n)=>t.order-n.order):[...r].sort((t,n)=>t.order-n.order)}class m{constructor(e=[]){this.sortedInterceptors=[],this.sortedInterceptors=R(e)}get name(){return this.constructor.name}get order(){return Number.MIN_SAFE_INTEGER}get interceptors(){return[...this.sortedInterceptors]}use(e){return this.sortedInterceptors.some(t=>t.name===e.name)?!1:(this.sortedInterceptors=R([...this.sortedInterceptors,e]),!0)}eject(e){const t=this.sortedInterceptors;return this.sortedInterceptors=R(t,n=>n.name!==e),t.length!==this.sortedInterceptors.length}clear(){this.sortedInterceptors=[]}async intercept(e){for(const t of this.sortedInterceptors)await t.intercept(e)}}class f extends h{constructor(e){super(`Request failed with status code ${e.response?.status} for ${e.request.url}`),this.exchange=e,this.name="HttpStatusValidationError",Object.setPrototypeOf(this,f.prototype)}}const Q=r=>r>=200&&r<300,D="ValidateStatusInterceptor",v=Number.MAX_SAFE_INTEGER-1e3;class M{constructor(e=Q){this.validateStatus=e}get name(){return D}get order(){return v}intercept(e){if(!e.response)return;const t=e.response.status;if(!this.validateStatus(t))throw new f(e)}}class d extends h{constructor(e,t){const n=t||e.error?.message||e.response?.statusText||`Request to ${e.request.url} failed during exchange`;super(n,e.error),this.exchange=e,this.name="ExchangeError",Object.setPrototypeOf(this,d.prototype)}}class V{constructor(){this.request=new m([new F,new w,new L]),this.response=new m([new M]),this.error=new m}async exchange(e){try{return await this.request.intercept(e),await this.response.intercept(e),e}catch(t){if(e.error=t,await this.error.intercept(e),!e.hasError())return e;throw new d(e)}}}class B{constructor(e,t,n,o){this.attributes={},this.fetcher=e,this.request=t,this.response=n,this.error=o}hasError(){return!!this.error}hasResponse(){return!!this.response}get requiredResponse(){if(!this.response)throw new d(this,`Request to ${this.request.url} failed with no response`);return this.response}}function I(r,e){if(!(r===void 0&&e===void 0))return e===void 0?r:r===void 0?e:{...r,...e}}const p={"Content-Type":l.APPLICATION_JSON},O={baseURL:"",headers:p};class H{constructor(e=O){this.headers=p,this.urlBuilder=new S(e.baseURL),this.headers=e.headers??p,this.timeout=e.timeout,this.interceptors=e.interceptors??new V}async fetch(e,t={}){const n=t;return n.url=e,(await this.request(n)).requiredResponse}async request(e){const t=I(e.headers,this.headers),n={...e,headers:t,timeout:g(e.timeout,this.timeout)},o=new B(this,n);return this.interceptors.exchange(o)}async methodFetch(e,t,n={}){return this.fetch(t,{...n,method:e})}async get(e,t={}){return this.methodFetch(c.GET,e,t)}async post(e,t={}){return this.methodFetch(c.POST,e,t)}async put(e,t={}){return this.methodFetch(c.PUT,e,t)}async delete(e,t={}){return this.methodFetch(c.DELETE,e,t)}async patch(e,t={}){return this.methodFetch(c.PATCH,e,t)}async head(e,t={}){return this.methodFetch(c.HEAD,e,t)}async options(e,t={}){return this.methodFetch(c.OPTIONS,e,t)}}const y="default";class j{constructor(){this.registrar=new Map}register(e,t){this.registrar.set(e,t)}unregister(e){return this.registrar.delete(e)}get(e){return this.registrar.get(e)}requiredGet(e){const t=this.get(e);if(!t)throw new Error(`Fetcher ${e} not found`);return t}get default(){return this.requiredGet(y)}set default(e){this.register(y,e)}get fetchers(){return new Map(this.registrar)}}const G=new j;function Y(r,e){if(Object.keys(r).length===0)return e;if(Object.keys(e).length===0)return r;const t={path:I(r.urlParams?.path,e.urlParams?.path),query:I(r.urlParams?.query,e.urlParams?.query)},n={...r.headers,...e.headers},o=e.method??r.method,u=e.body??r.body,i=e.timeout??r.timeout,E=e.signal??r.signal;return{...r,...e,method:o,urlParams:t,headers:n,body:u,timeout:i,signal:E}}class $ extends H{constructor(e,t=O){super(t),this.name=e,G.register(e,this)}}const X=new $(y);s.ContentTypeHeader=J,s.ContentTypeValues=l,s.DEFAULT_FETCHER_NAME=y,s.DEFAULT_OPTIONS=O,s.ExchangeError=d,s.FETCH_INTERCEPTOR_NAME=C,s.FETCH_INTERCEPTOR_ORDER=q,s.FetchExchange=B,s.FetchInterceptor=L,s.FetchTimeoutError=T,s.Fetcher=H,s.FetcherError=h,s.FetcherRegistrar=j,s.HttpMethod=c,s.HttpStatusValidationError=f,s.InterceptorManager=V,s.InterceptorRegistry=m,s.NamedFetcher=$,s.REQUEST_BODY_INTERCEPTOR_NAME=b,s.REQUEST_BODY_INTERCEPTOR_ORDER=U,s.RequestBodyInterceptor=w,s.URL_RESOLVE_INTERCEPTOR_NAME=N,s.URL_RESOLVE_INTERCEPTOR_ORDER=_,s.UrlBuilder=S,s.UrlResolveInterceptor=F,s.VALIDATE_STATUS_INTERCEPTOR_NAME=D,s.VALIDATE_STATUS_INTERCEPTOR_ORDER=v,s.ValidateStatusInterceptor=M,s.combineURLs=A,s.fetcher=X,s.fetcherRegistrar=G,s.isAbsoluteURL=a,s.mergeRecords=I,s.mergeRequest=Y,s.resolveTimeout=g,s.timeoutFetch=P,s.toSorted=R,Object.defineProperty(s,Symbol.toStringTag,{value:"Module"})}));
2
+ //# sourceMappingURL=index.umd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.umd.js","sources":["../src/urls.ts","../src/urlBuilder.ts","../src/fetcherError.ts","../src/timeout.ts","../src/urlResolveInterceptor.ts","../src/fetchRequest.ts","../src/requestBodyInterceptor.ts","../src/fetchInterceptor.ts","../src/orderedCapable.ts","../src/interceptor.ts","../src/validateStatusInterceptor.ts","../src/interceptorManager.ts","../src/fetchExchange.ts","../src/utils.ts","../src/fetcher.ts","../src/fetcherRegistrar.ts","../src/mergeRequest.ts","../src/namedFetcher.ts"],"sourcesContent":["/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Checks if the given URL is an absolute URL\n *\n * @param url - URL string to check\n * @returns boolean - Returns true if it's an absolute URL, false otherwise\n *\n * @example\n * ```typescript\n * isAbsoluteURL('https://api.example.com/users'); // true\n * isAbsoluteURL('/users'); // false\n * isAbsoluteURL('users'); // false\n * ```\n */\nexport function isAbsoluteURL(url: string) {\n return /^([a-z][a-z\\d+\\-.]*:)?\\/\\//i.test(url);\n}\n\n/**\n * Combines a base URL and a relative URL into a complete URL\n *\n * @param baseURL - Base URL\n * @param relativeURL - Relative URL\n * @returns string - Combined complete URL\n *\n * @remarks\n * If the relative URL is already an absolute URL, it will be returned as-is.\n * Otherwise, the base URL and relative URL will be combined with proper path separator handling.\n *\n * @example\n * ```typescript\n * combineURLs('https://api.example.com', '/users'); // https://api.example.com/users\n * combineURLs('https://api.example.com/', 'users'); // https://api.example.com/users\n * combineURLs('https://api.example.com', 'https://other.com/users'); // https://other.com/users\n * ```\n */\nexport function combineURLs(baseURL: string, relativeURL: string) {\n if (isAbsoluteURL(relativeURL)) {\n return relativeURL;\n }\n // If relative URL exists, combine base URL and relative URL, otherwise return base URL\n return relativeURL\n ? baseURL.replace(/\\/?\\/$/, '') + '/' + relativeURL.replace(/^\\/+/, '')\n : baseURL;\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { combineURLs } from './urls';\nimport { BaseURLCapable, FetchRequest } from './fetchRequest';\n\n/**\n * Container for URL parameters including path and query parameters.\n *\n * Used to define dynamic parts of a URL including path placeholders and query string parameters.\n */\nexport interface UrlParams {\n /**\n * Path parameter object used to replace placeholders in the URL (e.g., {id}).\n *\n * These parameters are used to substitute named placeholders in the URL path.\n *\n * @example\n * ```typescript\n * // For URL template '/users/{id}/posts/{postId}'\n * const path = { id: 123, postId: 456 };\n * ```\n */\n path?: Record<string, any>;\n\n /**\n * Query parameter object to be added to the URL query string.\n *\n * These parameters are appended to the URL as a query string.\n *\n * @example\n * ```typescript\n * const query = { filter: 'active', page: 1, limit: 10 };\n * // Results in query string: ?filter=active&page=1&limit=10\n * ```\n */\n query?: Record<string, any>;\n}\n\n/**\n * Utility class for constructing complete URLs with path parameters and query parameters.\n *\n * Handles URL composition, path parameter interpolation, and query string generation.\n * Combines a base URL with a path, replaces path placeholders with actual values, and appends\n * query parameters to create a complete URL.\n *\n * @example\n * ```typescript\n * const urlBuilder = new UrlBuilder('https://api.example.com');\n * const url = urlBuilder.build('/users/{id}', {\n * path: { id: 123 },\n * query: { filter: 'active' }\n * });\n * // Result: https://api.example.com/users/123?filter=active\n * ```\n */\nexport class UrlBuilder implements BaseURLCapable {\n /**\n * Base URL that all constructed URLs will be based on.\n *\n * This is typically the root of your API endpoint (e.g., 'https://api.example.com').\n */\n readonly baseURL: string;\n\n /**\n * Initializes a new UrlBuilder instance.\n *\n * @param baseURL - Base URL that all constructed URLs will be based on\n *\n * @example\n * ```typescript\n * const urlBuilder = new UrlBuilder('https://api.example.com');\n * ```\n */\n constructor(baseURL: string) {\n this.baseURL = baseURL;\n }\n\n /**\n * Builds a complete URL, including path parameter replacement and query parameter addition.\n *\n * @param url - URL path to build (e.g., '/users/{id}/posts')\n * @param params - URL parameters including path and query parameters\n * @returns Complete URL string with base URL, path parameters interpolated, and query string appended\n * @throws Error when required path parameters are missing\n *\n * @example\n * ```typescript\n * const urlBuilder = new UrlBuilder('https://api.example.com');\n * const url = urlBuilder.build('/users/{id}/posts/{postId}', {\n * path: { id: 123, postId: 456 },\n * query: { filter: 'active', limit: 10 }\n * });\n * // Result: https://api.example.com/users/123/posts/456?filter=active&limit=10\n * ```\n */\n build(url: string, params?: UrlParams): string {\n const path = params?.path;\n const query = params?.query;\n const combinedURL = combineURLs(this.baseURL, url);\n let finalUrl = this.interpolateUrl(combinedURL, path);\n if (query) {\n const queryString = new URLSearchParams(query).toString();\n if (queryString) {\n finalUrl += '?' + queryString;\n }\n }\n return finalUrl;\n }\n\n /**\n * Resolves a complete URL from a FetchRequest.\n *\n * Used internally by the Fetcher to build the final URL for a request\n * by combining the request URL with its URL parameters using this UrlBuilder.\n *\n * @param request - The FetchRequest containing URL and URL parameters\n * @returns Complete resolved URL string\n */\n resolveRequestUrl(request: FetchRequest): string {\n return this.build(request.url, request.urlParams);\n }\n\n /**\n * Replaces placeholders in the URL with path parameters.\n *\n * @param url - Path string containing placeholders, e.g., \"http://localhost/users/{id}/posts/{postId}\"\n * @param path - Path parameter object used to replace placeholders in the URL\n * @returns Path string with placeholders replaced\n * @throws Error when required path parameters are missing\n *\n * @example\n * ```typescript\n * const urlBuilder = new UrlBuilder('https://api.example.com');\n * const result = urlBuilder.interpolateUrl('/users/{id}/posts/{postId}', {\n * path: { id: 123, postId: 456 }\n * });\n * // Result: https://api.example.com/users/123/posts/456\n * ```\n *\n * @example\n * ```typescript\n * // Missing required parameter throws an error\n * try {\n * urlBuilder.interpolateUrl('/users/{id}', { name: 'John' });\n * } catch (error) {\n * console.error(error.message); // \"Missing required path parameter: id\"\n * }\n * ```\n */\n interpolateUrl(url: string, path?: Record<string, any> | null): string {\n if (!path) return url;\n return url.replace(/{([^}]+)}/g, (_, key) => {\n const value = path[key];\n // If path parameter is undefined, throw an error instead of preserving the placeholder\n if (value === undefined) {\n throw new Error(`Missing required path parameter: ${key}`);\n }\n return String(value);\n });\n }\n}\n\n/**\n * Interface for objects that have a UrlBuilder capability.\n *\n * Indicates that an object has a UrlBuilder instance for URL construction.\n */\nexport interface UrlBuilderCapable {\n /**\n * The UrlBuilder instance.\n */\n urlBuilder: UrlBuilder;\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Base error class for all Fetcher-related errors.\n *\n * This class extends the native Error class and provides a foundation for\n * all custom errors thrown by the Fetcher library. It includes support for\n * error chaining through the cause property.\n *\n * @example\n * ```typescript\n * try {\n * await fetcher.get('/api/users');\n * } catch (error) {\n * if (error instanceof FetcherError) {\n * console.log('Fetcher error:', error.message);\n * if (error.cause) {\n * console.log('Caused by:', error.cause);\n * }\n * }\n * }\n * ```\n */\nexport class FetcherError extends Error {\n /**\n * Creates a new FetcherError instance.\n *\n * @param errorMsg - Optional error message. If not provided, will use the cause's message or a default message.\n * @param cause - Optional underlying error that caused this error.\n */\n constructor(\n errorMsg?: string,\n public readonly cause?: Error | any,\n ) {\n const errorMessage =\n errorMsg || cause?.message || 'An error occurred in the fetcher';\n super(errorMessage);\n this.name = 'FetcherError';\n\n // Copy stack trace from cause if available\n if (cause?.stack) {\n this.stack = cause.stack;\n }\n\n // Set prototype for instanceof checks to work correctly\n Object.setPrototypeOf(this, FetcherError.prototype);\n }\n}\n\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { FetchRequest } from './fetchRequest';\nimport { FetcherError } from './fetcherError';\n\n\n/**\n * Exception class thrown when an HTTP request times out.\n *\n * This error is thrown by the timeoutFetch function when a request exceeds its timeout limit.\n *\n * @example\n * ```typescript\n * try {\n * const response = await timeoutFetch('https://api.example.com/users', {}, 1000);\n * } catch (error) {\n * if (error instanceof FetchTimeoutError) {\n * console.log(`Request timed out after ${error.timeout}ms`);\n * }\n * }\n * ```\n */\nexport class FetchTimeoutError extends FetcherError {\n /**\n * The request options that timed out.\n */\n request: FetchRequest;\n\n /**\n * Creates a new FetchTimeoutError instance.\n *\n * @param request - The request options that timed out\n */\n constructor(request: FetchRequest) {\n const method = request.method || 'GET';\n const message = `Request timeout of ${request.timeout}ms exceeded for ${method} ${request.url}`;\n super(message);\n this.name = 'FetchTimeoutError';\n this.request = request;\n // Fix prototype chain\n Object.setPrototypeOf(this, FetchTimeoutError.prototype);\n }\n}\n\n/**\n * Interface that defines timeout capability for HTTP requests.\n *\n * Objects implementing this interface can specify timeout values for HTTP requests.\n */\nexport interface TimeoutCapable {\n /**\n * Request timeout in milliseconds.\n *\n * When the value is 0, it indicates no timeout should be set.\n * The default value is undefined.\n */\n timeout?: number;\n}\n\n/**\n * Resolves request timeout settings, prioritizing request-level timeout settings.\n *\n * @param requestTimeout - Request-level timeout setting\n * @param optionsTimeout - Configuration-level timeout setting\n * @returns Resolved timeout setting\n *\n * @remarks\n * If requestTimeout is defined, it takes precedence over optionsTimeout.\n * Otherwise, optionsTimeout is returned. If both are undefined, undefined is returned.\n */\nexport function resolveTimeout(\n requestTimeout?: number,\n optionsTimeout?: number,\n): number | undefined {\n if (typeof requestTimeout !== 'undefined') {\n return requestTimeout;\n }\n return optionsTimeout;\n}\n\n/**\n * HTTP request method with timeout control.\n *\n * Uses Promise.race to implement timeout control, initiating both\n * fetch request and timeout Promise simultaneously. When either Promise completes,\n * it returns the result or throws an exception.\n *\n * @param request - The request initialization options\n * @returns Promise<Response> HTTP response Promise\n * @throws FetchTimeoutError Thrown when the request times out\n *\n * @example\n * ```typescript\n * // With timeout\n * try {\n * const response = await timeoutFetch('https://api.example.com/users', { method: 'GET' }, 5000);\n * console.log('Request completed successfully');\n * } catch (error) {\n * if (error instanceof FetchTimeoutError) {\n * console.log(`Request timed out after ${error.timeout}ms`);\n * }\n * }\n *\n * // Without timeout (delegates to regular fetch)\n * const response = await timeoutFetch('https://api.example.com/users', { method: 'GET' });\n * ```\n */\nexport async function timeoutFetch(request: FetchRequest): Promise<Response> {\n const url = request.url;\n const timeout = request.timeout;\n const requestInit = request as RequestInit;\n // Extract timeout from request\n if (!timeout) {\n return fetch(url, requestInit);\n }\n\n // Create AbortController for fetch request cancellation\n const controller = new AbortController();\n // Create a new request object to avoid modifying the original request object\n const fetchRequest: RequestInit = {\n ...requestInit,\n signal: controller.signal,\n };\n\n // Timer resource management\n let timerId: ReturnType<typeof setTimeout> | null = null;\n // Create timeout Promise that rejects after specified time\n const timeoutPromise = new Promise<Response>((_, reject) => {\n timerId = setTimeout(() => {\n // Clean up timer resources and handle timeout error\n if (timerId) {\n clearTimeout(timerId);\n }\n const error = new FetchTimeoutError(request);\n controller.abort(error);\n reject(error);\n }, timeout);\n });\n\n try {\n // Race between fetch request and timeout Promise\n return await Promise.race([fetch(url, fetchRequest), timeoutPromise]);\n } finally {\n // Clean up timer resources\n if (timerId) {\n clearTimeout(timerId);\n }\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { RequestInterceptor } from './interceptor';\nimport { FetchExchange } from './fetchExchange';\n\n/**\n * The name of the UrlResolveInterceptor.\n */\nexport const URL_RESOLVE_INTERCEPTOR_NAME = 'UrlResolveInterceptor';\n\n/**\n * The order of the UrlResolveInterceptor.\n * Set to Number.MIN_SAFE_INTEGER + 1000 to ensure it runs earliest among request interceptors.\n */\nexport const URL_RESOLVE_INTERCEPTOR_ORDER = Number.MIN_SAFE_INTEGER + 1000;\n\n/**\n * Interceptor responsible for resolving the final URL for a request.\n *\n * This interceptor combines the base URL, path parameters, and query parameters\n * to create the final URL for a request. It should be executed earliest in\n * the interceptor chain to ensure the URL is properly resolved before other interceptors\n * process the request.\n *\n * @remarks\n * This interceptor runs at the very beginning of the request interceptor chain to ensure\n * URL resolution happens before any other request processing. The order is set to\n * URL_RESOLVE_INTERCEPTOR_ORDER to ensure it executes before all other request interceptors,\n * establishing the foundation for subsequent processing.\n *\n * @example\n * // With baseURL: 'https://api.example.com'\n * // Request URL: '/users/{id}'\n * // Path params: { id: 123 }\n * // Query params: { filter: 'active' }\n * // Final URL: 'https://api.example.com/users/123?filter=active'\n */\nexport class UrlResolveInterceptor implements RequestInterceptor {\n /**\n * The name of this interceptor.\n */\n readonly name = URL_RESOLVE_INTERCEPTOR_NAME;\n\n /**\n * The order of this interceptor (executed earliest).\n *\n * This interceptor should run at the very beginning of the request interceptor chain to ensure\n * URL resolution happens before any other request processing. The order is set to\n * URL_RESOLVE_INTERCEPTOR_ORDER to ensure it executes before all other request interceptors,\n * establishing the foundation for subsequent processing.\n */\n readonly order = URL_RESOLVE_INTERCEPTOR_ORDER;\n\n /**\n * Resolves the final URL by combining the base URL, path parameters, and query parameters.\n *\n * @param exchange - The fetch exchange containing the request information\n */\n intercept(exchange: FetchExchange) {\n const request = exchange.request;\n request.url = exchange.fetcher.urlBuilder.resolveRequestUrl(request);\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { TimeoutCapable } from './timeout';\nimport { UrlParams } from './urlBuilder';\n\n/**\n * Interface for objects that can have a base URL\n *\n * This interface defines a baseURL property that can be used to set a base URL\n * for HTTP requests. When the baseURL is empty, it means no base URL is set.\n */\nexport interface BaseURLCapable {\n /**\n * The base URL for requests\n * When empty, indicates no base URL is set. Default is undefined.\n */\n baseURL: string;\n}\n\n/**\n * HTTP method enumeration constants\n *\n * Defines the standard HTTP methods that can be used for requests.\n * Each method is represented as a string literal type.\n */\nexport enum HttpMethod {\n GET = 'GET',\n POST = 'POST',\n PUT = 'PUT',\n DELETE = 'DELETE',\n PATCH = 'PATCH',\n HEAD = 'HEAD',\n OPTIONS = 'OPTIONS',\n}\n\nexport const ContentTypeHeader = 'Content-Type';\n\nexport enum ContentTypeValues {\n APPLICATION_JSON = 'application/json',\n TEXT_EVENT_STREAM = 'text/event-stream',\n}\n\n/**\n * Request headers interface\n *\n * Defines common HTTP headers that can be sent with requests.\n * Allows for additional custom headers through index signature.\n */\nexport interface RequestHeaders {\n 'Content-Type'?: string;\n Accept?: string;\n Authorization?: string;\n\n [key: string]: string | undefined;\n}\n\n/**\n * Interface for objects that can have request headers\n *\n * This interface defines an optional headers property for HTTP requests.\n */\nexport interface RequestHeadersCapable {\n /**\n * Request headers\n */\n headers?: RequestHeaders;\n}\n\n/**\n * Fetcher request configuration interface\n *\n * This interface defines all the configuration options available for making HTTP requests\n * with the Fetcher client. It extends the standard RequestInit interface while adding\n * Fetcher-specific features like path parameters, query parameters, and timeout control.\n *\n * @example\n * ```typescript\n * const request: FetchRequestInit = {\n * method: 'GET',\n * urlParams: {\n * path: { id: 123 },\n * query: { include: 'profile' }\n * },\n * headers: { 'Authorization': 'Bearer token' },\n * timeout: 5000\n * };\n *\n * const response = await fetcher.fetch('/users/{id}', request);\n * ```\n */\nexport interface FetchRequestInit\n extends TimeoutCapable,\n RequestHeadersCapable,\n Omit<RequestInit, 'body' | 'headers'> {\n urlParams?: UrlParams;\n\n /**\n * Request body\n *\n * The body of the request. Can be a string, Blob, ArrayBuffer, FormData,\n * URLSearchParams, or a plain object. Plain objects are automatically\n * converted to JSON and the appropriate Content-Type header is set.\n *\n * @example\n * ```typescript\n * // Plain object (automatically converted to JSON)\n * const request = {\n * method: 'POST',\n * body: { name: 'John', email: 'john@example.com' }\n * };\n *\n * // FormData\n * const formData = new FormData();\n * formData.append('name', 'John');\n * const request = {\n * method: 'POST',\n * body: formData\n * };\n * ```\n */\n body?: BodyInit | Record<string, any> | string | null;\n}\n\n/**\n * Fetcher request interface\n *\n * Extends FetchRequestInit with a required URL property.\n * Represents a complete request configuration ready to be executed.\n */\nexport interface FetchRequest extends FetchRequestInit {\n /**\n * The URL for this request\n */\n url: string;\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { RequestInterceptor } from './interceptor';\nimport { FetchExchange } from './fetchExchange';\nimport { ContentTypeValues } from './fetchRequest';\nimport { URL_RESOLVE_INTERCEPTOR_ORDER } from './urlResolveInterceptor';\n\n/**\n * The name of the RequestBodyInterceptor.\n */\nexport const REQUEST_BODY_INTERCEPTOR_NAME = 'RequestBodyInterceptor';\n\n/**\n * The order of the RequestBodyInterceptor.\n * Set to URL_RESOLVE_INTERCEPTOR_ORDER + 1000 to ensure it runs early among request interceptors.\n */\nexport const REQUEST_BODY_INTERCEPTOR_ORDER =\n URL_RESOLVE_INTERCEPTOR_ORDER + 1000;\n\n/**\n * Interceptor responsible for converting plain objects to JSON strings for HTTP request bodies.\n *\n * This interceptor ensures that object request bodies are properly serialized and that\n * the appropriate Content-Type header is set. It runs early in the request processing chain\n * to ensure request bodies are properly formatted before other interceptors process them.\n *\n * @remarks\n * This interceptor runs after URL resolution (UrlResolveInterceptor) but before\n * the actual HTTP request is made (FetchInterceptor). The order is set to\n * REQUEST_BODY_INTERCEPTOR_ORDER to ensure it executes in the correct position\n * in the interceptor chain, allowing for other interceptors to run between URL resolution\n * and request body processing. This positioning ensures that URL parameters are resolved\n * first, then request bodies are properly formatted, and finally the HTTP request is executed.\n */\nexport class RequestBodyInterceptor implements RequestInterceptor {\n /**\n * Interceptor name, used for identification and management.\n */\n readonly name = REQUEST_BODY_INTERCEPTOR_NAME;\n\n /**\n * Interceptor execution order, set to run after UrlResolveInterceptor but before FetchInterceptor.\n *\n * This interceptor should run after URL resolution (UrlResolveInterceptor) but before\n * the actual HTTP request is made (FetchInterceptor). The order is set to\n * REQUEST_BODY_INTERCEPTOR_ORDER to ensure it executes in the correct position\n * in the interceptor chain, allowing for other interceptors to run between URL resolution\n * and request body processing. This positioning ensures that URL parameters are resolved\n * first, then request bodies are properly formatted, and finally the HTTP request is executed.\n */\n readonly order = REQUEST_BODY_INTERCEPTOR_ORDER;\n\n /**\n * Attempts to convert request body to a valid fetch API body type.\n *\n * According to the Fetch API specification, body can be multiple types, but for\n * plain objects, they need to be converted to JSON strings.\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#setting_a_body}\n *\n * Supported types:\n * - a string\n * - ArrayBuffer\n * - TypedArray\n * - DataView\n * - Blob\n * - File\n * - URLSearchParams\n * - FormData\n * - ReadableStream\n *\n * For unsupported object types (like plain objects), they will be automatically\n * converted to JSON strings.\n *\n * @param exchange - The exchange object containing the request to process\n *\n * @example\n * // Plain object body will be converted to JSON\n * const fetcher = new Fetcher();\n * const exchange = new FetchExchange(\n * fetcher,\n * {\n * body: { name: 'John', age: 30 }\n * }\n * );\n * interceptor.intercept(exchange);\n * // exchange.request.body will be '{\"name\":\"John\",\"age\":30}'\n * // exchange.request.headers will include 'Content-Type: application/json'\n */\n intercept(exchange: FetchExchange) {\n const request = exchange.request;\n // If there's no request body, return unchanged\n if (request.body === undefined || request.body === null) {\n return;\n }\n\n // If request body is not an object, return unchanged\n if (typeof request.body !== 'object') {\n return;\n }\n\n // Check if it's a supported type\n if (\n request.body instanceof ArrayBuffer ||\n ArrayBuffer.isView(request.body) || // Includes TypedArray and DataView\n request.body instanceof Blob ||\n request.body instanceof File ||\n request.body instanceof URLSearchParams ||\n request.body instanceof FormData ||\n request.body instanceof ReadableStream\n ) {\n return;\n }\n\n // For plain objects, convert to JSON string\n // Also ensure Content-Type header is set to application/json\n const modifiedRequest = { ...request };\n modifiedRequest.body = JSON.stringify(request.body);\n\n // Set Content-Type header\n if (!modifiedRequest.headers) {\n modifiedRequest.headers = {};\n }\n\n // Only set default Content-Type if not explicitly set\n const headers = modifiedRequest.headers;\n if (!headers['Content-Type']) {\n headers['Content-Type'] = ContentTypeValues.APPLICATION_JSON;\n }\n exchange.request = modifiedRequest;\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { RequestInterceptor } from './interceptor';\nimport { timeoutFetch } from './timeout';\nimport { FetchExchange } from './fetchExchange';\n\n/**\n * The name of the FetchInterceptor.\n */\nexport const FETCH_INTERCEPTOR_NAME = 'FetchInterceptor';\n\n/**\n * The order of the FetchInterceptor.\n * Set to Number.MAX_SAFE_INTEGER - 1000 to ensure it runs latest among request interceptors.\n */\nexport const FETCH_INTERCEPTOR_ORDER = Number.MAX_SAFE_INTEGER - 1000;\n\n/**\n * Interceptor implementation responsible for executing actual HTTP requests.\n *\n * This is an interceptor implementation responsible for executing actual HTTP requests\n * and handling timeout control. It is the latest interceptor in the Fetcher request\n * processing chain, ensuring that the actual network request is executed after all\n * previous interceptors have completed processing.\n *\n * @remarks\n * This interceptor runs at the very end of the request interceptor chain to ensure\n * that the actual HTTP request is executed after all other request processing is complete.\n * The order is set to FETCH_INTERCEPTOR_ORDER to ensure it executes after all other\n * request interceptors, completing the request processing pipeline before the network\n * request is made. This positioning ensures that all request preprocessing is\n * completed before the actual network request is made.\n *\n * @example\n * // Usually not created manually as Fetcher uses it automatically\n * const fetcher = new Fetcher();\n * // FetchInterceptor is automatically registered in fetcher.interceptors.request\n */\nexport class FetchInterceptor implements RequestInterceptor {\n /**\n * Interceptor name, used to identify and manage interceptor instances.\n *\n * Each interceptor must have a unique name for identification and manipulation\n * within the interceptor manager.\n */\n readonly name = FETCH_INTERCEPTOR_NAME;\n\n /**\n * Interceptor execution order, set to near maximum safe integer to ensure latest execution.\n *\n * Since this is the interceptor that actually executes HTTP requests, it should\n * execute after all other request interceptors, so its order is set to\n * FETCH_INTERCEPTOR_ORDER. This ensures that all request preprocessing is\n * completed before the actual network request is made, while still allowing\n * other interceptors to run after it if needed. The positioning at the end\n * of the request chain ensures that all transformations and validations are\n * completed before the network request is executed.\n */\n readonly order = FETCH_INTERCEPTOR_ORDER;\n\n /**\n * Intercept and process HTTP requests.\n *\n * Executes the actual HTTP request and applies timeout control. This is the final\n * step in the request processing chain, responsible for calling the timeoutFetch\n * function to send the network request.\n *\n * @param exchange - Exchange object containing request information\n *\n * @throws {FetchTimeoutError} Throws timeout exception when request times out\n *\n * @example\n * // Usually called internally by Fetcher\n * const fetcher = new Fetcher();\n * const exchange = new FetchExchange(\n * fetcher,\n * {\n * url: 'https://api.example.com/users',\n * method: 'GET',\n * timeout: 5000\n * }\n * );\n * await fetchInterceptor.intercept(exchange);\n * console.log(exchange.response); // HTTP response object\n */\n async intercept(exchange: FetchExchange) {\n exchange.response = await timeoutFetch(exchange.request);\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * OrderedCapable Interface\n *\n * Interface that provides ordering capability for types that implement it.\n * Implementing types must provide an order property to determine execution order.\n * Lower numerical values have higher priority, and elements with the same value\n * maintain their relative order.\n */\nexport interface OrderedCapable {\n /**\n * Order value\n *\n * Lower numerical values have higher priority. Negative numbers, zero, and\n * positive numbers are all supported.\n * When multiple elements have the same order value, their relative order\n * will remain unchanged (stable sort).\n */\n order: number;\n}\n\n/**\n * Sorts an array of elements that implement the OrderedCapable interface\n *\n * This function creates and returns a new sorted array without modifying the\n * original array. It supports an optional filter function to select elements\n * that should participate in sorting.\n *\n * @template T - Array element type that must implement the OrderedCapable interface\n * @param array - The array to be sorted\n * @param filter - Optional filter function to select elements that should be sorted\n * @returns A new array sorted in ascending order by the order property\n *\n * @example\n * ```typescript\n * const items: OrderedCapable[] = [\n * { order: 10 },\n * { order: 5 },\n * { order: 1 },\n * ];\n *\n * const sortedItems = toSorted(items);\n * // Result: [{ order: 1 }, { order: 5 }, { order: 10 }]\n *\n * // Using filter function\n * const filteredAndSorted = toSorted(items, item => item.order > 3);\n * // Result: [{ order: 5 }, { order: 10 }]\n * ```\n */\nexport function toSorted<T extends OrderedCapable>(\n array: T[],\n filter?: (item: T) => boolean,\n): T[] {\n if (filter) {\n return array.filter(filter).sort((a, b) => a.order - b.order);\n }\n return [...array].sort((a, b) => a.order - b.order);\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { NamedCapable } from './types';\nimport { OrderedCapable, toSorted } from './orderedCapable';\nimport { FetchExchange } from './fetchExchange';\n\n/**\n * Interface for HTTP interceptors in the fetcher pipeline.\n *\n * Interceptors are middleware components that can modify requests, responses, or handle errors\n * at different stages of the HTTP request lifecycle. They follow the Chain of Responsibility\n * pattern, where each interceptor can process the exchange object and pass it to the next.\n *\n * @example\n * // Example of a custom request interceptor\n * const customRequestInterceptor: Interceptor = {\n * name: 'CustomRequestInterceptor',\n * order: 100,\n * async intercept(exchange: FetchExchange): Promise<void> {\n * // Modify request headers\n * exchange.request.headers = {\n * ...exchange.request.headers,\n * 'X-Custom-Header': 'custom-value'\n * };\n * }\n * };\n */\nexport interface Interceptor extends NamedCapable, OrderedCapable {\n /**\n * Unique identifier for the interceptor.\n *\n * Used by InterceptorRegistry to manage interceptors, including adding, removing,\n * and preventing duplicates. Each interceptor must have a unique name.\n */\n readonly name: string;\n\n /**\n * Interceptor method that modifies the request or response.\n *\n * @param exchange - The current exchange object, which contains the request and response.\n * @returns A promise that resolves to the modified exchange object.\n */\n readonly order: number;\n\n /**\n * Process the exchange object in the interceptor pipeline.\n *\n * This method is called by InterceptorRegistry to process the exchange object.\n * Interceptors can modify request, response, or error properties directly.\n *\n * @param exchange - The exchange object containing request, response, and error information\n *\n * @remarks\n * Interceptors should modify the exchange object directly rather than returning it.\n * They can also throw errors or transform errors into responses.\n */\n intercept(exchange: FetchExchange): void | Promise<void>;\n}\n\n/**\n * Interface for request interceptors.\n *\n * Request interceptors are executed before the HTTP request is sent.\n * They can modify the request configuration, add headers, or perform\n * other preprocessing tasks.\n *\n * @example\n * // Example of a request interceptor that adds an authorization header\n * const authInterceptor: RequestInterceptor = {\n * name: 'AuthorizationInterceptor',\n * order: 100,\n * async intercept(exchange: FetchExchange): Promise<void> {\n * const token = getAuthToken();\n * if (token) {\n * exchange.request.headers = {\n * ...exchange.request.headers,\n * 'Authorization': `Bearer ${token}`\n * };\n * }\n * }\n * };\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface RequestInterceptor extends Interceptor {\n}\n\n/**\n * Interface for response interceptors.\n *\n * Response interceptors are executed after the HTTP response is received\n * but before it's processed by the application. They can modify the response,\n * transform data, or handle response-specific logic.\n *\n * @example\n * // Example of a response interceptor that parses JSON data\n * const jsonInterceptor: ResponseInterceptor = {\n * name: 'JsonResponseInterceptor',\n * order: 100,\n * async intercept(exchange: FetchExchange): Promise<void> {\n * if (exchange.response && exchange.response.headers.get('content-type')?.includes('application/json')) {\n * const data = await exchange.response.json();\n * // Attach parsed data to a custom property\n * (exchange.response as any).jsonData = data;\n * }\n * }\n * };\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface ResponseInterceptor extends Interceptor {\n}\n\n/**\n * Interface for error interceptors.\n *\n * Error interceptors are executed when an HTTP request fails.\n * They can handle errors, transform them, or implement retry logic.\n *\n * @example\n * // Example of an error interceptor that retries failed requests\n * const retryInterceptor: ErrorInterceptor = {\n * name: 'RetryInterceptor',\n * order: 100,\n * async intercept(exchange: FetchExchange): Promise<void> {\n * if (exchange.error && isRetryableError(exchange.error)) {\n * // Implement retry logic\n * const retryCount = (exchange.request as any).retryCount || 0;\n * if (retryCount < 3) {\n * (exchange.request as any).retryCount = retryCount + 1;\n * // Retry the request\n * exchange.response = await fetch(exchange.request);\n * // Clear the error since we've recovered\n * exchange.error = undefined;\n * }\n * }\n * }\n * };\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface ErrorInterceptor extends Interceptor {\n}\n\n/**\n * Registry for a collection of interceptors of the same type.\n *\n * Handles adding, removing, and executing interceptors in the correct order.\n * Each InterceptorRegistry instance manages one type of interceptor (request, response, or error).\n *\n * @remarks\n * Interceptors are executed in ascending order of their `order` property.\n * Interceptors with the same order value are executed in the order they were added.\n *\n * @example\n * // Create an interceptor registry with initial interceptors\n * const requestRegistry = new InterceptorRegistry([interceptor1, interceptor2]);\n *\n * // Add a new interceptor\n * requestRegistry.use(newInterceptor);\n *\n * // Remove an interceptor by name\n * requestRegistry.eject('InterceptorName');\n *\n * // Process an exchange through all interceptors\n * const result = await requestRegistry.intercept(exchange);\n */\nexport class InterceptorRegistry implements Interceptor {\n /**\n * Gets the name of this interceptor registry.\n *\n * @returns The constructor name of this class\n */\n get name(): string {\n return this.constructor.name;\n }\n\n /**\n * Gets the order of this interceptor registry.\n *\n * @returns Number.MIN_SAFE_INTEGER, indicating this registry should execute early\n */\n get order(): number {\n return Number.MIN_SAFE_INTEGER;\n }\n\n /**\n * Array of interceptors managed by this registry, sorted by their order property.\n */\n private sortedInterceptors: Interceptor[] = [];\n\n /**\n * Initializes a new InterceptorRegistry instance.\n *\n * @param interceptors - Initial array of interceptors to manage\n *\n * @remarks\n * The provided interceptors will be sorted by their order property immediately\n * upon construction.\n */\n constructor(interceptors: Interceptor[] = []) {\n this.sortedInterceptors = toSorted(interceptors);\n }\n\n /**\n * Returns an array of all interceptors in the registry.\n */\n get interceptors(): Interceptor[] {\n return [...this.sortedInterceptors];\n }\n\n /**\n * Adds an interceptor to this registry.\n *\n * @param interceptor - The interceptor to add\n * @returns True if the interceptor was added, false if an interceptor with the\n * same name already exists\n *\n * @remarks\n * Interceptors are uniquely identified by their name property. Attempting to add\n * an interceptor with a name that already exists in the registry will fail.\n *\n * After adding, interceptors are automatically sorted by their order property.\n */\n use(interceptor: Interceptor): boolean {\n if (this.sortedInterceptors.some(item => item.name === interceptor.name)) {\n return false;\n }\n this.sortedInterceptors = toSorted([\n ...this.sortedInterceptors,\n interceptor,\n ]);\n return true;\n }\n\n /**\n * Removes an interceptor by name.\n *\n * @param name - The name of the interceptor to remove\n * @returns True if an interceptor was removed, false if no interceptor with the\n * given name was found\n */\n eject(name: string): boolean {\n const original = this.sortedInterceptors;\n this.sortedInterceptors = toSorted(\n original,\n interceptor => interceptor.name !== name,\n );\n return original.length !== this.sortedInterceptors.length;\n }\n\n /**\n * Removes all interceptors from this registry.\n */\n clear(): void {\n this.sortedInterceptors = [];\n }\n\n\n /**\n * Executes all managed interceptors on the given exchange object.\n *\n * @param exchange - The exchange object to process\n * @returns A promise that resolves when all interceptors have been executed\n *\n * @remarks\n * Interceptors are executed in order, with each interceptor receiving the result\n * of the previous interceptor. The first interceptor receives the original\n * exchange object.\n *\n * If any interceptor throws an error, the execution chain is broken and the error\n * is propagated to the caller.\n */\n async intercept(exchange: FetchExchange): Promise<void> {\n for (const interceptor of this.sortedInterceptors) {\n // Each interceptor processes the output of the previous interceptor\n await interceptor.intercept(exchange);\n }\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ResponseInterceptor } from './interceptor';\nimport { FetchExchange } from './fetchExchange';\nimport { FetcherError } from './fetcherError';\n\n/**\n * Error thrown when response status validation fails.\n *\n * This error is thrown by ValidateStatusInterceptor when the response status\n * does not pass the validation defined by the validateStatus function.\n */\nexport class HttpStatusValidationError extends FetcherError {\n constructor(public readonly exchange: FetchExchange) {\n super(\n `Request failed with status code ${exchange.response?.status} for ${exchange.request.url}`,\n );\n this.name = 'HttpStatusValidationError';\n Object.setPrototypeOf(this, HttpStatusValidationError.prototype);\n }\n}\n\n/**\n * Defines whether to resolve or reject the promise for a given HTTP response status code.\n * If `validateStatus` returns `true` (or is set to `null` or `undefined`), the promise will be resolved;\n * otherwise, the promise will be rejected.\n *\n * @param status - HTTP response status code\n * @returns true to resolve the promise, false to reject it\n *\n * @example\n * ```typescript\n * // Default behavior (2xx status codes are resolved)\n * const fetcher = new Fetcher();\n *\n * // Custom behavior (only 200 status code is resolved)\n * const fetcher = new Fetcher({\n * validateStatus: (status) => status === 200\n * });\n *\n * // Always resolve (never reject based on status)\n * const fetcher = new Fetcher({\n * validateStatus: (status) => true\n * });\n * ```\n */\ntype ValidateStatus = (status: number) => boolean;\n\nconst DEFAULT_VALIDATE_STATUS: ValidateStatus = (status: number) =>\n status >= 200 && status < 300;\n\n/**\n * The name of the ValidateStatusInterceptor.\n */\nexport const VALIDATE_STATUS_INTERCEPTOR_NAME = 'ValidateStatusInterceptor';\n\n/**\n * The order of the ValidateStatusInterceptor.\n * Set to Number.MAX_SAFE_INTEGER - 1000 to ensure it runs latest among response interceptors,\n * but still allows other interceptors to run after it if needed.\n */\nexport const VALIDATE_STATUS_INTERCEPTOR_ORDER = Number.MAX_SAFE_INTEGER - 1000;\n\n/**\n * Response interceptor that validates HTTP status codes.\n *\n * This interceptor implements behavior similar to axios's validateStatus option.\n * It checks the response status code against a validation function and throws\n * an error if the status is not valid.\n *\n * @remarks\n * This interceptor runs at the very beginning of the response interceptor chain to ensure\n * status validation happens before any other response processing. The order is set to\n * VALIDATE_STATUS_INTERCEPTOR_ORDER to ensure it executes early in the response chain,\n * allowing for other response interceptors to run after it if needed. This positioning\n * ensures that invalid responses are caught and handled early in the response processing\n * pipeline, before other response handlers attempt to process them.\n *\n * @example\n * ```typescript\n * // Default behavior (2xx status codes are valid)\n * const interceptor = new ValidateStatusInterceptor();\n *\n * // Custom validation (only 200 status code is valid)\n * const interceptor = new ValidateStatusInterceptor((status) => status === 200);\n *\n * // Always valid (never throws based on status)\n * const interceptor = new ValidateStatusInterceptor((status) => true);\n * ```\n */\nexport class ValidateStatusInterceptor implements ResponseInterceptor {\n /**\n * Gets the name of this interceptor.\n *\n * @returns The name of this interceptor\n */\n get name(): string {\n return VALIDATE_STATUS_INTERCEPTOR_NAME;\n }\n\n /**\n * Gets the order of this interceptor.\n *\n * @returns VALIDATE_STATUS_INTERCEPTOR_ORDER, indicating this interceptor should execute early\n */\n get order(): number {\n return VALIDATE_STATUS_INTERCEPTOR_ORDER;\n }\n\n /**\n * Creates a new ValidateStatusInterceptor instance.\n *\n * @param validateStatus - Function that determines if a status code is valid\n */\n constructor(\n private readonly validateStatus: ValidateStatus = DEFAULT_VALIDATE_STATUS,\n ) {\n }\n\n /**\n * Validates the response status code.\n *\n * @param exchange - The exchange containing the response to validate\n * @throws HttpStatusValidationError if the status code is not valid\n *\n * @remarks\n * This method runs at the beginning of the response interceptor chain to ensure\n * status validation happens before any other response processing. Invalid responses\n * are caught and converted to HttpStatusValidationError early in the pipeline,\n * preventing other response handlers from attempting to process them. Valid responses\n * proceed through the rest of the response interceptor chain normally.\n */\n intercept(exchange: FetchExchange) {\n // Only validate if there's a response\n if (!exchange.response) {\n return;\n }\n\n const status = exchange.response.status;\n // If status is valid, do nothing\n if (this.validateStatus(status)) {\n return;\n }\n throw new HttpStatusValidationError(exchange);\n }\n}\n","import { UrlResolveInterceptor } from './urlResolveInterceptor';\nimport { RequestBodyInterceptor } from './requestBodyInterceptor';\nimport { FetchInterceptor } from './fetchInterceptor';\nimport { FetchExchange } from './fetchExchange';\nimport { FetcherError } from './fetcherError';\nimport { InterceptorRegistry } from './interceptor';\nimport { ValidateStatusInterceptor } from './validateStatusInterceptor';\n\n/**\n * Custom error class for FetchExchange related errors.\n *\n * This error is thrown when there are issues with the HTTP exchange process,\n * such as when a request fails and no response is generated. It provides\n * comprehensive information about the failed request through the exchange object.\n *\n * @example\n * ```typescript\n * try {\n * await fetcher.get('/api/users');\n * } catch (error) {\n * if (error instanceof ExchangeError) {\n * console.log('Request URL:', error.exchange.request.url);\n * console.log('Request method:', error.exchange.request.method);\n * if (error.exchange.error) {\n * console.log('Underlying error:', error.exchange.error);\n * }\n * }\n * }\n * ```\n */\nexport class ExchangeError extends FetcherError {\n /**\n * Creates a new ExchangeError instance.\n *\n * @param exchange - The FetchExchange object containing request/response/error information.\n * @param errorMsg - An optional error message.\n */\n constructor(public readonly exchange: FetchExchange, errorMsg?: string) {\n const errorMessage = errorMsg ||\n exchange.error?.message ||\n exchange.response?.statusText ||\n `Request to ${exchange.request.url} failed during exchange`;\n super(errorMessage, exchange.error);\n this.name = 'ExchangeError';\n Object.setPrototypeOf(this, ExchangeError.prototype);\n }\n}\n\n/**\n * Collection of interceptor managers for the Fetcher client.\n *\n * Manages three types of interceptors:\n * 1. Request interceptors - Process requests before sending HTTP requests\n * 2. Response interceptors - Process responses after receiving HTTP responses\n * 3. Error interceptors - Handle errors when they occur during the request process\n *\n * Each type of interceptor is managed by an InterceptorRegistry instance, supporting\n * adding, removing, and executing interceptors.\n *\n * @example\n * // Create a custom interceptor\n * const customRequestInterceptor: Interceptor = {\n * name: 'CustomRequestInterceptor',\n * order: 100,\n * async intercept(exchange: FetchExchange): Promise<FetchExchange> {\n * // Modify request headers\n * exchange.request.headers = {\n * ...exchange.request.headers,\n * 'X-Custom-Header': 'custom-value'\n * };\n * return exchange;\n * }\n * };\n *\n * // Add interceptor to Fetcher\n * const fetcher = new Fetcher();\n * fetcher.interceptors.request.use(customRequestInterceptor);\n *\n * @remarks\n * By default, the request interceptor registry has three built-in interceptors registered:\n * 1. UrlResolveInterceptor - Resolves the final URL with parameters\n * 2. RequestBodyInterceptor - Automatically converts object-type request bodies to JSON strings\n * 3. FetchInterceptor - Executes actual HTTP requests and handles timeouts\n */\nexport class InterceptorManager {\n /**\n * Registry for request-phase interceptors.\n *\n * Executed before HTTP requests are sent. Contains three built-in interceptors by default:\n * UrlResolveInterceptor, RequestBodyInterceptor, and FetchInterceptor.\n *\n * @remarks\n * Request interceptors are executed in ascending order of their order values, with smaller\n * values having higher priority. The default interceptors are:\n * 1. UrlResolveInterceptor (order: Number.MIN_SAFE_INTEGER) - Resolves the final URL\n * 2. RequestBodyInterceptor (order: 0) - Converts object bodies to JSON\n * 3. FetchInterceptor (order: Number.MAX_SAFE_INTEGER) - Executes the actual HTTP request\n */\n readonly request: InterceptorRegistry = new InterceptorRegistry([\n new UrlResolveInterceptor(),\n new RequestBodyInterceptor(),\n new FetchInterceptor(),\n ]);\n\n /**\n * Manager for response-phase interceptors.\n *\n * Executed after HTTP responses are received. Contains ValidateStatusInterceptor by default\n * which validates HTTP status codes and throws errors for invalid statuses.\n *\n * @remarks\n * Response interceptors are executed in ascending order of their order values, with smaller\n * values having higher priority.\n *\n * By default, the response interceptor registry has one built-in interceptor registered:\n * 1. ValidateStatusInterceptor - Validates HTTP status codes and throws HttpStatusValidationError for invalid statuses\n */\n readonly response: InterceptorRegistry = new InterceptorRegistry([\n new ValidateStatusInterceptor(),\n ]);\n\n /**\n * Manager for error-handling phase interceptors.\n *\n * Executed when errors occur during HTTP requests. Empty by default, custom error handling\n * logic can be added as needed.\n *\n * @remarks\n * Error interceptors are executed in ascending order of their order values, with smaller\n * values having higher priority. Error interceptors can transform errors into normal responses,\n * avoiding thrown exceptions.\n */\n readonly error: InterceptorRegistry = new InterceptorRegistry();\n\n /**\n * Processes a FetchExchange through the interceptor pipeline.\n *\n * This method is the core of the Fetcher's interceptor system. It executes the three\n * phases of interceptors in sequence:\n * 1. Request interceptors - Process the request before sending\n * 2. Response interceptors - Process the response after receiving\n * 3. Error interceptors - Handle any errors that occurred during the process\n *\n * The interceptor pipeline follows the Chain of Responsibility pattern, where each\n * interceptor can modify the exchange object and decide whether to continue or\n * terminate the chain.\n *\n * @param fetchExchange - The exchange object containing request, response, and error information\n * @returns Promise that resolves to the processed FetchExchange\n * @throws ExchangeError if an unhandled error occurs during processing\n *\n * @remarks\n * The method handles three distinct phases:\n *\n * 1. Request Phase: Executes request interceptors which can modify headers, URL, body, etc.\n * Built-in interceptors handle URL resolution, body serialization, and actual HTTP execution.\n *\n * 2. Response Phase: Executes response interceptors which can transform or validate responses.\n * These interceptors only run if the request phase completed without throwing.\n *\n * 3. Error Phase: Executes error interceptors when any phase throws an error. Error interceptors\n * can handle errors by clearing the error property. If error interceptors clear the error,\n * the exchange is returned successfully.\n *\n * Error Handling:\n * - If any interceptor throws an error, the error phase is triggered\n * - Error interceptors can \"fix\" errors by clearing the error property on the exchange\n * - If errors remain after error interceptors run, they are wrapped in ExchangeError\n *\n * Order of Execution:\n * 1. Request interceptors (sorted by order property, ascending)\n * 2. Response interceptors (sorted by order property, ascending) - only if no error in request phase\n * 3. Error interceptors (sorted by order property, ascending) - only if an error occurred\n *\n * @example\n * ```typescript\n * // Create a fetcher with custom interceptors\n * const fetcher = new Fetcher();\n *\n * // Add a request interceptor\n * fetcher.interceptors.request.use({\n * name: 'AuthInterceptor',\n * order: 100,\n * async intercept(exchange: FetchExchange) {\n * exchange.request.headers = {\n * ...exchange.request.headers,\n * 'Authorization': 'Bearer ' + getToken()\n * };\n * }\n * });\n *\n * // Add a response interceptor\n * fetcher.interceptors.response.use({\n * name: 'ResponseLogger',\n * order: 100,\n * async intercept(exchange: FetchExchange) {\n * console.log(`Response status: ${exchange.response?.status}`);\n * }\n * });\n *\n * // Add an error interceptor\n * fetcher.interceptors.error.use({\n * name: 'ErrorLogger',\n * order: 100,\n * async intercept(exchange: FetchExchange) {\n * console.error(`Request to ${exchange.request.url} failed:`, exchange.error);\n * // Clear the error to indicate it's been handled\n * exchange.error = undefined;\n * }\n * });\n *\n * // Create and process an exchange\n * const request: FetchRequest = {\n * url: '/api/users',\n * method: HttpMethod.GET\n * };\n * const exchange = new FetchExchange(fetcher, request);\n * const result = await fetcher.exchange(exchange);\n * ```\n */\n async exchange(fetchExchange: FetchExchange): Promise<FetchExchange> {\n try {\n // Apply request interceptors\n await this.request.intercept(fetchExchange);\n // Apply response interceptors\n await this.response.intercept(fetchExchange);\n return fetchExchange;\n } catch (error: any) {\n // Apply error interceptors\n fetchExchange.error = error;\n await this.error.intercept(fetchExchange);\n\n // If error interceptors cleared the error (indicating it's been handled/fixed), return the exchange\n if (!fetchExchange.hasError()) {\n return fetchExchange;\n }\n\n // Otherwise, wrap the error in ExchangeError\n throw new ExchangeError(fetchExchange);\n }\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Fetcher } from './fetcher';\nimport { FetchRequest } from './fetchRequest';\nimport { ExchangeError } from './interceptorManager';\n\n/**\n * Container for HTTP request/response data that flows through the interceptor chain.\n *\n * Represents the complete exchange object that flows through the interceptor chain.\n * This object contains all the information about a request, response, and any errors\n * that occur during the HTTP request lifecycle. It also provides a mechanism for\n * sharing data between interceptors through the attributes property.\n *\n * FetchExchange instances are unique within a single request scope, meaning each HTTP\n * request creates its own FetchExchange instance that is passed through the interceptor\n * chain for that specific request.\n *\n * @example\n * ```typescript\n * // In a request interceptor\n * const requestInterceptor: Interceptor = {\n * name: 'RequestInterceptor',\n * order: 0,\n * intercept(exchange: FetchExchange) {\n * // Add custom data to share with other interceptors\n * exchange.attributes = exchange.attributes || {};\n * exchange.attributes.startTime = Date.now();\n * exchange.attributes.customHeader = 'my-value';\n * }\n * };\n *\n * // In a response interceptor\n * const responseInterceptor: Interceptor = {\n * name: 'ResponseInterceptor',\n * order: 0,\n * intercept(exchange: FetchExchange) {\n * // Access data shared by previous interceptors\n * if (exchange.attributes && exchange.attributes.startTime) {\n * const startTime = exchange.attributes.startTime;\n * const duration = Date.now() - startTime;\n * console.log(`Request took ${duration}ms`);\n * }\n * }\n * };\n * ```\n */\nexport class FetchExchange {\n /**\n * The Fetcher instance that initiated this exchange.\n */\n fetcher: Fetcher;\n\n /**\n * The request configuration including url, method, headers, body, etc.\n */\n request: FetchRequest;\n\n /**\n * The response object, undefined until the request completes successfully.\n */\n response: Response | undefined;\n\n /**\n * Any error that occurred during the request processing, undefined if no error occurred.\n */\n error: Error | any | undefined;\n\n /**\n * Shared attributes for passing data between interceptors.\n *\n * This property allows interceptors to share arbitrary data with each other.\n * Interceptors can read from and write to this object to pass information\n * along the interceptor chain.\n *\n * @remarks\n * - This property is optional and may be undefined initially\n * - Interceptors should initialize this property if they need to use it\n * - Use string keys to avoid conflicts between different interceptors\n * - Consider namespacing your keys (e.g., 'mylib.retryCount' instead of 'retryCount')\n * - Be mindful of memory usage when storing large objects\n */\n attributes: Record<string, any> = {};\n\n constructor(\n fetcher: Fetcher,\n request: FetchRequest,\n response?: Response,\n error?: Error | any,\n ) {\n this.fetcher = fetcher;\n this.request = request;\n this.response = response;\n this.error = error;\n }\n\n /**\n * Checks if the exchange has an error.\n *\n * @returns true if an error is present, false otherwise\n */\n hasError(): boolean {\n return !!this.error;\n }\n\n /**\n * Checks if the exchange has a response.\n *\n * @returns true if a response is present, false otherwise\n */\n hasResponse(): boolean {\n return !!this.response;\n }\n\n /**\n * Gets the required response object, throwing an error if no response is available.\n *\n * This getter ensures that a response object is available, and throws an ExchangeError\n * with details about the request if no response was received. This is useful for\n * guaranteeing that downstream code always has a valid Response object to work with.\n *\n * @throws {ExchangeError} If no response is available for the current exchange\n * @returns The Response object for this exchange\n */\n get requiredResponse(): Response {\n if (!this.response) {\n throw new ExchangeError(\n this,\n `Request to ${this.request.url} failed with no response`,\n );\n }\n return this.response;\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Merges two record objects, giving precedence to the second record for overlapping keys.\n *\n * This utility function is used to merge configuration objects where the second object\n * takes precedence over the first when there are conflicting keys.\n *\n * @template V - The type of values in the records\n * @param first - The first record to merge (lower precedence)\n * @param second - The second record to merge (higher precedence)\n * @returns A new merged record, or undefined if both inputs are undefined\n *\n * @example\n * ```typescript\n * // Merge two objects\n * const defaults = { timeout: 5000, retries: 3 };\n * const overrides = { timeout: 10000 };\n * const result = mergeRecords(defaults, overrides);\n * // Result: { timeout: 10000, retries: 3 }\n *\n * // Handle undefined values\n * const result2 = mergeRecords(undefined, { timeout: 5000 });\n * // Result: { timeout: 5000 }\n *\n * // Return undefined when both are undefined\n * const result3 = mergeRecords(undefined, undefined);\n * // Result: undefined\n * ```\n */\nexport function mergeRecords<V>(\n first?: Record<string, V>,\n second?: Record<string, V>,\n): Record<string, V> | undefined {\n // If both records are undefined, return undefined\n if (first === undefined && second === undefined) {\n return undefined;\n }\n\n // If second record is undefined, return first record\n if (second === undefined) {\n return first;\n }\n\n // If first record is undefined, return second record\n if (first === undefined) {\n return second;\n }\n\n // Merge both records, with second taking precedence\n return { ...first, ...second };\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { UrlBuilder, UrlBuilderCapable } from './urlBuilder';\nimport { resolveTimeout, TimeoutCapable } from './timeout';\nimport { FetchExchange } from './fetchExchange';\nimport {\n BaseURLCapable,\n ContentTypeValues,\n FetchRequest,\n FetchRequestInit,\n HttpMethod,\n RequestHeaders,\n RequestHeadersCapable,\n} from './fetchRequest';\nimport { mergeRecords } from './utils';\nimport { InterceptorManager } from './interceptorManager';\n\n/**\n * Configuration options for the Fetcher client.\n *\n * Defines the customizable aspects of a Fetcher instance including base URL,\n * default headers, timeout settings, and interceptors.\n *\n * @example\n * ```typescript\n * const options: FetcherOptions = {\n * baseURL: 'https://api.example.com',\n * headers: { 'Content-Type': 'application/json' },\n * timeout: 5000,\n * interceptors: new InterceptorManager()\n * };\n * ```\n */\nexport interface FetcherOptions\n extends BaseURLCapable,\n RequestHeadersCapable,\n TimeoutCapable {\n interceptors?: InterceptorManager;\n}\n\nconst DEFAULT_HEADERS: RequestHeaders = {\n 'Content-Type': ContentTypeValues.APPLICATION_JSON,\n};\n\nexport const DEFAULT_OPTIONS: FetcherOptions = {\n baseURL: '',\n headers: DEFAULT_HEADERS,\n};\n\n/**\n * HTTP client with support for interceptors, URL building, and timeout control.\n *\n * The Fetcher class provides a flexible and extensible HTTP client implementation\n * that follows the interceptor pattern. It supports URL parameter interpolation,\n * request/response transformation, and timeout handling.\n *\n * @example\n * ```typescript\n * const fetcher = new Fetcher({ baseURL: 'https://api.example.com' });\n * const response = await fetcher.fetch('/users/{id}', {\n * urlParams: {\n * path: { id: 123 },\n * query: { filter: 'active' }\n * },\n * timeout: 5000\n * });\n * ```\n */\nexport class Fetcher\n implements UrlBuilderCapable, RequestHeadersCapable, TimeoutCapable {\n readonly urlBuilder: UrlBuilder;\n readonly headers?: RequestHeaders = DEFAULT_HEADERS;\n readonly timeout?: number;\n readonly interceptors: InterceptorManager;\n\n /**\n * Initializes a new Fetcher instance with optional configuration.\n *\n * Creates a Fetcher with default settings that can be overridden through the options parameter.\n * If no interceptors are provided, a default set of interceptors will be used.\n *\n * @param options - Configuration options for the Fetcher instance\n */\n constructor(options: FetcherOptions = DEFAULT_OPTIONS) {\n this.urlBuilder = new UrlBuilder(options.baseURL);\n this.headers = options.headers ?? DEFAULT_HEADERS;\n this.timeout = options.timeout;\n this.interceptors = options.interceptors ?? new InterceptorManager();\n }\n\n /**\n * Executes an HTTP request with the specified URL and options.\n *\n * This is the primary method for making HTTP requests. It processes the request\n * through the interceptor chain and returns the resulting Response.\n *\n * @param url - The URL path for the request (relative to baseURL if set)\n * @param request - Request configuration including headers, body, parameters, etc.\n * @returns Promise that resolves to the HTTP response\n * @throws FetchError if the request fails and no response is generated\n */\n async fetch(url: string, request: FetchRequestInit = {}): Promise<Response> {\n const fetchRequest = request as FetchRequest;\n fetchRequest.url = url;\n const exchange = await this.request(fetchRequest);\n return exchange.requiredResponse;\n }\n\n /**\n * Processes an HTTP request through the Fetcher's internal workflow.\n *\n * This method prepares the request by merging headers and timeout settings,\n * creates a FetchExchange object, and passes it through the exchange method\n * for interceptor processing.\n *\n * @param request - Complete request configuration object\n * @returns Promise that resolves to a FetchExchange containing request/response data\n * @throws Error if an unhandled error occurs during request processing\n */\n async request(request: FetchRequest): Promise<FetchExchange> {\n // Merge default headers and request-level headers\n const mergedHeaders = mergeRecords(request.headers, this.headers);\n // Merge request options\n const fetchRequest: FetchRequest = {\n ...request,\n headers: mergedHeaders,\n timeout: resolveTimeout(request.timeout, this.timeout),\n };\n const exchange: FetchExchange = new FetchExchange(this, fetchRequest);\n return this.interceptors.exchange(exchange);\n }\n\n /**\n * Internal helper method for making HTTP requests with a specific method.\n *\n * This private method is used by the public HTTP method methods (get, post, etc.)\n * to execute requests with the appropriate HTTP verb.\n *\n * @param method - The HTTP method to use for the request\n * @param url - The URL path for the request\n * @param request - Additional request options\n * @returns Promise that resolves to the HTTP response\n */\n private async methodFetch(\n method: HttpMethod,\n url: string,\n request: FetchRequestInit = {},\n ): Promise<Response> {\n return this.fetch(url, {\n ...request,\n method,\n });\n }\n\n /**\n * Makes a GET HTTP request.\n *\n * Convenience method for making GET requests. The request body is omitted\n * as GET requests should not contain a body according to HTTP specification.\n *\n * @param url - The URL path for the request\n * @param request - Request options excluding method and body\n * @returns Promise that resolves to the HTTP response\n */\n async get(\n url: string,\n request: Omit<FetchRequestInit, 'method' | 'body'> = {},\n ): Promise<Response> {\n return this.methodFetch(HttpMethod.GET, url, request);\n }\n\n /**\n * Makes a POST HTTP request.\n *\n * Convenience method for making POST requests, commonly used for creating resources.\n *\n * @param url - The URL path for the request\n * @param request - Request options including body and other parameters\n * @returns Promise that resolves to the HTTP response\n */\n async post(\n url: string,\n request: Omit<FetchRequestInit, 'method'> = {},\n ): Promise<Response> {\n return this.methodFetch(HttpMethod.POST, url, request);\n }\n\n /**\n * Makes a PUT HTTP request.\n *\n * Convenience method for making PUT requests, commonly used for updating resources.\n *\n * @param url - The URL path for the request\n * @param request - Request options including body and other parameters\n * @returns Promise that resolves to the HTTP response\n */\n async put(\n url: string,\n request: Omit<FetchRequestInit, 'method'> = {},\n ): Promise<Response> {\n return this.methodFetch(HttpMethod.PUT, url, request);\n }\n\n /**\n * Makes a DELETE HTTP request.\n *\n * Convenience method for making DELETE requests, commonly used for deleting resources.\n *\n * @param url - The URL path for the request\n * @param request - Request options excluding method and body\n * @returns Promise that resolves to the HTTP response\n */\n async delete(\n url: string,\n request: Omit<FetchRequestInit, 'method'> = {},\n ): Promise<Response> {\n return this.methodFetch(HttpMethod.DELETE, url, request);\n }\n\n /**\n * Makes a PATCH HTTP request.\n *\n * Convenience method for making PATCH requests, commonly used for partial updates.\n *\n * @param url - The URL path for the request\n * @param request - Request options including body and other parameters\n * @returns Promise that resolves to the HTTP response\n */\n async patch(\n url: string,\n request: Omit<FetchRequestInit, 'method'> = {},\n ): Promise<Response> {\n return this.methodFetch(HttpMethod.PATCH, url, request);\n }\n\n /**\n * Makes a HEAD HTTP request.\n *\n * Convenience method for making HEAD requests, which retrieve headers only.\n * The request body is omitted as HEAD requests should not contain a body.\n *\n * @param url - The URL path for the request\n * @param request - Request options excluding method and body\n * @returns Promise that resolves to the HTTP response\n */\n async head(\n url: string,\n request: Omit<FetchRequestInit, 'method' | 'body'> = {},\n ): Promise<Response> {\n return this.methodFetch(HttpMethod.HEAD, url, request);\n }\n\n /**\n * Makes an OPTIONS HTTP request.\n *\n * Convenience method for making OPTIONS requests, commonly used for CORS preflight.\n * The request body is omitted as OPTIONS requests typically don't contain a body.\n *\n * @param url - The URL path for the request\n * @param request - Request options excluding method and body\n * @returns Promise that resolves to the HTTP response\n */\n async options(\n url: string,\n request: Omit<FetchRequestInit, 'method' | 'body'> = {},\n ): Promise<Response> {\n return this.methodFetch(HttpMethod.OPTIONS, url, request);\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Fetcher } from './fetcher';\n\n/**\n * Default fetcher name used when no name is specified\n */\nexport const DEFAULT_FETCHER_NAME = 'default';\n\n/**\n * FetcherRegistrar is a registry for managing multiple Fetcher instances.\n * It allows registering, retrieving, and unregistering Fetcher instances by name.\n * This is useful for applications that need to manage multiple HTTP clients\n * with different configurations.\n *\n * @example\n * // Register a fetcher\n * const fetcher = new Fetcher({ baseURL: 'https://api.example.com' });\n * fetcherRegistrar.register('api', fetcher);\n *\n * // Retrieve a fetcher\n * const apiFetcher = fetcherRegistrar.get('api');\n *\n * // Use the default fetcher\n * const defaultFetcher = fetcherRegistrar.default;\n *\n * // Unregister a fetcher\n * fetcherRegistrar.unregister('api');\n */\nexport class FetcherRegistrar {\n /**\n * Internal map for storing registered fetchers\n * @private\n */\n private registrar: Map<string, Fetcher> = new Map();\n\n /**\n * Register a Fetcher instance with a given name\n *\n * @param name - The name to register the fetcher under\n * @param fetcher - The Fetcher instance to register\n * @example\n * const fetcher = new Fetcher({ baseURL: 'https://api.example.com' });\n * fetcherRegistrar.register('api', fetcher);\n */\n register(name: string, fetcher: Fetcher): void {\n this.registrar.set(name, fetcher);\n }\n\n /**\n * Unregister a Fetcher instance by name\n *\n * @param name - The name of the fetcher to unregister\n * @returns boolean - True if the fetcher was successfully unregistered, false otherwise\n * @example\n * const success = fetcherRegistrar.unregister('api');\n * if (success) {\n * console.log('Fetcher unregistered successfully');\n * }\n */\n unregister(name: string): boolean {\n return this.registrar.delete(name);\n }\n\n /**\n * Get a Fetcher instance by name\n *\n * @param name - The name of the fetcher to retrieve\n * @returns Fetcher | undefined - The Fetcher instance if found, undefined otherwise\n * @example\n * const fetcher = fetcherRegistrar.get('api');\n * if (fetcher) {\n * // Use the fetcher\n * }\n */\n get(name: string): Fetcher | undefined {\n return this.registrar.get(name);\n }\n\n /**\n * Get a Fetcher instance by name, throwing an error if not found\n *\n * @param name - The name of the fetcher to retrieve\n * @returns Fetcher - The Fetcher instance\n * @throws Error - If no fetcher is registered with the given name\n * @example\n * try {\n * const fetcher = fetcherRegistrar.requiredGet('api');\n * // Use the fetcher\n * } catch (error) {\n * console.error('Fetcher not found:', error.message);\n * }\n */\n requiredGet(name: string): Fetcher {\n const fetcher = this.get(name);\n if (!fetcher) {\n throw new Error(`Fetcher ${name} not found`);\n }\n return fetcher;\n }\n\n /**\n * Get the default Fetcher instance\n *\n * @returns Fetcher - The default Fetcher instance\n * @throws Error - If no default fetcher is registered\n * @example\n * const defaultFetcher = fetcherRegistrar.default;\n */\n get default(): Fetcher {\n return this.requiredGet(DEFAULT_FETCHER_NAME);\n }\n\n /**\n * Set the default Fetcher instance\n *\n * @param fetcher - The Fetcher instance to set as default\n * @example\n * const fetcher = new Fetcher({ baseURL: 'https://api.example.com' });\n * fetcherRegistrar.default = fetcher;\n */\n set default(fetcher: Fetcher) {\n this.register(DEFAULT_FETCHER_NAME, fetcher);\n }\n\n /**\n * Get a copy of all registered fetchers\n *\n * @returns Map<string, Fetcher> - A copy of the internal registrar map\n * @example\n * const allFetchers = fetcherRegistrar.fetchers;\n * for (const [name, fetcher] of allFetchers) {\n * console.log(`Fetcher ${name}:`, fetcher);\n * }\n */\n get fetchers(): Map<string, Fetcher> {\n return new Map(this.registrar);\n }\n}\n\n/**\n * Global instance of FetcherRegistrar\n * This is the default registrar used throughout the application\n *\n * @example\n * import { fetcherRegistrar } from '@ahoo-wang/fetcher';\n *\n * // Register a fetcher\n * const fetcher = new Fetcher({ baseURL: 'https://api.example.com' });\n * fetcherRegistrar.register('api', fetcher);\n *\n * // Retrieve a fetcher\n * const apiFetcher = fetcherRegistrar.get('api');\n */\nexport const fetcherRegistrar = new FetcherRegistrar();\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { FetchRequestInit } from './fetchRequest';\nimport { UrlParams } from './urlBuilder';\nimport { mergeRecords } from './utils';\n\n/**\n * Merges two FetcherRequest objects into one.\n *\n * This function combines two FetcherRequest objects, with the second object's properties\n * taking precedence over the first object's properties. Special handling is applied\n * to nested objects like path, query, and headers which are merged recursively.\n * For primitive values, the second object's values override the first's.\n *\n * @param first - The first request object (lower priority)\n * @param second - The second request object (higher priority)\n * @returns A new FetcherRequest object with merged properties\n *\n * @example\n * ```typescript\n * const request1 = {\n * method: 'GET',\n * urlParams: {\n * path: { id: 1 }\n * },\n * headers: { 'Content-Type': 'application/json' }\n * };\n *\n * const request2 = {\n * method: 'POST',\n * urlParams: {\n * query: { filter: 'active' }\n * },\n * headers: { 'Authorization': 'Bearer token' }\n * };\n *\n * const merged = mergeRequest(request1, request2);\n * // Result: {\n * // method: 'POST',\n * // urlParams: {\n * // path: { id: 1 },\n * // query: { filter: 'active' }\n * // },\n * // headers: {\n * // 'Content-Type': 'application/json',\n * // 'Authorization': 'Bearer token'\n * // }\n * // }\n * ```\n */\nexport function mergeRequest(\n first: FetchRequestInit,\n second: FetchRequestInit,\n): FetchRequestInit {\n // If first request is empty, return second request\n if (Object.keys(first).length === 0) {\n return second;\n }\n\n // If second request is empty, return first request\n if (Object.keys(second).length === 0) {\n return first;\n }\n\n // Merge nested objects\n const urlParams: UrlParams = {\n path: mergeRecords(first.urlParams?.path, second.urlParams?.path),\n query: mergeRecords(first.urlParams?.query, second.urlParams?.query),\n };\n\n const headers = {\n ...first.headers,\n ...second.headers,\n };\n\n // For primitive values, second takes precedence\n const method = second.method ?? first.method;\n const body = second.body ?? first.body;\n const timeout = second.timeout ?? first.timeout;\n const signal = second.signal ?? first.signal;\n\n // Return merged request with second object's top-level properties taking precedence\n return {\n ...first,\n ...second,\n method,\n urlParams,\n headers,\n body,\n timeout,\n signal,\n };\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { NamedCapable } from './types';\nimport { DEFAULT_OPTIONS, Fetcher, FetcherOptions } from './fetcher';\nimport { DEFAULT_FETCHER_NAME, fetcherRegistrar } from './fetcherRegistrar';\n\n/**\n * NamedFetcher is an extension of the Fetcher class that automatically registers\n * itself with the global fetcherRegistrar using a provided name.\n * This allows for easy management and retrieval of multiple fetcher instances\n * throughout an application by name.\n *\n * @example\n * // Create a named fetcher that automatically registers itself\n * const apiFetcher = new NamedFetcher('api', {\n * baseURL: 'https://api.example.com',\n * timeout: 5000\n * });\n *\n * // Retrieve the fetcher later by name\n * const sameFetcher = fetcherRegistrar.get('api');\n * console.log(apiFetcher === sameFetcher); // true\n *\n * // Use the fetcher normally\n * const response = await apiFetcher.get('/users');\n */\nexport class NamedFetcher extends Fetcher implements NamedCapable {\n /**\n * The name of this fetcher instance, used for registration and retrieval\n */\n name: string;\n\n /**\n * Create a NamedFetcher instance and automatically register it with the global fetcherRegistrar\n *\n * @param name - The name to register this fetcher under\n * @param options - Fetcher configuration options (same as Fetcher constructor)\n *\n * @example\n * // Create with default options\n * const fetcher1 = new NamedFetcher('default');\n *\n * // Create with custom options\n * const fetcher2 = new NamedFetcher('api', {\n * baseURL: 'https://api.example.com',\n * timeout: 5000,\n * headers: { 'Authorization': 'Bearer token' }\n * });\n */\n constructor(name: string, options: FetcherOptions = DEFAULT_OPTIONS) {\n super(options);\n this.name = name;\n fetcherRegistrar.register(name, this);\n }\n}\n\n/**\n * Default named fetcher instance registered with the name 'default'.\n * This provides a convenient way to use a pre-configured fetcher instance\n * without having to create and register one manually.\n *\n * @example\n * // Use the default fetcher directly\n * import { fetcher } from '@ahoo-wang/fetcher';\n *\n * fetcher.get('/users')\n * .then(response => response.json())\n * .then(data => console.log(data));\n *\n * // Or retrieve it from the registrar\n * import { fetcherRegistrar } from '@ahoo-wang/fetcher';\n *\n * const defaultFetcher = fetcherRegistrar.default;\n * defaultFetcher.get('/users')\n * .then(response => response.json())\n * .then(data => console.log(data));\n */\nexport const fetcher = new NamedFetcher(DEFAULT_FETCHER_NAME);\n"],"names":["isAbsoluteURL","url","combineURLs","baseURL","relativeURL","UrlBuilder","params","path","query","combinedURL","finalUrl","queryString","request","_","key","value","FetcherError","errorMsg","cause","errorMessage","FetchTimeoutError","method","message","resolveTimeout","requestTimeout","optionsTimeout","timeoutFetch","timeout","requestInit","controller","fetchRequest","timerId","timeoutPromise","reject","error","URL_RESOLVE_INTERCEPTOR_NAME","URL_RESOLVE_INTERCEPTOR_ORDER","UrlResolveInterceptor","exchange","HttpMethod","ContentTypeHeader","ContentTypeValues","REQUEST_BODY_INTERCEPTOR_NAME","REQUEST_BODY_INTERCEPTOR_ORDER","RequestBodyInterceptor","modifiedRequest","headers","FETCH_INTERCEPTOR_NAME","FETCH_INTERCEPTOR_ORDER","FetchInterceptor","toSorted","array","filter","a","b","InterceptorRegistry","interceptors","interceptor","item","name","original","HttpStatusValidationError","DEFAULT_VALIDATE_STATUS","status","VALIDATE_STATUS_INTERCEPTOR_NAME","VALIDATE_STATUS_INTERCEPTOR_ORDER","ValidateStatusInterceptor","validateStatus","ExchangeError","InterceptorManager","fetchExchange","FetchExchange","fetcher","response","mergeRecords","first","second","DEFAULT_HEADERS","DEFAULT_OPTIONS","Fetcher","options","mergedHeaders","DEFAULT_FETCHER_NAME","FetcherRegistrar","fetcherRegistrar","mergeRequest","urlParams","body","signal","NamedFetcher"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ahoo-wang/fetcher",
3
- "version": "0.11.1",
3
+ "version": "0.11.2",
4
4
  "description": "Ultra-lightweight (1.9kB) HTTP client with built-in path parameters and Axios-like API",
5
5
  "keywords": [
6
6
  "fetch",