import { n as HeadersWithSuggestions, t as HeadersRecord } from "./http-well-known-BK9MGKQf.js"; import { i as RetryOptions, n as QueueOptions, t as EventEmitter } from "./emitter-yGbfYzOM.js"; //#region src/utils/metrics.d.ts interface Metrics { requestCount: number; errorCount: number; /** Total duration of all requests in milliseconds */ totalLatency: number; /** Minimum request duration in milliseconds */ minLatency: number; /** Maximum request duration in milliseconds */ maxLatency: number; /** Timestamp of the last request (Unix timestamp in milliseconds) */ lastRequestTimestamp: number; } interface RequestMetrics { url: string; method: string; /** Start time (Unix timestamp in milliseconds) */ startTime: number; /** End time (Unix timestamp in milliseconds) */ endTime: number; /** Request duration in milliseconds */ duration: number; status: number; success: boolean; } /** * Collector for HTTP client performance metrics. * Tracks latency, error rates, and throughput. */ declare class MetricsCollector { private metrics; private readonly requestLog; private readonly maxLogSize; private onUpdate?; constructor(maxLogSize?: number, onUpdate?: (metrics: Metrics) => void); /** * Records a completed request metric. */ record(metric: Omit): void; /** * Returns a snapshot of current aggregate metrics. */ getSnapshot(): Metrics & { averageLatency: number; }; /** * Returns the list of recent request logs. */ getRecentRequests(): RequestMetrics[]; /** * Clears all metrics and logs. */ clear(): void; } //#endregion //#region src/types/result.d.ts /** * @file result.ts * * Railway-oriented error handling for reixo. * * Every HTTP client call can fail in at least two ways: a network error * (ECONNRESET, timeout, …) or an HTTP error (4xx, 5xx). Traditional * try/catch forces callers to remember to wrap *every* await. The `Result` * type makes the failure path explicit in the return type so TypeScript * enforces handling. * * @example * // Without Result — easy to forget the try/catch: * const res = await client.get('/me'); // throws on 401 * * // With Result — failure is encoded in the type: * const result = await client.tryGet('/me'); * if (!result.ok) { * console.error(result.error.status); // fully typed HTTPError * return; * } * console.log(result.data.name); // TypeScript knows data is User */ /** A successful result carrying the resolved value. */ interface Ok { readonly ok: true; readonly data: T; readonly error: null; } /** A failed result carrying the thrown error. */ interface Err { readonly ok: false; readonly data: null; readonly error: E; } /** * Discriminated union of {@link Ok} and {@link Err}. * * Narrow with `if (result.ok)` to access `.data` / `.error` without a cast. * * @typeParam T The success value type. * @typeParam E The error type (defaults to `Error`; use `HTTPError` for HTTP calls). */ type Result = Ok | Err; /** Wrap a value in an {@link Ok} result. */ declare function ok(data: T): Ok; /** Wrap an error in an {@link Err} result. */ declare function err(error: E): Err; /** * Await a Promise and capture its outcome as a {@link Result} — never throws. * * @example * const result = await toResult(fetch('/api')); * if (!result.ok) handleError(result.error); */ declare function toResult(promise: Promise): Promise>; /** * Map the success value of a Result without unwrapping it. * * @example * const userResult = mapResult(responseResult, r => r.data); */ declare function mapResult(result: Result, fn: (value: T) => U): Result; /** * Unwrap a Result, throwing its error if not `ok`. * * @example * const data = unwrap(result); // throws if result.ok === false */ declare function unwrap(result: Result): T; /** * Unwrap a Result, returning a fallback value if not `ok`. * * @example * const data = unwrapOr(result, defaultUser); */ declare function unwrapOr(result: Result, fallback: T): T; //#endregion //#region src/utils/http.d.ts /** * All valid HTTP methods as a string-literal union. * Overrides `RequestInit['method']` (plain `string`) for strict typing. */ type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS'; /** * A scalar query-param value (string, number, or boolean). * Arrays of scalars are serialized as repeated keys: `tags=a&tags=b`. * Plain objects are serialized with bracket notation: `filter[status]=active`. */ type ParamScalar = string | number | boolean; /** * Supported shape for `HTTPOptions.params`. * Allows flat scalars, scalar arrays, and one level of nested objects. */ type ParamsValue = Record>; /** * Options accepted by every request helper (`client.get()`, `client.post()`, etc.) * and the low-level `http()` function. * * Extends the browser-native `RequestInit` — the same shape that `fetch()` accepts — * while overriding `method` and `headers` with richer types that provide IDE * autocomplete without losing backwards compatibility. * * @example * await client.get('/users', { * headers: { * Authorization: 'Bearer eyJhbGc…', * Accept: 'application/json', * }, * params: { role: 'admin', active: true }, * timeoutMs: 5000, * }); */ interface HTTPOptions extends Omit { /** * HTTP verb for the request. * @default 'GET' */ method?: HTTPMethod; /** * Request headers. Accepts a `Headers` instance, an array of `[name, value]` * tuples, or a plain object. Common header names are suggested by IntelliSense. * * @example * headers: { * 'Authorization': 'Bearer ', * 'Content-Type': 'application/json', * 'Accept': 'application/json', * 'X-Request-ID': crypto.randomUUID(), * } */ headers?: HeadersWithSuggestions; /** * Retry strategy. Pass `true` for sensible defaults (3 retries, exponential * back-off, only 5xx / 429 / 408), `false` to disable, or a `RetryOptions` * object to customise. * @default true */ retry?: RetryOptions | boolean; /** * Per-request timeout in milliseconds. The request is aborted and an error * is thrown when this limit is exceeded. * @default 30000 */ timeoutMs?: number; /** * Base URL prepended to the request path. Trailing / leading slashes are * normalised automatically. * @example 'https://api.example.com/v1' */ baseURL?: string; /** @internal Resolved request URL (set by the HTTP layer before interceptors run). */ url?: string; /** * Query-string parameters appended to the URL. * * Supports flat values, arrays (repeated keys), and nested objects (bracket notation): * - `{ page: 2 }` → `?page=2` * - `{ tags: ['a','b'] }` → `?tags=a&tags=b` * - `{ filter: { status: 'active', date: '2026-01-01' } }` → `?filter[status]=active&filter[date]=2026-01-01` * * For custom serialization (e.g. comma-separated arrays), provide a {@link HTTPOptions.paramsSerializer}. * * @example * params: { page: 2, limit: 20, tags: ['news', 'tech'], filter: { active: true } } */ params?: ParamsValue; /** * Custom query-string serializer. When provided, replaces the built-in serialization * with your own implementation. The function receives the `params` object and must * return a query string **without** the leading `?`. * * @example * // Comma-separated arrays instead of repeated keys * paramsSerializer: (p) => Object.entries(p) * .map(([k, v]) => `${k}=${Array.isArray(v) ? v.join(',') : v}`) * .join('&') */ paramsSerializer?: (params: ParamsValue) => string; /** * Per-request cache configuration. Pass `true` to use the client's default * cache settings, `false` to bypass the cache for this request only, or a * `CacheOptions` object for full control. */ cacheConfig?: CacheOptions | boolean; /** Node.js `http.Agent` / `https.Agent` for keep-alive and connection pooling. */ agent?: unknown; /** @internal Marks a request as a retry attempt to prevent duplicate queuing. */ _retry?: boolean; /** * How the response body should be parsed. * When omitted, the `Content-Type` header drives auto-detection * (`application/json` → JSON, everything else → text). */ responseType?: 'json' | 'text' | 'stream' | 'blob' | 'arraybuffer'; /** * Called periodically with download progress. Receives `loaded` bytes, * `total` bytes (or `null` if unknown), and a 0–100 `progress` percentage. */ onDownloadProgress?: (progress: { loaded: number; total: number | null; progress: number | null; }) => void; /** * Called periodically with upload progress. Uses XHR under the hood in * browsers because the Fetch API does not expose upload progress natively. */ onUploadProgress?: (progress: { loaded: number; total: number | null; progress: number | null; }) => void; /** * Zod schema (`.parse(data)`) or validation function applied to the parsed * response body. Throws a `ValidationError` on failure. * * @example * import { z } from 'zod'; * const UserSchema = z.object({ id: z.number(), name: z.string() }); * await client.get('/me', { validationSchema: UserSchema }); */ validationSchema?: ValidationSchema; /** * When `true`, the request body is serialised as `multipart/form-data` * instead of JSON. Useful for file uploads with a plain-object payload. */ useFormData?: boolean; /** * Priority hint for the offline task queue (higher = processed first). * Only relevant when `offlineQueue` is enabled on the client. * @default 0 */ taskPriority?: number; /** * When `true` (the default), identical in-flight GET/HEAD/OPTIONS requests * are collapsed — the same Promise is shared among all callers until the * first response settles, preventing thundering-herd duplicates. * * Set to `false` to bypass deduplication for this specific request (e.g. * when you intentionally need a fresh uncached result). * * Deduplication is only applied to safe, idempotent methods (GET, HEAD, * OPTIONS). POST/PUT/PATCH/DELETE are never deduplicated. * * @default true */ deduplicate?: boolean; } /** * Schema used to validate the parsed response body. * * Compatible with Zod schemas (`.parse(data)`) and plain validation functions. * * @example * // Zod * const schema = z.object({ id: z.number() }); * // Plain function * const schema = (data: unknown) => { if (!data) throw new Error(); return data as MyType; }; */ type ValidationSchema = { parse: (data: unknown) => T; } | ((data: unknown) => T); /** * Thrown when the server response body does not satisfy the `validationSchema` * provided in `HTTPOptions`. */ declare class ValidationError extends Error { /** The raw (unvalidated) response body that failed validation. */ readonly data: unknown; /** The underlying schema / validation error. */ readonly originalError: unknown; constructor(message: string, data: unknown, originalError: unknown); } /** * Normalised HTTP response returned by every request method. * * @template T Type of the parsed response body. * * @example * const { data, status, headers } = await client.get('/me'); * if (status === 200) console.log(data.name); */ /** * Cache metadata attached to responses served from the cache layer. * Present only when a cached value was returned; `undefined` for live responses. * * @example * const res = await client.get('/api/users'); * if (res.cacheMetadata?.hit) { * console.log(`Served from cache, ${res.cacheMetadata.age}s old`); * } */ interface CacheMetadata { /** `true` when the response was served from cache. Always `true` when present. */ hit: boolean; /** Seconds elapsed since the response was cached. */ age: number; /** Remaining TTL in seconds (0 if stale or expiry unknown). */ ttl: number; /** The caching strategy that was active when this response was stored. */ strategy: 'cache-first' | 'stale-while-revalidate' | 'network-first' | 'cache-only' | 'unknown'; } interface HTTPResponse { /** Parsed response body. */ data: T; /** HTTP status code (e.g. `200`, `404`). */ status: number; /** HTTP status text (e.g. `'OK'`, `'Not Found'`). */ statusText: string; /** Response headers as a native `Headers` instance. */ headers: Headers; /** The original request options that produced this response. */ config: HTTPOptions; /** * Cache metadata. Defined only when the response was served from the in-memory cache. * Useful for debugging cache behavior and displaying staleness indicators in the UI. */ cacheMetadata?: CacheMetadata; } /** Minimal interface implemented by `HTTPClient` — useful for testing with mock clients. */ interface IHTTPClient { get(url: string, options?: HTTPOptions): Promise>; } /** * Thrown when the network request itself fails before a response is received — * e.g. no internet connection, DNS failure, or CORS rejection at the network level. * * Distinct from {@link HTTPError} (which requires a response with a status code). * * @example * try { * await client.get('/api/data'); * } catch (err) { * if (err instanceof NetworkError) { * showOfflineBanner(); * } * } */ declare class NetworkError extends Error { /** The original `fetch()` rejection reason, if available. */ readonly cause?: Error; constructor(message: string, cause?: Error); } /** * Thrown when a request exceeds the configured `timeoutMs` before the server * responds. The in-flight fetch is aborted automatically. * * @example * try { * await client.get('/slow-endpoint', { timeoutMs: 3000 }); * } catch (err) { * if (err instanceof TimeoutError) { * console.warn(`Request timed out after ${err.timeoutMs}ms`); * } * } */ declare class TimeoutError extends Error { /** The timeout limit (in ms) that was exceeded. */ readonly timeoutMs: number; constructor(timeoutMs: number); } /** * Thrown when a request is cancelled via an `AbortSignal` or by calling * `client.cancel()` / `client.cancelAll()`. * * @example * const controller = new AbortController(); * setTimeout(() => controller.abort(), 2000); * try { * await client.get('/api/data', { signal: controller.signal }); * } catch (err) { * if (err instanceof AbortError) { * console.log('Request was cancelled'); * } * } */ declare class AbortError extends Error { constructor(message?: string); } /** * Thrown when a request is blocked because the {@link CircuitBreaker} is in the * OPEN state. Catching this lets you serve cached / fallback data immediately * instead of waiting for a guaranteed-to-fail network round-trip. * * @example * try { * await client.get('/api/data'); * } catch (err) { * if (err instanceof CircuitOpenError) { * return cachedData; // serve stale data while circuit recovers * } * } */ declare class CircuitOpenError extends Error { constructor(message?: string); } /** * Thrown for non-2xx responses and network-level failures. * * @example * try { * await client.get('/protected'); * } catch (err) { * if (err instanceof HTTPError && err.status === 401) { * console.log('Unauthorised'); * } * } */ declare class HTTPError extends Error { readonly status?: number; readonly statusText?: string; readonly config?: HTTPOptions; readonly response?: Response; constructor(message: string, options?: { status?: number; statusText?: string; config?: HTTPOptions; response?: Response; }); } declare function http(url: string, options?: HTTPOptions): Promise>; //#endregion //#region src/utils/cache.d.ts interface CacheEntry { data: T; expiry: number; createdAt: number; tags?: string[]; } interface StorageAdapter { get(key: string): CacheEntry | null; set(key: string, entry: CacheEntry): void; delete(key: string): void; clear(): void; size(): number; keys(): IterableIterator | string[]; } declare class MemoryAdapter implements StorageAdapter { private cache; private readonly maxEntries; constructor(maxEntries?: number); get(key: string): CacheEntry | null; set(key: string, entry: CacheEntry): void; delete(key: string): void; clear(): void; size(): number; keys(): IterableIterator; } declare class WebStorageAdapter implements StorageAdapter { private storage; private prefix; /** * @throws Error if the storage type is not available (SSR / Node.js). * Callers should catch this and fall back to MemoryAdapter. */ constructor(type: 'local' | 'session', prefix?: string); private getKey; get(key: string): CacheEntry | null; set(key: string, entry: CacheEntry): void; delete(key: string): void; clear(): void; size(): number; keys(): string[]; } interface CacheOptions { ttl?: number; maxEntries?: number; storage?: 'memory' | 'local' | 'session' | StorageAdapter; keyPrefix?: string; strategy?: 'cache-first' | 'network-only' | 'stale-while-revalidate'; } /** * Cache manager with support for Memory, LocalStorage, and SessionStorage. */ declare class CacheManager { private adapter; private readonly defaultTTL; constructor(options?: CacheOptions); /** * Generates a cache key from URL and options. * Delegates to the shared utility. */ generateKey(url: string, params?: ParamsValue): string; set(key: string, data: T, ttl?: number, tags?: string[]): void; /** * Returns the raw cache entry including metadata. * Does NOT auto-delete expired entries. */ getEntry(key: string): CacheEntry | null; get(key: string): T | null; delete(key: string): void; invalidateByTag(tag: string): void; invalidateByPattern(pattern: RegExp | string): void; clear(): void; get size(): number; } //#endregion //#region src/utils/circuit-breaker.d.ts declare const CircuitState: { readonly CLOSED: "CLOSED"; readonly OPEN: "OPEN"; readonly HALF_OPEN: "HALF_OPEN"; }; type CircuitState = (typeof CircuitState)[keyof typeof CircuitState]; interface CircuitBreakerOptions { failureThreshold?: number; resetTimeoutMs?: number; halfOpenRetries?: number; fallback?: () => unknown; onStateChange?: (state: CircuitState) => void; } /** * Implements the Circuit Breaker pattern to prevent cascading failures. * Tracks failures and opens the circuit when a threshold is reached. */ declare class CircuitBreaker { private state; private failures; private successes; private nextAttempt; private readonly failureThreshold; private readonly resetTimeoutMs; private readonly halfOpenRetries; private readonly fallback?; private readonly onStateChange?; constructor(options?: CircuitBreakerOptions); /** * Executes a function through the circuit breaker. * If the circuit is OPEN, it throws immediately without executing the function. * * @param fn The async function to execute * @returns The result of the function * @throws Error if circuit is OPEN or if execution fails */ execute(fn: () => Promise): Promise; get currentState(): CircuitState; private onSuccess; private onFailure; private transitionTo; } //#endregion //#region src/utils/connection.d.ts /** PEM-encoded certificate/key content, as string or raw Buffer. */ type PemValue$1 = string | Buffer | Array; interface ConnectionPoolOptions { maxSockets?: number; keepAlive?: boolean; keepAliveMsecs?: number; timeout?: number; rejectUnauthorized?: boolean; ca?: PemValue$1; cert?: PemValue$1; key?: PemValue$1; passphrase?: string; secureProtocol?: string; } //#endregion //#region src/utils/infinite-query.d.ts /** Query parameters derived from a page cursor or page number. */ type PageParams = Record; interface InfiniteQueryOptions { client: IHTTPClient; url: string; /** * Function to generate query parameters for a given page parameter. * If not provided, the pageParam will be used as the 'page' or 'cursor' query param directly * if it's a primitive, or merged if it's an object. */ params?: (pageParam: unknown) => PageParams; /** * Calculate the next page parameter based on the last fetched page. * Return undefined or null to indicate there are no more pages. */ getNextPageParam: (lastPage: TData, allPages: TData[]) => unknown | undefined | null; /** * Calculate the previous page parameter based on the first fetched page. * Return undefined or null to indicate there are no previous pages. */ getPreviousPageParam?: (firstPage: TData, allPages: TData[]) => unknown | undefined | null; /** * The initial page parameter to use for the first fetch. * Defaults to 1 or undefined depending on implementation. */ initialPageParam?: unknown; /** * Optional direction to fetch initially. */ direction?: 'forward' | 'backward'; } interface InfiniteData { pages: TData[]; pageParams: unknown[]; } declare class InfiniteQuery { private client; private url; private options; private _data; private _isFetching; private _isFetchingNextPage; private _isFetchingPreviousPage; private _error; /** AbortController for the current in-flight fetch, if any. */ private _abortController; constructor(options: InfiniteQueryOptions); get data(): InfiniteData; get isFetching(): boolean; get isFetchingNextPage(): boolean; get isFetchingPreviousPage(): boolean; get error(): unknown | null; get hasNextPage(): boolean; get hasPreviousPage(): boolean; private fetch; /** * Fetches the next page. * If it's the first fetch, uses initialPageParam. * * Any previous in-flight fetch is cancelled before a new one begins. */ fetchNextPage(): Promise>; /** * Cancel the current in-flight fetch (if any). * * Safe to call even when no fetch is in progress. * The pending `fetchNextPage` / `fetchPreviousPage` call will resolve with * the existing data rather than throwing. */ abort(): void; /** * Fetches the previous page. */ fetchPreviousPage(): Promise>; /** * Resets the infinite query state. * Cancels any in-flight fetch before clearing data. */ reset(): void; } //#endregion //#region src/utils/otel.d.ts /** * An externally-supplied span context (e.g. from an incoming HTTP request or * an active OTel SDK span). Used to continue an existing distributed trace. */ interface SpanContext { /** 32 hex-char trace ID. */ traceId: string; /** 16 hex-char span ID of the *parent* span. */ spanId: string; /** Trace flags (0x01 = sampled). */ traceFlags?: number; /** Raw `tracestate` header value from the incoming request, if any. */ traceState?: string; } /** Lifecycle hooks called for each outgoing request span. */ interface OTelSpanHooks { /** Called when the span starts (just before the request headers are set). */ onSpanStart?: (ctx: SpanContext & { url: string; method: string; }) => void; /** Called when the response arrives (or the request errors out). */ onSpanEnd?: (ctx: SpanContext & { url: string; method: string; durationMs: number; status?: number; }) => void; } interface OTelConfig { /** * Service name propagated in the `baggage` header. * @example 'checkout-service' */ serviceName?: string; /** * Whether requests should be marked as sampled (trace flag `01`). * When `false` the flag is `00`, which tells downstream collectors to skip * recording. Defaults to `true`. */ sampled?: boolean; /** * Continue an existing trace instead of starting a new root trace. * The provided `traceId` will be reused and the supplied `spanId` becomes * the `parentId` of the new outgoing span. */ parentContext?: SpanContext; /** * Application-level key/value pairs propagated via the W3C `baggage` header. * Keys and values are percent-encoded per RFC 8941. * * @example { 'user.id': '42', 'tenant': 'acme' } */ baggage?: Record; /** Optional lifecycle hooks for custom telemetry integration. */ hooks?: OTelSpanHooks; } /** * Parse an existing `traceparent` header value into its components. * Returns `null` if the value is invalid (missing, wrong format, invalid hex). */ declare function parseTraceparent(value: string | undefined | null): SpanContext | null; /** * Serialise a `SpanContext` into a `traceparent` header string. * * Format: `00-{traceId}-{spanId}-{flags}` */ declare function formatTraceparent(ctx: SpanContext): string; /** * Create a request interceptor that injects W3C Trace Context headers * (`traceparent`, `tracestate`, `baggage`) into every outgoing request. * * Existing `traceparent` headers from upstream are respected — the interceptor * will start a *child span* that carries the upstream `traceId` forward. */ declare function createOTelInterceptor(config?: OTelConfig): { onFulfilled: (options: HTTPOptions) => HTTPOptions; }; //#endregion //#region src/utils/queue.d.ts interface PersistentQueueOptions extends QueueOptions { storage?: 'memory' | 'local' | 'session' | StorageAdapter; storageKey?: string; syncWithNetwork?: boolean; } type QueueEvents = { 'queue:restored': [Array<{ id: string; priority?: number; dependencies?: string[]; data?: unknown; }>]; 'task:start': [{ id: string; }]; 'task:completed': [{ id: string; result: unknown; }]; 'task:error': [{ id: string; error: unknown; }]; 'task:added': [{ id: string; priority?: number; }]; 'task:cancelled': [{ id: string; }]; 'queue:paused': []; 'queue:resumed': []; 'queue:cleared': []; 'queue:drain': []; }; /** * Manages concurrent execution of async tasks with priority and dependencies. */ declare class TaskQueue extends EventEmitter { private queue; private activeTaskIds; private completedTasks; private activeCount; private isPaused; private readonly concurrency; private readonly autoStart; private readonly storage?; private readonly storageKey; private readonly networkMonitor?; /** * @param options Configuration options including concurrency limit */ constructor(options?: PersistentQueueOptions); private loadQueue; private saveQueue; /** * Adds a task to the queue. * * @param fn The async function to execute * @param options Task options (priority, ID, dependencies) * @returns Promise resolving to the task result */ add(fn: () => Promise, options?: { priority?: number; id?: string; dependencies?: string[]; data?: unknown; }): Promise; /** * Cancels a pending task by ID. * @param taskId The ID of the task to cancel * @returns True if cancelled, false if not found */ cancel(taskId: string): boolean; /** * Pauses queue processing. Active tasks will continue to completion. */ pause(): void; /** * Resumes queue processing. */ resume(): void; /** * Clears all pending tasks. */ clear(): void; get size(): number; get active(): number; get isQueuePaused(): boolean; /** * Returns a Promise that resolves when the queue has no more pending or * active tasks. * * If the queue is already empty and idle, the Promise resolves immediately. * Useful for "wait until all uploads finish before navigating away" patterns. * * @example * queue.add(() => uploadFile(file)); * await queue.drain(); // wait for all tasks to complete */ drain(): Promise; [Symbol.asyncIterator](): AsyncIterator; private applyPriorityInheritance; private sortQueue; private processNext; } //#endregion //#region src/utils/timing.d.ts declare function debounce unknown>(func: T, wait: number, options?: { leading?: boolean; trailing?: boolean; }): (this: ThisParameterType, ...args: Parameters) => void; declare function throttle unknown>(func: T, limit: number, options?: { leading?: boolean; trailing?: boolean; }): (this: ThisParameterType, ...args: Parameters) => void; declare function delay(ms: number): Promise; //#endregion //#region src/core/http-client.d.ts /** JSON-serialisable primitive. */ /** PEM-encoded certificate/key content, as string or raw Buffer. */ type PemValue = string | Buffer | Array; type JsonPrimitive = string | number | boolean | null; /** JSON-serialisable array. */ type JsonArray = JsonValue[]; /** JSON-serialisable object. */ type JsonObject = { [key: string]: JsonValue; }; /** Any JSON-serialisable value. */ type JsonValue = JsonPrimitive | JsonObject | JsonArray; /** * Accepted body-data types for mutation requests (`post`, `put`, `patch`). * Covers JSON-serialisable values plus native browser body types. */ type BodyData = JsonValue | FormData | Blob | ArrayBuffer | URLSearchParams | ReadableStream; /** * Accepted type for the optional `meta` parameter on logger methods. * Covers structured objects, primitives, and `null`/`undefined`. */ type LogMeta = Record | JsonValue | Error | undefined; interface RequestInterceptor { onFulfilled?: (config: HTTPOptions) => HTTPOptions | Promise; /** Receives the thrown value from a previous interceptor or from the request itself. */ onRejected?: (error: unknown) => unknown; } interface ResponseInterceptor { onFulfilled?: (response: HTTPResponse) => HTTPResponse | Promise>; /** Receives the thrown value; re-throw or return a fallback response. */ onRejected?: (error: Error | HTTPError | unknown) => HTTPResponse | unknown; } type HTTPRequestFunction = (url: string, options: HTTPOptions) => Promise>; interface Logger { info(message: string, meta?: LogMeta): void; warn(message: string, meta?: LogMeta): void; error(message: string, meta?: LogMeta): void; } /** * Configuration object passed to `new HTTPClient(config)` or `HTTPBuilder.create()`. * * Every option can be overridden per-request via `HTTPOptions`. * * @example * const client = new HTTPClient({ * baseURL: 'https://api.example.com/v1', * timeoutMs: 10_000, * headers: { * Authorization: 'Bearer ', * Accept: 'application/json', * }, * retry: { maxRetries: 3, backoffFactor: 2 }, * }); */ interface HTTPClientConfig { /** * Base URL prepended to every request path. Trailing / leading slashes are * normalised automatically. * @example 'https://api.example.com/v1' */ baseURL?: string; /** * Default request timeout in milliseconds. Individual requests may override * this via `HTTPOptions.timeoutMs`. * @default 30000 */ timeoutMs?: number; /** * Default headers sent with every request. Common header names are suggested * by IntelliSense — any custom header is still accepted. * * @example * headers: { * Authorization: 'Bearer ', * 'X-Api-Key': 'secret', * Accept: 'application/json', * } */ headers?: HeadersWithSuggestions; /** * Swap out the default `fetch`-based transport. Useful for testing with a * `MockAdapter` or for Node.js environments that require a custom agent. * * @example * const mock = new MockAdapter(); * const client = new HTTPClient({ transport: mock.transport }); */ transport?: HTTPRequestFunction; /** * Custom logger. Implement `info`, `warn`, and `error` to route log output * to your preferred sink (e.g. Winston, Pino, Datadog). * * @example * import { ConsoleLogger, LogLevel } from 'reixo'; * logger: new ConsoleLogger(LogLevel.DEBUG) */ logger?: Logger; /** * Default retry strategy applied to all requests. Pass `true` for sensible * defaults (3 retries, exponential back-off, 5xx / 429 / 408 only), `false` * to disable retries globally, or a `RetryOptions` object to customise. * @default true */ retry?: RetryOptions | boolean; /** * HTTP connection-pool settings (Node.js only). * Controls the maximum number of sockets and idle keep-alive timeouts. */ pool?: ConnectionPoolOptions; /** * TLS/SSL options forwarded to the Node.js `https` module. * Has no effect in browser environments. */ ssl?: { /** Reject servers with self-signed or untrusted certificates. @default true */ rejectUnauthorized?: boolean; /** Custom CA certificate(s) in PEM format. */ ca?: PemValue; /** Client certificate in PEM format (mutual TLS). */ cert?: PemValue; /** Client private key in PEM format (mutual TLS). */ key?: PemValue; /** Passphrase for an encrypted private key. */ passphrase?: string; }; /** * Token-bucket rate limiter applied globally before every outgoing request. * * @example * rateLimit: { requests: 60, interval: 60_000 } // 60 req / min */ rateLimit?: { /** Maximum number of requests allowed within `interval` milliseconds. */ requests: number; /** Window duration in milliseconds. */ interval: number; }; /** * Per-URL-pattern retry overrides. The first matching pattern wins. * Useful when some endpoints need stricter (or more lenient) retry behaviour. * * @example * retryPolicies: [ * { pattern: /\/auth\//, retry: false }, // never retry auth * { pattern: '/api/upload', retry: { maxRetries: 1 } }, * ] */ retryPolicies?: Array<{ /** String prefix or RegExp matched against the full request URL. */ pattern: string | RegExp; retry: RetryOptions | boolean; }>; /** * Response cache. Pass `true` to enable in-memory caching with defaults, * or a `CacheOptions` object for full control (TTL, strategy, storage * adapter, etc.). */ cacheConfig?: CacheOptions | boolean; /** * Collect per-request timing, error-rate, and throughput metrics. * Access them via `client.getMetrics()` or the `onMetricsUpdate` callback. * @default false */ enableMetrics?: boolean; /** * Buffer requests made while the device is offline and replay them once * connectivity is restored. Pass `true` for defaults or a * `PersistentQueueOptions` object to persist the queue across page reloads. * @default false */ offlineQueue?: boolean | PersistentQueueOptions; /** * Deduplicate identical in-flight GET requests — concurrent calls to the same * URL + params share a single network request. * @default false */ enableDeduplication?: boolean; /** * Called every time the internal metrics snapshot is updated. Useful for * forwarding metrics to an observability platform. * * @example * onMetricsUpdate: (m) => console.log(`Error rate: ${m.errorRate}%`) */ onMetricsUpdate?: (metrics: Metrics) => void; /** * Global upload-progress callback (applies to all requests). * For per-request progress, use `HTTPOptions.onUploadProgress` instead. */ onUploadProgress?: (progress: { loaded: number; total: number | null; progress: number | null; }) => void; /** * Global download-progress callback (applies to all requests). * For per-request progress, use `HTTPOptions.onDownloadProgress` instead. */ onDownloadProgress?: (progress: { loaded: number; total: number | null; progress: number | null; }) => void; /** * API version string appended to every request, either as a URL segment * (`/v2/users`) or as a header, depending on `versioningStrategy`. * @example 'v2' */ apiVersion?: string; /** * How `apiVersion` is included in requests. * - `'url'` — prepends the version as a path segment: `/v2/users` * - `'header'` — sends the version in a request header (see `versionHeader`) * @default 'url' */ versioningStrategy?: 'header' | 'url'; /** * Header name used when `versioningStrategy` is `'header'`. * @default 'Accept-Version' */ versionHeader?: string; /** * Re-fetch stale cache entries when the browser tab regains focus * (similar to SWR / React Query behaviour). * @default false */ revalidateOnFocus?: boolean; /** * Re-fetch stale cache entries when the device reconnects to the network. * @default false */ revalidateOnReconnect?: boolean; /** * Attach a circuit breaker to all requests made by this client. * When the failure threshold is exceeded the circuit opens and subsequent * requests are rejected immediately with a {@link CircuitOpenError} — no * network round-trip is made until the reset timeout elapses. * * Pass `CircuitBreakerOptions` to create a new breaker automatically, or * pass an existing `CircuitBreaker` instance to share state across clients. * * @example * // Auto-create with options * circuitBreaker: { failureThreshold: 5, resetTimeoutMs: 30_000 } * * @example * // Shared instance * const breaker = new CircuitBreaker({ failureThreshold: 3 }); * const client = new HTTPClient({ circuitBreaker: breaker }); */ circuitBreaker?: CircuitBreakerOptions | CircuitBreaker; } type HTTPEvents = { 'upload:progress': [{ url: string; loaded: number; total: number | null; progress: number | null; }]; 'download:progress': [{ url: string; loaded: number; total: number | null; progress: number | null; }]; 'request:start': [{ url: string; method: string; requestId: string; }]; 'response:success': [{ url: string; method: string; status: number; requestId: string; duration: number; }]; 'response:error': [{ url: string; method: string; error: unknown; requestId: string; duration: number; }]; 'cache:revalidate': [{ url: string; key: string; data: unknown; }]; focus: []; online: []; }; /** * Main HTTP Client class providing a fluent API for making HTTP requests. * Supports interceptors, retries, timeout, and progress tracking. */ declare class HTTPClient extends EventEmitter implements IHTTPClient { private readonly config; interceptors: { request: RequestInterceptor[]; response: ResponseInterceptor[]; }; private connectionPool?; private rateLimiter?; private cacheManager?; private circuitBreaker?; private inFlightRequests; /** Dedicated promise store for the Suspense read() method — prevents infinite re-fetch loops */ private suspenseRequests; private cleanupCallbacks; private abortControllers; private offlineQueue?; private activeQueries; readonly metrics?: MetricsCollector; /** * Monotonically incrementing counter used to generate cheap request IDs. * Replaces `crypto.randomUUID()` on the hot path — a simple string concat is * ~10x faster for IDs that only need to be unique within this client instance. */ private _reqSeq; /** * Pre-normalised base headers from `config.headers` — computed once in the * constructor so the per-request hot path never calls normalizeHeaders() on * the (immutable) config object again. */ private _cachedBaseHeaders; /** * True when `config.retryPolicies` is absent/empty — lets _executeRequest() * skip the `.find()` scan and use `config.retry` directly. */ private _hasRetryPolicies; /** * True when at least one request interceptor is registered — avoids the * async reduce() overhead when the interceptor array is empty. */ private _hasRequestInterceptors; /** * True when at least one response interceptor is registered — same gain on * the response side. */ private _hasResponseInterceptors; /** * Pre-computed subset of config fields the fetch transport actually uses. * Avoids spreading the full HTTPClientConfig (22+ fields, many undefined) on * every request — only baseURL and timeoutMs are transport-relevant config * fields that are not overridden per-request by other logic. */ private _baseTransportOpts; /** * True when `config.onUploadProgress` is set — pre-computed so the hot path * avoids a property lookup into the full config object on every request. * Combined with a per-request check to decide whether to allocate the * upload-progress closure (saved on ~99% of requests). */ private _hasGlobalUploadProgress; /** * True when `config.onDownloadProgress` is set — same rationale as * `_hasGlobalUploadProgress`. */ private _hasGlobalDownloadProgress; static readonly debounce: typeof debounce; static readonly throttle: typeof throttle; static readonly delay: typeof delay; constructor(config: HTTPClientConfig); /** * Rebuild all hot-path caches. Called once from the constructor and again * whenever the interceptor arrays or base headers are mutated at runtime. * @internal */ /** @internal */ _rebuildPerfCaches(): void; /** * Clean up all resources and prevent memory leaks */ /** * Cancel a specific in-flight request by its ID. * * The request ID is exposed in the `'request:start'` event payload and via * `requestWithId()`. The cancelled request rejects with an {@link AbortError}. * * @param requestId The UUID of the request to cancel. * @returns `true` if the request was found and cancelled, `false` otherwise. * * @example * const { requestId, response } = client.requestWithId('/api/data'); * // user navigates away * client.cancel(requestId); */ cancel(requestId: string): boolean; /** * Cancel ALL in-flight requests managed by this client. * Each request rejects with an {@link AbortError}. * * @example * // Component unmount cleanup * useEffect(() => () => client.cancelAll(), []); */ cancelAll(): void; /** * Like `request()` but also returns the `requestId` so you can cancel this * specific request later via `client.cancel(requestId)`. * * @example * const { requestId, response } = client.requestWithId('/api/data'); * // Cancel if the component unmounts before the response arrives * client.cancel(requestId); * const data = (await response).data; */ requestWithId(url: string, options?: HTTPOptions): { requestId: string; response: Promise>; }; dispose(): void; /** * Register a cleanup callback for resource disposal */ onCleanup(callback: () => void): void; /** * Setup offline request queue for handling requests when offline */ private setupOfflineQueue; /** * Queue a request for execution when the client comes back online */ private queueOfflineRequest; /** * Setup automatic cleanup for browser environments */ private setupAutomaticCleanup; /** * Destroys the client and cleans up all resources. * Alias for dispose() — prefer dispose() for explicit resource management. * @deprecated Use dispose() instead for full cleanup. */ destroy(): void; private setupLogging; /** * Subscribe to changes for a specific URL. * Returns an unsubscribe function. */ subscribe(url: string, onChange: (data: T) => void, options?: HTTPOptions): () => void; /** * Revalidates all active queries (those with observers). */ revalidateActiveQueries(): Promise; /** * Manually set data in the cache. * Useful for optimistic updates. */ setQueryData(url: string, data: T, params?: Record>): void; /** * Optimistically update the cache and optionally revalidate. */ mutate(url: string, data: T | ((oldData: T | undefined) => T), options?: { revalidate?: boolean; params?: Record>; }): Promise; /** * Get data from cache synchronously. */ getQueryData(url: string, params?: ParamsValue): T | null; /** * Suspense-ready read method. * Throws a promise if data is loading/missing. * Returns data if available. */ read(url: string, options?: HTTPOptions): T; /** * Creates an InfiniteQuery instance for handling pagination/infinite scrolling. */ infiniteQuery(url: string, options: Omit, 'client' | 'url'>): InfiniteQuery; private notifyObservers; /** * Generic request method that handles interceptors, progress, and error propagation. * * @template T The expected response data type * @param url The URL to request * @param options Request configuration options * @returns Promise resolving to the HTTP response */ request(url: string, options?: HTTPOptions): Promise>; /** * Prefetch a URL and cache the result for faster subsequent reads. * * Returns a handle with a `cancel()` method so you can abort the background * fetch if the user navigates away before it completes (e.g. on hover-intent * patterns where the user leaves before the link loads). * * @example * ```ts * const handle = client.prefetch('/api/dashboard'); * * // If the user moves away before hover completes: * handle.cancel(); * * // Check whether the fetch already completed: * if (handle.completed) { * console.log('Already cached!'); * } * ``` */ prefetch(url: string, options?: HTTPOptions): { cancel: () => void; readonly completed: boolean; }; private _executeRequest; /** * Generates a cURL command equivalent for the given request. * Useful for debugging and sharing reproducible request examples. * * **Note:** Interceptors are NOT applied — the output reflects the raw * configuration passed in, not what would actually be sent over the wire. * * @param url The request URL (relative URLs are resolved against `baseURL`). * @param options Optional per-request overrides (headers, body, method, …). * @returns A ready-to-paste cURL command string. * * @example * ```ts * const curl = client.generateCurl('/users/1', { * method: 'GET', * headers: { Authorization: 'Bearer tok' }, * }); * // → curl -X GET -H 'Authorization: Bearer tok' 'https://api.example.com/users/1' * ``` */ generateCurl(url: string, options?: HTTPOptions): string; /** * Shared body serialization for POST / PUT / PATCH requests. * Handles FormData (smart Content-Type removal) and JSON stringify. * Extracted to avoid triplicating the same logic across all mutation methods. */ private _serializeBody; /** * Send a GET request. * @param url Request URL (relative URLs are resolved against `baseURL`). * @param options Optional per-request overrides. * @returns Promise that resolves to an {@link HTTPResponse} with the parsed body as `T`. * * @example * ```ts * const { data } = await client.get('/users'); * ``` */ get(url: string, options?: HTTPOptions): Promise>; /** * Send a HEAD request. Response has no body; useful for checking whether a * resource exists or reading its headers (e.g. `Content-Length`, `ETag`). * @param url Request URL. * @param options Optional per-request overrides. */ head(url: string, options?: HTTPOptions): Promise>; /** * Send an OPTIONS request. Useful for manual CORS preflight checks and API * capability discovery via the `Allow` response header. * @param url Request URL. * @param options Optional per-request overrides. */ options(url: string, options?: HTTPOptions): Promise>; /** * Send a POST request. Plain objects are automatically serialised to JSON * and `Content-Type: application/json` is set unless already provided. * Pass `options.useFormData: true` to send as `multipart/form-data` instead. * @param url Request URL. * @param data Request body — any JSON-serialisable value or `FormData`. * @param options Optional per-request overrides. * * @example * ```ts * const { data } = await client.post('/users', { name: 'Alice' }); * ``` */ post(url: string, data?: BodyData, options?: HTTPOptions): Promise>; /** * Send a PUT request. Replaces the target resource entirely. * Body serialisation follows the same rules as {@link post}. * @param url Request URL. * @param data Replacement body. * @param options Optional per-request overrides. * * @example * ```ts * await client.put('/users/1', { name: 'Bob', email: 'bob@example.com' }); * ``` */ put(url: string, data?: BodyData, options?: HTTPOptions): Promise>; /** * Send a DELETE request. * @param url Request URL. * @param options Optional per-request overrides (e.g. a body for bulk deletes). * * @example * ```ts * await client.delete('/users/1'); * ``` */ delete(url: string, options?: HTTPOptions): Promise>; /** * Send a PATCH request. Applies a partial update to the target resource. * Body serialisation follows the same rules as {@link post}. * @param url Request URL. * @param data Partial update payload. * @param options Optional per-request overrides. * * @example * ```ts * await client.patch('/users/1', { email: 'new@example.com' }); * ``` */ patch(url: string, data?: BodyData, options?: HTTPOptions): Promise>; /** * Like {@link request} but wraps the response in a {@link Result} discriminated * union instead of throwing. Ideal for error-handling without try/catch. * * @example * const result = await client.tryRequest('/me'); * if (!result.ok) { * console.error(result.error.status); // fully typed * return; * } * console.log(result.data.name); */ tryRequest(url: string, options?: HTTPOptions): Promise, HTTPError>>; /** Like {@link get} but returns a {@link Result} instead of throwing. */ tryGet(url: string, options?: HTTPOptions): Promise, HTTPError>>; /** Like {@link post} but returns a {@link Result} instead of throwing. */ tryPost(url: string, data?: BodyData, options?: HTTPOptions): Promise, HTTPError>>; /** Like {@link put} but returns a {@link Result} instead of throwing. */ tryPut(url: string, data?: BodyData, options?: HTTPOptions): Promise, HTTPError>>; /** Like {@link delete} but returns a {@link Result} instead of throwing. */ tryDelete(url: string, options?: HTTPOptions): Promise, HTTPError>>; /** Like {@link patch} but returns a {@link Result} instead of throwing. */ tryPatch(url: string, data?: BodyData, options?: HTTPOptions): Promise, HTTPError>>; } /** * Builder pattern for creating configured HTTPClient instances. */ declare class HTTPBuilder { private config; private requestInterceptors; private responseInterceptors; constructor(baseURL?: string); /** * Sets the base URL for all requests. */ withBaseURL(baseURL: string): this; /** * Sets the default timeout for requests. * @param timeoutMs Timeout in milliseconds */ withTimeout(timeoutMs: number): this; /** * Set a single default header by name and value. * * @example * builder.withHeader('Authorization', 'Bearer ') * builder.withHeader('X-Api-Key', 'my-key') */ withHeader(key: string, value: string): this; /** * Merge additional default headers. * Accepts a `HeadersRecord` (plain object with IntelliSense suggestions for * well-known header names), a `Headers` instance, or a `[name, value][]` array. * * @example * builder.withHeaders({ * Authorization: 'Bearer ', * 'Content-Type': 'application/json', * Accept: 'application/json', * }) */ withHeaders(headers: HeadersWithSuggestions): this; /** * Shortcut: set a `defaultHeaders` alias. Same as `.withHeaders()`. * @deprecated Prefer {@link withHeaders}. */ withDefaultHeaders(headers: HeadersWithSuggestions): this; withRetry(options: RetryOptions | boolean): this; withCache(options: CacheOptions | boolean): this; withTransport(transport: HTTPRequestFunction): this; withUploadProgress(callback: (progress: { loaded: number; total: number | null; progress: number | null; }) => void): this; withDownloadProgress(callback: (progress: { loaded: number; total: number | null; progress: number | null; }) => void): this; withRateLimit(options: { requests: number; interval: number; }): this; withOfflineQueue(options: boolean | PersistentQueueOptions): this; withDeduplication(enabled?: boolean): this; /** * Inject W3C Trace Context headers (`traceparent`, `tracestate`, `baggage`) * into every outgoing request — zero external dependencies, works with any * OpenTelemetry-compatible backend (Jaeger, Zipkin, Datadog, Honeycomb, etc.). * * @example * // Auto-generates trace IDs per request * builder.withOpenTelemetry() * * // With service name in baggage * builder.withOpenTelemetry({ * serviceName: 'checkout-service', * baggage: { 'user.tier': 'premium' }, * }) * * // Continue an upstream trace from an Express request * builder.withOpenTelemetry({ parentContext: extractTraceContext(req) }) */ withOpenTelemetry(config?: OTelConfig): this; withMetrics(enabled?: boolean, onUpdate?: (metrics: Metrics) => void): this; addRequestInterceptor(onFulfilled?: (config: HTTPOptions) => HTTPOptions | Promise, onRejected?: RequestInterceptor['onRejected']): this; addResponseInterceptor(onFulfilled?: (response: HTTPResponse) => HTTPResponse | Promise>, onRejected?: ResponseInterceptor['onRejected']): this; withRevalidation(options: { focus?: boolean; reconnect?: boolean; }): this; /** * Attach a custom logger to route request/response/error output to your * preferred sink (Winston, Pino, Datadog, etc.). * * @example * import { ConsoleLogger, LogLevel } from 'reixo'; * builder.withLogger(new ConsoleLogger(LogLevel.WARN)) */ withLogger(logger: Logger): this; /** * Enable a circuit breaker for all requests. Once the failure threshold is * exceeded the circuit opens and requests are rejected immediately with a * {@link CircuitOpenError} until the reset timeout elapses. * * @example * builder.withCircuitBreaker({ failureThreshold: 5, resetTimeoutMs: 30_000 }) */ withCircuitBreaker(options: CircuitBreakerOptions | CircuitBreaker): this; /** * Configure a Node.js HTTP/HTTPS connection pool (keep-alive, max sockets). * Has no effect in browser environments. * * @example * builder.withConnectionPool({ maxSockets: 50, keepAlive: true }) */ withConnectionPool(options: ConnectionPoolOptions): this; /** * Define per-URL-pattern retry overrides. The first matching pattern wins, * allowing you to disable retries on auth endpoints while keeping the global * retry policy for everything else. * * @example * builder.withRetryPolicies([ * { pattern: /\/auth\//, retry: false }, * { pattern: '/api/upload', retry: { maxRetries: 1 } }, * ]) */ withRetryPolicies(policies: Array<{ pattern: string | RegExp; retry: RetryOptions | boolean; }>): this; /** * Configure API versioning. Version can be prepended as a URL segment * (`/v2/users`) or injected as a header. * * @example * builder.withVersioning('v2', 'url') * builder.withVersioning('2026-01-01', 'header', 'API-Version') */ withVersioning(version: string, strategy?: 'url' | 'header', headerName?: string): this; build(): HTTPClient; static create(baseURL?: string): HTTPBuilder; static readonly debounce: typeof debounce; static readonly throttle: typeof throttle; static readonly delay: typeof delay; } //#endregion export { mapResult as $, CacheManager as A, HTTPOptions as B, InfiniteData as C, CircuitBreakerOptions as D, CircuitBreaker as E, AbortError as F, TimeoutError as G, NetworkError as H, CacheMetadata as I, http as J, ValidationError as K, CircuitOpenError as L, MemoryAdapter as M, StorageAdapter as N, CircuitState as O, WebStorageAdapter as P, err as Q, HTTPError as R, parseTraceparent as S, InfiniteQueryOptions as T, ParamScalar as U, HTTPResponse as V, ParamsValue as W, Ok as X, Err as Y, Result as Z, OTelConfig as _, HTTPRequestFunction as a, MetricsCollector as at, createOTelInterceptor as b, JsonPrimitive as c, Logger as d, ok as et, RequestInterceptor as f, TaskQueue as g, QueueEvents as h, HTTPClientConfig as i, Metrics as it, CacheOptions as j, CacheEntry as k, JsonValue as l, PersistentQueueOptions as m, HTTPBuilder as n, unwrap as nt, JsonArray as o, RequestMetrics as ot, ResponseInterceptor as p, ValidationSchema as q, HTTPClient as r, unwrapOr as rt, JsonObject as s, BodyData as t, toResult as tt, LogMeta as u, OTelSpanHooks as v, InfiniteQuery as w, formatTraceparent as x, SpanContext as y, HTTPMethod as z }; //# sourceMappingURL=http-client-BUW7Yoqd.d.ts.map