UNPKG

create-request

Version:

A modern, chainable wrapper for fetch with automatic retries, timeouts, comprehensive error handling, and first-class TypeScript support

828 lines (827 loc) 32.3 kB
import { type HttpMethod, RedirectMode, RequestMode, RequestPriority, ReferrerPolicy, CredentialsPolicy } from "./enums.js"; import type { RetryConfig, RetryCallback, CookiesRecord, CookieOptions, RequestOptions, GraphQLOptions, ErrorInterceptor, RequestInterceptor, ResponseInterceptor } from "./types.js"; import { ResponseWrapper } from "./ResponseWrapper.js"; /** * Base class with common functionality for all request types * Provides the core request building and execution capabilities. */ export declare abstract class BaseRequest { protected abstract method: HttpMethod; protected url: string; protected requestOptions: RequestOptions; protected abortController?: AbortController; protected queryParams: URLSearchParams; protected autoApplyCsrfProtection: boolean; private requestInterceptors; private responseInterceptors; private errorInterceptors; constructor(url: string); /** * Get GraphQL options if set (only for BodyRequest subclasses) * @returns GraphQL options or undefined */ protected getGraphQLOptions(): GraphQLOptions | undefined; /** * Creates a fluent API for setting enum-based options * Combines direct setter with convenience methods */ private createFluentSetter; private validateUrl; /** * Add multiple HTTP headers to the request * * @param headers - Key-value pairs of header names and values * @returns The request instance for chaining * * @example * request.withHeaders({ * 'Accept': 'application/json', * 'X-Custom-Header': 'value' * }); */ withHeaders(headers: Record<string, string>): this; /** * Add a single HTTP header to the request * * @param key - The header name * @param value - The header value * @returns The request instance for chaining * * @example * request.withHeader('Accept', 'application/json'); */ withHeader(key: string, value: string): this; /** * Set a timeout for the request * If the request takes longer than the specified timeout, it will be aborted. * * @param timeout - The timeout in milliseconds * @returns The request instance for chaining * @throws RequestError if timeout is not a positive number * * @example * request.withTimeout(5000); // 5 seconds timeout */ withTimeout(timeout: number): this; /** * Configure automatic retry behavior for failed requests * * @param retries - Number of retry attempts before failing, or a configuration object * @returns The request instance for chaining * @throws RequestError if retries is not a non-negative integer or invalid config * * @example * // Simple number (backward compatible) * request.withRetries(3); // Retry up to 3 times * * @example * // With fixed delay * request.withRetries({ attempts: 3, delay: 1000 }); // Retry 3 times with 1 second delay * * @example * // With exponential backoff function * request.withRetries({ * attempts: 3, * delay: ({ attempt }) => Math.min(1000 * Math.pow(2, attempt - 1), 10000) * }); * * @example * // With delay function based on error * request.withRetries({ * attempts: 3, * delay: ({ attempt, error }) => { * if (error.status === 429) return 5000; // Rate limited, wait longer * return attempt * 1000; // Exponential backoff * } * }); */ withRetries(retries: number | RetryConfig): this; /** * Set a callback to be invoked before each retry attempt * Useful for implementing backoff strategies or logging retry attempts. * * @param callback - Function to call before retrying * @returns The request instance for chaining * * @example * request.onRetry(({ attempt, error }) => { * console.log(`Retry attempt ${attempt} after error: ${error.message}`); * return new Promise(resolve => setTimeout(resolve, attempt * 1000)); * }); */ onRetry(callback: RetryCallback): this; /** * Sets the credentials policy for the request, controlling whether cookies and authentication * headers are sent with cross-origin requests. * * @param credentialsPolicy - The credentials policy to use: * - `"include"` or `CredentialsPolicy.INCLUDE`: Always send credentials (cookies, authorization headers) with the request, even for cross-origin requests. * - `"omit"` or `CredentialsPolicy.OMIT`: Never send credentials, even for same-origin requests. * - `"same-origin"` or `CredentialsPolicy.SAME_ORIGIN`: Only send credentials for same-origin requests (default behavior in most browsers). * * @returns The request instance for chaining * * @example * // Using string values * request.withCredentials("include") * * @example * // Using enum values * request.withCredentials(CredentialsPolicy.INCLUDE) * * @example * // Using fluent API * request.withCredentials.INCLUDE() */ get withCredentials(): ((credentialsPolicy: CredentialsPolicy | string) => BaseRequest) & { INCLUDE: () => BaseRequest; OMIT: () => BaseRequest; SAME_ORIGIN: () => BaseRequest; }; /** * Allows providing an external AbortController to cancel the request. * This is useful when you need to cancel a request from outside the request chain, * * @param controller - The AbortController to use for this request. When `controller.abort()` is called, * the request will be cancelled and throw an abort error. * * @returns The request instance for chaining * * @example * const controller = new AbortController(); * const request = createRequest('/api/data') * .withAbortController(controller) * .getJson(); * * // Later, cancel the request * controller.abort(); * * @example * // Share abort controller across multiple requests * const controller = new AbortController(); * request1.withAbortController(controller).getJson(); * request2.withAbortController(controller).getJson(); * // Aborting will cancel both requests * controller.abort(); */ withAbortController(controller: AbortController): this; /** * Sets the referrer URL for the request. The referrer is the URL of the page that initiated the request. * This can be used to override the default referrer that the browser would normally send. * * @param referrer - The referrer URL to send with the request. Can be: * - A full URL (e.g., "https://example.com/page") * - An empty string to omit the referrer * - A relative URL (will be resolved relative to the current page) * * @returns The request instance for chaining * * @example * request.withReferrer("https://example.com/previous-page") * * @example * // Omit referrer * request.withReferrer("") */ withReferrer(referrer: string): this; /** * Sets the referrer policy for the request, controlling how much referrer information * is sent with the request. This helps balance privacy and functionality. * * @param policy - The referrer policy to use: * - `"no-referrer"` or `ReferrerPolicy.NO_REFERRER`: Never send the referrer header. * - `"no-referrer-when-downgrade"` or `ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE`: Send full referrer for same-origin or HTTPS→HTTPS, omit for HTTPS→HTTP (default in most browsers). * - `"origin"` or `ReferrerPolicy.ORIGIN`: Only send the origin (scheme, host, port), not the full URL. * - `"origin-when-cross-origin"` or `ReferrerPolicy.ORIGIN_WHEN_CROSS_ORIGIN`: Send full referrer for same-origin, only origin for cross-origin. * - `"same-origin"` or `ReferrerPolicy.SAME_ORIGIN`: Send full referrer for same-origin requests only, omit for cross-origin. * - `"strict-origin"` or `ReferrerPolicy.STRICT_ORIGIN`: Send origin for HTTPS→HTTPS or HTTP→HTTP, omit for HTTPS→HTTP. * - `"strict-origin-when-cross-origin"` or `ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN`: Send full referrer for same-origin, origin for cross-origin HTTPS→HTTPS, omit for HTTPS→HTTP. * - `"unsafe-url"` or `ReferrerPolicy.UNSAFE_URL`: Always send the full referrer URL (may leak sensitive information). * * @returns The request instance for chaining * * @example * // Using string values * request.withReferrerPolicy("no-referrer") * * @example * // Using enum values * request.withReferrerPolicy(ReferrerPolicy.NO_REFERRER) * * @example * // Using fluent API * request.withReferrerPolicy.NO_REFERRER() */ get withReferrerPolicy(): ((policy: ReferrerPolicy | string) => BaseRequest) & { ORIGIN: () => BaseRequest; UNSAFE_URL: () => BaseRequest; SAME_ORIGIN: () => BaseRequest; NO_REFERRER: () => BaseRequest; STRICT_ORIGIN: () => BaseRequest; ORIGIN_WHEN_CROSS_ORIGIN: () => BaseRequest; NO_REFERRER_WHEN_DOWNGRADE: () => BaseRequest; STRICT_ORIGIN_WHEN_CROSS_ORIGIN: () => BaseRequest; }; /** * Sets how the request handles HTTP redirects (3xx status codes). * * @param redirect - The redirect handling mode: * - `"follow"` or `RedirectMode.FOLLOW`: Automatically follow redirects. The fetch will transparently follow redirects and return the final response (default behavior). * - `"error"` or `RedirectMode.ERROR`: Treat redirects as errors. If a redirect occurs, the request will fail with an error. * - `"manual"` or `RedirectMode.MANUAL`: Return the redirect response without following it. The response will have a `type` of "opaqueredirect" and you can manually handle the redirect. * * @returns The request instance for chaining * * @example * // Using string values * request.withRedirect("follow") * * @example * // Using enum values * request.withRedirect(RedirectMode.FOLLOW) * * @example * // Using fluent API * request.withRedirect.FOLLOW() * * @example * // Fail on redirects * request.withRedirect.ERROR() */ get withRedirect(): ((redirect: RedirectMode | string) => BaseRequest) & { FOLLOW: () => BaseRequest; ERROR: () => BaseRequest; MANUAL: () => BaseRequest; }; /** * Sets the keepalive flag for the request. When enabled, the request can continue * even after the page that initiated it is closed. This is useful for analytics, * logging, or other background requests that should complete even if the user navigates away. * * @param keepalive - Whether to allow the request to outlive the page: * - `true`: The request will continue even if the page is closed or navigated away. * - `false`: The request will be cancelled if the page is closed (default). * * @returns The request instance for chaining * * @example * // Send analytics event that should complete even if user navigates away * request.withKeepAlive(true) */ withKeepAlive(keepalive: boolean): this; /** * Sets the priority hint for the request, indicating to the browser how important * this request is relative to other requests. This helps the browser optimize resource loading. * * @param priority - The request priority: * - `"high"` or `RequestPriority.HIGH`: High priority - the browser should prioritize this request. * - `"low"` or `RequestPriority.LOW`: Low priority - the browser can defer this request if needed. * - `"auto"` or `RequestPriority.AUTO`: Automatic priority based on the request type (default). * * @returns The request instance for chaining * * @example * // Using string values * request.withPriority("high") * * @example * // Using enum values * request.withPriority(RequestPriority.HIGH) * * @example * // Using fluent API * request.withPriority.HIGH() * * @example * // Low priority for non-critical requests * request.withPriority.LOW() */ get withPriority(): ((priority: RequestPriority | string) => BaseRequest) & { HIGH: () => BaseRequest; LOW: () => BaseRequest; AUTO: () => BaseRequest; }; /** * Sets the integrity hash for Subresource Integrity (SRI) verification. * This allows the browser to verify that the fetched resource hasn't been tampered with * by comparing its hash against the provided value. If the hashes don't match, the request fails. * * @param integrity - The integrity hash string in the format `"algorithm-hash"`: * - Example: `"sha256-abcdef1234567890..."` (SHA-256 hash) * - Example: `"sha384-abcdef1234567890..."` (SHA-384 hash) * - Example: `"sha512-abcdef1234567890..."` (SHA-512 hash) * - Multiple hashes can be separated by spaces: `"sha256-... sha384-..."` * * @returns The request instance for chaining * * @example * request.withIntegrity("sha256-abcdef1234567890...") * * @example * // Multiple algorithms for better compatibility * request.withIntegrity("sha256-... sha384-...") */ withIntegrity(integrity: string): this; /** * Sets the cache mode for the request, controlling how the browser's HTTP cache * is used for this request. * * @param cache - The cache mode: * - `"default"` or `CacheMode.DEFAULT`: Use the browser's default cache behavior. The browser will check the cache and use it if valid, otherwise fetch from network. * - `"no-store"` or `CacheMode.NO_STORE`: Never use the cache and don't store the response in cache. Always fetch from network. * - `"reload"` or `CacheMode.RELOAD`: Bypass the cache but store the response. Always fetch from network, ignoring cached responses. * - `"no-cache"` or `CacheMode.NO_CACHE`: Check the cache but revalidate with the server. Use cached response only if server confirms it's still valid. * - `"force-cache"` or `CacheMode.FORCE_CACHE`: Use the cache if available, even if stale. Only fetch from network if not in cache. * - `"only-if-cached"` or `CacheMode.ONLY_IF_CACHED`: Only use the cache. If not in cache, return an error. Never fetch from network. * * @returns The request instance for chaining * * @example * // Using string values * request.withCache("no-cache") * * @example * // Using enum values * request.withCache(CacheMode.NO_CACHE) * * @example * // Using fluent API * request.withCache.NO_CACHE() * * @example * // Always fetch fresh data * request.withCache.RELOAD() * * @example * // Use cache only, fail if not cached * request.withCache.ONLY_IF_CACHED() */ get withCache(): ((cache: string) => BaseRequest) & { DEFAULT: () => BaseRequest; NO_STORE: () => BaseRequest; RELOAD: () => BaseRequest; NO_CACHE: () => BaseRequest; FORCE_CACHE: () => BaseRequest; ONLY_IF_CACHED: () => BaseRequest; }; /** * Adds query parameters to the request URL. * Multiple calls will append parameters. Array values will create multiple query parameters with the same key. * Null and undefined values are ignored. * * @param params - An object containing query parameter key-value pairs. * Values can be strings, numbers, booleans, arrays (for multiple values), or null/undefined (ignored). * @returns The request instance for chaining * * @example * ```typescript * // Simple parameters * request.withQueryParams({ page: 1, limit: 10, active: true }); * // Results in: ?page=1&limit=10&active=true * ``` * * @example * ```typescript * // Array values create multiple parameters * request.withQueryParams({ tags: ['js', 'ts', 'node'] }); * // Results in: ?tags=js&tags=ts&tags=node * ``` * * @example * ```typescript * // Null/undefined values are ignored * request.withQueryParams({ page: 1, filter: null, sort: undefined }); * // Results in: ?page=1 * ``` */ withQueryParams(params: Record<string, string | string[] | number | boolean | null | undefined>): this; /** * Adds a single query parameter to the request URL. * Convenience method for adding one parameter at a time. * * @param key - The query parameter name * @param value - The query parameter value. Can be a string, number, boolean, array (for multiple values), or null/undefined (ignored). * @returns The request instance for chaining * * @example * ```typescript * request.withQueryParam('page', 1).withQueryParam('limit', 10); * // Results in: ?page=1&limit=10 * ``` * * @example * ```typescript * // Array values create multiple parameters * request.withQueryParam('tags', ['js', 'ts']); * // Results in: ?tags=js&tags=ts * ``` */ withQueryParam(key: string, value: string | string[] | number | boolean | null | undefined): this; /** * Sets the request mode, which determines the CORS (Cross-Origin Resource Sharing) behavior * for the request. This controls how the browser handles cross-origin requests. * * @param mode - The request mode: * - `"cors"` or `RequestMode.CORS`: Enable CORS. The browser will send CORS headers and enforce CORS rules. This is the default for most cross-origin requests. * - `"no-cors"` or `RequestMode.NO_CORS`: Disable CORS. The request is sent as a "simple" request without CORS headers. The response will be opaque (you can't read it). * - `"same-origin"` or `RequestMode.SAME_ORIGIN`: Only allow same-origin requests. Cross-origin requests will fail. * - `"navigate"` or `RequestMode.NAVIGATE`: Used for navigation requests (typically only used by the browser itself). * * @returns The request instance for chaining * * @example * // Using string values * request.withMode("cors") * * @example * // Using enum values * request.withMode(RequestMode.CORS) * * @example * // Using fluent API * request.withMode.CORS() * * @example * // Restrict to same-origin only * request.withMode.SAME_ORIGIN() */ get withMode(): ((mode: RequestMode | string) => BaseRequest) & { CORS: () => BaseRequest; NO_CORS: () => BaseRequest; SAME_ORIGIN: () => BaseRequest; NAVIGATE: () => BaseRequest; }; /** * Sets the Content-Type header for the request. * Shorthand for `withHeader('Content-Type', contentType)`. * * @param contentType - The MIME type (e.g., `'application/json'`, `'text/plain'`, `'multipart/form-data'`) * @returns The request instance for chaining * * @example * ```typescript * request.withContentType('application/json'); * ``` * * @example * ```typescript * request.withContentType('application/xml'); * ``` */ withContentType(contentType: string): this; /** * Sets the Authorization header for the request. * Shorthand for `withHeader('Authorization', authValue)`. * For Bearer tokens, use `withBearerToken()` instead. For Basic auth, use `withBasicAuth()`. * * @param authValue - The full authorization header value (e.g., `'Bearer token123'`, `'Basic base64string'`) * @returns The request instance for chaining * * @example * ```typescript * request.withAuthorization('Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'); * ``` * * @example * ```typescript * request.withAuthorization('CustomScheme customToken'); * ``` */ withAuthorization(authValue: string): this; /** * Sets up HTTP Basic Authentication. * Encodes the username and password in base64 and sets the Authorization header. * * @param username - The username for Basic authentication * @param password - The password for Basic authentication * @returns The request instance for chaining * * @example * ```typescript * request.withBasicAuth('myuser', 'mypassword'); * // Sets: Authorization: Basic bXl1c2VyOm15cGFzc3dvcmQ= * ``` */ withBasicAuth(username: string, password: string): this; /** * Cross-environment base64 encoding * Works in both browser and Node.js environments */ private encodeBase64; /** * Sets a Bearer token for authentication. * Shorthand for `withAuthorization('Bearer ' + token)`. * * @param token - The Bearer token (JWT, OAuth token, etc.) * @returns The request instance for chaining * * @example * ```typescript * request.withBearerToken('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'); * // Sets: Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... * ``` */ withBearerToken(token: string): this; /** * Safely get headers as a Record<string, string> * @returns The headers object */ private getHeadersRecord; /** * Helper function to check for header presence in a case-insensitive way * @param headerName Header name to check * @returns Boolean indicating if the header exists (case-insensitive) */ private hasHeader; /** * Sets cookies for the request. * Cookies are sent in the Cookie header. Multiple calls will merge cookies. * Cookie values can be simple strings or objects with additional cookie options. * * @param cookies - An object where keys are cookie names and values are either: * - A string (the cookie value) * - A CookieOptions object with `value` and optional properties (secure, httpOnly, sameSite, expires, path, domain, maxAge) * @returns The request instance for chaining * * @example * ```typescript * // Simple string cookies * request.withCookies({ sessionId: 'abc123', userId: '456' }); * ``` * * @example * ```typescript * // Cookies with options (note: options are for documentation only in request cookies) * request.withCookies({ * sessionId: 'abc123', * token: { value: 'xyz789', secure: true } * }); * ``` */ withCookies(cookies: CookiesRecord): this; /** * Sets a single cookie for the request. * Convenience method for adding one cookie at a time. * * @param name - The cookie name * @param value - The cookie value as a string, or a CookieOptions object with `value` and optional properties * @returns The request instance for chaining * * @example * ```typescript * request.withCookie('sessionId', 'abc123'); * ``` * * @example * ```typescript * request.withCookie('token', { value: 'xyz789', secure: true }); * ``` */ withCookie(name: string, value: string | CookieOptions): this; /** * Sets a CSRF (Cross-Site Request Forgery) token in the request headers. * This is commonly used to protect against CSRF attacks in web applications. * * @param token - The CSRF token value * @param headerName - The name of the header to use. Defaults to `'X-CSRF-Token'`. * @returns The request instance for chaining * * @example * ```typescript * request.withCsrfToken('csrf-token-123'); * // Sets: X-CSRF-Token: csrf-token-123 * ``` * * @example * ```typescript * request.withCsrfToken('token', 'X-Custom-CSRF-Header'); * // Sets: X-Custom-CSRF-Header: token * ``` */ withCsrfToken(token: string, headerName?: string): this; /** * Disables automatic anti-CSRF protection. * By default, X-Requested-With: XMLHttpRequest header is sent with all requests. * @returns The instance for chaining */ withoutCsrfProtection(): this; /** * Sets common security headers to help prevent CSRF attacks * @returns The instance for chaining */ withAntiCsrfHeaders(): this; /** * Add a request interceptor for this specific request * Request interceptors can modify the request configuration or return an early response * * @param interceptor - The request interceptor function * @returns The instance for chaining * * @example * request.withRequestInterceptor((config) => { * config.headers['X-Custom'] = 'value'; * return config; * }); */ withRequestInterceptor(interceptor: RequestInterceptor): this; /** * Add a response interceptor for this specific request * Response interceptors can transform the response * * @param interceptor - The response interceptor function * @returns The instance for chaining * * @example * request.withResponseInterceptor((response) => { * console.log('Status:', response.status); * return response; * }); */ withResponseInterceptor(interceptor: ResponseInterceptor): this; /** * Add an error interceptor for this specific request * Error interceptors can handle or transform errors * * @param interceptor - The error interceptor function * @returns The instance for chaining * * @example * request.withErrorInterceptor((error) => { * console.error('Request failed:', error); * throw error; * }); */ withErrorInterceptor(interceptor: ErrorInterceptor): this; /** * Execute the request and return the ResponseWrapper * This is the base method for getting the full response. * * @returns A promise that resolves to the ResponseWrapper * * @example * const response = await request.getResponse(); * console.log(response.status); */ getResponse(): Promise<ResponseWrapper>; /** * Execute the request and parse the response as JSON * * @returns A promise that resolves to the parsed JSON data * @throws {RequestError} When the request fails, JSON parsing fails, GraphQL errors occur (if throwOnError enabled), or body is already consumed * * @example * const users = await request.getJson<User[]>(); * * @example * // Error handling - errors are always RequestError * try { * const data = await request.getJson(); * } catch (error) { * if (error instanceof RequestError) { * console.log(error.status, error.url, error.method); * } * } */ getJson<T = unknown>(): Promise<T>; /** * Execute the request and get the response body as text. * * @returns A promise that resolves to the response body as a string * @throws {RequestError} When the request fails or reading the response fails * * @example * ```typescript * const text = await request.getText(); * console.log(text); // "Hello, world!" * ``` */ getText(): Promise<string>; /** * Execute the request and get the response body as a Blob. * Useful for downloading files or handling binary data. * * @returns A promise that resolves to the response body as a Blob * @throws {RequestError} When the request fails or reading the response fails * * @example * ```typescript * const blob = await request.getBlob(); * const url = URL.createObjectURL(blob); * // Use the blob URL (e.g., for downloading or displaying) * ``` */ getBlob(): Promise<Blob>; /** * Execute the request and get the response body as an ArrayBuffer. * Useful for processing binary data at a low level. * * @returns A promise that resolves to the response body as an ArrayBuffer * @throws {RequestError} When the request fails or reading the response fails * * @example * ```typescript * const buffer = await request.getArrayBuffer(); * const uint8Array = new Uint8Array(buffer); * // Process the binary data * ``` */ getArrayBuffer(): Promise<ArrayBuffer>; /** * Execute the request and get the response body as a ReadableStream. * Note: Unlike other methods, streams cannot be cached. The body can only be consumed once. * * @returns A promise that resolves to the response body as a ReadableStream, or `null` if the body is not available * @throws {RequestError} When the request fails or the body has already been consumed * * @example * ```typescript * const stream = await request.getBody(); * if (stream) { * const reader = stream.getReader(); * // Process the stream chunk by chunk * } * ``` */ getBody(): Promise<ReadableStream<Uint8Array> | null>; /** * Execute the request and extract specific data using a selector function * If no selector is provided, returns the full JSON response. * * @param selector - Optional function to extract and transform data * @returns A promise that resolves to the selected data * @throws {RequestError} When the request fails, JSON parsing fails, or the selector throws an error * * @example * // Get full response * const data = await request.getData(); * * // Extract specific data * const users = await request.getData(data => data.results.users); * * @example * // Error handling - errors are always RequestError * try { * const data = await request.getData(); * } catch (error) { * if (error instanceof RequestError) { * console.log(error.status, error.url, error.method); * } * } */ getData<T = unknown, R = T>(selector?: (data: T) => R): Promise<T | R>; /** * Apply CSRF protection headers based on configuration */ private applyCsrfProtection; /** * Formats the URL with any query parameters * @param url The base URL * @returns The URL with query parameters appended */ private formatUrlWithQueryParams; /** * Executes a request with configured retry logic * @param url The formatted URL to send the request to * @param fetchOptions The fetch options to use * @returns A wrapped response object * @throws RequestError if the request fails after all retries */ private executeWithRetries; /** * Run request interceptors in order: global interceptors first, then per-request * @param configParam - The request configuration * @returns Modified config or a Response to short-circuit */ private runRequestInterceptors; /** * Run response interceptors in reverse order: per-request interceptors first, then global in reverse * @param response - The response wrapper * @returns Modified response wrapper */ private runResponseInterceptors; /** * Run error interceptors in reverse order: per-request interceptors first, then global in reverse * @param error - The error that occurred * @returns Modified error or a ResponseWrapper to recover */ private runErrorInterceptors; /** * Helper to create an abort signal with timeout support * Handles various AbortSignal API levels gracefully */ private createAbortSignal; /** * Manually combine two abort signals for older environments * Returns the first signal and listens to the second */ private combineSignalsManually; /** * Convert fetchOptions to RequestConfig with proper typing */ private createRequestConfig; /** * Apply interceptor results back to fetchOptions */ private applyRequestConfig; private executeRequest; }