@happy-ts/fetch-t
Version:
Type-safe Fetch API wrapper with abortable requests, timeout support, progress tracking, automatic retry, and Rust-like Result error handling.
1 lines • 53.1 kB
Source Map (JSON)
{"version":3,"file":"main.cjs","sources":["../src/fetch/constants.ts","../src/fetch/defines.ts","../src/fetch/fetch.ts"],"sourcesContent":["/**\n * Error name for aborted fetch requests.\n *\n * This matches the standard `AbortError` name used by the Fetch API when a request\n * is cancelled via `AbortController.abort()`.\n *\n * @since 1.0.0\n * @example\n * ```typescript\n * import { fetchT, ABORT_ERROR } from '@happy-ts/fetch-t';\n *\n * const task = fetchT('https://api.example.com/data', { abortable: true });\n * task.abort();\n *\n * const result = await task.result;\n * result.inspectErr((err) => {\n * if (err.name === ABORT_ERROR) {\n * console.log('Request was aborted');\n * }\n * });\n * ```\n */\nexport const ABORT_ERROR = 'AbortError' as const;\n\n/**\n * Error name for timed out fetch requests.\n *\n * This is set on the `Error.name` property when a request exceeds the specified\n * `timeout` duration and is automatically aborted.\n *\n * @since 1.0.0\n * @example\n * ```typescript\n * import { fetchT, TIMEOUT_ERROR } from '@happy-ts/fetch-t';\n *\n * const result = await fetchT('https://api.example.com/slow-endpoint', {\n * timeout: 5000, // 5 seconds\n * });\n *\n * result.inspectErr((err) => {\n * if (err.name === TIMEOUT_ERROR) {\n * console.log('Request timed out after 5 seconds');\n * }\n * });\n * ```\n */\nexport const TIMEOUT_ERROR = 'TimeoutError' as const;\n","import type { AsyncIOResult, IOResult } from 'happy-rusty';\n\n/**\n * Union type of all possible fetchT response data types.\n *\n * Used when `responseType` is a dynamic string value rather than a literal type,\n * as the exact return type cannot be determined at compile time.\n *\n * @since 1.9.0\n * @example\n * ```typescript\n * import { fetchT, type FetchResponseData } from '@happy-ts/fetch-t';\n *\n * // When responseType is dynamic, return type is FetchResponseData\n * const responseType = getResponseType(); // returns string\n * const result = await fetchT('https://api.example.com/data', { responseType });\n * // result is Result<FetchResponseData, Error>\n * ```\n */\nexport type FetchResponseData =\n | string\n | ArrayBuffer\n | Blob\n | Uint8Array<ArrayBuffer>\n | ReadableStream<Uint8Array<ArrayBuffer>>\n | Response\n | null;\n\n/**\n * Represents the result of a fetch operation as an async Result type.\n *\n * This is an alias for `AsyncIOResult<T>` from the `happy-rusty` library,\n * providing Rust-like error handling without throwing exceptions.\n *\n * @typeParam T - The type of the data expected in a successful response.\n * @since 1.0.0\n * @example\n * ```typescript\n * import { fetchT, type FetchResult } from '@happy-ts/fetch-t';\n *\n * // FetchResult is a Promise that resolves to Result<T, Error>\n * const result: FetchResult<string> = fetchT('https://api.example.com', {\n * responseType: 'text',\n * });\n *\n * const res = await result;\n * res\n * .inspect((text) => console.log('Success:', text))\n * .inspectErr((err) => console.error('Error:', err));\n * ```\n */\nexport type FetchResult<T> = AsyncIOResult<T>;\n\n/**\n * Represents an abortable fetch operation with control methods.\n *\n * Returned when `abortable: true` is set in the fetch options. Provides\n * the ability to cancel the request and check its abort status.\n *\n * @typeParam T - The type of the data expected in the response.\n * @since 1.0.0\n * @example\n * ```typescript\n * import { fetchT, type FetchTask } from '@happy-ts/fetch-t';\n *\n * interface User {\n * id: number;\n * name: string;\n * }\n *\n * const task: FetchTask<User> = fetchT<User>('https://api.example.com/user/1', {\n * abortable: true,\n * responseType: 'json',\n * });\n *\n * // Check if aborted\n * console.log('Is aborted:', task.aborted); // false\n *\n * // Abort with optional reason\n * task.abort('User navigated away');\n *\n * // Access the result (will be an error after abort)\n * const result = await task.result;\n * result.inspectErr((err) => console.log('Aborted:', err.message));\n * ```\n */\nexport interface FetchTask<T> {\n /**\n * Aborts the fetch task, optionally with a reason.\n *\n * Once aborted, the `result` promise will resolve to an `Err` containing\n * an `AbortError`. The abort reason can be any value and will be passed\n * to the underlying `AbortController.abort()`.\n *\n * @param reason - An optional value indicating why the task was aborted.\n * This can be an Error, string, or any other value.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n abort(reason?: any): void;\n\n /**\n * Indicates whether the fetch task has been aborted.\n *\n * Returns `true` if `abort()` was called or if the request timed out.\n */\n readonly aborted: boolean;\n\n /**\n * The result promise of the fetch task.\n *\n * Resolves to `Ok<T>` on success, or `Err<Error>` on failure (including abort).\n */\n readonly result: FetchResult<T>;\n}\n\n/**\n * Specifies the expected response type for automatic parsing.\n *\n * - `'text'` - Parse response as string via `Response.text()`\n * - `'json'` - Parse response as JSON via `Response.json()`\n * - `'arraybuffer'` - Parse response as ArrayBuffer via `Response.arrayBuffer()`\n * - `'bytes'` - Parse response as Uint8Array<ArrayBuffer> via `Response.bytes()` (with fallback for older environments)\n * - `'blob'` - Parse response as Blob via `Response.blob()`\n * - `'stream'` - Return the raw `ReadableStream` for streaming processing\n *\n * If not specified, the raw `Response` object is returned.\n *\n * @since 1.0.0\n * @example\n * ```typescript\n * import { fetchT, type FetchResponseType } from '@happy-ts/fetch-t';\n *\n * const responseType: FetchResponseType = 'json';\n *\n * const result = await fetchT('https://api.example.com/data', { responseType });\n * ```\n */\nexport type FetchResponseType = 'text' | 'arraybuffer' | 'blob' | 'json' | 'bytes' | 'stream';\n\n/**\n * Represents the download progress of a fetch operation.\n *\n * Passed to the `onProgress` callback when tracking download progress.\n * Note: Progress tracking requires the server to send a `Content-Length` header.\n *\n * @since 1.0.0\n * @example\n * ```typescript\n * import { fetchT, type FetchProgress } from '@happy-ts/fetch-t';\n *\n * await fetchT('https://example.com/file.zip', {\n * responseType: 'blob',\n * onProgress: (result) => {\n * result.inspect((progress: FetchProgress) => {\n * const percent = (progress.completedByteLength / progress.totalByteLength) * 100;\n * console.log(`Downloaded: ${percent.toFixed(1)}%`);\n * });\n * },\n * });\n * ```\n */\nexport interface FetchProgress {\n /**\n * The total number of bytes to be received (from Content-Length header).\n */\n totalByteLength: number;\n\n /**\n * The number of bytes received so far.\n */\n completedByteLength: number;\n}\n\n/**\n * Options for configuring retry behavior.\n *\n * @since 1.8.0\n */\nexport interface FetchRetryOptions {\n /**\n * Number of times to retry the request on failure.\n *\n * By default, only network errors trigger retries. HTTP errors (4xx, 5xx)\n * require explicit configuration via `when`.\n *\n * @default 0 (no retries)\n */\n retries?: number;\n\n /**\n * Delay between retry attempts in milliseconds.\n *\n * Can be a static number or a function for custom strategies like exponential backoff.\n * The function receives the current attempt number (1-indexed).\n *\n * @default 0 (immediate retry)\n */\n delay?: number | ((attempt: number) => number);\n\n /**\n * Conditions under which to retry the request.\n *\n * Can be an array of HTTP status codes or a custom function.\n * By default, only network errors (not FetchError) trigger retries.\n */\n when?: number[] | ((error: Error, attempt: number) => boolean);\n\n /**\n * Callback invoked before each retry attempt.\n *\n * Useful for logging, metrics, or adjusting request parameters.\n */\n onRetry?: (error: Error, attempt: number) => void;\n}\n\n/**\n * Extended fetch options that add additional capabilities to the standard `RequestInit`.\n *\n * @since 1.0.0\n * @example\n * ```typescript\n * import { fetchT, type FetchInit } from '@happy-ts/fetch-t';\n *\n * const options: FetchInit = {\n * // Standard RequestInit options\n * method: 'POST',\n * headers: { 'Content-Type': 'application/json' },\n * body: JSON.stringify({ key: 'value' }),\n *\n * // Extended options\n * abortable: true, // Return FetchTask for manual abort control\n * responseType: 'json', // Auto-parse response as JSON\n * timeout: 10000, // Abort after 10 seconds\n * onProgress: (result) => { // Track download progress\n * result.inspect(({ completedByteLength, totalByteLength }) => {\n * console.log(`${completedByteLength}/${totalByteLength}`);\n * });\n * },\n * onChunk: (chunk) => { // Receive raw data chunks\n * console.log('Received chunk:', chunk.byteLength, 'bytes');\n * },\n * };\n *\n * const task = fetchT('https://api.example.com/upload', options);\n * ```\n */\nexport interface FetchInit extends RequestInit {\n /**\n * When `true`, returns a `FetchTask` instead of `FetchResult`.\n *\n * The `FetchTask` provides `abort()` method and `aborted` status.\n *\n * @default false\n */\n abortable?: boolean;\n\n /**\n * Specifies how the response body should be parsed.\n *\n * - `'text'` - Returns `string`\n * - `'json'` - Returns parsed JSON (type `T`)\n * - `'arraybuffer'` - Returns `ArrayBuffer`\n * - `'bytes'` - Returns `Uint8Array<ArrayBuffer>` (with fallback for older environments)\n * - `'blob'` - Returns `Blob`\n * - `'stream'` - Returns `ReadableStream<Uint8Array<ArrayBuffer>>`\n * - `undefined` - Returns raw `Response` object\n *\n * When using a dynamic string value (not a literal type), the return type\n * will be `FetchResponseData` (union of all possible types).\n */\n responseType?: FetchResponseType;\n\n /**\n * Maximum time in milliseconds to wait for the request to complete.\n *\n * If exceeded, the request is automatically aborted with a `TimeoutError`.\n * Must be a positive number.\n */\n timeout?: number;\n\n /**\n * Retry options.\n *\n * Can be a number (shorthand for retries count) or an options object.\n *\n * @example\n * ```typescript\n * // Retry up to 3 times on network errors\n * const result = await fetchT('https://api.example.com/data', {\n * retry: 3,\n * });\n *\n * // Detailed configuration\n * const result = await fetchT('https://api.example.com/data', {\n * retry: {\n * retries: 3,\n * delay: 1000,\n * when: [500, 502],\n * onRetry: (error, attempt) => console.log(error),\n * },\n * });\n * ```\n */\n retry?: number | FetchRetryOptions;\n\n /**\n * Callback invoked during download to report progress.\n *\n * Receives an `IOResult<FetchProgress>`:\n * - `Ok(FetchProgress)` - Progress update with byte counts\n * - `Err(Error)` - If `Content-Length` header is missing (called once)\n *\n * **Note**: This feature uses `response.clone()` internally. The cloned stream shares\n * the same underlying data source (via `tee()`), so it does NOT double memory usage.\n * However, if the two streams consume data at different speeds, chunks may be buffered\n * temporarily until both streams have read them.\n *\n * @param progressResult - The progress result, either success with progress data or error.\n */\n onProgress?: (progressResult: IOResult<FetchProgress>) => void;\n\n /**\n * Callback invoked when a chunk of data is received.\n *\n * Useful for streaming or processing data as it arrives.\n * Each chunk is a `Uint8Array<ArrayBuffer>` containing the raw bytes.\n *\n * **Note**: This feature uses `response.clone()` internally. The cloned stream shares\n * the same underlying data source (via `tee()`), so it does NOT double memory usage.\n * However, if the two streams consume data at different speeds, chunks may be buffered\n * temporarily until both streams have read them.\n *\n * @param chunk - The raw data chunk received from the response stream.\n */\n onChunk?: (chunk: Uint8Array<ArrayBuffer>) => void;\n}\n\n/**\n * Custom error class for HTTP error responses (non-2xx status codes).\n *\n * Thrown when `Response.ok` is `false`. Contains the HTTP status code\n * for programmatic error handling.\n *\n * @since 1.0.0\n * @example\n * ```typescript\n * import { fetchT, FetchError } from '@happy-ts/fetch-t';\n *\n * const result = await fetchT('https://api.example.com/not-found', {\n * responseType: 'json',\n * });\n *\n * result.inspectErr((err) => {\n * if (err instanceof FetchError) {\n * console.log('HTTP Status:', err.status); // e.g., 404\n * console.log('Status Text:', err.message); // e.g., \"Not Found\"\n *\n * // Handle specific status codes\n * switch (err.status) {\n * case 401:\n * console.log('Unauthorized - please login');\n * break;\n * case 404:\n * console.log('Resource not found');\n * break;\n * case 500:\n * console.log('Server error');\n * break;\n * }\n * }\n * });\n * ```\n */\nexport class FetchError extends Error {\n /**\n * The error name, always `'FetchError'`.\n */\n override name = 'FetchError';\n\n /**\n * The HTTP status code of the response (e.g., 404, 500).\n */\n status: number;\n\n /**\n * Creates a new FetchError instance.\n *\n * @param message - The status text from the HTTP response (e.g., \"Not Found\").\n * @param status - The HTTP status code (e.g., 404).\n */\n constructor(message: string, status: number) {\n super(message);\n this.status = status;\n }\n}\n","import { Err, Ok, type AsyncIOResult } from 'happy-rusty';\nimport { ABORT_ERROR } from './constants.ts';\nimport { FetchError, type FetchInit, type FetchResponseData, type FetchResponseType, type FetchResult, type FetchRetryOptions, type FetchTask } from './defines.ts';\n\n// #region Overload Declarations\n\n/**\n * Fetches a resource from the network as a text string and returns an abortable `FetchTask`.\n *\n * @param url - The resource to fetch. Can be a URL object or a string representing a URL.\n * @param init - Additional options for the fetch operation, must include `abortable: true` and `responseType: 'text'`.\n * @returns A `FetchTask` representing the abortable operation with a `string` response.\n */\nexport function fetchT(url: string | URL, init: FetchInit & {\n abortable: true;\n responseType: 'text';\n}): FetchTask<string>;\n\n/**\n * Fetches a resource from the network as an ArrayBuffer and returns an abortable `FetchTask`.\n *\n * @param url - The resource to fetch. Can be a URL object or a string representing a URL.\n * @param init - Additional options for the fetch operation, must include `abortable: true` and `responseType: 'arraybuffer'`.\n * @returns A `FetchTask` representing the abortable operation with an `ArrayBuffer` response.\n */\nexport function fetchT(url: string | URL, init: FetchInit & {\n abortable: true;\n responseType: 'arraybuffer';\n}): FetchTask<ArrayBuffer>;\n\n/**\n * Fetches a resource from the network as a Blob and returns an abortable `FetchTask`.\n *\n * @param url - The resource to fetch. Can be a URL object or a string representing a URL.\n * @param init - Additional options for the fetch operation, must include `abortable: true` and `responseType: 'blob'`.\n * @returns A `FetchTask` representing the abortable operation with a `Blob` response.\n */\nexport function fetchT(url: string | URL, init: FetchInit & {\n abortable: true;\n responseType: 'blob';\n}): FetchTask<Blob>;\n\n/**\n * Fetches a resource from the network and parses it as JSON, returning an abortable `FetchTask`.\n *\n * @typeParam T - The expected type of the parsed JSON data.\n * @param url - The resource to fetch. Can be a URL object or a string representing a URL.\n * @param init - Additional options for the fetch operation, must include `abortable: true` and `responseType: 'json'`.\n * @returns A `FetchTask` representing the abortable operation with a response parsed as type `T`.\n */\nexport function fetchT<T>(url: string | URL, init: FetchInit & {\n abortable: true;\n responseType: 'json';\n}): FetchTask<T | null>;\n\n/**\n * Fetches a resource from the network as a ReadableStream and returns an abortable `FetchTask`.\n *\n * @param url - The resource to fetch. Can be a URL object or a string representing a URL.\n * @param init - Additional options for the fetch operation, must include `abortable: true` and `responseType: 'stream'`.\n * @returns A `FetchTask` representing the abortable operation with a `ReadableStream<Uint8Array<ArrayBuffer>>` response.\n */\nexport function fetchT(url: string | URL, init: FetchInit & {\n abortable: true;\n responseType: 'stream';\n}): FetchTask<ReadableStream<Uint8Array<ArrayBuffer>> | null>;\n\n/**\n * Fetches a resource from the network as a Uint8Array<ArrayBuffer> and returns an abortable `FetchTask`.\n *\n * @param url - The resource to fetch. Can be a URL object or a string representing a URL.\n * @param init - Additional options for the fetch operation, must include `abortable: true` and `responseType: 'bytes'`.\n * @returns A `FetchTask` representing the abortable operation with a `Uint8Array<ArrayBuffer>` response.\n */\nexport function fetchT(url: string | URL, init: FetchInit & {\n abortable: true;\n responseType: 'bytes';\n}): FetchTask<Uint8Array<ArrayBuffer>>;\n\n/**\n * Fetches a resource from the network and returns an abortable `FetchTask` with a dynamic response type.\n *\n * Use this overload when `responseType` is a `FetchResponseType` union type.\n *\n * @param url - The resource to fetch. Can be a URL object or a string representing a URL.\n * @param init - Additional options for the fetch operation, must include `abortable: true` and a `FetchResponseType`.\n * @returns A `FetchTask` representing the abortable operation with a `FetchResponseData` response.\n */\nexport function fetchT(url: string | URL, init: FetchInit & {\n abortable: true;\n responseType: FetchResponseType;\n}): FetchTask<FetchResponseData>;\n\n/**\n * Fetches a resource from the network as a text string.\n *\n * @param url - The resource to fetch. Can be a URL object or a string representing a URL.\n * @param init - Additional options for the fetch operation, must include `responseType: 'text'` and `abortable` must be `false` or omitted.\n * @returns A `FetchResult` representing the operation with a `string` response.\n */\nexport function fetchT(url: string | URL, init: FetchInit & {\n abortable?: false;\n responseType: 'text';\n}): FetchResult<string>;\n\n/**\n * Fetches a resource from the network as an ArrayBuffer.\n *\n * @param url - The resource to fetch. Can be a URL object or a string representing a URL.\n * @param init - Additional options for the fetch operation, must include `responseType: 'arraybuffer'` and `abortable` must be `false` or omitted.\n * @returns A `FetchResult` representing the operation with an `ArrayBuffer` response.\n */\nexport function fetchT(url: string | URL, init: FetchInit & {\n abortable?: false;\n responseType: 'arraybuffer';\n}): FetchResult<ArrayBuffer>;\n\n/**\n * Fetches a resource from the network as a Blob.\n *\n * @param url - The resource to fetch. Can be a URL object or a string representing a URL.\n * @param init - Additional options for the fetch operation, must include `responseType: 'blob'` and `abortable` must be `false` or omitted.\n * @returns A `FetchResult` representing the operation with a `Blob` response.\n */\nexport function fetchT(url: string | URL, init: FetchInit & {\n abortable?: false;\n responseType: 'blob';\n}): FetchResult<Blob>;\n\n/**\n * Fetches a resource from the network and parses it as JSON.\n *\n * @typeParam T - The expected type of the parsed JSON data.\n * @param url - The resource to fetch. Can be a URL object or a string representing a URL.\n * @param init - Additional options for the fetch operation, must include `responseType: 'json'` and `abortable` must be `false` or omitted.\n * @returns A `FetchResult` representing the operation with a response parsed as type `T`.\n */\nexport function fetchT<T>(url: string | URL, init: FetchInit & {\n abortable?: false;\n responseType: 'json';\n}): FetchResult<T | null>;\n\n/**\n * Fetches a resource from the network as a ReadableStream.\n *\n * @param url - The resource to fetch. Can be a URL object or a string representing a URL.\n * @param init - Additional options for the fetch operation, must include `responseType: 'stream'` and `abortable` must be `false` or omitted.\n * @returns A `FetchResult` representing the operation with a `ReadableStream<Uint8Array<ArrayBuffer>>` response.\n */\nexport function fetchT(url: string | URL, init: FetchInit & {\n abortable?: false;\n responseType: 'stream';\n}): FetchResult<ReadableStream<Uint8Array<ArrayBuffer>> | null>;\n\n/**\n * Fetches a resource from the network as a Uint8Array<ArrayBuffer>.\n *\n * @param url - The resource to fetch. Can be a URL object or a string representing a URL.\n * @param init - Additional options for the fetch operation, must include `responseType: 'bytes'` and `abortable` must be `false` or omitted.\n * @returns A `FetchResult` representing the operation with a `Uint8Array<ArrayBuffer>` response.\n */\nexport function fetchT(url: string | URL, init: FetchInit & {\n abortable?: false;\n responseType: 'bytes';\n}): FetchResult<Uint8Array<ArrayBuffer>>;\n\n/**\n * Fetches a resource from the network with a dynamic response type (non-abortable).\n *\n * Use this overload when `responseType` is a `FetchResponseType` union type.\n *\n * @param url - The resource to fetch. Can be a URL object or a string representing a URL.\n * @param init - Additional options for the fetch operation with a `FetchResponseType`, and `abortable` must be `false` or omitted.\n * @returns A `FetchResult` representing the operation with a `FetchResponseData` response.\n */\nexport function fetchT(url: string | URL, init: FetchInit & {\n abortable?: false;\n responseType: FetchResponseType;\n}): FetchResult<FetchResponseData>;\n\n/**\n * Fetches a resource from the network and returns an abortable `FetchTask` with a generic `Response`.\n *\n * @param url - The resource to fetch. Can be a URL object or a string representing a URL.\n * @param init - Additional options for the fetch operation, must include `abortable: true`.\n * @returns A `FetchTask` representing the abortable operation with a `Response` object.\n */\nexport function fetchT(url: string | URL, init: FetchInit & {\n abortable: true;\n}): FetchTask<Response>;\n\n/**\n * Fetches a resource from the network and returns a `FetchResult` with a generic `Response` object.\n *\n * @param url - The resource to fetch. Can be a URL object or a string representing a URL.\n * @param init - Optional additional options for the fetch operation, and `abortable` must be `false` or omitted.\n * @returns A `FetchResult` representing the operation with a `Response` object.\n */\nexport function fetchT(url: string | URL, init?: FetchInit & {\n abortable?: false;\n}): FetchResult<Response>;\n\n/**\n * Fallback overload for when `FetchInit` is passed as a variable (not an object literal).\n *\n * When TypeScript cannot determine the literal value of `abortable` at compile time\n * (e.g., when passing a `FetchInit` variable), this overload is matched.\n * The return type is a union of `FetchTask` and `FetchResult`.\n *\n * @param url - The resource to fetch. Can be a URL object or a string representing a URL.\n * @param init - Optional fetch options. When passed as a `FetchInit` variable, the return type is a union.\n * @returns Either a `FetchTask` or `FetchResult` depending on the runtime value of `abortable`.\n */\nexport function fetchT(url: string | URL, init?: FetchInit): FetchTask<FetchResponseData> | FetchResult<FetchResponseData>;\n\n// #endregion\n\n// #region Main Implementation\n\n/**\n * Enhanced fetch function that wraps the native Fetch API with additional capabilities.\n *\n * Features:\n * - **Abortable requests**: Set `abortable: true` to get a `FetchTask` with `abort()` method.\n * - **Type-safe responses**: Use `responseType` to automatically parse responses as text, JSON, ArrayBuffer, Blob, bytes, or stream.\n * - **Timeout support**: Set `timeout` in milliseconds to auto-abort long-running requests.\n * - **Progress tracking**: Use `onProgress` callback to track download progress (requires Content-Length header).\n * - **Chunk streaming**: Use `onChunk` callback to receive raw data chunks as they arrive.\n * - **Retry support**: Use `retry` to automatically retry failed requests with configurable delay and conditions.\n * - **Result type error handling**: Returns `Result<T, Error>` instead of throwing exceptions for runtime errors.\n *\n * **Note**: Invalid parameters throw synchronously (fail-fast) rather than returning rejected Promises.\n * This differs from native `fetch` behavior and helps catch programming errors during development.\n *\n * @typeParam T - The expected type of the response data.\n * @param url - The resource to fetch. Can be a URL object or a string representing a URL.\n * @param init - Additional options for the fetch operation, extending standard `RequestInit` with custom properties.\n * @returns A `FetchTask<T>` if `abortable: true`, otherwise a `FetchResult<T>` (which is `AsyncIOResult<T>`).\n * @throws {TypeError} If `url` is invalid or a relative URL in non-browser environment.\n * @throws {TypeError} If `responseType` is not a valid response type.\n * @throws {TypeError} If `timeout` is not a number.\n * @throws {Error} If `timeout` is not greater than 0.\n * @throws {TypeError} If `onProgress` or `onChunk` is provided but not a function.\n * @throws {TypeError} If `retry.retries` is not an integer.\n * @throws {Error} If `retry.retries` is negative.\n * @throws {TypeError} If `retry.delay` is not a number or function.\n * @throws {Error} If `retry.delay` is a negative number.\n * @throws {TypeError} If `retry.when` is not an array or function.\n * @throws {TypeError} If `retry.onRetry` is provided but not a function.\n * @since 1.0.0\n * @example\n * // Basic GET request - returns Response object wrapped in Result\n * const result = await fetchT('https://api.example.com/data');\n * result\n * .inspect((res) => console.log('Status:', res.status))\n * .inspectErr((err) => console.error('Error:', err));\n *\n * @example\n * // GET JSON with type safety\n * interface User {\n * id: number;\n * name: string;\n * }\n * const result = await fetchT<User>('https://api.example.com/user/1', {\n * responseType: 'json',\n * });\n * result.inspect((user) => console.log(user.name));\n *\n * @example\n * // POST request with JSON body\n * const result = await fetchT<User>('https://api.example.com/users', {\n * method: 'POST',\n * headers: { 'Content-Type': 'application/json' },\n * body: JSON.stringify({ name: 'John' }),\n * responseType: 'json',\n * });\n *\n * @example\n * // Abortable request with timeout\n * const task = fetchT('https://api.example.com/data', {\n * abortable: true,\n * timeout: 5000, // 5 seconds\n * });\n *\n * // Cancel the request if needed\n * task.abort('User cancelled');\n *\n * // Check if aborted\n * console.log('Aborted:', task.aborted);\n *\n * // Wait for result\n * const result = await task.result;\n *\n * @example\n * // Track download progress\n * const result = await fetchT('https://example.com/large-file.zip', {\n * responseType: 'blob',\n * onProgress: (progressResult) => {\n * progressResult\n * .inspect(({ completedByteLength, totalByteLength }) => {\n * const percent = ((completedByteLength / totalByteLength) * 100).toFixed(1);\n * console.log(`Progress: ${percent}%`);\n * })\n * .inspectErr((err) => console.warn('Progress unavailable:', err.message));\n * },\n * });\n *\n * @example\n * // Stream data chunks\n * const chunks: Uint8Array[] = [];\n * const result = await fetchT('https://example.com/stream', {\n * onChunk: (chunk) => chunks.push(chunk),\n * });\n *\n * @example\n * // Retry with exponential backoff\n * const result = await fetchT('https://api.example.com/data', {\n * retry: {\n * retries: 3,\n * delay: (attempt) => Math.min(1000 * Math.pow(2, attempt - 1), 10000),\n * when: [500, 502, 503, 504],\n * onRetry: (error, attempt) => console.log(`Retry ${attempt}: ${error.message}`),\n * },\n * responseType: 'json',\n * });\n */\nexport function fetchT(url: string | URL, init?: FetchInit): FetchTask<FetchResponseData> | FetchResult<FetchResponseData> {\n // Validate and parse URL\n const parsedUrl = validateUrl(url);\n\n const fetchInit = init ?? {};\n\n const {\n retries,\n delay: retryDelay,\n when: retryWhen,\n onRetry,\n } = validateOptions(fetchInit);\n\n const {\n // default not abortable\n abortable = false,\n responseType,\n timeout,\n onProgress,\n onChunk,\n ...rest\n } = fetchInit;\n\n // Preserve user's original signal before modifications (rest.signal will be reassigned in setSignal)\n const userSignal = rest.signal;\n\n // User controller for manual abort (stops all retries)\n let userController: AbortController | undefined;\n if (abortable) {\n userController = new AbortController();\n }\n\n /**\n * Determines if the error should trigger a retry.\n * By default, only network errors (not FetchError) trigger retries.\n */\n const shouldRetry = (error: Error, attempt: number): boolean => {\n // Never retry on user abort\n if (error.name === ABORT_ERROR) {\n return false;\n }\n\n if (!retryWhen) {\n // Default: only retry on network errors (not FetchError/HTTP errors)\n return !(error instanceof FetchError);\n }\n\n if (Array.isArray(retryWhen)) {\n // Retry on specific HTTP status codes\n return error instanceof FetchError && retryWhen.includes(error.status);\n }\n\n // Custom retry condition\n return retryWhen(error, attempt);\n };\n\n /**\n * Calculates the delay before the next retry attempt.\n */\n const getRetryDelay = (attempt: number): number => {\n return typeof retryDelay === 'function'\n ? retryDelay(attempt)\n : retryDelay;\n };\n\n /**\n * Configures the abort signal for a fetch attempt.\n *\n * Combines multiple signals:\n * - User's external signal (from init.signal)\n * - Internal abort controller signal (for abortable requests)\n * - Timeout signal (creates a new one each call for per-attempt timeout)\n *\n * Must be called before each fetch attempt to ensure fresh timeout signal on retries.\n */\n const configureSignal = (): void => {\n const signals: AbortSignal[] = [];\n\n // Merge user's signal from init (if provided)\n if (userSignal) {\n signals.push(userSignal);\n }\n\n if (userController) {\n signals.push(userController.signal);\n }\n\n if (typeof timeout === 'number') {\n signals.push(AbortSignal.timeout(timeout));\n }\n\n // Combine all signals\n if (signals.length > 0) {\n rest.signal = signals.length === 1\n ? signals[0]\n : AbortSignal.any(signals);\n }\n };\n\n /**\n * Performs a single fetch attempt with optional timeout.\n */\n const doFetch = async (): AsyncIOResult<FetchResponseData> => {\n configureSignal();\n\n try {\n const response = await fetch(parsedUrl, rest);\n\n if (!response.ok) {\n // Cancel the response body to free resources\n response.body?.cancel().catch(() => {\n // Silently ignore stream cancel errors\n });\n return Err(new FetchError(response.statusText, response.status));\n }\n\n return await processResponse(response);\n } catch (err) {\n return Err(err instanceof Error\n ? err\n // Non-Error type, most likely an abort reason\n : wrapAbortReason(err),\n );\n }\n };\n\n /**\n * Sets up progress tracking and chunk callbacks using a cloned response.\n * The original response is returned unchanged for further processing.\n */\n const setupProgressCallbacks = async (response: Response): Promise<void> => {\n let totalByteLength: number | undefined;\n let completedByteLength = 0;\n\n if (onProgress) {\n const contentLength = response.headers.get('content-length');\n if (contentLength == null) {\n try {\n onProgress(Err(new Error('No content-length in response headers')));\n } catch {\n // Silently ignore user callback errors\n }\n } else {\n totalByteLength = Number.parseInt(contentLength, 10);\n }\n }\n\n const body = response.clone().body as ReadableStream<Uint8Array<ArrayBuffer>>;\n\n try {\n for await (const chunk of body) {\n if (onChunk) {\n try {\n onChunk(chunk);\n } catch {\n // Silently ignore user callback errors\n }\n }\n\n if (onProgress && totalByteLength != null) {\n completedByteLength += chunk.byteLength;\n try {\n onProgress(Ok({\n totalByteLength,\n completedByteLength,\n }));\n } catch {\n // Silently ignore user callback errors\n }\n }\n }\n } catch {\n // Silently ignore stream read errors\n }\n };\n\n /**\n * Processes the response based on responseType and callbacks.\n */\n const processResponse = async (response: Response): AsyncIOResult<FetchResponseData> => {\n // Setup progress/chunk callbacks if needed (uses cloned response internally)\n if (response.body && (onProgress || onChunk)) {\n setupProgressCallbacks(response);\n }\n\n switch (responseType) {\n case 'json': {\n // Align with stream behavior: no body yields Ok(null)\n if (response.body == null) {\n return Ok(null);\n }\n try {\n return Ok(await response.json());\n } catch {\n return Err(new Error('Response is invalid json while responseType is json'));\n }\n }\n case 'text': {\n return Ok(await response.text());\n }\n case 'bytes': {\n // Use native bytes() if available, otherwise fallback to arrayBuffer()\n if (typeof response.bytes === 'function') {\n return Ok(await response.bytes());\n }\n // Fallback for older environments\n return Ok(new Uint8Array(await response.arrayBuffer()));\n }\n case 'arraybuffer': {\n return Ok(await response.arrayBuffer());\n }\n case 'blob': {\n return Ok(await response.blob());\n }\n case 'stream': {\n return Ok(response.body);\n }\n default: {\n // default return the original Response object to preserve all metadata\n return Ok(response);\n }\n }\n };\n\n /**\n * Performs fetch with retry logic.\n */\n const fetchWithRetry = async (): FetchResult<FetchResponseData> => {\n let lastError: Error | undefined;\n let attempt = 0;\n\n do {\n // Before retry (not first attempt), wait for delay\n if (attempt > 0) {\n // Check if user aborted before delay (e.g., aborted in `when` callback)\n if (userController?.signal.aborted) {\n return Err(userController.signal.reason as Error);\n }\n\n const delayMs = getRetryDelay(attempt);\n // Wait for delay if necessary\n if (delayMs > 0) {\n await delay(delayMs);\n\n // Check if user aborted during delay\n if (userController?.signal.aborted) {\n return Err(userController.signal.reason as Error);\n }\n }\n\n // Call onRetry right before the actual retry request\n try {\n onRetry?.(lastError as Error, attempt);\n } catch {\n // Silently ignore user callback errors\n }\n }\n\n const result = await doFetch();\n\n if (result.isOk()) {\n return result;\n }\n\n lastError = result.unwrapErr();\n attempt++;\n\n // Check if we should retry\n } while (attempt <= retries && shouldRetry(lastError, attempt));\n\n // No more retries or should not retry\n // lastError is guaranteed to be defined here because:\n // 1. do...while loop executes at least once\n // 2. We only reach here if result.isErr()\n return Err(lastError);\n };\n\n const result = fetchWithRetry();\n\n if (abortable && userController) {\n return {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n abort(reason?: any): void {\n if (reason instanceof Error) {\n userController.abort(reason);\n } else if (reason != null) {\n userController.abort(wrapAbortReason(reason));\n } else {\n userController.abort();\n }\n },\n\n get aborted(): boolean {\n return userController.signal.aborted;\n },\n\n get result(): FetchResult<FetchResponseData> {\n return result;\n },\n };\n }\n\n return result;\n}\n\n// #endregion\n\n// #region Internal Functions\n\n/**\n * Delays execution for the specified number of milliseconds.\n */\nfunction delay(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n/**\n * Wraps a non-Error abort reason into an Error with ABORT_ERROR name.\n */\nfunction wrapAbortReason(reason: unknown): Error {\n const error = new Error(typeof reason === 'string' ? reason : String(reason));\n error.name = ABORT_ERROR;\n error.cause = reason;\n return error;\n}\n\n/**\n * Parsed retry options with defaults applied.\n */\ninterface ParsedRetryOptions extends FetchRetryOptions {\n retries: number;\n delay: number | ((attempt: number) => number);\n}\n\n/**\n * Validates fetch options and parses retry configuration.\n */\nfunction validateOptions(init: FetchInit): ParsedRetryOptions {\n const {\n responseType,\n timeout,\n retry: retryOptions = 0,\n onProgress,\n onChunk,\n } = init;\n\n if (responseType != null) {\n const validTypes = ['text', 'arraybuffer', 'blob', 'json', 'bytes', 'stream'];\n if (!validTypes.includes(responseType)) {\n throw new TypeError(`responseType must be one of ${ validTypes.join(', ') } but received ${ responseType }`);\n }\n }\n\n if (timeout != null) {\n if (typeof timeout !== 'number') {\n throw new TypeError(`timeout must be a number but received ${ typeof timeout }`);\n }\n if (timeout <= 0) {\n throw new Error(`timeout must be a number greater than 0 but received ${ timeout }`);\n }\n }\n\n if (onProgress != null) {\n if (typeof onProgress !== 'function') {\n throw new TypeError(`onProgress callback must be a function but received ${ typeof onProgress }`);\n }\n }\n\n if (onChunk != null) {\n if (typeof onChunk !== 'function') {\n throw new TypeError(`onChunk callback must be a function but received ${ typeof onChunk }`);\n }\n }\n\n // Parse retry options\n let retries = 0;\n let delay: number | ((attempt: number) => number) = 0;\n let when: ((error: Error, attempt: number) => boolean) | number[] | undefined;\n let onRetry: ((error: Error, attempt: number) => void) | undefined;\n\n if (typeof retryOptions === 'number') {\n retries = retryOptions;\n } else if (retryOptions && typeof retryOptions === 'object') {\n retries = retryOptions.retries ?? 0;\n delay = retryOptions.delay ?? 0;\n when = retryOptions.when;\n onRetry = retryOptions.onRetry;\n }\n\n if (!Number.isInteger(retries)) {\n throw new TypeError(`Retry count must be an integer but received ${ retries }`);\n }\n if (retries < 0) {\n throw new Error(`Retry count must be non-negative but received ${ retries }`);\n }\n\n if (typeof delay === 'number') {\n if (delay < 0) {\n throw new Error(`Retry delay must be a non-negative number but received ${ delay }`);\n }\n } else {\n if (typeof delay !== 'function') {\n throw new TypeError(`Retry delay must be a number or a function but received ${ typeof delay }`);\n }\n }\n\n if (when != null) {\n if (!Array.isArray(when) && typeof when !== 'function') {\n throw new TypeError(`Retry when condition must be an array of status codes or a function but received ${ typeof when }`);\n }\n }\n\n if (onRetry != null) {\n if (typeof onRetry !== 'function') {\n throw new TypeError(`Retry onRetry callback must be a function but received ${ typeof onRetry }`);\n }\n }\n\n return { retries, delay, when, onRetry };\n}\n\n/**\n * Validates and parses a URL string or URL object.\n * In browser environments, relative URLs are resolved against `location.href`.\n * In non-browser environments (Node/Deno/Bun), only absolute URLs are valid.\n */\nfunction validateUrl(url: string | URL): URL {\n if (url instanceof URL) {\n return url;\n }\n\n try {\n // In browser, use location.href as base for relative URLs\n // In Node/Deno/Bun, location is undefined, so relative URLs will fail\n const base = typeof location !== 'undefined' ? location.href : undefined;\n return new URL(url, base);\n } catch {\n throw new TypeError(`Invalid URL: ${ url }`);\n }\n}\n\n// #endregion\n"],"names":["Err","Ok","result","delay"],"mappings":"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