@zayne-labs/callapi
Version:
A lightweight wrapper over fetch with quality of life improvements like built-in request cancellation, retries, interceptors and more
1,813 lines • 80.5 kB
TypeScript
//#region src/types/type-helpers.d.ts
type AnyString = string & NonNullable<unknown>;
type AnyNumber = number & NonNullable<unknown>;
type AnyFunction<TResult = unknown> = (...args: any[]) => TResult;
type Prettify<TObject> = NonNullable<unknown> & { [Key in keyof TObject]: TObject[Key] };
type WriteableLevel = "deep" | "shallow";
/**
* Makes all properties in an object type writeable (removes readonly modifiers).
* Supports both shallow and deep modes, and handles special cases like arrays, tuples, and unions.
* @template TObject - The object type to make writeable
* @template TVariant - The level of writeable transformation ("shallow" | "deep")
*/
type ArrayOrObject = Record<number | string | symbol, unknown> | unknown[];
type Writeable<TObject, TLevel extends WriteableLevel = "shallow"> = TObject extends readonly [...infer TTupleItems] ? [...{ [Index in keyof TTupleItems]: TLevel extends "deep" ? Writeable<TTupleItems[Index], "deep"> : TTupleItems[Index] }] : TObject extends ArrayOrObject ? { -readonly [Key in keyof TObject]: TLevel extends "deep" ? Writeable<TObject[Key], "deep"> : TObject[Key] } : TObject;
type UnionToIntersection<TUnion> = (TUnion extends unknown ? (param: TUnion) => void : never) extends ((param: infer TParam) => void) ? TParam : never;
type UnmaskType<TValue> = {
_: TValue;
}["_"];
type Awaitable<TValue> = Promise<TValue> | TValue;
type CommonRequestHeaders = "Access-Control-Allow-Credentials" | "Access-Control-Allow-Headers" | "Access-Control-Allow-Methods" | "Access-Control-Allow-Origin" | "Access-Control-Expose-Headers" | "Access-Control-Max-Age" | "Age" | "Allow" | "Cache-Control" | "Clear-Site-Data" | "Content-Disposition" | "Content-Encoding" | "Content-Language" | "Content-Length" | "Content-Location" | "Content-Range" | "Content-Security-Policy-Report-Only" | "Content-Security-Policy" | "Cookie" | "Cross-Origin-Embedder-Policy" | "Cross-Origin-Opener-Policy" | "Cross-Origin-Resource-Policy" | "Date" | "ETag" | "Expires" | "Last-Modified" | "Location" | "Permissions-Policy" | "Pragma" | "Retry-After" | "Save-Data" | "Sec-CH-Prefers-Color-Scheme" | "Sec-CH-Prefers-Reduced-Motion" | "Sec-CH-UA-Arch" | "Sec-CH-UA-Bitness" | "Sec-CH-UA-Form-Factor" | "Sec-CH-UA-Full-Version-List" | "Sec-CH-UA-Full-Version" | "Sec-CH-UA-Mobile" | "Sec-CH-UA-Model" | "Sec-CH-UA-Platform-Version" | "Sec-CH-UA-Platform" | "Sec-CH-UA-WoW64" | "Sec-CH-UA" | "Sec-Fetch-Dest" | "Sec-Fetch-Mode" | "Sec-Fetch-Site" | "Sec-Fetch-User" | "Sec-GPC" | "Server-Timing" | "Server" | "Service-Worker-Navigation-Preload" | "Set-Cookie" | "Strict-Transport-Security" | "Timing-Allow-Origin" | "Trailer" | "Transfer-Encoding" | "Upgrade" | "Vary" | "Warning" | "WWW-Authenticate" | "X-Content-Type-Options" | "X-DNS-Prefetch-Control" | "X-Frame-Options" | "X-Permitted-Cross-Domain-Policies" | "X-Powered-By" | "X-Robots-Tag" | "X-XSS-Protection" | AnyString;
type CommonAuthorizationHeaders = `${"Basic" | "Bearer" | "Token"} ${string}`;
type CommonContentTypes = "application/epub+zip" | "application/gzip" | "application/json" | "application/ld+json" | "application/octet-stream" | "application/ogg" | "application/pdf" | "application/rtf" | "application/vnd.ms-fontobject" | "application/wasm" | "application/xhtml+xml" | "application/xml" | "application/zip" | "audio/aac" | "audio/mpeg" | "audio/ogg" | "audio/opus" | "audio/webm" | "audio/x-midi" | "font/otf" | "font/ttf" | "font/woff" | "font/woff2" | "image/avif" | "image/bmp" | "image/gif" | "image/jpeg" | "image/png" | "image/svg+xml" | "image/tiff" | "image/webp" | "image/x-icon" | "model/gltf-binary" | "model/gltf+json" | "text/calendar" | "text/css" | "text/csv" | "text/html" | "text/javascript" | "text/plain" | "video/3gpp" | "video/3gpp2" | "video/av1" | "video/mp2t" | "video/mp4" | "video/mpeg" | "video/ogg" | "video/webm" | "video/x-msvideo" | AnyString;
//#endregion
//#region src/auth.d.ts
type ValueOrFunctionResult<TValue> = TValue | (() => TValue);
type ValidAuthValue = ValueOrFunctionResult<Awaitable<string | null | undefined>>;
/**
* Bearer Or Token authentication
*
* The value of `bearer` will be added to a header as
* `auth: Bearer some-auth-token`,
*
* The value of `token` will be added to a header as
* `auth: Token some-auth-token`,
*/
type BearerOrTokenAuth = {
type?: "Bearer";
bearer?: ValidAuthValue;
token?: never;
} | {
type?: "Token";
bearer?: never;
token?: ValidAuthValue;
};
/**
* Basic auth
*/
type BasicAuth = {
type: "Basic";
username: ValidAuthValue;
password: ValidAuthValue;
};
/**
* Custom auth
*
* @param prefix - prefix of the header
* @param authValue - value of the header
*
* @example
* ```ts
* {
* type: "Custom",
* prefix: "Token",
* authValue: "token"
* }
* ```
*/
type CustomAuth = {
type: "Custom";
prefix: ValidAuthValue;
value: ValidAuthValue;
};
type Auth = BearerOrTokenAuth | BasicAuth | CustomAuth;
//#endregion
//#region src/constants/common.d.ts
declare const fetchSpecificKeys: (keyof RequestInit | "duplex")[];
//#endregion
//#region src/types/standard-schema.d.ts
/**
* The Standard Schema interface.
* @see https://github.com/standard-schema/standard-schema
*/
interface StandardSchemaV1<Input = unknown, Output = Input> {
/**
* The Standard Schema properties.
*/
readonly "~standard": StandardSchemaV1.Props<Input, Output>;
}
declare namespace StandardSchemaV1 {
/**
* The Standard Schema properties interface.
*/
interface Props<Input = unknown, Output = Input> {
/**
* Inferred types associated with the schema.
*/
readonly types?: Types<Input, Output> | undefined;
/**
* Validates unknown input values.
*/
readonly validate: (value: unknown) => Promise<Result<Output>> | Result<Output>;
/**
* The vendor name of the schema library.
*/
readonly vendor: string;
/**
* The version number of the standard.
*/
readonly version: 1;
}
/**
* The result interface of the validate function.
*/
type Result<Output> = FailureResult | SuccessResult<Output>;
/**
* The result interface if validation succeeds.
*/
interface SuccessResult<Output> {
/**
* The non-existent issues.
*/
readonly issues?: undefined;
/**
* The typed output value.
*/
readonly value: Output;
}
/**
* The result interface if validation fails.
*/
interface FailureResult {
/**
* The issues of failed validation.
*/
readonly issues: readonly Issue[];
}
/**
* The issue interface of the failure output.
*/
interface Issue {
/**
* The error message of the issue.
*/
readonly message: string;
/**
* The path of the issue, if any.
*/
readonly path?: ReadonlyArray<PathSegment | PropertyKey> | undefined;
}
/**
* The path segment interface of the issue.
*/
interface PathSegment {
/**
* The key representing a path segment.
*/
readonly key: PropertyKey;
}
/**
* The Standard Schema types interface.
*/
interface Types<Input = unknown, Output = Input> {
/** The input type of the schema. */
readonly input: Input;
/** The output type of the schema. */
readonly output: Output;
}
/**
* Infers the input type of a Standard Schema.
*/
type InferInput<Schema extends StandardSchemaV1> = NonNullable<Schema["~standard"]["types"]>["input"];
/**
* Infers the output type of a Standard Schema.
*/
type InferOutput<Schema extends StandardSchemaV1> = NonNullable<Schema["~standard"]["types"]>["output"];
}
//#endregion
//#region src/error.d.ts
type HTTPErrorDetails<TErrorData> = Pick<CallApiExtraOptions, "defaultHTTPErrorMessage"> & {
errorData: TErrorData;
response: Response;
};
declare class HTTPError<TErrorData = Record<string, unknown>> extends Error {
errorData: HTTPErrorDetails<TErrorData>["errorData"];
httpErrorSymbol: symbol;
isHTTPError: boolean;
name: "HTTPError";
response: HTTPErrorDetails<TErrorData>["response"];
constructor(errorDetails: HTTPErrorDetails<TErrorData>, errorOptions?: ErrorOptions);
/**
* @description Checks if the given error is an instance of HTTPError
* @param error - The error to check
* @returns true if the error is an instance of HTTPError, false otherwise
*/
static isError<TErrorData>(error: unknown): error is HTTPError<TErrorData>;
}
type ValidationErrorDetails = {
issues: readonly StandardSchemaV1.Issue[];
response: Response | null;
};
declare class ValidationError extends Error {
errorData: ValidationErrorDetails["issues"];
name: string;
response: ValidationErrorDetails["response"];
validationErrorSymbol: symbol;
constructor(details: ValidationErrorDetails, errorOptions?: ErrorOptions);
/**
* @description Checks if the given error is an instance of HTTPError
* @param error - The error to check
* @returns true if the error is an instance of HTTPError, false otherwise
*/
static isError(error: unknown): error is ValidationError;
}
//#endregion
//#region src/url.d.ts
type AllowedQueryParamValues = UnmaskType<boolean | number | string>;
type RecordStyleParams = UnmaskType<Record<string, AllowedQueryParamValues>>;
type TupleStyleParams = UnmaskType<AllowedQueryParamValues[]>;
type Params = UnmaskType<RecordStyleParams | TupleStyleParams>;
type Query = UnmaskType<Record<string, AllowedQueryParamValues>>;
type InitURLOrURLObject = AnyString | RouteKeyMethodsURLUnion | URL;
interface URLOptions {
/**
* Base URL for all API requests. Will only be prepended to relative URLs.
*
* Absolute URLs (starting with http/https) will not be prepended by the baseURL.
*
* @example
* ```ts
* // Set base URL for all requests
* baseURL: "https://api.example.com/v1"
*
* // Then use relative URLs in requests
* callApi("/users") // → https://api.example.com/v1/users
* callApi("/posts/123") // → https://api.example.com/v1/posts/123
*
* // Environment-specific base URLs
* baseURL: process.env.NODE_ENV === "production"
* ? "https://api.example.com"
* : "http://localhost:3000/api"
* ```
*/
baseURL?: string;
/**
* Resolved request URL after processing baseURL, parameters, and query strings (readonly)
*
* This is the final URL that will be used for the HTTP request, computed from
* baseURL, initURL, params, and query parameters.
*
*/
readonly fullURL?: string;
/**
* The original URL string passed to the callApi instance (readonly)
*
* This preserves the original URL as provided, including any method modifiers like "@get/" or "@post/".
*
*/
readonly initURL?: string;
/**
* The URL string after normalization, with method modifiers removed(readonly)
*
* Method modifiers like "@get/", "@post/" are stripped to create a clean URL
* for parameter substitution and final URL construction.
*
*/
readonly initURLNormalized?: string;
/**
* Parameters to be substituted into URL path segments.
*
* Supports both object-style (named parameters) and array-style (positional parameters)
* for flexible URL parameter substitution.
*
* @example
* ```typescript
* // Object-style parameters (recommended)
* const namedParams: URLOptions = {
* initURL: "/users/:userId/posts/:postId",
* params: { userId: "123", postId: "456" }
* };
* // Results in: /users/123/posts/456
*
* // Array-style parameters (positional)
* const positionalParams: URLOptions = {
* initURL: "/users/:userId/posts/:postId",
* params: ["123", "456"] // Maps in order: userId=123, postId=456
* };
* // Results in: /users/123/posts/456
*
* // Single parameter
* const singleParam: URLOptions = {
* initURL: "/users/:id",
* params: { id: "user-123" }
* };
* // Results in: /users/user-123
* ```
*/
params?: Params;
/**
* Query parameters to append to the URL as search parameters.
*
* These will be serialized into the URL query string using standard
* URL encoding practices.
*
* @example
* ```typescript
* // Basic query parameters
* const queryOptions: URLOptions = {
* initURL: "/users",
* query: {
* page: 1,
* limit: 10,
* search: "john doe",
* active: true
* }
* };
* // Results in: /users?page=1&limit=10&search=john%20doe&active=true
*
* // Filtering and sorting
* const filterOptions: URLOptions = {
* initURL: "/products",
* query: {
* category: "electronics",
* minPrice: 100,
* maxPrice: 500,
* sortBy: "price",
* order: "asc"
* }
* };
* // Results in: /products?category=electronics&minPrice=100&maxPrice=500&sortBy=price&order=asc
* ```
*/
query?: Query;
}
//#endregion
//#region src/validation.d.ts
type InferSchemaResult<TSchema, TFallbackResult = unknown> = undefined extends TSchema ? TFallbackResult : TSchema extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<TSchema> : TSchema extends AnyFunction<infer TResult> ? Awaited<TResult> : TFallbackResult;
interface CallApiSchemaConfig {
/**
* The base url of the schema. By default it's the baseURL of the callApi instance.
*/
baseURL?: string;
/**
* Disables runtime validation for the schema.
*/
disableRuntimeValidation?: boolean;
/**
* If `true`, the original input value will be used instead of the transformed/validated output.
*
* This is useful when you want to validate the input but don't want any transformations
* applied by the validation schema (e.g., type coercion, default values, etc).
*/
disableValidationOutputApplication?: boolean;
/**
* Optional url prefix that will be substituted for the `baseURL` of the schemaConfig at runtime.
*
* This allows you to reuse the same schema against different base URLs (for example,
* swapping between `/api/v1` and `/api/v2`) without redefining the entire schema.
*/
prefix?: string;
/**
* Controls the strictness of API route validation.
*
* When true:
* - Only routes explicitly defined in the schema will be considered valid to typescript and the runtime.
* - Attempting to call routes not defined in the schema will result in both type errors and runtime validation errors.
* - Useful for ensuring API calls conform exactly to your schema definition
*
* When false or undefined (default):
* - All routes will be allowed, whether they are defined in the schema or not
*/
strict?: boolean;
}
interface CallApiSchema {
/**
* The schema to use for validating the request body.
*/
body?: StandardSchemaV1<Body> | ((body: Body) => Awaitable<Body>);
/**
* The schema to use for validating the response data.
*/
data?: StandardSchemaV1 | ((data: unknown) => unknown);
/**
* The schema to use for validating the response error data.
*/
errorData?: StandardSchemaV1 | ((errorData: unknown) => unknown);
/**
* The schema to use for validating the request headers.
*/
headers?: StandardSchemaV1<HeadersOption | undefined> | ((headers: HeadersOption) => Awaitable<HeadersOption | undefined>);
/**
* The schema to use for validating the meta option.
*/
meta?: StandardSchemaV1<GlobalMeta | undefined> | ((meta: GlobalMeta) => Awaitable<GlobalMeta | undefined>);
/**
* The schema to use for validating the request method.
*/
method?: StandardSchemaV1<MethodUnion | undefined> | ((method: MethodUnion) => Awaitable<MethodUnion | undefined>);
/**
* The schema to use for validating the request url parameters.
*/
params?: StandardSchemaV1<Params | undefined> | ((params: Params) => Awaitable<Params | undefined>);
/**
* The schema to use for validating the request url queries.
*/
query?: StandardSchemaV1<Query | undefined> | ((query: Query) => Awaitable<Query | undefined>);
}
declare const routeKeyMethods: ["delete", "get", "patch", "post", "put"];
type RouteKeyMethods = (typeof routeKeyMethods)[number];
type RouteKeyMethodsURLUnion = `@${RouteKeyMethods}/`;
type BaseCallApiSchemaRoutes = Partial<Record<AnyString | RouteKeyMethodsURLUnion, CallApiSchema>>;
type BaseCallApiSchemaAndConfig = {
config?: CallApiSchemaConfig;
routes: BaseCallApiSchemaRoutes;
};
declare const fallBackRouteSchemaKey = ".";
type FallBackRouteSchemaKey = typeof fallBackRouteSchemaKey;
//#endregion
//#region src/plugins.d.ts
type PluginSetupContext<TPluginExtraOptions = unknown> = RequestContext & PluginExtraOptions<TPluginExtraOptions> & {
initURL: string;
};
type PluginInitResult = Partial<Omit<PluginSetupContext, "initURL" | "request"> & {
initURL: InitURLOrURLObject;
request: CallApiRequestOptions;
}>;
type PluginHooksWithMoreOptions<TMoreOptions = unknown> = HooksOrHooksArray<never, never, TMoreOptions>;
type PluginHooks<TData = never, TErrorData = never, TMoreOptions = unknown> = HooksOrHooksArray<TData, TErrorData, TMoreOptions>;
interface CallApiPlugin {
/**
* Defines additional options that can be passed to callApi
*/
defineExtraOptions?: (...params: never[]) => unknown;
/**
* A description for the plugin
*/
description?: string;
/**
* Hooks / Interceptors for the plugin
*/
hooks?: PluginHooks;
/**
* A unique id for the plugin
*/
id: string;
/**
* A name for the plugin
*/
name: string;
/**
* Base schema for the client.
*/
schema?: BaseCallApiSchemaAndConfig;
/**
* A function that will be called when the plugin is initialized. This will be called before the any of the other internal functions.
*/
setup?: (context: PluginSetupContext) => Awaitable<PluginInitResult> | Awaitable<void>;
/**
* A version for the plugin
*/
version?: string;
}
//#endregion
//#region src/types/default-types.d.ts
type DefaultDataType = unknown;
type DefaultPluginArray = CallApiPlugin[];
type DefaultThrowOnError = boolean;
//#endregion
//#region src/result.d.ts
type Parser<TData> = (responseString: string) => Awaitable<TData>;
declare const getResponseType: <TResponse>(response: Response, parser: Parser<TResponse>) => {
arrayBuffer: () => Promise<ArrayBuffer>;
blob: () => Promise<Blob>;
formData: () => Promise<FormData>;
json: () => Promise<TResponse>;
stream: () => ReadableStream<Uint8Array<ArrayBuffer>> | null;
text: () => Promise<string>;
};
type InitResponseTypeMap<TResponse = unknown> = ReturnType<typeof getResponseType<TResponse>>;
type ResponseTypeUnion = keyof InitResponseTypeMap | null;
type ResponseTypeMap<TResponse> = { [Key in keyof InitResponseTypeMap<TResponse>]: Awaited<ReturnType<InitResponseTypeMap<TResponse>[Key]>> };
type GetResponseType<TResponse, TResponseType extends ResponseTypeUnion, TComputedResponseTypeMap extends ResponseTypeMap<TResponse> = ResponseTypeMap<TResponse>> = null extends TResponseType ? TComputedResponseTypeMap["json"] : TResponseType extends NonNullable<ResponseTypeUnion> ? TComputedResponseTypeMap[TResponseType] : never;
type CallApiResultSuccessVariant<TData> = {
data: NoInfer<TData>;
error: null;
response: Response;
};
type PossibleJavaScriptError = UnmaskType<{
errorData: false;
message: string;
name: "AbortError" | "Error" | "SyntaxError" | "TimeoutError" | "TypeError" | AnyString;
originalError: DOMException | Error | SyntaxError | TypeError;
}>;
type PossibleHTTPError<TErrorData> = UnmaskType<{
errorData: NoInfer<TErrorData>;
message: string;
name: "HTTPError";
originalError: HTTPError;
}>;
type PossibleValidationError = UnmaskType<{
errorData: ValidationError["errorData"];
message: string;
name: "ValidationError";
originalError: ValidationError;
}>;
type PossibleJavaScriptOrValidationError = UnmaskType<PossibleJavaScriptError | PossibleValidationError>;
type CallApiResultErrorVariant<TErrorData> = {
data: null;
error: PossibleHTTPError<TErrorData>;
response: Response;
} | {
data: null;
error: PossibleJavaScriptOrValidationError;
response: Response | null;
};
type ResultModeMap<TData = DefaultDataType, TErrorData = DefaultDataType, TResponseType extends ResponseTypeUnion = ResponseTypeUnion, TComputedData = GetResponseType<TData, TResponseType>, TComputedErrorData = GetResponseType<TErrorData, TResponseType>> = UnmaskType<{
all: CallApiResultSuccessVariant<TComputedData> | CallApiResultErrorVariant<TComputedErrorData>;
allWithException: CallApiResultSuccessVariant<TComputedData>;
onlySuccess: CallApiResultErrorVariant<TComputedErrorData>["data"] | CallApiResultSuccessVariant<TComputedData>["data"];
onlySuccessWithException: CallApiResultSuccessVariant<TComputedData>["data"];
}>;
type ResultModeUnion = keyof ResultModeMap | null;
type GetCallApiResult<TData, TErrorData, TResultMode extends ResultModeUnion, TThrowOnError extends ThrowOnErrorUnion, TResponseType extends ResponseTypeUnion> = TErrorData extends false ? ResultModeMap<TData, TErrorData, TResponseType>["onlySuccessWithException"] : TErrorData extends false | undefined ? ResultModeMap<TData, TErrorData, TResponseType>["onlySuccessWithException"] : TErrorData extends false | null ? ResultModeMap<TData, TErrorData, TResponseType>["onlySuccess"] : null extends TResultMode ? TThrowOnError extends true ? ResultModeMap<TData, TErrorData, TResponseType>["allWithException"] : ResultModeMap<TData, TErrorData, TResponseType>["all"] : TResultMode extends NonNullable<ResultModeUnion> ? ResultModeMap<TData, TErrorData, TResponseType>[TResultMode] : never;
//#endregion
//#region src/stream.d.ts
type StreamProgressEvent = {
/**
* Current chunk of data being streamed
*/
chunk: Uint8Array;
/**
* Progress in percentage
*/
progress: number;
/**
* Total size of data in bytes
*/
totalBytes: number;
/**
* Amount of data transferred so far
*/
transferredBytes: number;
};
declare global {
interface ReadableStream<R> {
[Symbol.asyncIterator]: () => AsyncIterableIterator<R>;
}
}
//#endregion
//#region src/hooks.d.ts
type PluginExtraOptions<TPluginOptions = unknown> = {
/** Plugin-specific options passed to the plugin configuration */
options: Partial<TPluginOptions>;
};
interface Hooks<TData = DefaultDataType, TErrorData = DefaultDataType, TPluginOptions = unknown> {
/**
* Hook called when any error occurs within the request/response lifecycle.
*
* This is a unified error handler that catches both request errors (network failures,
* timeouts, etc.) and response errors (HTTP error status codes). It's essentially
* a combination of `onRequestError` and `onResponseError` hooks.
*
* @param context - Error context containing error details, request info, and response (if available)
* @returns Promise or void - Hook can be async or sync
*/
onError?: (context: ErrorContext<TErrorData> & PluginExtraOptions<TPluginOptions>) => Awaitable<unknown>;
/**
* Hook called just before the HTTP request is sent.
*
* This is the ideal place to modify request headers, add authentication,
* implement request logging, or perform any setup before the network call.
*
* @param context - Request context with mutable request object and configuration
* @returns Promise or void - Hook can be async or sync
*
*/
onRequest?: (context: RequestContext & PluginExtraOptions<TPluginOptions>) => Awaitable<unknown>;
/**
* Hook called when an error occurs during the fetch request itself.
*
* This handles network-level errors like connection failures, timeouts,
* DNS resolution errors, or other issues that prevent getting an HTTP response.
* Note that HTTP error status codes (4xx, 5xx) are handled by `onResponseError`.
*
* @param context - Request error context with error details and null response
* @returns Promise or void - Hook can be async or sync
*/
onRequestError?: (context: RequestErrorContext & PluginExtraOptions<TPluginOptions>) => Awaitable<unknown>;
/**
* Hook called during upload stream progress tracking.
*
* This hook is triggered when uploading data (like file uploads) and provides
* progress information about the upload. Useful for implementing progress bars
* or upload status indicators.
*
* @param context - Request stream context with progress event and request instance
* @returns Promise or void - Hook can be async or sync
*
*/
onRequestStream?: (context: RequestStreamContext & PluginExtraOptions<TPluginOptions>) => Awaitable<unknown>;
/**
* Hook called when any HTTP response is received from the API.
*
* This hook is triggered for both successful (2xx) and error (4xx, 5xx) responses.
* It's useful for response logging, metrics collection, or any processing that
* should happen regardless of response status.
*
* @param context - Response context with either success data or error information
* @returns Promise or void - Hook can be async or sync
*
*/
onResponse?: (context: ResponseContext<TData, TErrorData> & PluginExtraOptions<TPluginOptions>) => Awaitable<unknown>;
/**
* Hook called when an HTTP error response (4xx, 5xx) is received from the API.
*
* This handles server-side errors where an HTTP response was successfully received
* but indicates an error condition. Different from `onRequestError` which handles
* network-level failures.
*
* @param context - Response error context with HTTP error details and response
* @returns Promise or void - Hook can be async or sync
*/
onResponseError?: (context: ResponseErrorContext<TErrorData> & PluginExtraOptions<TPluginOptions>) => Awaitable<unknown>;
/**
* Hook called during download stream progress tracking.
*
* This hook is triggered when downloading data (like file downloads) and provides
* progress information about the download. Useful for implementing progress bars
* or download status indicators.
*
* @param context - Response stream context with progress event and response
* @returns Promise or void - Hook can be async or sync
*
*/
onResponseStream?: (context: ResponseStreamContext & PluginExtraOptions<TPluginOptions>) => Awaitable<unknown>;
/**
* Hook called when a request is being retried.
*
* This hook is triggered before each retry attempt, providing information about
* the previous failure and the current retry attempt number. Useful for implementing
* custom retry logic, exponential backoff, or retry logging.
*
* @param context - Retry context with error details and retry attempt count
* @returns Promise or void - Hook can be async or sync
*
*/
onRetry?: (response: RetryContext<TErrorData> & PluginExtraOptions<TPluginOptions>) => Awaitable<unknown>;
/**
* Hook called when a successful response (2xx status) is received from the API.
*
* This hook is triggered only for successful responses and provides access to
* the parsed response data. Ideal for success logging, caching, or post-processing
* of successful API responses.
*
* @param context - Success context with parsed response data and response object
* @returns Promise or void - Hook can be async or sync
*
*/
onSuccess?: (context: SuccessContext<TData> & PluginExtraOptions<TPluginOptions>) => Awaitable<unknown>;
/**
* Hook called when a validation error occurs.
*
* This hook is triggered when request or response data fails validation against
* a defined schema. It provides access to the validation error details and can
* be used for custom error handling, logging, or fallback behavior.
*
* @param context - Validation error context with error details and response (if available)
* @returns Promise or void - Hook can be async or sync
*
*/
onValidationError?: (context: ValidationErrorContext & PluginExtraOptions<TPluginOptions>) => Awaitable<unknown>;
}
type HooksOrHooksArray<TData = DefaultDataType, TErrorData = DefaultDataType, TMoreOptions = unknown> = { [Key in keyof Hooks<TData, TErrorData, TMoreOptions>]: Hooks<TData, TErrorData, TMoreOptions>[Key] | Array<Hooks<TData, TErrorData, TMoreOptions>[Key]> };
interface HookConfigOptions {
/**
* Controls the execution mode of all composed hooks (main + plugin hooks).
*
* - **"parallel"**: All hooks execute simultaneously via Promise.all() for better performance
* - **"sequential"**: All hooks execute one by one in registration order via await in a loop
*
* This affects how ALL hooks execute together, regardless of their source (main or plugin).
*
* Use `hookRegistrationOrder` to control the registration order of main vs plugin hooks.
*
* @default "parallel"
*
* @example
* ```ts
* // Parallel execution (default) - all hooks run simultaneously
* hooksExecutionMode: "parallel"
*
* // Sequential execution - hooks run one after another
* hooksExecutionMode: "sequential"
*
* // Use case: Hooks have dependencies and must run in order
* const client = callApi.create({
* hooksExecutionMode: "sequential",
* hookRegistrationOrder: "mainFirst",
* plugins: [transformPlugin],
* onRequest: (ctx) => {
* // This runs first, then transform plugin runs
* ctx.request.headers["x-request-id"] = generateId();
* }
* });
*
* // Use case: Independent operations can run in parallel for speed
* const client = callApi.create({
* hooksExecutionMode: "parallel", // Default
* plugins: [metricsPlugin, cachePlugin, loggingPlugin],
* onRequest: (ctx) => {
* // All hooks (main + plugins) run simultaneously
* addRequestTimestamp(ctx.request);
* }
* });
*
* // Use case: Error handling hooks that need sequential processing
* const client = callApi.create({
* hooksExecutionMode: "sequential",
* onError: [
* (ctx) => logError(ctx.error), // Log first
* (ctx) => reportError(ctx.error), // Then report
* (ctx) => cleanupResources(ctx) // Finally cleanup
* ]
* });
* ```
*/
hooksExecutionMode?: "parallel" | "sequential";
/**
* Controls the registration order of main hooks relative to plugin hooks.
*
* - **"pluginsFirst"**: Plugin hooks register first, then main hooks (default)
* - **"mainFirst"**: Main hooks register first, then plugin hooks
*
* This determines the order hooks are added to the registry, which affects
* their execution sequence when using sequential execution mode.
*
* @default "pluginsFirst"
*
* @example
* ```ts
* // Plugin hooks register first (default behavior)
* hookRegistrationOrder: "pluginsFirst"
*
* // Main hooks register first
* hookRegistrationOrder: "mainFirst"
*
* // Use case: Main validation before plugin processing
* const client = callApi.create({
* hookRegistrationOrder: "mainFirst",
* hooksExecutionMode: "sequential",
* plugins: [transformPlugin],
* onRequest: (ctx) => {
* // This main hook runs first in sequential mode
* if (!ctx.request.headers.authorization) {
* throw new Error("Authorization required");
* }
* }
* });
*
* // Use case: Plugin setup before main logic (default)
* const client = callApi.create({
* hookRegistrationOrder: "pluginsFirst", // Default
* hooksExecutionMode: "sequential",
* plugins: [setupPlugin],
* onRequest: (ctx) => {
* // Plugin runs first, then this main hook
* console.log("Request prepared:", ctx.request.url);
* }
* });
*
* // Use case: Parallel mode (registration order less important)
* const client = callApi.create({
* hookRegistrationOrder: "pluginsFirst",
* hooksExecutionMode: "parallel", // All run simultaneously
* plugins: [metricsPlugin, cachePlugin],
* onRequest: (ctx) => {
* // All hooks run in parallel regardless of registration order
* addRequestId(ctx.request);
* }
* });
* ```
*/
hooksRegistrationOrder?: "mainFirst" | "pluginsFirst";
}
type RequestContext = {
/**
* Base configuration object passed to createFetchClient.
*
* Contains the foundational configuration that applies to all requests
* made by this client instance, such as baseURL, default headers, and
* global options.
*/
baseConfig: BaseCallApiExtraOptions & CallApiRequestOptions;
/**
* Instance-specific configuration object passed to the callApi instance.
*
* Contains configuration specific to this particular API call, which
* can override or extend the base configuration.
*/
config: CallApiExtraOptions & CallApiRequestOptions;
/**
* Merged options combining base config, instance config, and default options.
*
* This is the final resolved configuration that will be used for the request,
* with proper precedence applied (instance > base > defaults).
*/
options: CallApiExtraOptionsForHooks;
/**
* Merged request object ready to be sent.
*
* Contains the final request configuration including URL, method, headers,
* body, and other fetch options. This object can be modified in onRequest
* hooks to customize the outgoing request.
*/
request: CallApiRequestOptionsForHooks;
};
type ValidationErrorContext = UnmaskType<RequestContext & {
/** Validation error containing details about what failed validation */
error: ValidationError;
/** HTTP response object if validation failed on response, null if on request */
response: Response | null;
}>;
type SuccessContext<TData> = UnmaskType<RequestContext & {
/** Parsed response data with the expected success type */
data: TData;
/** HTTP response object for the successful request */
response: Response;
}>;
type ResponseContext<TData, TErrorData> = UnmaskType<RequestContext & (Prettify<CallApiResultSuccessVariant<TData>> | Prettify<Extract<CallApiResultErrorVariant<TErrorData>, {
error: PossibleHTTPError<TErrorData>;
}>>)>;
type RequestErrorContext = RequestContext & {
/** Error that occurred during the request (network, timeout, etc.) */
error: PossibleJavaScriptOrValidationError;
/** Always null for request errors since no response was received */
response: null;
};
type ErrorContext<TErrorData> = UnmaskType<RequestContext & ({
/** HTTP error with response data */
error: PossibleHTTPError<TErrorData>;
/** HTTP response object containing error status */
response: Response;
} | {
/** Request-level error (network, timeout, validation, etc.) */
error: PossibleJavaScriptOrValidationError;
/** Response object if available, null for request errors */
response: Response | null;
})>;
type ResponseErrorContext<TErrorData> = UnmaskType<Extract<ErrorContext<TErrorData>, {
error: PossibleHTTPError<TErrorData>;
}> & RequestContext>;
type RetryContext<TErrorData> = UnmaskType<ErrorContext<TErrorData> & {
/** Current retry attempt number (1-based, so 1 = first retry) */
retryAttemptCount: number;
}>;
type RequestStreamContext = UnmaskType<RequestContext & {
/** Progress event containing loaded/total bytes information */
event: StreamProgressEvent;
/** The actual Request instance being uploaded */
requestInstance: Request;
}>;
type ResponseStreamContext = UnmaskType<RequestContext & {
/** Progress event containing loaded/total bytes information */
event: StreamProgressEvent;
/** HTTP response object being downloaded */
response: Response;
}>;
//#endregion
//#region src/dedupe.d.ts
type DedupeStrategyUnion = UnmaskType<"cancel" | "defer" | "none">;
type DedupeOptions = {
/**
* Controls the scope of request deduplication caching.
*
* - `"global"`: Shares deduplication cache across all `createFetchClient` instances with the same `dedupeCacheScopeKey`.
* Useful for applications with multiple API clients that should share deduplication state.
* - `"local"`: Limits deduplication to requests within the same `createFetchClient` instance.
* Provides better isolation and is recommended for most use cases.
*
*
* **Real-world Scenarios:**
* - Use `"global"` when you have multiple API clients (user service, auth service, etc.) that might make overlapping requests
* - Use `"local"` (default) for single-purpose clients or when you want strict isolation between different parts of your app
*
* @example
* ```ts
* // Local scope - each client has its own deduplication cache
* const userClient = createFetchClient({ baseURL: "/api/users" });
* const postClient = createFetchClient({ baseURL: "/api/posts" });
* // These clients won't share deduplication state
*
* // Global scope - share cache across related clients
* const userClient = createFetchClient({
* baseURL: "/api/users",
* dedupeCacheScope: "global",
* });
* const postClient = createFetchClient({
* baseURL: "/api/posts",
* dedupeCacheScope: "global",
* });
* // These clients will share deduplication state
* ```
*
* @default "local"
*/
dedupeCacheScope?: "global" | "local";
/**
* Unique namespace for the global deduplication cache when using `dedupeCacheScope: "global"`.
*
* This creates logical groupings of deduplication caches. All instances with the same key
* will share the same cache namespace, allowing fine-grained control over which clients
* share deduplication state.
*
* **Best Practices:**
* - Use descriptive names that reflect the logical grouping (e.g., "user-service", "analytics-api")
* - Keep scope keys consistent across related API clients
* - Consider using different scope keys for different environments (dev, staging, prod)
* - Avoid overly broad scope keys that might cause unintended cache sharing
*
* **Cache Management:**
* - Each scope key maintains its own independent cache
* - Caches are automatically cleaned up when no references remain
* - Consider the memory implications of multiple global scopes
*
* @example
* ```ts
* // Group related API clients together
* const userClient = createFetchClient({
* baseURL: "/api/users",
* dedupeCacheScope: "global",
* dedupeCacheScopeKey: "user-service"
* });
* const profileClient = createFetchClient({
* baseURL: "/api/profiles",
* dedupeCacheScope: "global",
* dedupeCacheScopeKey: "user-service" // Same scope - will share cache
* });
*
* // Separate analytics client with its own cache
* const analyticsClient = createFetchClient({
* baseURL: "/api/analytics",
* dedupeCacheScope: "global",
* dedupeCacheScopeKey: "analytics-service" // Different scope
* });
*
* // Environment-specific scoping
* const apiClient = createFetchClient({
* dedupeCacheScope: "global",
* dedupeCacheScopeKey: `api-${process.env.NODE_ENV}` // "api-development", "api-production", etc.
* });
* ```
*
* @default "default"
*/
dedupeCacheScopeKey?: "default" | AnyString;
/**
* Custom key generator for request deduplication.
*
* Override the default key generation strategy to control exactly which requests
* are considered duplicates. The default key combines URL, method, body, and
* relevant headers (excluding volatile ones like 'Date', 'Authorization', etc.).
*
* **Default Key Generation:**
* The auto-generated key includes:
* - Full request URL (including query parameters)
* - HTTP method (GET, POST, etc.)
* - Request body (for POST/PUT/PATCH requests)
* - Stable headers (excludes Date, Authorization, User-Agent, etc.)
*
* **Custom Key Best Practices:**
* - Include only the parts of the request that should affect deduplication
* - Avoid including volatile data (timestamps, random IDs, etc.)
* - Consider performance - simpler keys are faster to compute and compare
* - Ensure keys are deterministic for the same logical request
* - Use consistent key formats across your application
*
* **Performance Considerations:**
* - Function-based keys are computed on every request - keep them lightweight
* - String keys are fastest but least flexible
* - Consider caching expensive key computations if needed
*
* @example
* ```ts
* import { callApi } from "@zayne-labs/callapi";
*
* // Simple static key - useful for singleton requests
* const config = callApi("/api/config", {
* dedupeKey: "app-config",
* dedupeStrategy: "defer" // Share the same config across all requests
* });
*
* // URL and method only - ignore headers and body
* const userData = callApi("/api/user/123", {
* dedupeKey: (context) => `${context.options.method}:${context.options.fullURL}`
* });
*
* // Include specific headers in deduplication
* const apiCall = callApi("/api/data", {
* dedupeKey: (context) => {
* const authHeader = context.request.headers.get("Authorization");
* return `${context.options.fullURL}-${authHeader}`;
* }
* });
*
* // User-specific deduplication
* const userSpecificCall = callApi("/api/dashboard", {
* dedupeKey: (context) => {
* const userId = context.options.fullURL.match(/user\/(\d+)/)?.[1];
* return `dashboard-${userId}`;
* }
* });
*
* // Ignore certain query parameters
* const searchCall = callApi("/api/search?q=test×tamp=123456", {
* dedupeKey: (context) => {
* const url = new URL(context.options.fullURL);
* url.searchParams.delete("timestamp"); // Remove volatile param
* return `search:${url.toString()}`;
* }
* });
* ```
*
* @default Auto-generated from request details
*/
dedupeKey?: string | ((context: RequestContext) => string);
/**
* Strategy for handling duplicate requests. Can be a static string or callback function.
*
* **Available Strategies:**
* - `"cancel"`: Cancel previous request when new one starts (good for search)
* - `"defer"`: Share response between duplicate requests (good for config loading)
* - `"none"`: No deduplication, all requests execute independently
*
* @example
* ```ts
* // Static strategies
* const searchClient = createFetchClient({
* dedupeStrategy: "cancel" // Cancel previous searches
* });
*
* const configClient = createFetchClient({
* dedupeStrategy: "defer" // Share config across components
* });
*
* // Dynamic strategy based on request
* const smartClient = createFetchClient({
* dedupeStrategy: (context) => {
* return context.options.method === "GET" ? "defer" : "cancel";
* }
* });
*
* // Search-as-you-type with cancel strategy
* const handleSearch = async (query: string) => {
* try {
* const { data } = await callApi("/api/search", {
* method: "POST",
* body: { query },
* dedupeStrategy: "cancel",
* dedupeKey: "search" // Cancel previous searches, only latest one goes through
* });
*
* updateSearchResults(data);
* } catch (error) {
* if (error.name === "AbortError") {
* // Previous search cancelled - (expected behavior)
* return;
* }
* console.error("Search failed:", error);
* }
* };
*
* ```
*
* @default "cancel"
*/
dedupeStrategy?: DedupeStrategyUnion | ((context: RequestContext) => DedupeStrategyUnion);
};
//#endregion
//#region src/retry.d.ts
declare const defaultRetryStatusCodesLookup: () => {
408: "Request Timeout";
409: "Conflict";
425: "Too Early";
429: "Too Many Requests";
500: "Internal Server Error";
502: "Bad Gateway";
503: "Service Unavailable";
504: "Gateway Timeout";
};
type RetryStatusCodes = UnmaskType<AnyNumber | keyof ReturnType<typeof defaultRetryStatusCodesLookup>>;
type RetryCondition<TErrorData> = (context: ErrorContext<TErrorData>) => Awaitable<boolean>;
type InnerRetryKeys<TErrorData> = Exclude<keyof RetryOptions<TErrorData>, "~retryAttemptCount" | "retry">;
type InnerRetryOptions<TErrorData> = { [Key in InnerRetryKeys<TErrorData> as Key extends `retry${infer TRest}` ? Uncapitalize<TRest> extends "attempts" ? never : Uncapitalize<TRest> : Key]?: RetryOptions<TErrorData>[Key] } & {
attempts: NonNullable<RetryOptions<TErrorData>["retryAttempts"]>;
};
interface RetryOptions<TErrorData> {
/**
* Keeps track of the number of times the request has already been retried
*
* @deprecated **NOTE**: This property is used internally to track retries. Please abstain from modifying it.
*/
readonly ["~retryAttemptCount"]?: number;
/**
* All retry options in a single object instead of separate properties
*/
retry?: InnerRetryOptions<TErrorData>;
/**
* Number of allowed retry attempts on HTTP errors
* @default 0
*/
retryAttempts?: number;
/**
* Callback whose return value determines if a request should be retried or not
*/
retryCondition?: RetryCondition<TErrorData>;
/**
* Delay between retries in milliseconds
* @default 1000
*/
retryDelay?: number | ((currentAttemptCount: number) => number);
/**
* Maximum delay in milliseconds. Only applies to exponential strategy
* @default 10000
*/
retryMaxDelay?: number;
/**
* HTTP methods that are allowed to retry
* @default ["GET", "POST"]
*/
retryMethods?: MethodUnion[];
/**
* HTTP status codes that trigger a retry
*/
retryStatusCodes?: RetryStatusCodes[];
/**
* Strategy to use when retrying
* @default "linear"
*/
retryStrategy?: "exponential" | "linear";
}
//#endregion
//#region src/types/conditional-types.d.ts
/**
* @description Makes a type partial if the output type of TSchema is not provided or has undefined in the union, otherwise makes it required
*/
type MakeSchemaOptionRequiredIfDefined<TSchemaOption extends CallApiSchema[keyof CallApiSchema], TObject> = undefined extends InferSchemaResult<TSchemaOption, undefined> ? TObject : Required<TObject>;
type ApplyURLBasedConfig<TSchemaConfig extends CallApiSchemaConfig, TSchemaRouteKeys extends string> = TSchemaConfig["prefix"] extends string ? `${TSchemaConfig["prefix"]}${TSchemaRouteKeys}` : TSchemaConfig["baseURL"] extends string ? `${TSchemaConfig["baseURL"]}${TSchemaRouteKeys}` : TSchemaRouteKeys;
type ApplyStrictConfig<TSchemaConfig extends CallApiSchemaConfig, TSchemaRouteKeys extends string> = TSchemaConfig["strict"] extends true ? TSchemaRouteKeys :
// eslint-disable-next-line perfectionist/sort-union-types -- Don't sort union types
TSchemaRouteKeys | Exclude<InitURLOrURLObject, RouteKeyMethodsURLUnion>;
type ApplySchemaConfiguration<TSchemaConfig extends CallApiSchemaConfig, TSchemaRouteKeys extends string> = ApplyStrictConfig<TSchemaConfig, ApplyURLBasedConfig<TSchemaConfig, TSchemaRouteKeys>>;
type InferAllRouteKeys<TBaseSchemaRoutes extends BaseCallApiSchemaRoutes, TSchemaConfig extends CallApiSchemaConfig> = ApplySchemaConfiguration<TSchemaConfig, Exclude<Extract<keyof TBaseSchemaRoutes, string>, FallBackRouteSchemaKey>>;
type InferInitURL<TBaseSchemaRoutes extends BaseCallApiSchemaRoutes, TSchemaConfig extends CallApiSchemaConfig> = keyof TBaseSchemaRoutes extends never ? InitURLOrURLObject : InferAllRouteKeys<TBaseSchemaRoutes, TSchemaConfig>;
type GetCurrentRouteSchemaKey<TSchemaConfig extends CallApiSchemaConfig, TPath> = TPath extends URL ? string : TSchemaConfig["baseURL"] extends string ? TPath extends `${TSchemaConfig["baseURL"]}${infer TCurrentRoute}` ? TCurrentRoute extends string ? TCurrentRoute : string : TPath extends `${TSchemaConfig["prefix"]}${infer TCurrentRoute}` ? TCurrentRoute extends string ? TCurrentRoute : string : string : TPath;
type GetCurrentRouteSchema<TBaseSchemaRoutes extends BaseCallApiSchemaRoutes, TCurrentRouteSchemaKey extends string, TComputedRouteSchema extends CallApiSchema = Omit<TBaseSchemaRoutes[FallBackRouteSchemaKey], keyof TBaseSchemaRoutes[TCurrentRouteSchemaKey]> & TBaseSchemaRoutes[TCurrentRouteSchemaKey], TComputedWriteableRouteSchema extends CallApiSchema = NonNullable<Writeable<TComputedRouteSchema, "deep">>> = TComputedWriteableRouteSchema extends CallApiSchema ? TComputedWriteableRouteSchema : CallApiSchema;
type JsonPrimitive = boolean | number | string | null | undefined;
type SerializableObject = Record<keyof object, unknown>;
type SerializableArray = Array<JsonPrimitive | SerializableArray | SerializableObject> | ReadonlyArray<JsonPrimitive | SerializableArray | SerializableObject>;
type Body = UnmaskType<RequestInit["body"] | SerializableArray | SerializableObject>;
type InferBodyOption<TSchema extends CallApiSchema> = MakeSchemaOptionRequiredIfDefined<TSchema["body"], {
/**
* Body of the request, can be a object or any other supported body type.
*/
body?: InferSchemaResult<TSchema["body"], Body>;
}>;
type MethodUnion = UnmaskType<"CONNECT" | "DELETE" | "GET" | "HEAD" | "OPTIONS" | "PATCH" | "POST" | "PUT" | "TRACE" | AnyString>;
type InferMethodFromURL<TInitURL> = string extends TInitURL ? MethodUnion : TInitURL extends `@${infer TMethod extends RouteKeyMethods}/${string}` ? Uppercase<TMethod> : MethodUnion;
type InferMethodOption<TSchema extends CallApiSchema, TInitURL> = MakeSchemaOptionRequiredIfDefined<TSchema["method"], {
/**
* HTTP method for the request.
* @default "GET"
*/
method?: InferSchemaResult<TSchema["method"], InferMethodFromURL<TInitURL>>;
}>;
type HeadersOption = UnmaskType<Record<"Authorization", CommonAuthorizationHeaders | undefined> | Record<"Content-Type", CommonContentTypes | undefined> | Record<CommonRequestHeaders, string | undefined> | Record<string, string | undefined> | Array<[string, string]>>;
type InferHeadersOption<TSchema extends CallApiSchema> = MakeSchemaOptionRequiredIfDefined<TSchema["headers"], {
/**
* Headers to be used in the request.
*/
headers?: InferSchemaResult<TSchema["headers"], HeadersOption> | ((context: {
baseHeaders: NonNullable<HeadersOption>;
}) => InferSchemaResult<TSchema["headers"], HeadersOption>);
}>;
type InferRequestOptions<TSchema extends CallApiSchema, TInitURL extends InferInitURL<BaseCallApiSchemaRoutes, CallApiSchemaConfig>> = InferBodyOption<TSchema> & InferHeadersOption<TSchema> & InferMethodOption<TSchema, TInitURL>;
interface Register {}
type GlobalMeta = Register extends {
meta?: infer TMeta extends Record<string, unknown>;
} ? TMeta : never;
type InferMetaOption<TSchema extends CallApiSchema> = MakeSchemaOptionRequiredIfDefined<TSchema["meta"], {
/**
* - An optional field you can fill with additional information,
* to associate with the request, typically used for logging or tracing.
*
* - A good use case for this, would be to use the info to handle specific cases in any of the shared interceptors.
*
* @example
* ```ts
* const callMainApi = callApi.create({
* baseURL: "https://main-api.com",
* onResponseError: ({ response, options }) => {
* if (options.meta?.userId) {
* console.error(`User ${options.meta.userId} made an error`);
* }
* },
* });
*
* const response = await callMainApi({
* url: "https://example.com/api/data",
* meta: { userId: "123" },
* });
* ```
*/
meta?: InferSchemaResult<TSchema["meta"], GlobalMeta>;
}>;
type InferQueryOption<TSchema extends CallApiSchema> = MakeSchemaOptionRequiredIfDefined<TSchema["query"], {
/**
* Parameters to be appended to the URL (i.e: /:id)
*/
query?: InferSchemaResult<TSchema["query"], Query>;
}>;
type EmptyString = "";
type EmptyTuple = readonly [];
type StringTuple = readonly string[];
type PossibleParamNamePatterns = `${string}:${string}${"" | "/"}${"" | AnyString}` | `${string}{${string}}${"" | "/"}${"" | AnyString}`;
type ExtractRouteParamNames<TCurrentRoute, TParamNamesAccumulator extends StringTuple = EmptyTuple> = TCurrentRoute extends PossibleParamNamePatterns ? TCurrentRoute extends `${infer TRoutePrefix}:${infer TParamAndRemainingRoute}` ? TParamAndRemainingRoute extends `${infer TCurrentParam}/${infer TRemainingRoute}` ? TCurrentParam extends EmptyString ? ExtractRouteParamNames<`${TRoutePrefix}/${TRemainingRoute}`, TParamNamesAccumulator> : ExtractRouteParamNames<`${TRoutePrefix}/${TRemainingRoute}`, [...TParamNamesAccumulator, TCurrentParam]> : TParamAndRemainingRoute extends `${infer TCurrentParam}` ? TCurrentParam extends EmptyString ? ExtractRouteParamNames<TRoutePrefix, TParamNamesAccumulator> : ExtractRouteParamNames<TRoutePrefix, [...TParamNamesAccumulator, TCurrentParam]> : ExtractRouteParamNames<TRoutePrefix, TParamNamesAccumulator> : TCurrentRoute extends `${infer TRoutePrefix}{${infer TCurrentParam}}${infer TRemainingRoute}` ? TCurrentParam extends EmptyString ? ExtractRouteParamNames<`${TRoutePrefix}${TRemainingRoute}`, TParamNamesAccumulator> : ExtractRouteParamNames<`${TRoutePrefix}${TRemainingRoute}`, [...TParamNamesAccumulator, TCurrentParam]> : TParamNamesAccumulator : TParamNamesAccumulator;
type ConvertParamNamesToRecord<TParamNames extends StringTuple> = Prettify<TParamNames extends (readonly [infer TFirstParamName extends string, ...infer TRemainingParamNames extends StringTuple]) ? Record<TFirstParamName, AllowedQueryParamValues> & ConvertParamNamesToRecord<TRemainingParamNames> : NonNullable<unknown>>;
type ConvertParamNamesToTuple<TParamNames extends StringTuple> = TParamNames extends readonly [string, ...infer TRemainingParamNames extends StringTuple] ? [AllowedQueryParamValues, ...ConvertParamNamesToTuple<TRemainingParamNames>] : [];
type InferParamsFromRoute<TCurrentRoute> = ExtractRouteParamNames<TCurrentRoute> extends StringTuple ? ExtractRouteParamNames<TCurrentRoute> extends EmptyTuple ? Params : ConvertParamNamesToRecord<ExtractRouteParamNames<TCurrentRoute>> | ConvertParamNamesToTuple<ExtractRouteParamNames<TCurrentRoute>> : Params;
type MakeParamsOptionRequired<TParamsSchemaOption extends CallApiSchema["params"], TBaseSchemaRoutes extends BaseCallApiSchemaRoutes, TCurrentRouteSchemaKey extends string, TObject> = MakeSchemaOptionRequiredIfDefined<TParamsSchemaOption, TCurrentRouteSchemaKey extends PossibleParamNamePatterns ? TCurrentRouteSchemaKey extends Extract<keyof TBaseSchemaRoutes, TCurrentRouteSchemaKey> ? undefined extends InferSchemaResult<TParamsSchemaOption, undefined> ? TObject : Required<TObject> : TObject : TObject>;
type InferParamsOption<TSchema extends CallApiSchema, TBaseSchemaRoutes extends BaseCallApiSchemaRoutes, TCurrentRouteSchemaKey extends string> = MakeParamsOptionRequired<TSchema["params"], TBaseSchemaRoutes, TCurrentRouteSchemaKey, {
/**
* Parameters to be appended to the URL (i.e: /:id)
*/
params?: InferSchemaResult<TSchema["params"], InferParamsFromRoute<TCurrentRouteSchemaKey>>;
}>;
type InferExtraOptions<TSchema extends CallApiSchema, TBaseSchemaRoutes extends BaseCallApiSchemaRoutes, TCurrentRouteSchemaKey extends string> = InferMetaOption<TSchema> & InferParamsOption<TSchema, TBaseSchemaRoutes, TCurrentRouteSchemaKey> & InferQueryOption<TSchema>;
type InferPluginOptions<TPluginArray extends CallApiPlugin[]> = UnionToIntersection<TPluginArray extends Array<infer TPlugin> ? TPlugin extends CallApiPlugin ? TPlugin["defineExtraOptions"] extends AnyFunction<infer TReturnedSchema> ? InferSchemaResult<TReturnedSchema> : never : never : never>;
type ExtractKeys<TUnion, TSelectedUnion extends TUnion> = Extract<TUnion, TSelectedUnion>;
type ResultModeOption<TErrorData, TResultMode extends ResultModeUnion> = TErrorData extends false ? {
resultMode: "onlySuccessWithException";
} : TErrorData extends false | undefined ? {
resultMode?: "onlySuccessWithException";
} : TErrorData extends false | null ? {
resultMode?: ExtractKeys<ResultModeUnion, "onlySuccess" | "onlySuccessWithException">;
} : {
resultMode?: TResultMode;
};
type ThrowOnErrorUnion = boolean;
type ThrowOnErrorOption<TErrorData, TThrowOnError extends ThrowOnErrorUnion> = TErrorData extends false ? {
throwOnError: true;
} : TErrorData extends false | undefined ? {
throwOnError?: true;
} : {
throwOnError?: TThrowOnError | ((context: ErrorContext<TErrorData>) => TThrowOnError);
};
//#endregion
//#region src/types/common.d.ts
type FetchSpecificKeysUnion = Exclude<(typeof fetchSpecificKeys)[number], "body" | "headers" | "method">;
type ModifiedRequestInit = RequestInit & {
duplex?: "half";
};
type CallApiRequestOptions = Prettify<{
/**
* Body of the request, can be a object or any other supported body type.
*/
body?: Body;
/**
* Headers to be used in the request.
*/
headers?: HeadersOption;
/**
* HTTP method for the request.
* @default "GET"
*/
method?: MethodUnion;
} & Pick<ModifiedRequestInit, FetchSpecificKeysUnion>>;
type CallApiRequestOptionsForHooks = Omit<CallApiRequestOptions, "headers"> & {
headers: Record<string, string | undefined>;
};
type FetchImpl = UnmaskType<(input: string | Request | URL, init?: RequestInit) => Promise<Response>>;
type SharedExtraOptions<TData = DefaultDataType, TErrorData = DefaultDataType, TResultMode extends ResultModeUnion = ResultModeUnion, TThrowOnError extends ThrowOnErrorUnion = DefaultThrowOnError, TResponseType extends ResponseTypeUnion = ResponseTypeUnion, TPluginArray extends CallApiPlugin[] = DefaultPluginArray> = DedupeOptions & HookConfigOptions & HooksOrHooksArray<TData, TErrorData, Partial<InferPluginOptions<TPluginArray>>> & Partial<InferPluginOptions<TPluginArray>> & ResultModeOption<TErrorData, TResultMode> & RetryOptions<TErrorData> & ThrowOnErrorOption<TErrorData, TThrowOnError> & URLOptions & {
/**
* Automatically add an Authorization header value.
*
* Supports multiple authentication patterns:
* - String: Direct authorization header value
* - Auth object: Structured authentication configuration
* - null: Explicitly removes authorization
*
* @example
* ```ts
* // Bearer token authentication
* auth: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
*
* // Basic authentication
* auth: "Basic dXNlcm5hbWU6cGFzc3dvcmQ="
*
* // Using Auth object for dynamic authentication
* auth: {
* type: "bearer",
* token: () => getAccessToken()
* }
*
* // Remove inherited auth from base config
* auth: null
* ```
*/
auth?: string | Auth | null;
/**
* Custom function to serialize request body objects into strings.
*
* Useful for custom serialization formats or when the default JSON
* serialization doesn't meet your needs.
*
* @example
* ```ts
* // Custom form data serialization
* bodySerializer: (data) => {
* const formData = new URLSearchParams();
* Object.entries(data).forEach(([key, value]) => {
* formData.append(key, String(value));
* });
* return formData.toString();
* }
*
* // XML serialization
* bodySerializer: (data) => {
* return `<request>${Object.entries(data)
* .map(([key, value]) => `<${key}>${value}</${key}>`)
* .join('')}</request>`;
* }
*
* // Custom JSON with specific formatting
* bodySerializer: (data) => JSON.stringify(data, null, 2)
* ```
*/
bodySerializer?: (bodyData: Record<string, unknown>) => string;
/**
* Whether to clone the response so it can be read multiple times.
*
* By default, response streams can only be consumed once. Enable this when you need
* to read the response in multiple places (e.g., in hooks and main code).
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/Response/clone
* @default false
*/
cloneResponse?: boolean;
/**
* Custom fetch implementation to replace the default fetch function.
*
* Useful for testing, adding custom behavior, or using alternative HTTP clients
* that implement the fetch API interface.
*
* @example
* ```ts
* // Use node-fetch in Node.js environments
* import fetch from 'node-fetch';
*
* // Mock fetch for testing
* customFetchImpl: async (url, init) => {
* return new Response(JSON.stringify({ mocked: true }), {
* status: 200,
* headers: { 'Content-Type': 'application/json' }
* });
* }
*
* // Add custom logging to all requests
* customFetchImpl: async (url, init) => {
* console.log(`Fetching: ${url}`);
* const response = await fetch(url, init);
* console.log(`Response: ${response.status}`);
* return response;
* }
*
* // Use with custom HTTP client
* customFetchImpl: async (url, init) => {
* // Convert to your preferred HTTP client format
* return await customHttpClient.request({
* url: url.toString(),
* method: init?.method || 'GET',
* headers: init?.headers,
* body: init?.body
* });
* }
* ```
*/
customFetchImpl?: FetchImpl;
/**
* Default HTTP error message when server doesn't provide one.
*
* Can be a static string or a function that receives error context
* to generate dynamic error messages based on the response.
*
* @default "Failed to fetch data from server!"
*
* @example
* ```ts
* // Static error message
* defaultHTTPErrorMessage: "API request failed. Please try again."
*
* // Dynamic error message based on status code
* defaultHTTPErrorMessage: ({ response }) => {
* switch (response.status) {
* case 401: return "Authentication required. Please log in.";
* case 403: return "Access denied. Insufficient permissions.";
* case 404: return "Resource not found.";
* case 429: return "Too many requests. Please wait and try again.";
* case 500: return "Server error. Please contact support.";
* default: return `Request failed with status ${response.status}`;
* }
* }
*
* // Include error data in message
* defaultHTTPErrorMessage: ({ errorData, response }) => {
* const userMessage = errorData?.message || "Unknown error occurred";
* return `${userMessage} (Status: ${response.status})`;
* }
* ```
*/
defaultHTTPErrorMessage?: string | ((context: Pick<HTTPError<TErrorData>, "errorData" | "response">) => string);
/**
* Forces calculation of total byte size from request/response body streams.
*
* Useful when the Content-Length header is missing or incorrect, and you need
* accurate size information for progress tracking or bandwidth monitoring.
*
* @default false
*
*/
forcefullyCalculateStreamSize?: boolean | {
request?: boolean;
response?: boolean;
};
/**
* Optional metadata field for associating additional information with requests.
*
* Useful for logging, tracing, or handling specific cases in shared interceptors.
* The meta object is passed through to all hooks and can be accessed in error handlers.
*
* @example
* ```ts
* const callMainApi = callApi.create({
* baseURL: "https://main-api.com",
* onResponseError: ({ response, options }) => {
* if (options.meta?.userId) {
* console.error(`User ${options.meta.userId} made an error`);
* }
* },
* });
*
* const response = await callMainApi({
* url: "https://example.com/api/data",
* meta: { userId: "123" },
* });
*
* // Use case: Request tracking
* const result = await callMainApi({
* url: "https://example.com/api/data",
* meta: {
* requestId: generateId(),
* source: "user-dashboard",
* priority: "high"
* }
* });
*
* // Use case: Feature flags
* const client = callApi.create({
* baseURL: "https://api.example.com",
* meta: {
* features: ["newUI", "betaFeature"],
* experiment: "variantA"
* }
* });
* ```
*/
meta?: GlobalMeta;
/**
* Custom function to parse response strings into actual value instead of the default response.json().
*
* Useful when you need custom parsing logic for specific response formats.
*
* @example
* ```ts
* responseParser: (responseString) => {
* return JSON.parse(responseString);
* }
*
* // Parse XML responses
* responseParser: (responseString) => {
* const parser = new DOMParser();
* const doc = parser.parseFromString(responseString, "text/xml");
* return xmlToObject(doc);
* }
*
* // Parse CSV responses
* responseParser: (responseString) => {
* const lines = responseString.split('\n');
* const headers = lines[0].split(',');
* const data = lines.slice(1).map(line => {
* const values = line.split(',');
* return headers.reduce((obj, header, index) => {
* obj[header] = values[index];
* return obj;
* }, {});
* });
* return data;
* }
*
* ```
*/
responseParser?: (responseString: string) => Awaitable<unknown>;
/**
* Expected response type, determines how the response body is parsed.
*
* Different response types trigger different parsing methods:
* - **"json"**: Parses as JSON using response.json()
* - **"text"**: Returns as plain text using response.text()
* - **"blob"**: Returns as Blob using response.blob()
* - **"arrayBuffer"**: Returns as ArrayBuffer using response.arrayBuffer()
* - **"stream"**: Returns the response body stream directly
*
* @default "json"
*
* @example
* ```ts
* // JSON API responses (default)
* responseType: "json"
*
* // Plain text responses
* responseType: "text"
* // Usage: const csvData = await callApi("/export.csv", { responseType: "text" });
*
* // File downloads
* responseType: "blob"
* // Usage: const file = await callApi("/download/file.pdf", { responseType: "blob" });
*
* // Binary data
* responseType: "arrayBuffer"
* // Usage: const buffer = await callApi("/binary-data", { responseType: "arrayBuffer" });
*
* // Streaming responses
* responseType: "stream"
* // Usage: const stream = await callApi("/large-dataset", { responseType: "stream" });
* ```
*/
responseType?: TResponseType;
/**
* Controls what data is included in the returned result object.
*
* Different modes return different combinations of data, error, and response:
* - **"all"**: Returns { data, error, response } - complete result information
* - **"allWithException"**: Returns { data, error, response } but throws on errors
* - **"onlySuccess"**: Returns only data (null for errors), never throws
* - **"onlySuccessWithException"**: Returns only data but throws on errors
*
* @default "all"
*
* @example
* ```ts
* // Complete result with all information (default)
* resultMode: "all"
* const { data, error, response } = await callApi("/users");
* if (error) {
* console.error("Request failed:", error);
* } else {
* console.log("Users:", data);
* }
*
* // Complete result but throws on errors
* resultMode: "allWithException"
* try {
* const { data, response } = await callApi("/users", { resultMode: "allWithException" });
* console.log("Users:", data);
* } catch (error) {
* console.error("Request failed:", error);
* }
*
* // Only data, returns null on errors
* resultMode: "onlySuccess"
* const users = await callApi("/users", { resultMode: "onlySuccess" });
* if (users) {
* console.log("Users:", users);
* } else {
* console.log("Request failed");
* }
*
* // Only data with null, throws on errors
* resultMode: "onlySuccessWithException"
* try {
* const users = await callApi("/users", { resultMode: "onlySuccessWithException" });
* console.log("Users:", users);
* } catch (error) {
* console.error("Request failed:", error);
* }
* ```
*/
resultMode?: TResultMode;
/**
* Controls whether errors are thrown as exceptions or returned in the result.
*
* Can be a boolean or a function that receives the error and decides whether to throw.
* When true, errors are thrown as exceptions instead of being returned in the result object.
*
* @default false
*
* @example
* ```ts
* // Always throw errors
* throwOnError: true
* try {
* const data = await callApi("/users");
* console.log("Users:", data);
* } catch (error) {
* console.error("Request failed:", error);
* }
*
* // Never throw errors (default)
* throwOnError: false
* const { data, error } = await callApi("/users");
* if (error) {
* console.error("Request failed:", error);
* }
*
* // Conditionally throw based on error type
* throwOnError: (error) => {
* // Throw on client errors (4xx) but not server errors (5xx)
* return error.response?.status >= 400 && error.response?.status < 500;
* }
*
* // Throw only on specific status codes
* throwOnError: (error) => {
* const criticalErrors = [401, 403, 404];
* return criticalErrors.includes(error.response?.status);
* }
*
* // Throw on validation errors but not network errors
* throwOnError: (error) => {
* return error.type === "validation";
* }
* ```
*/
throwOnError?: TThrowOnError | ((context: ErrorContext<TErrorData>) => TThrowOnError);
/**
* Request timeout in milliseconds. Request will be aborted if it takes longer.
*
* Useful for preventing requests from hanging indefinitely and providing
* better user experience with predictable response times.
*
* @example
* ```ts
* // 5 second timeout
* timeout: 5000
*
* // Different timeouts for different endpoints
* const quickApi = createFetchClient({ timeout: 3000 }); // 3s for fast endpoints
* const slowApi = createFetchClient({ timeout: 30000 }); // 30s for slow operations
*
* // Per-request timeout override
* await callApi("/quick-data", { timeout: 1000 });
* await callApi("/slow-report", { timeout: 60000 });
*
* // No timeout (use with caution)
* timeout: 0
* ```
*/
timeout?: number;
};
type BaseCallApiExtraOptions<TBaseData = DefaultDataType, TBaseErrorData = DefaultDataType, TBaseResultMode extends ResultModeUnion = ResultModeUnion, TBaseThrowOnError extends ThrowOnErrorUnion = DefaultThrowOnError, TBaseResponseType extends ResponseTypeUnion = ResponseTypeUnion, TBasePluginArray extends CallApiPlugin[] = DefaultPluginArray, TBaseSchemaAndConfig extends BaseCallApiSchemaAndConfig = BaseCallApiSchemaAndConfig> = SharedExtraOptions<TBaseData, TBaseErrorData, TBaseResultMode, TBaseThrowOnError, TBaseResponseType, TBasePluginArray> & {
/**
* Array of base CallApi plugins to extend library functionality.
*
* Base plugins are applied to all instances created from this base configuration
* and provide foundational functionality like authentication, logging, or caching.
*
* @example
* ```ts
* // Add logging plugin
*
* // Create base client with common plugins
* const callApi = createFetchClient({
* baseURL: "https://api.example.com",
* plugins: [loggerPlugin({ enabled: true })]
* });
*
* // All requests inherit base plugins
* await callApi("/users");
* await callApi("/posts");
*
* ```
*/
plugins?: TBasePluginArray;
/**
* Base validation schemas for the client configuration.
*
* Defines validation rules for requests and responses that apply to all
* instances created from this base configuration. Provides type safety
* and runtime validation for API interactions.
*/
schema?: TBaseSchemaAndConfig;
/**
* Controls which configuration parts skip automatic merging between base and instance configs.
*
* By default, CallApi automatically merges base configuration with instance configuration.
* This option allows you to disable automatic merging for specific parts when you need
* manual control over how configurations are combined.
*
* @enum
* - **"all"**: Disables automatic merging for both request options and extra options
* - **"options"**: Disables automatic merging of extra options only (hooks, plugins, etc.)
* - **"request"**: Disables automatic merging of request options only (headers, body, etc.)
*
* @example
* ```ts
* // Skip all automatic merging - full manual control
* const client = callApi.create((ctx) => ({
* skipAutoMergeFor: "all",
*
* // Manually decide what to merge
* baseURL: ctx.options.baseURL, // Keep base URL
* timeout: 5000, // Override timeout
* headers: {
* ...ctx.request.headers, // Merge headers manually
* "X-Custom": "value" // Add custom header
* }
* }));
*
* // Skip options merging - manual plugin/hook control
* const client = callApi.create((ctx) => ({
* skipAutoMergeFor: "options",
*
* // Manually control which plugins to use
* plugins: [
* ...ctx.options.plugins?.filter(p => p.name !== "unwanted") || [],
* customPlugin
* ],
*
* // Request options still auto-merge
* method: "POST"
* }));
*
* // Skip request merging - manual request control
* const client = callApi.create((ctx) => ({
* skipAutoMergeFor: "request",
*
* // Extra options still auto-merge (plugins, hooks, etc.)
*
* // Manually control request options
* headers: {
* "Content-Type": "application/json",
* // Don't merge base headers
* },
* method: ctx.request.method || "GET"
* }));
*
* // Use case: Conditional merging based on request
* const client = createFetchClient((ctx) => ({
* skipAutoMergeFor: "options",
*
* // Only use auth plugin for protected routes
* plugins: ctx.initURL.includes("/protected/")
* ? [...(ctx.options.plugins || []), authPlugin]
* : ctx.options.plugins?.filter(p => p.name !== "auth") || []
* }));
* ```
*/
skipAutoMergeFor?: "all" | "options" | "request";
};
type CallApiExtraOptions<TData = DefaultDataType, TErrorData = DefaultDataType, TResultMode extends ResultModeUnion = ResultModeUnion, TThrowOnError extends ThrowOnErrorUnion = DefaultThrowOnError, TResponseType extends ResponseTypeUnion = ResponseTypeUnion, TBasePluginArray extends CallApiPlugin[] = DefaultPluginArray, TPluginArray extends CallApiPlugin[] = DefaultPluginArray, TBaseSchemaRoutes extends BaseCallApiSchemaRoutes = BaseCallApiSchemaRoutes, TSchema extends CallApiSchema = CallApiSchema, TBaseSchemaConfig extends CallApiSchemaConfig = CallApiSchemaConfig, TSchemaConfig extends CallApiSchemaConfig = CallApiSchemaConfig, TCurrentRouteSchemaKey extends string = string> = SharedExtraOptions<TData, TErrorData, TResultMode, TThrowOnError, TResponseType, TPluginArray> & {
/**
* Array of instance-specific CallApi plugins or a function to configure plugins.
*
* Instance plugins are added to the base plugins and provide functionality
* specific to this particular API instance. Can be a static array or a function
* that receives base plugins and returns the instance plugins.
*
*/
plugins?: TPluginArray | ((context: {
basePlugins: Writeable<TBasePluginArray, "deep">;
}) => TPluginArray);
/**
* For instance-specific validation schemas
*
* Defines validation rules specific to this API instance, extending or overriding the base schema.
*
* Can be a static schema object or a function that receives base schema context and returns instance schemas.
*
*/
schema?: TSchema | ((context: {
baseSchemaRoutes: Writeable<TBaseSchemaRoutes, "deep">;
currentRouteSchema: GetCurrentRouteSchema<TBaseSchemaRoutes, TCurrentRouteSchemaKey>;
}) => TSchema);
/**
* Instance-specific schema configuration or a function to configure schema behavior.
*
* Controls how validation schemas are applied and behave for this specific API instance.
* Can override base schema configuration or extend it with instance-specific validation rules.
*
*/
schemaConfig?: TSchemaConfig | ((context: {
baseSchemaConfig: Writeable<TBaseSchemaConfig, "deep">;
}) => TSchemaConfig);
};
type CallApiExtraOptionsForHooks = Hooks & Omit<CallApiExtraOptions, keyof Hooks>;
type BaseCallApiConfig<TBaseData = DefaultDataType, TBaseErrorData = DefaultDataType, TBaseResultMode extends ResultModeUnion = ResultModeUnion, TBaseThrowOnError extends ThrowOnErrorUnion = DefaultThrowOnError, TBaseResponseType extends ResponseTypeUnion = ResponseTypeUnion, TBaseSchemaAndConfig extends BaseCallApiSchemaAndConfig = BaseCallApiSchemaAndConfig, TBasePluginArray extends CallApiPlugin[] = DefaultPluginArray> = (CallApiRequestOptions & BaseCallApiExtraOptions<TBaseData, TBaseErrorData, TBaseResultMode, TBaseThrowOnError, TBaseResponseType, TBasePluginArray, TBaseSchemaAndConfig>) | ((context: {
initURL: string;
options: CallApiExtraOptions;
request: CallApiRequestOptions;
}) => CallApiRequestOptions & BaseCallApiExtraOptions<TBaseData, TBaseErrorData, TBaseResultMode, TBaseThrowOnError, TBaseResponseType, TBasePluginArray, TBaseSchemaAndConfig>);
type CallApiConfig<TData = DefaultDataType, TErrorData = DefaultDataType, TResultMode extends ResultModeUnion = ResultModeUnion, TThrowOnError extends ThrowOnErrorUnion = DefaultThrowOnError, TResponseType extends ResponseTypeUnion = ResponseTypeUnion, TBaseSchemaRoutes extends BaseCallApiSchemaRoutes = BaseCallApiSchemaRoutes, TSchema extends CallApiSchema = CallApiSchema, TBaseSchemaConfig extends CallApiSchemaConfig = CallApiSchemaConfig, TSchemaConfig extends CallApiSchemaConfig = CallApiSchemaConfig, TInitURL extends InitURLOrURLObject = InitURLOrURLObject, TCurrentRouteSchemaKey extends string = string, TBasePluginArray extends CallApiPlugin[] = DefaultPluginArray, TPluginArray extends CallApiPlugin[] = DefaultPluginArray> = InferExtraOptions<TSchema, TBaseSchemaRoutes, TCurrentRouteSchemaKey> & InferRequestOptions<TSchema, TInitURL> & Omit<CallApiExtraOptions<TData, TErrorData, TResultMode, TThrowOnError, TResponseType, TBasePluginArray, TPluginArray, TBaseSchemaRoutes, TSchema, TBaseSchemaConfig, TSchemaConfig, TCurrentRouteSchemaKey>, keyof InferExtraOptions<CallApiSchema, BaseCallApiSchemaRoutes, string>> & Omit<CallApiRequestOptions, keyof InferRequestOptions<CallApiSchema, string>>;
type CallApiParameters<TData = DefaultDataType, TErrorData = DefaultDataType, TResultMode extends ResultModeUnion = ResultModeUnion, TThrowOnError extends ThrowOnErrorUnion = DefaultThrowOnError, TResponseType extends ResponseTypeUnion = ResponseTypeUnion, TBaseSchemaRoutes extends BaseCallApiSchemaRoutes = BaseCallApiSchemaRoutes, TSchema extends CallApiSchema = CallApiSchema, TBaseSchemaConfig extends CallApiSchemaConfig = CallApiSchemaConfig, TSchemaConfig extends CallApiSchemaConfig = CallApiSchemaConfig, TInitURL extends InitURLOrURLObject = InitURLOrURLObject, TCurrentRouteSchemaKey extends string = string, TBasePluginArray extends CallApiPlugin[] = DefaultPluginArray, TPluginArray extends CallApiPlugin[] = DefaultPluginArray> = [initURL: TInitURL, config?: CallApiConfig<TData, TErrorData, TResultMode, TThrowOnError, TResponseType, TBaseSchemaRoutes, TSchema, TBaseSchemaConfig, TSchemaConfig, TInitURL, TCurrentRouteSchemaKey, TBasePluginArray, TPluginArray>];
type CallApiResult<TData, TErrorData, TResultMode extends ResultModeUnion, TThrowOnError extends ThrowOnErrorUnion, TResponseType extends ResponseTypeUnion> = Promise<GetCallApiResult<TData, TErrorData, TResultMode, TThrowOnError, TResponseType>>;
//#endregion
export { AnyFunction, AnyString, ApplyStrictConfig, ApplyURLBasedConfig, BaseCallApiConfig, BaseCallApiExtraOptions, BaseCallApiSchemaAndConfig, BaseCallApiSchemaRoutes, CallApiConfig, CallApiExtraOptions, CallApiExtraOptionsForHooks, CallApiParameters, CallApiPlugin, CallApiRequestOptions, CallApiRequestOptionsForHooks, CallApiResult, CallApiResultErrorVariant, CallApiResultSuccessVariant, CallApiSchema, CallApiSchemaConfig, DedupeOptions, DefaultDataType, DefaultPluginArray, DefaultThrowOnError, ErrorContext, GetCurrentRouteSchema, GetCurrentRouteSchemaKey, HTTPError, Hooks, HooksOrHooksArray, InferInitURL, InferParamsFromRoute, InferSchemaResult, PluginExtraOptions, PluginHooks, PluginHooksWithMoreOptions, PluginSetupContext, PossibleHTTPError, PossibleJavaScriptError, PossibleJavaScriptOrValidationError, PossibleValidationError, Register, RequestContext, RequestStreamContext, ResponseContext, ResponseErrorContext, ResponseStreamContext, ResponseTypeUnion, ResultModeUnion, RetryOptions, SuccessContext, ThrowOnErrorUnion, URLOptions, ValidationError, Writeable, fallBackRouteSchemaKey };
//# sourceMappingURL=common-DIX84WHS.d.ts.map