UNPKG

@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
//#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&timestamp=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