@happy-ts/fetch-t
Version:
Abortable fetch wrapper with the ability to specify the return type.
1 lines • 19.2 kB
Source Map (JSON)
{"version":3,"file":"main.mjs","sources":["../src/fetch/constants.ts","../src/fetch/defines.ts","../src/fetch/fetch.ts"],"sourcesContent":["/**\n * Name of abort error;\n */\nexport const ABORT_ERROR = 'AbortError' as const;\n\n/**\n * Name of timeout error;\n */\nexport const TIMEOUT_ERROR = 'TimeoutError' as const;","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type { AsyncResult, IOResult } from 'happy-rusty';\n\n/**\n * Represents the response of a fetch operation, encapsulating the result data or any error that occurred.\n *\n * @typeParam T - The type of the data expected in the response.\n */\nexport type FetchResponse<T, E = any> = AsyncResult<T, E>;\n\n/**\n * Defines the structure and behavior of a fetch task, including the ability to abort the task and check its status.\n *\n * @typeParam T - The type of the data expected in the response.\n */\nexport interface FetchTask<T> {\n /**\n * Aborts the fetch task, optionally with a reason for the abortion.\n *\n * @param reason - An optional parameter to indicate why the task was aborted.\n */\n abort(reason?: any): void;\n\n /**\n * Indicates whether the fetch task has been aborted.\n */\n readonly aborted: boolean;\n\n /**\n * The response of the fetch task, represented as an `AsyncResult`.\n */\n readonly response: FetchResponse<T>;\n}\n\n/**\n * Specifies the expected response type of the fetch request.\n */\nexport type FetchResponseType = 'text' | 'arraybuffer' | 'blob' | 'json';\n\n/**\n * Represents the progress of a fetch operation.\n */\nexport interface FetchProgress {\n /**\n * The total number of bytes to be received.\n */\n totalByteLength: number;\n\n /**\n * The number of bytes received so far.\n */\n completedByteLength: number;\n}\n\n/**\n * Extends the standard `RequestInit` interface from the Fetch API to include additional custom options.\n */\nexport interface FetchInit extends RequestInit {\n /**\n * Indicates whether the fetch request should be abortable.\n */\n abortable?: boolean;\n\n /**\n * Specifies the expected response type of the fetch request.\n */\n responseType?: FetchResponseType;\n\n /**\n * Specifies the maximum time in milliseconds to wait for the fetch request to complete.\n */\n timeout?: number;\n\n /**\n * Specifies a function to be called when the fetch request makes progress.\n * @param progressResult - The progress of the fetch request.\n */\n onProgress?: (progressResult: IOResult<FetchProgress>) => void;\n\n /**\n * Specifies a function to be called when the fetch request receives a chunk of data.\n * @param chunk - The chunk of data received.\n */\n onChunk?: (chunk: Uint8Array) => void;\n}\n\n/**\n * Represents an error that occurred during a fetch operation when the response is not ok.\n */\nexport class FetchError extends Error {\n /**\n * The name of the error.\n */\n name = 'FetchError';\n /**\n * The status code of the response.\n */\n status = 0;\n\n constructor(message: string, status: number) {\n super(message);\n this.status = status;\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 a `FetchTask` representing the operation.\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, including custom `FetchInit` properties.\n * @returns A `FetchTask` representing the 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 a `FetchTask` representing the operation.\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, including custom `FetchInit` properties.\n * @returns A `FetchTask` representing the 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 a `FetchTask` representing the operation.\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, including custom `FetchInit` properties.\n * @returns A `FetchTask` representing the 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 a `FetchTask` representing the operation.\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, including custom `FetchInit` properties.\n * @returns A `FetchTask` representing the operation with a response parsed as JSON.\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 and returns a `FetchResponse` representing the operation.\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, specifying the response type as '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 and returns a `FetchResponse` representing the operation.\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, specifying the response type as '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 and returns a `FetchResponse` representing the operation.\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, specifying the response type as '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, returning a `FetchResponse` representing the operation.\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, specifying the response type as 'json'.\n * @returns A `FetchResponse` representing the operation with a response parsed as JSON.\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 a `FetchTask` representing the operation 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, indicating that the operation should be abortable.\n * @returns A `FetchTask` representing the operation with a generic `Response`.\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` or `FetchTask` based on the provided options.\n *\n * @typeParam T - The expected type of the response data when not using a specific `responseType`.\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, including custom `FetchInit` properties.\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 * Fetches a resource from the network and returns either a `FetchTask` or `FetchResponse` based on the provided options.\n *\n * @typeParam T - The expected type of the response data when not using a specific `responseType`.\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, including custom `FetchInit` properties.\n * @returns A `FetchTask` or `FetchResponse` depending on the provided options in `init`.\n */\nexport function fetchT<T>(url: string | URL, init?: FetchInit): FetchTask<T> | FetchResponse<T> {\n // most cases\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 abort able\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 // may has no content-length\n let totalByteLength: number | null = null;\n let completedByteLength = 0;\n\n if (shouldNotifyProgress) {\n // try to get content-length\n // compatible with http/2\n const contentLength = res.headers.get('content-length') ?? res.headers.get('Content-Length');\n if (contentLength == null) {\n // response headers has no content-length\n onProgress(Err(new Error('No content-length in response headers.')));\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 onChunk(value);\n }\n\n // notify progress\n if (shouldNotifyProgress && totalByteLength != null) {\n completedByteLength += value.byteLength;\n onProgress(Ok({\n totalByteLength,\n completedByteLength,\n }));\n }\n\n\n // continue to read\n reader.read().then(notify);\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 if (!controller.signal.aborted) {\n const error = new Error();\n error.name = TIMEOUT_ERROR;\n controller.abort(error);\n }\n }, timeout);\n\n cancelTimer = (): void => {\n if (timer) {\n clearTimeout(timer);\n }\n\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 } else {\n return response;\n }\n}"],"names":[],"mappings":"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"}