@happy-ts/fetch-t 1.4.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/README.cn.md +17 -79
- package/README.md +17 -79
- package/dist/main.cjs +196 -84
- package/dist/main.cjs.map +1 -1
- package/dist/main.mjs +196 -84
- package/dist/main.mjs.map +1 -1
- package/dist/types.d.ts +87 -1
- package/package.json +6 -6
package/dist/main.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"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 * @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.response;\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 * @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","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type { AsyncResult, IOResult } from 'happy-rusty';\n\n/**\n * Represents the response of a fetch operation as an async Result type.\n *\n * This is an alias for `AsyncResult<T, E>` 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 * @typeParam E - The type of the error (defaults to `any`). Typically `Error` or `FetchError`.\n *\n * @example\n * ```typescript\n * import { fetchT, type FetchResponse } from '@happy-ts/fetch-t';\n *\n * // FetchResponse is a Promise that resolves to Result<T, E>\n * const response: FetchResponse<string> = fetchT('https://api.example.com', {\n * responseType: 'text',\n * });\n *\n * const result = await response;\n * result\n * .inspect((text) => console.log('Success:', text))\n * .inspectErr((err) => console.error('Error:', err));\n * ```\n */\nexport type FetchResponse<T, E = any> = AsyncResult<T, E>;\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 *\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 response (will be an error after abort)\n * const result = await task.response;\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 `response` 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 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 response promise of the fetch task.\n *\n * Resolves to `Ok<T>` on success, or `Err<Error>` on failure (including abort).\n */\n readonly response: FetchResponse<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 * - `'blob'` - Parse response as Blob via `Response.blob()`\n *\n * If not specified, the raw `Response` object is returned.\n *\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';\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 * @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 * Extended fetch options that add additional capabilities to the standard `RequestInit`.\n *\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 `FetchResponse`.\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 * - `'blob'` - Returns `Blob`\n * - `undefined` - Returns raw `Response` object\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 * 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 * @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` containing the raw bytes.\n *\n * @param chunk - The raw data chunk received from the response stream.\n */\n onChunk?: (chunk: Uint8Array) => 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 * @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 = 0;\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 } from 'happy-rusty';\nimport invariant from 'tiny-invariant';\nimport { TIMEOUT_ERROR } from './constants.ts';\nimport { FetchError, type FetchInit, type FetchResponse, type FetchTask } from './defines.ts';\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>;\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'`.\n * @returns A `FetchResponse` representing the operation with a `string` response.\n */\nexport function fetchT(url: string | URL, init: FetchInit & {\n responseType: 'text';\n}): FetchResponse<string, Error>;\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'`.\n * @returns A `FetchResponse` representing the operation with an `ArrayBuffer` response.\n */\nexport function fetchT(url: string | URL, init: FetchInit & {\n responseType: 'arraybuffer';\n}): FetchResponse<ArrayBuffer, Error>;\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'`.\n * @returns A `FetchResponse` representing the operation with a `Blob` response.\n */\nexport function fetchT(url: string | URL, init: FetchInit & {\n responseType: 'blob';\n}): FetchResponse<Blob, Error>;\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'`.\n * @returns A `FetchResponse` representing the operation with a response parsed as type `T`.\n */\nexport function fetchT<T>(url: string | URL, init: FetchInit & {\n responseType: 'json';\n}): FetchResponse<T, Error>;\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 `FetchResponse` 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.\n * @returns A `FetchResponse` representing the operation with a `Response` object.\n */\nexport function fetchT(url: string | URL, init?: FetchInit): FetchResponse<Response>;\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, or Blob.\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 * - **Result type error handling**: Returns `Result<T, Error>` instead of throwing exceptions.\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 `FetchResponse<T>` (which is `AsyncResult<T, Error>`).\n * @throws {Error} If `url` is not a string or URL object.\n * @throws {Error} If `timeout` is specified but is not a positive number.\n *\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 response\n * const result = await task.response;\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 */\nexport function fetchT<T>(url: string | URL, init?: FetchInit): FetchTask<T> | FetchResponse<T> {\n // Fast path: most URLs are passed as strings\n if (typeof url !== 'string') {\n invariant(url instanceof URL, () => `Url must be a string or URL object but received ${ url }.`);\n }\n\n const {\n // default not abortable\n abortable = false,\n responseType,\n timeout,\n onProgress,\n onChunk,\n ...rest\n } = init ?? {};\n\n const shouldWaitTimeout = timeout != null;\n let cancelTimer: (() => void) | null;\n\n if (shouldWaitTimeout) {\n invariant(typeof timeout === 'number' && timeout > 0, () => `Timeout must be a number greater than 0 but received ${ timeout }.`);\n }\n\n let controller: AbortController;\n\n if (abortable || shouldWaitTimeout) {\n controller = new AbortController();\n rest.signal = controller.signal;\n }\n\n const response: FetchResponse<T> = fetch(url, rest).then(async (res): FetchResponse<T> => {\n cancelTimer?.();\n\n if (!res.ok) {\n await res.body?.cancel();\n return Err(new FetchError(res.statusText, res.status));\n }\n\n if (res.body) {\n // should notify progress or data chunk?\n const shouldNotifyProgress = typeof onProgress === 'function';\n const shouldNotifyChunk = typeof onChunk === 'function';\n\n if ((shouldNotifyProgress || shouldNotifyChunk)) {\n // tee the original stream to two streams, one for notify progress, another for response\n const [stream1, stream2] = res.body.tee();\n\n const reader = stream1.getReader();\n // Content-Length may not be present in response headers\n let totalByteLength: number | null = null;\n let completedByteLength = 0;\n\n if (shouldNotifyProgress) {\n // Headers.get() is case-insensitive per spec\n const contentLength = res.headers.get('content-length');\n if (contentLength == null) {\n // response headers has no content-length\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 = parseInt(contentLength, 10);\n }\n }\n\n reader.read().then(function notify({ done, value }) {\n if (done) {\n return;\n }\n\n // notify chunk\n if (shouldNotifyChunk) {\n try {\n onChunk(value);\n } catch {\n // Silently ignore user callback errors\n }\n }\n\n // notify progress\n if (shouldNotifyProgress && totalByteLength != null) {\n completedByteLength += value.byteLength;\n try {\n onProgress(Ok({\n totalByteLength,\n completedByteLength,\n }));\n } catch {\n // Silently ignore user callback errors\n }\n }\n\n // Continue reading the stream\n reader.read().then(notify).catch(() => {\n // Silently ignore stream read errors (will be handled by main response)\n });\n }).catch(() => {\n // Silently ignore initial stream read errors (will be handled by main response)\n });\n\n // replace the original response with the new one\n res = new Response(stream2, {\n headers: res.headers,\n status: res.status,\n statusText: res.statusText,\n });\n }\n }\n\n switch (responseType) {\n case 'arraybuffer': {\n return Ok(await res.arrayBuffer() as T);\n }\n case 'blob': {\n return Ok(await res.blob() as T);\n }\n case 'json': {\n try {\n return Ok(await res.json() as T);\n } catch {\n return Err(new Error('Response is invalid json while responseType is json'));\n }\n }\n case 'text': {\n return Ok(await res.text() as T);\n }\n default: {\n // default return the Response object\n return Ok(res as T);\n }\n }\n }).catch((err) => {\n cancelTimer?.();\n\n return Err(err);\n });\n\n if (shouldWaitTimeout) {\n const timer = setTimeout(() => {\n const error = new Error();\n error.name = TIMEOUT_ERROR;\n controller.abort(error);\n }, timeout);\n\n cancelTimer = (): void => {\n clearTimeout(timer);\n cancelTimer = null;\n };\n }\n\n if (abortable) {\n return {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n abort(reason?: any): void {\n cancelTimer?.();\n controller.abort(reason);\n },\n\n get aborted(): boolean {\n return controller.signal.aborted;\n },\n\n get response(): FetchResponse<T> {\n return response;\n },\n };\n }\n\n return response;\n}"],"names":["Err","Ok"],"mappings":"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"}
|
|
1
|
+
{"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 * @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.response;\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 * @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","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type { AsyncResult, IOResult } from 'happy-rusty';\n\n/**\n * Represents the response of a fetch operation as an async Result type.\n *\n * This is an alias for `AsyncResult<T, E>` 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 * @typeParam E - The type of the error (defaults to `any`). Typically `Error` or `FetchError`.\n *\n * @example\n * ```typescript\n * import { fetchT, type FetchResponse } from '@happy-ts/fetch-t';\n *\n * // FetchResponse is a Promise that resolves to Result<T, E>\n * const response: FetchResponse<string> = fetchT('https://api.example.com', {\n * responseType: 'text',\n * });\n *\n * const result = await response;\n * result\n * .inspect((text) => console.log('Success:', text))\n * .inspectErr((err) => console.error('Error:', err));\n * ```\n */\nexport type FetchResponse<T, E = any> = AsyncResult<T, E>;\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 *\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 response (will be an error after abort)\n * const result = await task.response;\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 `response` 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 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 response promise of the fetch task.\n *\n * Resolves to `Ok<T>` on success, or `Err<Error>` on failure (including abort).\n */\n readonly response: FetchResponse<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 * - `'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 * @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' | '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 * @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 */\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 * @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 `FetchResponse`.\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 * - `'blob'` - Returns `Blob`\n * - `'stream'` - Returns `ReadableStream<Uint8Array>`\n * - `undefined` - Returns raw `Response` object\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 * @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` containing the raw bytes.\n *\n * @param chunk - The raw data chunk received from the response stream.\n */\n onChunk?: (chunk: Uint8Array) => 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 * @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 = 0;\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 AsyncResult } from 'happy-rusty';\nimport invariant from 'tiny-invariant';\nimport { ABORT_ERROR } from './constants.ts';\nimport { FetchError, type FetchInit, type FetchResponse, type FetchRetryOptions, type FetchTask } from './defines.ts';\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>;\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>` response.\n */\nexport function fetchT(url: string | URL, init: FetchInit & {\n abortable: true;\n responseType: 'stream';\n}): FetchTask<ReadableStream<Uint8Array>>;\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'`.\n * @returns A `FetchResponse` representing the operation with a `string` response.\n */\nexport function fetchT(url: string | URL, init: FetchInit & {\n responseType: 'text';\n}): FetchResponse<string, Error>;\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'`.\n * @returns A `FetchResponse` representing the operation with an `ArrayBuffer` response.\n */\nexport function fetchT(url: string | URL, init: FetchInit & {\n responseType: 'arraybuffer';\n}): FetchResponse<ArrayBuffer, Error>;\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'`.\n * @returns A `FetchResponse` representing the operation with a `Blob` response.\n */\nexport function fetchT(url: string | URL, init: FetchInit & {\n responseType: 'blob';\n}): FetchResponse<Blob, Error>;\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'`.\n * @returns A `FetchResponse` representing the operation with a response parsed as type `T`.\n */\nexport function fetchT<T>(url: string | URL, init: FetchInit & {\n responseType: 'json';\n}): FetchResponse<T, Error>;\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'`.\n * @returns A `FetchResponse` representing the operation with a `ReadableStream<Uint8Array>` response.\n */\nexport function fetchT(url: string | URL, init: FetchInit & {\n responseType: 'stream';\n}): FetchResponse<ReadableStream<Uint8Array>, Error>;\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 `FetchResponse` 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.\n * @returns A `FetchResponse` representing the operation with a `Response` object.\n */\nexport function fetchT(url: string | URL, init?: FetchInit): FetchResponse<Response>;\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, or Blob.\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.\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 `FetchResponse<T>` (which is `AsyncResult<T, Error>`).\n * @throws {Error} If `url` is not a string or URL object.\n * @throws {Error} If `timeout` is specified but is not a positive number.\n *\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 response\n * const result = await task.response;\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<T>(url: string | URL, init?: FetchInit): FetchTask<T> | FetchResponse<T> {\n // Fast path: most URLs are passed as strings\n if (typeof url !== 'string') {\n invariant(url instanceof URL, () => `Url must be a string or URL object but received ${ url }`);\n }\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 // 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 * Performs a single fetch attempt with optional timeout.\n */\n const doFetch = async (): AsyncResult<T, Error> => {\n const signals: AbortSignal[] = [];\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 if (signals.length > 0) {\n rest.signal = signals.length === 1\n ? signals[0]\n : AbortSignal.any(signals);\n }\n\n try {\n const res = await fetch(url, rest);\n\n if (!res.ok) {\n await res.body?.cancel();\n return Err(new FetchError(res.statusText, res.status));\n }\n\n return await processResponse(res);\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 * Processes the response based on responseType and callbacks.\n */\n const processResponse = async (res: Response): AsyncResult<T, Error> => {\n let response = res;\n\n // should notify progress or data chunk?\n if (res.body && (onProgress || onChunk)) {\n // tee the original stream to two streams, one for notify progress, another for response\n const [stream1, stream2] = res.body.tee();\n\n const reader = stream1.getReader();\n // Content-Length may not be present in response headers\n let totalByteLength: number | null = null;\n let completedByteLength = 0;\n\n if (onProgress) {\n // Headers.get() is case-insensitive per spec\n const contentLength = res.headers.get('content-length');\n if (contentLength == null) {\n // response headers has no content-length\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 = parseInt(contentLength, 10);\n }\n }\n\n reader.read().then(function notify({ done, value }) {\n if (done) {\n return;\n }\n\n // notify chunk\n if (onChunk) {\n try {\n onChunk(value);\n } catch {\n // Silently ignore user callback errors\n }\n }\n\n // notify progress\n if (onProgress && totalByteLength != null) {\n completedByteLength += value.byteLength;\n try {\n onProgress(Ok({\n totalByteLength,\n completedByteLength,\n }));\n } catch {\n // Silently ignore user callback errors\n }\n }\n\n // Continue reading the stream\n reader.read().then(notify).catch(() => {\n // Silently ignore stream read errors (will be handled by main response)\n });\n }).catch(() => {\n // Silently ignore initial stream read errors (will be handled by main response)\n });\n\n // replace the original response with the new one\n response = new Response(stream2, {\n headers: res.headers,\n status: res.status,\n statusText: res.statusText,\n });\n }\n\n switch (responseType) {\n case 'arraybuffer': {\n return Ok(await response.arrayBuffer() as T);\n }\n case 'blob': {\n return Ok(await response.blob() as T);\n }\n case 'json': {\n try {\n return Ok(await response.json() as T);\n } catch {\n return Err(new Error('Response is invalid json while responseType is json'));\n }\n }\n case 'stream': {\n return Ok(response.body as T);\n }\n case 'text': {\n return Ok(await response.text() as T);\n }\n default: {\n // default return the Response object\n return Ok(response as T);\n }\n }\n };\n\n /**\n * Performs fetch with retry logic.\n */\n const fetchWithRetry = async (): FetchResponse<T, Error> => {\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 return Err(lastError);\n };\n\n const response = 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 response(): FetchResponse<T> {\n return response;\n },\n };\n }\n\n return response;\n}\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\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', 'stream'];\n invariant(validTypes.includes(responseType), () => `responseType must be one of ${ validTypes.join(', ') } but received ${ responseType }`);\n }\n\n if (timeout != null) {\n invariant(typeof timeout === 'number' && timeout > 0, () => `timeout must be a number greater than 0 but received ${ timeout }`);\n }\n\n if (onProgress != null) {\n invariant(typeof onProgress === 'function', () => `onProgress callback must be a function but received ${ typeof onProgress }`);\n }\n\n if (onChunk != null) {\n invariant(typeof onChunk === 'function', () => `onChunk callback must be a function but received ${ typeof onChunk }`);\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 invariant(Number.isInteger(retries) && retries >= 0, () => `Retry count must be a non-negative integer but received ${ retries }`);\n\n if (typeof delay === 'number') {\n invariant(delay >= 0, () => `Retry delay must be a non-negative number but received ${ delay }`);\n } else {\n invariant(typeof delay === 'function', () => `Retry delay must be a number or a function but received ${ typeof delay }`);\n }\n\n if (when != null) {\n invariant(Array.isArray(when) || typeof when === 'function', () => `Retry when condition must be an array of status codes or a function but received ${ typeof when }`);\n }\n\n if (onRetry != null) {\n invariant(typeof onRetry === 'function', () => `Retry onRetry callback must be a function but received ${ typeof onRetry }`);\n }\n\n return { retries, delay, when, onRetry };\n}\n"],"names":["Err","response","Ok","delay"],"mappings":"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"}
|
package/dist/main.mjs
CHANGED
|
@@ -27,8 +27,15 @@ class FetchError extends Error {
|
|
|
27
27
|
|
|
28
28
|
function fetchT(url, init) {
|
|
29
29
|
if (typeof url !== "string") {
|
|
30
|
-
invariant(url instanceof URL, () => `Url must be a string or URL object but received ${url}
|
|
30
|
+
invariant(url instanceof URL, () => `Url must be a string or URL object but received ${url}`);
|
|
31
31
|
}
|
|
32
|
+
const fetchInit = init ?? {};
|
|
33
|
+
const {
|
|
34
|
+
retries,
|
|
35
|
+
delay: retryDelay,
|
|
36
|
+
when: retryWhen,
|
|
37
|
+
onRetry
|
|
38
|
+
} = validateOptions(fetchInit);
|
|
32
39
|
const {
|
|
33
40
|
// default not abortable
|
|
34
41
|
abortable = false,
|
|
@@ -37,118 +44,167 @@ function fetchT(url, init) {
|
|
|
37
44
|
onProgress,
|
|
38
45
|
onChunk,
|
|
39
46
|
...rest
|
|
40
|
-
} =
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
invariant(typeof timeout === "number" && timeout > 0, () => `Timeout must be a number greater than 0 but received ${timeout}.`);
|
|
45
|
-
}
|
|
46
|
-
let controller;
|
|
47
|
-
if (abortable || shouldWaitTimeout) {
|
|
48
|
-
controller = new AbortController();
|
|
49
|
-
rest.signal = controller.signal;
|
|
47
|
+
} = fetchInit;
|
|
48
|
+
let userController;
|
|
49
|
+
if (abortable) {
|
|
50
|
+
userController = new AbortController();
|
|
50
51
|
}
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
const shouldRetry = (error, attempt) => {
|
|
53
|
+
if (error.name === ABORT_ERROR) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
if (!retryWhen) {
|
|
57
|
+
return !(error instanceof FetchError);
|
|
58
|
+
}
|
|
59
|
+
if (Array.isArray(retryWhen)) {
|
|
60
|
+
return error instanceof FetchError && retryWhen.includes(error.status);
|
|
61
|
+
}
|
|
62
|
+
return retryWhen(error, attempt);
|
|
63
|
+
};
|
|
64
|
+
const getRetryDelay = (attempt) => {
|
|
65
|
+
return typeof retryDelay === "function" ? retryDelay(attempt) : retryDelay;
|
|
66
|
+
};
|
|
67
|
+
const doFetch = async () => {
|
|
68
|
+
const signals = [];
|
|
69
|
+
if (userController) {
|
|
70
|
+
signals.push(userController.signal);
|
|
56
71
|
}
|
|
57
|
-
if (
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
if (typeof timeout === "number") {
|
|
73
|
+
signals.push(AbortSignal.timeout(timeout));
|
|
74
|
+
}
|
|
75
|
+
if (signals.length > 0) {
|
|
76
|
+
rest.signal = signals.length === 1 ? signals[0] : AbortSignal.any(signals);
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const res = await fetch(url, rest);
|
|
80
|
+
if (!res.ok) {
|
|
81
|
+
await res.body?.cancel();
|
|
82
|
+
return Err(new FetchError(res.statusText, res.status));
|
|
83
|
+
}
|
|
84
|
+
return await processResponse(res);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
return Err(
|
|
87
|
+
err instanceof Error ? err : wrapAbortReason(err)
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
const processResponse = async (res) => {
|
|
92
|
+
let response2 = res;
|
|
93
|
+
if (res.body && (onProgress || onChunk)) {
|
|
94
|
+
const [stream1, stream2] = res.body.tee();
|
|
95
|
+
const reader = stream1.getReader();
|
|
96
|
+
let totalByteLength = null;
|
|
97
|
+
let completedByteLength = 0;
|
|
98
|
+
if (onProgress) {
|
|
99
|
+
const contentLength = res.headers.get("content-length");
|
|
100
|
+
if (contentLength == null) {
|
|
101
|
+
try {
|
|
102
|
+
onProgress(Err(new Error("No content-length in response headers.")));
|
|
103
|
+
} catch {
|
|
74
104
|
}
|
|
105
|
+
} else {
|
|
106
|
+
totalByteLength = parseInt(contentLength, 10);
|
|
75
107
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
108
|
+
}
|
|
109
|
+
reader.read().then(function notify({ done, value }) {
|
|
110
|
+
if (done) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (onChunk) {
|
|
114
|
+
try {
|
|
115
|
+
onChunk(value);
|
|
116
|
+
} catch {
|
|
85
117
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
|
|
118
|
+
}
|
|
119
|
+
if (onProgress && totalByteLength != null) {
|
|
120
|
+
completedByteLength += value.byteLength;
|
|
121
|
+
try {
|
|
122
|
+
onProgress(Ok({
|
|
123
|
+
totalByteLength,
|
|
124
|
+
completedByteLength
|
|
125
|
+
}));
|
|
126
|
+
} catch {
|
|
95
127
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}).catch(() => {
|
|
99
|
-
});
|
|
100
|
-
res = new Response(stream2, {
|
|
101
|
-
headers: res.headers,
|
|
102
|
-
status: res.status,
|
|
103
|
-
statusText: res.statusText
|
|
128
|
+
}
|
|
129
|
+
reader.read().then(notify).catch(() => {
|
|
104
130
|
});
|
|
105
|
-
}
|
|
131
|
+
}).catch(() => {
|
|
132
|
+
});
|
|
133
|
+
response2 = new Response(stream2, {
|
|
134
|
+
headers: res.headers,
|
|
135
|
+
status: res.status,
|
|
136
|
+
statusText: res.statusText
|
|
137
|
+
});
|
|
106
138
|
}
|
|
107
139
|
switch (responseType) {
|
|
108
140
|
case "arraybuffer": {
|
|
109
|
-
return Ok(await
|
|
141
|
+
return Ok(await response2.arrayBuffer());
|
|
110
142
|
}
|
|
111
143
|
case "blob": {
|
|
112
|
-
return Ok(await
|
|
144
|
+
return Ok(await response2.blob());
|
|
113
145
|
}
|
|
114
146
|
case "json": {
|
|
115
147
|
try {
|
|
116
|
-
return Ok(await
|
|
148
|
+
return Ok(await response2.json());
|
|
117
149
|
} catch {
|
|
118
150
|
return Err(new Error("Response is invalid json while responseType is json"));
|
|
119
151
|
}
|
|
120
152
|
}
|
|
153
|
+
case "stream": {
|
|
154
|
+
return Ok(response2.body);
|
|
155
|
+
}
|
|
121
156
|
case "text": {
|
|
122
|
-
return Ok(await
|
|
157
|
+
return Ok(await response2.text());
|
|
123
158
|
}
|
|
124
159
|
default: {
|
|
125
|
-
return Ok(
|
|
160
|
+
return Ok(response2);
|
|
126
161
|
}
|
|
127
162
|
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
163
|
+
};
|
|
164
|
+
const fetchWithRetry = async () => {
|
|
165
|
+
let lastError;
|
|
166
|
+
let attempt = 0;
|
|
167
|
+
do {
|
|
168
|
+
if (attempt > 0) {
|
|
169
|
+
if (userController?.signal.aborted) {
|
|
170
|
+
return Err(userController.signal.reason);
|
|
171
|
+
}
|
|
172
|
+
const delayMs = getRetryDelay(attempt);
|
|
173
|
+
if (delayMs > 0) {
|
|
174
|
+
await delay(delayMs);
|
|
175
|
+
if (userController?.signal.aborted) {
|
|
176
|
+
return Err(userController.signal.reason);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
onRetry?.(lastError, attempt);
|
|
181
|
+
} catch {
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const result = await doFetch();
|
|
185
|
+
if (result.isOk()) {
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
lastError = result.unwrapErr();
|
|
189
|
+
attempt++;
|
|
190
|
+
} while (attempt <= retries && shouldRetry(lastError, attempt));
|
|
191
|
+
return Err(lastError);
|
|
192
|
+
};
|
|
193
|
+
const response = fetchWithRetry();
|
|
194
|
+
if (abortable && userController) {
|
|
144
195
|
return {
|
|
145
196
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
146
197
|
abort(reason) {
|
|
147
|
-
|
|
148
|
-
|
|
198
|
+
if (reason instanceof Error) {
|
|
199
|
+
userController.abort(reason);
|
|
200
|
+
} else if (reason != null) {
|
|
201
|
+
userController.abort(wrapAbortReason(reason));
|
|
202
|
+
} else {
|
|
203
|
+
userController.abort();
|
|
204
|
+
}
|
|
149
205
|
},
|
|
150
206
|
get aborted() {
|
|
151
|
-
return
|
|
207
|
+
return userController.signal.aborted;
|
|
152
208
|
},
|
|
153
209
|
get response() {
|
|
154
210
|
return response;
|
|
@@ -157,6 +213,62 @@ function fetchT(url, init) {
|
|
|
157
213
|
}
|
|
158
214
|
return response;
|
|
159
215
|
}
|
|
216
|
+
function delay(ms) {
|
|
217
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
218
|
+
}
|
|
219
|
+
function wrapAbortReason(reason) {
|
|
220
|
+
const error = new Error(typeof reason === "string" ? reason : String(reason));
|
|
221
|
+
error.name = ABORT_ERROR;
|
|
222
|
+
error.cause = reason;
|
|
223
|
+
return error;
|
|
224
|
+
}
|
|
225
|
+
function validateOptions(init) {
|
|
226
|
+
const {
|
|
227
|
+
responseType,
|
|
228
|
+
timeout,
|
|
229
|
+
retry: retryOptions = 0,
|
|
230
|
+
onProgress,
|
|
231
|
+
onChunk
|
|
232
|
+
} = init;
|
|
233
|
+
if (responseType != null) {
|
|
234
|
+
const validTypes = ["text", "arraybuffer", "blob", "json", "stream"];
|
|
235
|
+
invariant(validTypes.includes(responseType), () => `responseType must be one of ${validTypes.join(", ")} but received ${responseType}`);
|
|
236
|
+
}
|
|
237
|
+
if (timeout != null) {
|
|
238
|
+
invariant(typeof timeout === "number" && timeout > 0, () => `timeout must be a number greater than 0 but received ${timeout}`);
|
|
239
|
+
}
|
|
240
|
+
if (onProgress != null) {
|
|
241
|
+
invariant(typeof onProgress === "function", () => `onProgress callback must be a function but received ${typeof onProgress}`);
|
|
242
|
+
}
|
|
243
|
+
if (onChunk != null) {
|
|
244
|
+
invariant(typeof onChunk === "function", () => `onChunk callback must be a function but received ${typeof onChunk}`);
|
|
245
|
+
}
|
|
246
|
+
let retries = 0;
|
|
247
|
+
let delay2 = 0;
|
|
248
|
+
let when;
|
|
249
|
+
let onRetry;
|
|
250
|
+
if (typeof retryOptions === "number") {
|
|
251
|
+
retries = retryOptions;
|
|
252
|
+
} else if (retryOptions && typeof retryOptions === "object") {
|
|
253
|
+
retries = retryOptions.retries ?? 0;
|
|
254
|
+
delay2 = retryOptions.delay ?? 0;
|
|
255
|
+
when = retryOptions.when;
|
|
256
|
+
onRetry = retryOptions.onRetry;
|
|
257
|
+
}
|
|
258
|
+
invariant(Number.isInteger(retries) && retries >= 0, () => `Retry count must be a non-negative integer but received ${retries}`);
|
|
259
|
+
if (typeof delay2 === "number") {
|
|
260
|
+
invariant(delay2 >= 0, () => `Retry delay must be a non-negative number but received ${delay2}`);
|
|
261
|
+
} else {
|
|
262
|
+
invariant(typeof delay2 === "function", () => `Retry delay must be a number or a function but received ${typeof delay2}`);
|
|
263
|
+
}
|
|
264
|
+
if (when != null) {
|
|
265
|
+
invariant(Array.isArray(when) || typeof when === "function", () => `Retry when condition must be an array of status codes or a function but received ${typeof when}`);
|
|
266
|
+
}
|
|
267
|
+
if (onRetry != null) {
|
|
268
|
+
invariant(typeof onRetry === "function", () => `Retry onRetry callback must be a function but received ${typeof onRetry}`);
|
|
269
|
+
}
|
|
270
|
+
return { retries, delay: delay2, when, onRetry };
|
|
271
|
+
}
|
|
160
272
|
|
|
161
273
|
export { ABORT_ERROR, FetchError, TIMEOUT_ERROR, fetchT };
|
|
162
274
|
//# sourceMappingURL=main.mjs.map
|