@happy-ts/fetch-t 1.8.1 → 1.9.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 CHANGED
@@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.9.0] - 2026-01-13
9
+
10
+ ### Changed
11
+
12
+ - **BREAKING**: Rename `FetchResponse<T, E>` to `FetchResult<T>` (simplified type, `E` is always `Error`)
13
+ - **BREAKING**: Rename `FetchTask.response` property to `FetchTask.result`
14
+
15
+ ### Migration
16
+
17
+ ```typescript
18
+ // Before
19
+ import type { FetchResponse } from '@happy-ts/fetch-t';
20
+ const task = fetchT(url, { abortable: true });
21
+ const result = await task.response;
22
+
23
+ // After
24
+ import type { FetchResult } from '@happy-ts/fetch-t';
25
+ const task = fetchT(url, { abortable: true });
26
+ const result = await task.result;
27
+ ```
28
+
8
29
  ## [1.8.1] - 2026-01-13
9
30
 
10
31
  ### Changed
@@ -210,6 +231,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
210
231
  - Timeout support
211
232
  - Rust-like Result type error handling via `happy-rusty` library
212
233
 
234
+ [1.9.0]: https://github.com/JiangJie/fetch-t/compare/v1.8.1...v1.9.0
213
235
  [1.8.1]: https://github.com/JiangJie/fetch-t/compare/v1.8.0...v1.8.1
214
236
  [1.8.0]: https://github.com/JiangJie/fetch-t/compare/v1.7.0...v1.8.0
215
237
  [1.7.0]: https://github.com/JiangJie/fetch-t/compare/v1.6.0...v1.7.0
package/README.cn.md CHANGED
@@ -78,7 +78,7 @@ setTimeout(() => {
78
78
  task.abort('用户取消');
79
79
  }, 5000);
80
80
 
81
- const result = await task.response;
81
+ const result = await task.result;
82
82
  ```
83
83
 
84
84
  ### 自动重试
package/README.md CHANGED
@@ -78,7 +78,7 @@ setTimeout(() => {
78
78
  task.abort('User cancelled');
79
79
  }, 5000);
80
80
 
81
- const result = await task.response;
81
+ const result = await task.result;
82
82
  ```
83
83
 
84
84
  ### Automatic Retry
package/dist/main.cjs CHANGED
@@ -84,24 +84,24 @@ function fetchT(url, init) {
84
84
  const doFetch = async () => {
85
85
  configureSignal();
86
86
  try {
87
- const response2 = await fetch(parsedUrl, rest);
88
- if (!response2.ok) {
89
- response2.body?.cancel().catch(() => {
87
+ const response = await fetch(parsedUrl, rest);
88
+ if (!response.ok) {
89
+ response.body?.cancel().catch(() => {
90
90
  });
91
- return happyRusty.Err(new FetchError(response2.statusText, response2.status));
91
+ return happyRusty.Err(new FetchError(response.statusText, response.status));
92
92
  }
93
- return await processResponse(response2);
93
+ return await processResponse(response);
94
94
  } catch (err) {
95
95
  return happyRusty.Err(
96
96
  err instanceof Error ? err : wrapAbortReason(err)
97
97
  );
98
98
  }
99
99
  };
100
- const setupProgressCallbacks = async (response2) => {
100
+ const setupProgressCallbacks = async (response) => {
101
101
  let totalByteLength;
102
102
  let completedByteLength = 0;
103
103
  if (onProgress) {
104
- const contentLength = response2.headers.get("content-length");
104
+ const contentLength = response.headers.get("content-length");
105
105
  if (contentLength == null) {
106
106
  try {
107
107
  onProgress(happyRusty.Err(new Error("No content-length in response headers")));
@@ -111,7 +111,7 @@ function fetchT(url, init) {
111
111
  totalByteLength = Number.parseInt(contentLength, 10);
112
112
  }
113
113
  }
114
- const body = response2.clone().body;
114
+ const body = response.clone().body;
115
115
  try {
116
116
  for await (const chunk of body) {
117
117
  if (onChunk) {
@@ -134,41 +134,41 @@ function fetchT(url, init) {
134
134
  } catch {
135
135
  }
136
136
  };
137
- const processResponse = async (response2) => {
138
- if (response2.body && (onProgress || onChunk)) {
139
- setupProgressCallbacks(response2);
137
+ const processResponse = async (response) => {
138
+ if (response.body && (onProgress || onChunk)) {
139
+ setupProgressCallbacks(response);
140
140
  }
141
141
  switch (responseType) {
142
142
  case "json": {
143
- if (response2.body == null) {
143
+ if (response.body == null) {
144
144
  return happyRusty.Ok(null);
145
145
  }
146
146
  try {
147
- return happyRusty.Ok(await response2.json());
147
+ return happyRusty.Ok(await response.json());
148
148
  } catch {
149
149
  return happyRusty.Err(new Error("Response is invalid json while responseType is json"));
150
150
  }
151
151
  }
152
152
  case "text": {
153
- return happyRusty.Ok(await response2.text());
153
+ return happyRusty.Ok(await response.text());
154
154
  }
155
155
  case "bytes": {
156
- if (typeof response2.bytes === "function") {
157
- return happyRusty.Ok(await response2.bytes());
156
+ if (typeof response.bytes === "function") {
157
+ return happyRusty.Ok(await response.bytes());
158
158
  }
159
- return happyRusty.Ok(new Uint8Array(await response2.arrayBuffer()));
159
+ return happyRusty.Ok(new Uint8Array(await response.arrayBuffer()));
160
160
  }
161
161
  case "arraybuffer": {
162
- return happyRusty.Ok(await response2.arrayBuffer());
162
+ return happyRusty.Ok(await response.arrayBuffer());
163
163
  }
164
164
  case "blob": {
165
- return happyRusty.Ok(await response2.blob());
165
+ return happyRusty.Ok(await response.blob());
166
166
  }
167
167
  case "stream": {
168
- return happyRusty.Ok(response2.body);
168
+ return happyRusty.Ok(response.body);
169
169
  }
170
170
  default: {
171
- return happyRusty.Ok(response2);
171
+ return happyRusty.Ok(response);
172
172
  }
173
173
  }
174
174
  };
@@ -192,16 +192,16 @@ function fetchT(url, init) {
192
192
  } catch {
193
193
  }
194
194
  }
195
- const result = await doFetch();
196
- if (result.isOk()) {
197
- return result;
195
+ const result2 = await doFetch();
196
+ if (result2.isOk()) {
197
+ return result2;
198
198
  }
199
- lastError = result.unwrapErr();
199
+ lastError = result2.unwrapErr();
200
200
  attempt++;
201
201
  } while (attempt <= retries && shouldRetry(lastError, attempt));
202
202
  return happyRusty.Err(lastError);
203
203
  };
204
- const response = fetchWithRetry();
204
+ const result = fetchWithRetry();
205
205
  if (abortable && userController) {
206
206
  return {
207
207
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -217,12 +217,12 @@ function fetchT(url, init) {
217
217
  get aborted() {
218
218
  return userController.signal.aborted;
219
219
  },
220
- get response() {
221
- return response;
220
+ get result() {
221
+ return result;
222
222
  }
223
223
  };
224
224
  }
225
- return response;
225
+ return result;
226
226
  }
227
227
  function delay(ms) {
228
228
  return new Promise((resolve) => setTimeout(resolve, ms));
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 * 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 * @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 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 * - `'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 * @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 * @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 * - `'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 * @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 * @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 * @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 FetchResponse, type FetchResponseData, type FetchResponseType, 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 | 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 `FetchResponse` representing the operation with a `string` response.\n */\nexport function fetchT(url: string | URL, init: FetchInit & {\n abortable?: false;\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'` and `abortable` must be `false` or omitted.\n * @returns A `FetchResponse` representing the operation with an `ArrayBuffer` response.\n */\nexport function fetchT(url: string | URL, init: FetchInit & {\n abortable?: false;\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'` and `abortable` must be `false` or omitted.\n * @returns A `FetchResponse` representing the operation with a `Blob` response.\n */\nexport function fetchT(url: string | URL, init: FetchInit & {\n abortable?: false;\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'` and `abortable` must be `false` or omitted.\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 abortable?: false;\n responseType: 'json';\n}): FetchResponse<T | null, 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'` and `abortable` must be `false` or omitted.\n * @returns A `FetchResponse` 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}): FetchResponse<ReadableStream<Uint8Array<ArrayBuffer>> | null, Error>;\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 `FetchResponse` representing the operation with a `Uint8Array<ArrayBuffer>` response.\n */\nexport function fetchT(url: string | URL, init: FetchInit & {\n abortable?: false;\n responseType: 'bytes';\n}): FetchResponse<Uint8Array<ArrayBuffer>, Error>;\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 `FetchResponse` representing the operation with a `FetchResponseData` response.\n */\nexport function fetchT(url: string | URL, init: FetchInit & {\n abortable?: false;\n responseType: FetchResponseType;\n}): FetchResponse<FetchResponseData, 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, and `abortable` must be `false` or omitted.\n * @returns A `FetchResponse` representing the operation with a `Response` object.\n */\nexport function fetchT(url: string | URL, init?: FetchInit & {\n abortable?: false;\n}): 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(url: string | URL, init?: FetchInit): FetchTask<FetchResponseData> | FetchResponse<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 (): FetchResponse<FetchResponseData, 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 // 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 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<FetchResponseData> {\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', '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 *\n * @param url - The URL to validate, either a string or URL object.\n * @returns The parsed URL object.\n * @throws {TypeError} If the URL is invalid or cannot be parsed.\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"],"names":["response","Err","Ok","delay"],"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.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 * @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 * @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 *\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 *\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 * @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 * @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 `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 * @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 * @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 * @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/**\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 * 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 *\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/**\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', '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 *\n * @param url - The URL to validate, either a string or URL object.\n * @returns The parsed URL object.\n * @throws {TypeError} If the URL is invalid or cannot be parsed.\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"],"names":["Err","Ok","result","delay"],"mappings":"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"}
package/dist/main.mjs CHANGED
@@ -80,24 +80,24 @@ function fetchT(url, init) {
80
80
  const doFetch = async () => {
81
81
  configureSignal();
82
82
  try {
83
- const response2 = await fetch(parsedUrl, rest);
84
- if (!response2.ok) {
85
- response2.body?.cancel().catch(() => {
83
+ const response = await fetch(parsedUrl, rest);
84
+ if (!response.ok) {
85
+ response.body?.cancel().catch(() => {
86
86
  });
87
- return Err(new FetchError(response2.statusText, response2.status));
87
+ return Err(new FetchError(response.statusText, response.status));
88
88
  }
89
- return await processResponse(response2);
89
+ return await processResponse(response);
90
90
  } catch (err) {
91
91
  return Err(
92
92
  err instanceof Error ? err : wrapAbortReason(err)
93
93
  );
94
94
  }
95
95
  };
96
- const setupProgressCallbacks = async (response2) => {
96
+ const setupProgressCallbacks = async (response) => {
97
97
  let totalByteLength;
98
98
  let completedByteLength = 0;
99
99
  if (onProgress) {
100
- const contentLength = response2.headers.get("content-length");
100
+ const contentLength = response.headers.get("content-length");
101
101
  if (contentLength == null) {
102
102
  try {
103
103
  onProgress(Err(new Error("No content-length in response headers")));
@@ -107,7 +107,7 @@ function fetchT(url, init) {
107
107
  totalByteLength = Number.parseInt(contentLength, 10);
108
108
  }
109
109
  }
110
- const body = response2.clone().body;
110
+ const body = response.clone().body;
111
111
  try {
112
112
  for await (const chunk of body) {
113
113
  if (onChunk) {
@@ -130,41 +130,41 @@ function fetchT(url, init) {
130
130
  } catch {
131
131
  }
132
132
  };
133
- const processResponse = async (response2) => {
134
- if (response2.body && (onProgress || onChunk)) {
135
- setupProgressCallbacks(response2);
133
+ const processResponse = async (response) => {
134
+ if (response.body && (onProgress || onChunk)) {
135
+ setupProgressCallbacks(response);
136
136
  }
137
137
  switch (responseType) {
138
138
  case "json": {
139
- if (response2.body == null) {
139
+ if (response.body == null) {
140
140
  return Ok(null);
141
141
  }
142
142
  try {
143
- return Ok(await response2.json());
143
+ return Ok(await response.json());
144
144
  } catch {
145
145
  return Err(new Error("Response is invalid json while responseType is json"));
146
146
  }
147
147
  }
148
148
  case "text": {
149
- return Ok(await response2.text());
149
+ return Ok(await response.text());
150
150
  }
151
151
  case "bytes": {
152
- if (typeof response2.bytes === "function") {
153
- return Ok(await response2.bytes());
152
+ if (typeof response.bytes === "function") {
153
+ return Ok(await response.bytes());
154
154
  }
155
- return Ok(new Uint8Array(await response2.arrayBuffer()));
155
+ return Ok(new Uint8Array(await response.arrayBuffer()));
156
156
  }
157
157
  case "arraybuffer": {
158
- return Ok(await response2.arrayBuffer());
158
+ return Ok(await response.arrayBuffer());
159
159
  }
160
160
  case "blob": {
161
- return Ok(await response2.blob());
161
+ return Ok(await response.blob());
162
162
  }
163
163
  case "stream": {
164
- return Ok(response2.body);
164
+ return Ok(response.body);
165
165
  }
166
166
  default: {
167
- return Ok(response2);
167
+ return Ok(response);
168
168
  }
169
169
  }
170
170
  };
@@ -188,16 +188,16 @@ function fetchT(url, init) {
188
188
  } catch {
189
189
  }
190
190
  }
191
- const result = await doFetch();
192
- if (result.isOk()) {
193
- return result;
191
+ const result2 = await doFetch();
192
+ if (result2.isOk()) {
193
+ return result2;
194
194
  }
195
- lastError = result.unwrapErr();
195
+ lastError = result2.unwrapErr();
196
196
  attempt++;
197
197
  } while (attempt <= retries && shouldRetry(lastError, attempt));
198
198
  return Err(lastError);
199
199
  };
200
- const response = fetchWithRetry();
200
+ const result = fetchWithRetry();
201
201
  if (abortable && userController) {
202
202
  return {
203
203
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -213,12 +213,12 @@ function fetchT(url, init) {
213
213
  get aborted() {
214
214
  return userController.signal.aborted;
215
215
  },
216
- get response() {
217
- return response;
216
+ get result() {
217
+ return result;
218
218
  }
219
219
  };
220
220
  }
221
- return response;
221
+ return result;
222
222
  }
223
223
  function delay(ms) {
224
224
  return new Promise((resolve) => setTimeout(resolve, ms));
package/dist/main.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"main.mjs","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 * 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 * @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 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 * - `'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 * @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 * @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 * - `'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 * @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 * @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 * @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 FetchResponse, type FetchResponseData, type FetchResponseType, 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 | 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 `FetchResponse` representing the operation with a `string` response.\n */\nexport function fetchT(url: string | URL, init: FetchInit & {\n abortable?: false;\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'` and `abortable` must be `false` or omitted.\n * @returns A `FetchResponse` representing the operation with an `ArrayBuffer` response.\n */\nexport function fetchT(url: string | URL, init: FetchInit & {\n abortable?: false;\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'` and `abortable` must be `false` or omitted.\n * @returns A `FetchResponse` representing the operation with a `Blob` response.\n */\nexport function fetchT(url: string | URL, init: FetchInit & {\n abortable?: false;\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'` and `abortable` must be `false` or omitted.\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 abortable?: false;\n responseType: 'json';\n}): FetchResponse<T | null, 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'` and `abortable` must be `false` or omitted.\n * @returns A `FetchResponse` 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}): FetchResponse<ReadableStream<Uint8Array<ArrayBuffer>> | null, Error>;\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 `FetchResponse` representing the operation with a `Uint8Array<ArrayBuffer>` response.\n */\nexport function fetchT(url: string | URL, init: FetchInit & {\n abortable?: false;\n responseType: 'bytes';\n}): FetchResponse<Uint8Array<ArrayBuffer>, Error>;\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 `FetchResponse` representing the operation with a `FetchResponseData` response.\n */\nexport function fetchT(url: string | URL, init: FetchInit & {\n abortable?: false;\n responseType: FetchResponseType;\n}): FetchResponse<FetchResponseData, 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, and `abortable` must be `false` or omitted.\n * @returns A `FetchResponse` representing the operation with a `Response` object.\n */\nexport function fetchT(url: string | URL, init?: FetchInit & {\n abortable?: false;\n}): 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(url: string | URL, init?: FetchInit): FetchTask<FetchResponseData> | FetchResponse<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 (): FetchResponse<FetchResponseData, 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 // 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 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<FetchResponseData> {\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', '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 *\n * @param url - The URL to validate, either a string or URL object.\n * @returns The parsed URL object.\n * @throws {TypeError} If the URL is invalid or cannot be parsed.\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"],"names":["response","delay"],"mappings":"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"}
1
+ {"version":3,"file":"main.mjs","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.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 * @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 * @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 *\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 *\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 * @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 * @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 `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 * @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 * @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 * @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/**\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 * 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 *\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/**\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', '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 *\n * @param url - The URL to validate, either a string or URL object.\n * @returns The parsed URL object.\n * @throws {TypeError} If the URL is invalid or cannot be parsed.\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"],"names":["result","delay"],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AsyncResult } from 'happy-rusty';
1
+ import { AsyncIOResult } from 'happy-rusty';
2
2
  import { IOResult } from 'happy-rusty';
3
3
 
4
4
  /**
@@ -14,7 +14,7 @@ import { IOResult } from 'happy-rusty';
14
14
  * const task = fetchT('https://api.example.com/data', { abortable: true });
15
15
  * task.abort();
16
16
  *
17
- * const result = await task.response;
17
+ * const result = await task.result;
18
18
  * result.inspectErr((err) => {
19
19
  * if (err.name === ABORT_ERROR) {
20
20
  * console.log('Request was aborted');
@@ -109,7 +109,7 @@ export declare class FetchError extends Error {
109
109
  */
110
110
  export declare interface FetchInit extends RequestInit {
111
111
  /**
112
- * When `true`, returns a `FetchTask` instead of `FetchResponse`.
112
+ * When `true`, returns a `FetchTask` instead of `FetchResult`.
113
113
  *
114
114
  * The `FetchTask` provides `abort()` method and `aborted` status.
115
115
  *
@@ -215,32 +215,6 @@ export declare interface FetchProgress {
215
215
  completedByteLength: number;
216
216
  }
217
217
 
218
- /**
219
- * Represents the response of a fetch operation as an async Result type.
220
- *
221
- * This is an alias for `AsyncResult<T, E>` from the `happy-rusty` library,
222
- * providing Rust-like error handling without throwing exceptions.
223
- *
224
- * @typeParam T - The type of the data expected in a successful response.
225
- * @typeParam E - The type of the error (defaults to `any`). Typically `Error` or `FetchError`.
226
- *
227
- * @example
228
- * ```typescript
229
- * import { fetchT, type FetchResponse } from '@happy-ts/fetch-t';
230
- *
231
- * // FetchResponse is a Promise that resolves to Result<T, E>
232
- * const response: FetchResponse<string> = fetchT('https://api.example.com', {
233
- * responseType: 'text',
234
- * });
235
- *
236
- * const result = await response;
237
- * result
238
- * .inspect((text) => console.log('Success:', text))
239
- * .inspectErr((err) => console.error('Error:', err));
240
- * ```
241
- */
242
- export declare type FetchResponse<T, E = any> = AsyncResult<T, E>;
243
-
244
218
  /**
245
219
  * Union type of all possible fetchT response data types.
246
220
  *
@@ -282,6 +256,31 @@ export declare type FetchResponseData = string | ArrayBuffer | Blob | Uint8Array
282
256
  */
283
257
  export declare type FetchResponseType = 'text' | 'arraybuffer' | 'blob' | 'json' | 'bytes' | 'stream';
284
258
 
259
+ /**
260
+ * Represents the result of a fetch operation as an async Result type.
261
+ *
262
+ * This is an alias for `AsyncIOResult<T>` from the `happy-rusty` library,
263
+ * providing Rust-like error handling without throwing exceptions.
264
+ *
265
+ * @typeParam T - The type of the data expected in a successful response.
266
+ *
267
+ * @example
268
+ * ```typescript
269
+ * import { fetchT, type FetchResult } from '@happy-ts/fetch-t';
270
+ *
271
+ * // FetchResult is a Promise that resolves to Result<T, Error>
272
+ * const result: FetchResult<string> = fetchT('https://api.example.com', {
273
+ * responseType: 'text',
274
+ * });
275
+ *
276
+ * const res = await result;
277
+ * res
278
+ * .inspect((text) => console.log('Success:', text))
279
+ * .inspectErr((err) => console.error('Error:', err));
280
+ * ```
281
+ */
282
+ export declare type FetchResult<T> = AsyncIOResult<T>;
283
+
285
284
  /**
286
285
  * Options for configuring retry behavior.
287
286
  */
@@ -411,36 +410,36 @@ export declare function fetchT(url: string | URL, init: FetchInit & {
411
410
  *
412
411
  * @param url - The resource to fetch. Can be a URL object or a string representing a URL.
413
412
  * @param init - Additional options for the fetch operation, must include `responseType: 'text'` and `abortable` must be `false` or omitted.
414
- * @returns A `FetchResponse` representing the operation with a `string` response.
413
+ * @returns A `FetchResult` representing the operation with a `string` response.
415
414
  */
416
415
  export declare function fetchT(url: string | URL, init: FetchInit & {
417
416
  abortable?: false;
418
417
  responseType: 'text';
419
- }): FetchResponse<string, Error>;
418
+ }): FetchResult<string>;
420
419
 
421
420
  /**
422
421
  * Fetches a resource from the network as an ArrayBuffer.
423
422
  *
424
423
  * @param url - The resource to fetch. Can be a URL object or a string representing a URL.
425
424
  * @param init - Additional options for the fetch operation, must include `responseType: 'arraybuffer'` and `abortable` must be `false` or omitted.
426
- * @returns A `FetchResponse` representing the operation with an `ArrayBuffer` response.
425
+ * @returns A `FetchResult` representing the operation with an `ArrayBuffer` response.
427
426
  */
428
427
  export declare function fetchT(url: string | URL, init: FetchInit & {
429
428
  abortable?: false;
430
429
  responseType: 'arraybuffer';
431
- }): FetchResponse<ArrayBuffer, Error>;
430
+ }): FetchResult<ArrayBuffer>;
432
431
 
433
432
  /**
434
433
  * Fetches a resource from the network as a Blob.
435
434
  *
436
435
  * @param url - The resource to fetch. Can be a URL object or a string representing a URL.
437
436
  * @param init - Additional options for the fetch operation, must include `responseType: 'blob'` and `abortable` must be `false` or omitted.
438
- * @returns A `FetchResponse` representing the operation with a `Blob` response.
437
+ * @returns A `FetchResult` representing the operation with a `Blob` response.
439
438
  */
440
439
  export declare function fetchT(url: string | URL, init: FetchInit & {
441
440
  abortable?: false;
442
441
  responseType: 'blob';
443
- }): FetchResponse<Blob, Error>;
442
+ }): FetchResult<Blob>;
444
443
 
445
444
  /**
446
445
  * Fetches a resource from the network and parses it as JSON.
@@ -448,36 +447,36 @@ export declare function fetchT(url: string | URL, init: FetchInit & {
448
447
  * @typeParam T - The expected type of the parsed JSON data.
449
448
  * @param url - The resource to fetch. Can be a URL object or a string representing a URL.
450
449
  * @param init - Additional options for the fetch operation, must include `responseType: 'json'` and `abortable` must be `false` or omitted.
451
- * @returns A `FetchResponse` representing the operation with a response parsed as type `T`.
450
+ * @returns A `FetchResult` representing the operation with a response parsed as type `T`.
452
451
  */
453
452
  export declare function fetchT<T>(url: string | URL, init: FetchInit & {
454
453
  abortable?: false;
455
454
  responseType: 'json';
456
- }): FetchResponse<T | null, Error>;
455
+ }): FetchResult<T | null>;
457
456
 
458
457
  /**
459
458
  * Fetches a resource from the network as a ReadableStream.
460
459
  *
461
460
  * @param url - The resource to fetch. Can be a URL object or a string representing a URL.
462
461
  * @param init - Additional options for the fetch operation, must include `responseType: 'stream'` and `abortable` must be `false` or omitted.
463
- * @returns A `FetchResponse` representing the operation with a `ReadableStream<Uint8Array<ArrayBuffer>>` response.
462
+ * @returns A `FetchResult` representing the operation with a `ReadableStream<Uint8Array<ArrayBuffer>>` response.
464
463
  */
465
464
  export declare function fetchT(url: string | URL, init: FetchInit & {
466
465
  abortable?: false;
467
466
  responseType: 'stream';
468
- }): FetchResponse<ReadableStream<Uint8Array<ArrayBuffer>> | null, Error>;
467
+ }): FetchResult<ReadableStream<Uint8Array<ArrayBuffer>> | null>;
469
468
 
470
469
  /**
471
470
  * Fetches a resource from the network as a Uint8Array<ArrayBuffer>.
472
471
  *
473
472
  * @param url - The resource to fetch. Can be a URL object or a string representing a URL.
474
473
  * @param init - Additional options for the fetch operation, must include `responseType: 'bytes'` and `abortable` must be `false` or omitted.
475
- * @returns A `FetchResponse` representing the operation with a `Uint8Array<ArrayBuffer>` response.
474
+ * @returns A `FetchResult` representing the operation with a `Uint8Array<ArrayBuffer>` response.
476
475
  */
477
476
  export declare function fetchT(url: string | URL, init: FetchInit & {
478
477
  abortable?: false;
479
478
  responseType: 'bytes';
480
- }): FetchResponse<Uint8Array<ArrayBuffer>, Error>;
479
+ }): FetchResult<Uint8Array<ArrayBuffer>>;
481
480
 
482
481
  /**
483
482
  * Fetches a resource from the network with a dynamic response type (non-abortable).
@@ -486,12 +485,12 @@ export declare function fetchT(url: string | URL, init: FetchInit & {
486
485
  *
487
486
  * @param url - The resource to fetch. Can be a URL object or a string representing a URL.
488
487
  * @param init - Additional options for the fetch operation with a `FetchResponseType`, and `abortable` must be `false` or omitted.
489
- * @returns A `FetchResponse` representing the operation with a `FetchResponseData` response.
488
+ * @returns A `FetchResult` representing the operation with a `FetchResponseData` response.
490
489
  */
491
490
  export declare function fetchT(url: string | URL, init: FetchInit & {
492
491
  abortable?: false;
493
492
  responseType: FetchResponseType;
494
- }): FetchResponse<FetchResponseData, Error>;
493
+ }): FetchResult<FetchResponseData>;
495
494
 
496
495
  /**
497
496
  * Fetches a resource from the network and returns an abortable `FetchTask` with a generic `Response`.
@@ -505,15 +504,15 @@ export declare function fetchT(url: string | URL, init: FetchInit & {
505
504
  }): FetchTask<Response>;
506
505
 
507
506
  /**
508
- * Fetches a resource from the network and returns a `FetchResponse` with a generic `Response` object.
507
+ * Fetches a resource from the network and returns a `FetchResult` with a generic `Response` object.
509
508
  *
510
509
  * @param url - The resource to fetch. Can be a URL object or a string representing a URL.
511
510
  * @param init - Optional additional options for the fetch operation, and `abortable` must be `false` or omitted.
512
- * @returns A `FetchResponse` representing the operation with a `Response` object.
511
+ * @returns A `FetchResult` representing the operation with a `Response` object.
513
512
  */
514
513
  export declare function fetchT(url: string | URL, init?: FetchInit & {
515
514
  abortable?: false;
516
- }): FetchResponse<Response>;
515
+ }): FetchResult<Response>;
517
516
 
518
517
  /**
519
518
  * Represents an abortable fetch operation with control methods.
@@ -543,8 +542,8 @@ export declare function fetchT(url: string | URL, init?: FetchInit & {
543
542
  * // Abort with optional reason
544
543
  * task.abort('User navigated away');
545
544
  *
546
- * // Access the response (will be an error after abort)
547
- * const result = await task.response;
545
+ * // Access the result (will be an error after abort)
546
+ * const result = await task.result;
548
547
  * result.inspectErr((err) => console.log('Aborted:', err.message));
549
548
  * ```
550
549
  */
@@ -552,7 +551,7 @@ export declare interface FetchTask<T> {
552
551
  /**
553
552
  * Aborts the fetch task, optionally with a reason.
554
553
  *
555
- * Once aborted, the `response` promise will resolve to an `Err` containing
554
+ * Once aborted, the `result` promise will resolve to an `Err` containing
556
555
  * an `AbortError`. The abort reason can be any value and will be passed
557
556
  * to the underlying `AbortController.abort()`.
558
557
  *
@@ -567,11 +566,11 @@ export declare interface FetchTask<T> {
567
566
  */
568
567
  readonly aborted: boolean;
569
568
  /**
570
- * The response promise of the fetch task.
569
+ * The result promise of the fetch task.
571
570
  *
572
571
  * Resolves to `Ok<T>` on success, or `Err<Error>` on failure (including abort).
573
572
  */
574
- readonly response: FetchResponse<T>;
573
+ readonly result: FetchResult<T>;
575
574
  }
576
575
 
577
576
  /**
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Type-safe Fetch API wrapper with abortable requests, timeout support, progress tracking, automatic retry, and Rust-like Result error handling.",
4
4
  "author": "jiang115jie@gmail.com",
5
5
  "license": "MIT",
6
- "version": "1.8.1",
6
+ "version": "1.9.0",
7
7
  "type": "module",
8
8
  "main": "dist/main.cjs",
9
9
  "module": "dist/main.mjs",