@zayne-labs/callapi 1.11.34 → 1.11.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -92,434 +92,435 @@ type StreamProgressEvent = {
92
92
  transferredBytes: number;
93
93
  };
94
94
  //#endregion
95
- //#region src/types/standard-schema.d.ts
96
- /**
97
- * The Standard Schema interface.
98
- * @see https://github.com/standard-schema/standard-schema
99
- */
100
- /** The Standard Typed interface. This is a base type extended by other specs. */
101
- interface StandardTypedV1<Input = unknown, Output = Input> {
102
- /** The Standard properties. */
103
- readonly "~standard": StandardTypedV1.Props<Input, Output>;
104
- }
105
- declare namespace StandardTypedV1 {
106
- /** The Standard Typed properties interface. */
107
- interface Props<Input = unknown, Output = Input> {
108
- /** Inferred types associated with the schema. */
109
- readonly types?: Types<Input, Output> | undefined;
110
- /** The vendor name of the schema library. */
111
- readonly vendor: string;
112
- /** The version number of the standard. */
113
- readonly version: 1;
114
- }
115
- /** The Standard Typed types interface. */
116
- interface Types<Input = unknown, Output = Input> {
117
- /** The input type of the schema. */
118
- readonly input: Input;
119
- /** The output type of the schema. */
120
- readonly output: Output;
121
- }
122
- /** Infers the input type of a Standard Typed. */
123
- type InferInput<Schema extends StandardTypedV1> = NonNullable<Schema["~standard"]["types"]>["input"];
124
- /** Infers the output type of a Standard Typed. */
125
- type InferOutput<Schema extends StandardTypedV1> = NonNullable<Schema["~standard"]["types"]>["output"];
126
- }
127
- /** The Standard Schema interface. */
128
- interface StandardSchemaV1<Input = unknown, Output = Input> {
129
- /** The Standard Schema properties. */
130
- readonly "~standard": StandardSchemaV1.Props<Input, Output>;
131
- }
132
- declare namespace StandardSchemaV1 {
133
- /** The Standard Schema properties interface. */
134
- interface Props<Input = unknown, Output = Input> extends StandardTypedV1.Props<Input, Output> {
135
- /** Validates unknown input values. */
136
- readonly validate: (value: unknown, options?: StandardSchemaV1.Options) => Promise<Result<Output>> | Result<Output>;
137
- }
138
- /** The result interface of the validate function. */
139
- type Result<Output> = FailureResult | SuccessResult<Output>;
140
- /** The result interface if validation succeeds. */
141
- interface SuccessResult<Output> {
142
- /** A falsy value for `issues` indicates success. */
143
- readonly issues?: undefined;
144
- /** The typed output value. */
145
- readonly value: Output;
146
- }
147
- interface Options {
148
- /** Explicit support for additional vendor-specific parameters, if needed. */
149
- readonly libraryOptions?: Record<string, unknown> | undefined;
150
- }
151
- /** The result interface if validation fails. */
152
- interface FailureResult {
153
- /** The issues of failed validation. */
154
- readonly issues: readonly Issue[];
155
- }
156
- /** The issue interface of the failure output. */
157
- interface Issue {
158
- /** The error message of the issue. */
159
- readonly message: string;
160
- /** The path of the issue, if any. */
161
- readonly path?: ReadonlyArray<PathSegment | PropertyKey> | undefined;
162
- }
163
- /** The path segment interface of the issue. */
164
- interface PathSegment {
165
- /** The key representing a path segment. */
166
- readonly key: PropertyKey;
167
- }
168
- /** The Standard types interface. */
169
- type Types<Input = unknown, Output = Input> = StandardTypedV1.Types<Input, Output>;
170
- /** Infers the input type of a Standard. */
171
- type InferInput<Schema extends StandardTypedV1> = StandardTypedV1.InferInput<Schema>;
172
- /** Infers the output type of a Standard. */
173
- type InferOutput<Schema extends StandardTypedV1> = StandardTypedV1.InferOutput<Schema>;
174
- }
175
- //#endregion
176
- //#region src/validation.d.ts
177
- type ResultVariant = "infer-input" | "infer-output";
178
- type InferSchemaResult<TSchema, TFallbackResult, TResultVariant extends ResultVariant> = undefined extends TSchema ? TFallbackResult : TSchema extends StandardSchemaV1 ? TResultVariant extends "infer-input" ? StandardSchemaV1.InferInput<TSchema> : StandardSchemaV1.InferOutput<TSchema> : TSchema extends AnyFunction<infer TResult> ? Awaited<TResult> : TFallbackResult;
179
- type InferSchemaOutput<TSchema, TFallbackResult = unknown> = InferSchemaResult<TSchema, TFallbackResult, "infer-output">;
180
- type InferSchemaInput<TSchema, TFallbackResult = unknown> = InferSchemaResult<TSchema, TFallbackResult, "infer-input">;
181
- type BooleanObject = { [Key in keyof CallApiSchema]: boolean };
182
- interface CallApiSchemaConfig {
183
- /**
184
- * The base url of the schema. By default it's the baseURL of the callApi instance.
185
- */
186
- baseURL?: string;
187
- /**
188
- * Disables runtime validation for the schema.
189
- */
190
- disableRuntimeValidation?: boolean | BooleanObject;
95
+ //#region src/hooks.d.ts
96
+ interface Hooks<TCallApiContext extends CallApiContext = DefaultCallApiContext> {
191
97
  /**
192
- * If `true`, the original input value will be used instead of the transformed/validated output.
98
+ * Hook called when any error occurs within the request/response lifecycle.
193
99
  *
194
- * This is useful when you want to validate the input but don't want any transformations
195
- * applied by the validation schema (e.g., type coercion, default values, etc).
196
- */
197
- disableValidationOutputApplication?: boolean | BooleanObject;
198
- /**
199
- * Optional url prefix that will be substituted for the `baseURL` of the schemaConfig at runtime.
100
+ * This is a unified error handler that catches both request errors (network failures,
101
+ * timeouts, etc.) and response errors (HTTP error status codes). It's essentially
102
+ * a combination of `onRequestError` and `onResponseError` hooks.
200
103
  *
201
- * This allows you to reuse the same schema against different base URLs (for example,
202
- * swapping between `/api/v1` and `/api/v2`) without redefining the entire schema.
104
+ * @param context - Error context containing error details, request info, and response (if available)
105
+ * @returns Promise or void - Hook can be async or sync
203
106
  */
204
- prefix?: string;
107
+ onError?: (context: ErrorContext<TCallApiContext>) => Awaitable<unknown>;
205
108
  /**
206
- * Controls the strictness of API route validation.
109
+ * Hook called before the HTTP request is sent and before any internal processing of the request object begins.
207
110
  *
208
- * When true:
209
- * - Only routes explicitly defined in the schema will be considered valid to typescript and the runtime.
210
- * - Attempting to call routes not defined in the schema will result in both type errors and runtime validation errors.
211
- * - Useful for ensuring API calls conform exactly to your schema definition
111
+ * This is the ideal place to modify request headers, add authentication,
112
+ * implement request logging, or perform any setup before the network call.
113
+ *
114
+ * @param context - Request context with mutable request object and configuration
115
+ * @returns Promise or void - Hook can be async or sync
212
116
  *
213
- * When false or undefined (default):
214
- * - All routes will be allowed, whether they are defined in the schema or not
215
- */
216
- strict?: boolean;
217
- }
218
- interface CallApiSchema {
219
- auth?: StandardSchemaV1<AuthOption | undefined> | ((auth: AuthOption) => Awaitable<AuthOption | undefined>);
220
- /**
221
- * The schema to use for validating the request body.
222
- */
223
- body?: StandardSchemaV1<Body | undefined> | ((body: Body) => Awaitable<Body | undefined>);
224
- /**
225
- * The schema to use for validating the response data.
226
- */
227
- data?: StandardSchemaV1 | ((data: unknown) => unknown);
228
- /**
229
- * The schema to use for validating the response error data.
230
- */
231
- errorData?: StandardSchemaV1 | ((errorData: unknown) => unknown);
232
- /**
233
- * The schema to use for validating the request headers.
234
- */
235
- headers?: StandardSchemaV1<HeadersOption | undefined> | ((headers: HeadersOption) => Awaitable<HeadersOption | undefined>);
236
- /**
237
- * The schema to use for validating the meta option.
238
117
  */
239
- meta?: StandardSchemaV1<GlobalMeta | undefined> | ((meta: GlobalMeta) => Awaitable<GlobalMeta | undefined>);
118
+ onRequest?: (context: RequestContext<TCallApiContext>) => Awaitable<unknown>;
240
119
  /**
241
- * The schema to use for validating the request method.
120
+ * Hook called when an error occurs during the fetch request itself.
121
+ *
122
+ * This handles network-level errors like connection failures, timeouts,
123
+ * DNS resolution errors, or other issues that prevent getting an HTTP response.
124
+ * Note that HTTP error status codes (4xx, 5xx) are handled by `onResponseError`.
125
+ *
126
+ * @param context - Request error context with error details and null response
127
+ * @returns Promise or void - Hook can be async or sync
242
128
  */
243
- method?: StandardSchemaV1<MethodUnion | undefined> | ((method: MethodUnion) => Awaitable<MethodUnion | undefined>);
129
+ onRequestError?: (context: RequestErrorContext<TCallApiContext>) => Awaitable<unknown>;
244
130
  /**
245
- * The schema to use for validating the request url parameters.
131
+ * Hook called just before the HTTP request is sent and after the request has been processed.
132
+ *
133
+ * @param context - Request context with mutable request object and configuration
246
134
  */
247
- params?: StandardSchemaV1<Params | undefined> | ((params: Params) => Awaitable<Params | undefined>);
135
+ onRequestReady?: (context: RequestContext<TCallApiContext>) => Awaitable<unknown>;
248
136
  /**
249
- * The schema to use for validating the request url queries.
137
+ * Hook called during upload stream progress tracking.
138
+ *
139
+ * This hook is triggered when uploading data (like file uploads) and provides
140
+ * progress information about the upload. Useful for implementing progress bars
141
+ * or upload status indicators.
142
+ *
143
+ * @param context - Request stream context with progress event and request instance
144
+ * @returns Promise or void - Hook can be async or sync
145
+ *
250
146
  */
251
- query?: StandardSchemaV1<Query | undefined> | ((query: Query) => Awaitable<Query | undefined>);
252
- }
253
- declare const routeKeyMethods: readonly ["delete", "get", "patch", "post", "put"];
254
- type RouteKeyMethods = (typeof routeKeyMethods)[number];
255
- type RouteKeyMethodsURLUnion = `@${RouteKeyMethods}/`;
256
- type BaseSchemaRouteKeyPrefixes = FallBackRouteSchemaKey | RouteKeyMethodsURLUnion;
257
- type BaseCallApiSchemaRoutes = Partial<Record<AnyString | BaseSchemaRouteKeyPrefixes, CallApiSchema>>;
258
- type BaseCallApiSchemaAndConfig = {
259
- config?: CallApiSchemaConfig;
260
- routes: BaseCallApiSchemaRoutes;
261
- };
262
- //#endregion
263
- //#region src/url.d.ts
264
- type AllowedQueryParamValues = UnmaskType<boolean | number | string>;
265
- type RecordStyleParams = UnmaskType<Record<string, AllowedQueryParamValues>>;
266
- type TupleStyleParams = UnmaskType<AllowedQueryParamValues[]>;
267
- type Params = UnmaskType<RecordStyleParams | TupleStyleParams>;
268
- type Query = UnmaskType<Record<string, AllowedQueryParamValues>>;
269
- type InitURLOrURLObject = AnyString | RouteKeyMethodsURLUnion | URL;
270
- interface URLOptions {
147
+ onRequestStream?: (context: RequestStreamContext<TCallApiContext>) => Awaitable<unknown>;
271
148
  /**
272
- * Base URL for all API requests. Will only be prepended to relative URLs.
273
- *
274
- * Absolute URLs (starting with http/https) will not be prepended by the baseURL.
149
+ * Hook called when any HTTP response is received from the API.
275
150
  *
276
- * @example
277
- * ```ts
278
- * // Set base URL for all requests
279
- * baseURL: "https://api.example.com/v1"
151
+ * This hook is triggered for both successful (2xx) and error (4xx, 5xx) responses.
152
+ * It's useful for response logging, metrics collection, or any processing that
153
+ * should happen regardless of response status.
280
154
  *
281
- * // Then use relative URLs in requests
282
- * callApi("/users") // https://api.example.com/v1/users
283
- * callApi("/posts/123") // → https://api.example.com/v1/posts/123
155
+ * @param context - Response context with either success data or error information
156
+ * @returns Promise or void - Hook can be async or sync
284
157
  *
285
- * // Environment-specific base URLs
286
- * baseURL: process.env.NODE_ENV === "production"
287
- * ? "https://api.example.com"
288
- * : "http://localhost:3000/api"
289
- * ```
290
158
  */
291
- baseURL?: string;
159
+ onResponse?: (context: ResponseContext<TCallApiContext>) => Awaitable<unknown>;
292
160
  /**
293
- * Resolved request URL after processing baseURL, parameters, and query strings (readonly)
161
+ * Hook called when an HTTP error response (4xx, 5xx) is received from the API.
294
162
  *
295
- * This is the final URL that will be used for the HTTP request, computed from
296
- * baseURL, initURL, params, and query parameters.
163
+ * This handles server-side errors where an HTTP response was successfully received
164
+ * but indicates an error condition. Different from `onRequestError` which handles
165
+ * network-level failures.
297
166
  *
167
+ * @param context - Response error context with HTTP error details and response
168
+ * @returns Promise or void - Hook can be async or sync
298
169
  */
299
- readonly fullURL?: string;
170
+ onResponseError?: (context: ResponseErrorContext<TCallApiContext>) => Awaitable<unknown>;
300
171
  /**
301
- * The original URL string passed to the callApi instance (readonly)
172
+ * Hook called during download stream progress tracking.
302
173
  *
303
- * This preserves the original URL as provided, including any method modifiers like "@get/" or "@post/".
174
+ * This hook is triggered when downloading data (like file downloads) and provides
175
+ * progress information about the download. Useful for implementing progress bars
176
+ * or download status indicators.
177
+ *
178
+ * @param context - Response stream context with progress event and response
179
+ * @returns Promise or void - Hook can be async or sync
304
180
  *
305
181
  */
306
- readonly initURL?: string;
182
+ onResponseStream?: (context: ResponseStreamContext<TCallApiContext>) => Awaitable<unknown>;
307
183
  /**
308
- * The URL string after normalization, with method modifiers removed(readonly)
184
+ * Hook called when a request is being retried.
309
185
  *
310
- * Method modifiers like "@get/", "@post/" are stripped to create a clean URL
311
- * for parameter substitution and final URL construction.
186
+ * This hook is triggered before each retry attempt, providing information about
187
+ * the previous failure and the current retry attempt number. Useful for implementing
188
+ * custom retry logic, exponential backoff, or retry logging.
189
+ *
190
+ * @param context - Retry context with error details and retry attempt count
191
+ * @returns Promise or void - Hook can be async or sync
312
192
  *
313
193
  */
314
- readonly initURLNormalized?: string;
194
+ onRetry?: (response: RetryContext<TCallApiContext>) => Awaitable<unknown>;
315
195
  /**
316
- * Parameters to be substituted into URL path segments.
196
+ * Hook called when a successful response (2xx status) is received from the API.
317
197
  *
318
- * Supports both object-style (named parameters) and array-style (positional parameters)
319
- * for flexible URL parameter substitution.
198
+ * This hook is triggered only for successful responses and provides access to
199
+ * the parsed response data. Ideal for success logging, caching, or post-processing
200
+ * of successful API responses.
320
201
  *
321
- * @example
322
- * ```typescript
323
- * // Object-style parameters (recommended)
324
- * const namedParams: URLOptions = {
325
- * initURL: "/users/:userId/posts/:postId",
326
- * params: { userId: "123", postId: "456" }
327
- * };
328
- * // Results in: /users/123/posts/456
202
+ * @param context - Success context with parsed response data and response object
203
+ * @returns Promise or void - Hook can be async or sync
329
204
  *
330
- * // Array-style parameters (positional)
331
- * const positionalParams: URLOptions = {
332
- * initURL: "/users/:userId/posts/:postId",
333
- * params: ["123", "456"] // Maps in order: userId=123, postId=456
334
- * };
335
- * // Results in: /users/123/posts/456
205
+ */
206
+ onSuccess?: (context: SuccessContext<TCallApiContext>) => Awaitable<unknown>;
207
+ /**
208
+ * Hook called when a validation error occurs.
209
+ *
210
+ * This hook is triggered when request or response data fails validation against
211
+ * a defined schema. It provides access to the validation error details and can
212
+ * be used for custom error handling, logging, or fallback behavior.
213
+ *
214
+ * @param context - Validation error context with error details and response (if available)
215
+ * @returns Promise or void - Hook can be async or sync
336
216
  *
337
- * // Single parameter
338
- * const singleParam: URLOptions = {
339
- * initURL: "/users/:id",
340
- * params: { id: "user-123" }
341
- * };
342
- * // Results in: /users/user-123
343
- * ```
344
217
  */
345
- params?: Params;
218
+ onValidationError?: (context: ValidationErrorContext<TCallApiContext>) => Awaitable<unknown>;
219
+ }
220
+ type HooksOrHooksArray<TCallApiContext extends NoInfer<CallApiContext> = DefaultCallApiContext> = { [Key in keyof Hooks<TCallApiContext>]: Hooks<TCallApiContext>[Key] | Array<Hooks<TCallApiContext>[Key]> };
221
+ interface HookConfigOptions {
346
222
  /**
347
- * Query parameters to append to the URL as search parameters.
223
+ * Controls the execution mode of all composed hooks (main + plugin hooks).
348
224
  *
349
- * These will be serialized into the URL query string using standard
350
- * URL encoding practices.
225
+ * - **"parallel"**: All hooks execute simultaneously via Promise.all() for better performance
226
+ * - **"sequential"**: All hooks execute one by one in registration order via await in a loop
351
227
  *
352
- * @example
353
- * ```typescript
354
- * // Basic query parameters
355
- * const queryOptions: URLOptions = {
356
- * initURL: "/users",
357
- * query: {
358
- * page: 1,
359
- * limit: 10,
360
- * search: "john doe",
361
- * active: true
362
- * }
363
- * };
364
- * // Results in: /users?page=1&limit=10&search=john%20doe&active=true
228
+ * This affects how ALL hooks execute together, regardless of their source (main or plugin).
365
229
  *
366
- * // Filtering and sorting
367
- * const filterOptions: URLOptions = {
368
- * initURL: "/products",
369
- * query: {
370
- * category: "electronics",
371
- * minPrice: 100,
372
- * maxPrice: 500,
373
- * sortBy: "price",
374
- * order: "asc"
375
- * }
376
- * };
377
- * // Results in: /products?category=electronics&minPrice=100&maxPrice=500&sortBy=price&order=asc
378
- * ```
230
+ * @default "parallel"
379
231
  */
380
- query?: Query;
232
+ hooksExecutionMode?: "parallel" | "sequential";
381
233
  }
382
- //#endregion
383
- //#region src/types/conditional-types.d.ts
384
- /**
385
- * @description Makes a type partial if the output type of TSchema is not provided or has undefined in the union, otherwise makes it required
386
- */
387
- type MakeSchemaOptionRequiredIfDefined<TSchemaOption extends CallApiSchema[keyof CallApiSchema], TObject> = undefined extends InferSchemaOutput<TSchemaOption, undefined> ? TObject : Required<TObject>;
388
- type ApplyURLBasedConfig<TSchemaConfig extends CallApiSchemaConfig, TSchemaRouteKeys extends string> = TSchemaConfig["prefix"] extends string ? `${TSchemaConfig["prefix"]}${TSchemaRouteKeys}` : TSchemaConfig["baseURL"] extends string ? `${TSchemaConfig["baseURL"]}${TSchemaRouteKeys}` : TSchemaRouteKeys;
389
- type ApplyStrictConfig<TSchemaConfig extends CallApiSchemaConfig, TSchemaRouteKeys extends string> = TSchemaConfig["strict"] extends true ? TSchemaRouteKeys :
390
- // eslint-disable-next-line perfectionist/sort-union-types -- Don't sort union types
391
- TSchemaRouteKeys | Exclude<InitURLOrURLObject, RouteKeyMethodsURLUnion>;
392
- type ApplySchemaConfiguration<TSchemaConfig extends CallApiSchemaConfig, TSchemaRouteKeys extends string> = ApplyStrictConfig<TSchemaConfig, ApplyURLBasedConfig<TSchemaConfig, TSchemaRouteKeys>>;
393
- type InferAllRouteKeys<TBaseSchemaRoutes extends BaseCallApiSchemaRoutes, TSchemaConfig extends CallApiSchemaConfig> = ApplySchemaConfiguration<TSchemaConfig, Exclude<Extract<keyof TBaseSchemaRoutes, string>, FallBackRouteSchemaKey>>;
394
- type InferInitURL<TBaseSchemaRoutes extends BaseCallApiSchemaRoutes, TSchemaConfig extends CallApiSchemaConfig> = keyof TBaseSchemaRoutes extends never ? InitURLOrURLObject : InferAllRouteKeys<TBaseSchemaRoutes, TSchemaConfig>;
395
- 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;
396
- type GetCurrentRouteSchema<TBaseSchemaRoutes extends BaseCallApiSchemaRoutes, TCurrentRouteSchemaKey extends string, TComputedFallBackRouteSchema = TBaseSchemaRoutes[FallBackRouteSchemaKey], TComputedCurrentRouteSchema = TBaseSchemaRoutes[TCurrentRouteSchemaKey], TComputedRouteSchema extends CallApiSchema = NonNullable<Omit<TComputedFallBackRouteSchema, keyof TComputedCurrentRouteSchema> & TComputedCurrentRouteSchema>> = TComputedRouteSchema extends CallApiSchema ? Writeable<TComputedRouteSchema, "deep"> : CallApiSchema;
397
- type JsonPrimitive = boolean | number | string | null | undefined;
398
- type SerializableObject = Record<PropertyKey, unknown>;
399
- type SerializableArray = Array<JsonPrimitive | SerializableObject> | ReadonlyArray<JsonPrimitive | SerializableObject>;
400
- type Body = UnmaskType<Exclude<RequestInit["body"], undefined> | SerializableArray | SerializableObject>;
401
- type InferBodyOption<TSchema extends CallApiSchema> = MakeSchemaOptionRequiredIfDefined<TSchema["body"], {
234
+ type RequestContext<TCallApiContext extends Pick<CallApiContext, "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = {
402
235
  /**
403
- * Body of the request, can be a object or any other supported body type.
236
+ * Base configuration object passed to createFetchClient.
237
+ *
238
+ * Contains the foundational configuration that applies to all requests
239
+ * made by this client instance, such as baseURL, default headers, and
240
+ * global options.
404
241
  */
405
- body?: InferSchemaOutput<TSchema["body"], Body>;
406
- }>;
407
- type MethodUnion = UnmaskType<"CONNECT" | "DELETE" | "GET" | "HEAD" | "OPTIONS" | "PATCH" | "POST" | "PUT" | "TRACE" | AnyString>;
408
- type InferMethodFromURL<TInitURL> = string extends TInitURL ? MethodUnion : TInitURL extends `@${infer TMethod extends RouteKeyMethods}/${string}` ? Uppercase<TMethod> : MethodUnion;
409
- type InferMethodOption<TSchema extends CallApiSchema, TInitURL> = MakeSchemaOptionRequiredIfDefined<TSchema["method"], {
242
+ baseConfig: Exclude<BaseCallApiConfig, AnyFunction>;
410
243
  /**
411
- * HTTP method for the request.
412
- * @default "GET"
244
+ * Instance-specific configuration object passed to the callApi instance.
245
+ *
246
+ * Contains configuration specific to this particular API call, which
247
+ * can override or extend the base configuration.
413
248
  */
414
- method?: InferSchemaOutput<TSchema["method"], InferMethodFromURL<TInitURL>>;
415
- }>;
416
- type HeadersOption = UnmaskType<Headers | Record<"Authorization", CommonAuthorizationHeaders | undefined> | Record<"Content-Type", CommonContentTypes | undefined> | Record<CommonRequestHeaders, string | undefined> | Record<string, string | undefined> | Array<[string, string]>>;
417
- type InferHeadersOption<TSchema extends CallApiSchema> = MakeSchemaOptionRequiredIfDefined<TSchema["headers"], {
249
+ config: CallApiConfig;
418
250
  /**
419
- * Headers to be used in the request.
251
+ * Merged options combining base config, instance config, and default options.
252
+ *
253
+ * This is the final resolved configuration that will be used for the request,
254
+ * with proper precedence applied (instance > base > defaults).
420
255
  */
421
- headers?: InferSchemaOutput<TSchema["headers"], HeadersOption> | ((context: {
422
- baseHeaders: Extract<HeadersOption, Record<string, unknown>>;
423
- }) => InferSchemaOutput<TSchema["headers"], HeadersOption>);
424
- }>;
425
- type InferRequestOptions<TSchema extends CallApiSchema, TInitURL extends InferInitURL<BaseCallApiSchemaRoutes, CallApiSchemaConfig>> = InferBodyOption<TSchema> & InferHeadersOption<TSchema> & InferMethodOption<TSchema, TInitURL>;
426
- type InferMetaOption<TSchema extends CallApiSchema, TCallApiContext extends CallApiContext> = MakeSchemaOptionRequiredIfDefined<TSchema["meta"], {
256
+ options: CallApiExtraOptionsForHooks<TCallApiContext>;
427
257
  /**
428
- * - An optional field you can fill with additional information,
429
- * to associate with the request, typically used for logging or tracing.
258
+ * Merged request object ready to be sent.
430
259
  *
431
- * - A good use case for this, would be to use the info to handle specific cases in any of the shared interceptors.
260
+ * Contains the final request configuration including URL, method, headers,
261
+ * body, and other fetch options. This object can be modified in onRequest
262
+ * hooks to customize the outgoing request.
263
+ */
264
+ request: CallApiRequestOptionsForHooks;
265
+ };
266
+ type ValidationErrorContext<TCallApiContext extends Pick<CallApiContext, "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = RequestContext<TCallApiContext> & {
267
+ error: PossibleValidationError;
268
+ response: Response | null;
269
+ };
270
+ type SuccessContext<TCallApiContext extends Pick<CallApiContext, "Data" | "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = RequestContext<TCallApiContext> & {
271
+ data: TCallApiContext["Data"];
272
+ response: Response;
273
+ };
274
+ type ResponseContext<TCallApiContext extends Pick<CallApiContext, "Data" | "ErrorData" | "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = RequestContext<TCallApiContext> & (Prettify<CallApiResultSuccessVariant<TCallApiContext["Data"]>> | Prettify<Extract<CallApiResultErrorVariant<TCallApiContext["ErrorData"]>, {
275
+ error: PossibleHTTPError<TCallApiContext["ErrorData"]>;
276
+ }>>);
277
+ type RequestErrorContext<TCallApiContext extends Pick<CallApiContext, "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = RequestContext<TCallApiContext> & {
278
+ error: PossibleJavaScriptError;
279
+ response: null;
280
+ };
281
+ type ErrorContext<TCallApiContext extends Pick<CallApiContext, "ErrorData" | "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = RequestContext<TCallApiContext> & ({
282
+ error: PossibleHTTPError<TCallApiContext["ErrorData"]>;
283
+ response: Response;
284
+ } | {
285
+ error: PossibleJavaScriptOrValidationError;
286
+ response: Response | null;
287
+ });
288
+ type ResponseErrorContext<TCallApiContext extends Pick<CallApiContext, "ErrorData" | "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = Extract<ErrorContext<TCallApiContext>, {
289
+ error: PossibleHTTPError<TCallApiContext["ErrorData"]>;
290
+ }> & RequestContext<TCallApiContext>;
291
+ type RetryContext<TCallApiContext extends Pick<CallApiContext, "ErrorData" | "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = ErrorContext<TCallApiContext> & {
292
+ retryAttemptCount: number;
293
+ };
294
+ type RequestStreamContext<TCallApiContext extends Pick<CallApiContext, "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = RequestContext<TCallApiContext> & {
295
+ event: StreamProgressEvent;
296
+ requestInstance: Request;
297
+ };
298
+ type ResponseStreamContext<TCallApiContext extends Pick<CallApiContext, "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = RequestContext<TCallApiContext> & {
299
+ event: StreamProgressEvent;
300
+ response: Response;
301
+ };
302
+ //#endregion
303
+ //#region src/dedupe.d.ts
304
+ type DedupeStrategyUnion = UnmaskType<"cancel" | "defer" | "none">;
305
+ type DedupeOptionKeys = Exclude<keyof DedupeOptions, "dedupe">;
306
+ type InnerDedupeOptions = { [Key in DedupeOptionKeys as RemovePrefix<"dedupe", Key>]?: DedupeOptions[Key] };
307
+ type DedupeOptions = {
308
+ /**
309
+ * All dedupe options in a single object instead of separate properties
310
+ */
311
+ dedupe?: InnerDedupeOptions;
312
+ /**
313
+ * Controls the scope of request deduplication caching.
314
+ *
315
+ * - `"global"`: Shares deduplication cache across all `createFetchClient` instances with the same `dedupeCacheScopeKey`.
316
+ * Useful for applications with multiple API clients that should share deduplication state.
317
+ * - `"local"`: Limits deduplication to requests within the same `createFetchClient` instance.
318
+ * Provides better isolation and is recommended for most use cases.
319
+ *
320
+ *
321
+ * **Real-world Scenarios:**
322
+ * - Use `"global"` when you have multiple API clients (user service, auth service, etc.) that might make overlapping requests
323
+ * - Use `"local"` (default) for single-purpose clients or when you want strict isolation between different parts of your app
432
324
  *
433
325
  * @example
434
326
  * ```ts
435
- * const callMainApi = callApi.create({
436
- * baseURL: "https://main-api.com",
437
- * onResponseError: ({ response, options }) => {
438
- * if (options.meta?.userId) {
439
- * console.error(`User ${options.meta.userId} made an error`);
440
- * }
441
- * },
442
- * });
327
+ * // Local scope - each client has its own deduplication cache
328
+ * const userClient = createFetchClient({ baseURL: "/api/users" });
329
+ * const postClient = createFetchClient({ baseURL: "/api/posts" });
330
+ * // These clients won't share deduplication state
443
331
  *
444
- * const response = await callMainApi({
445
- * url: "https://example.com/api/data",
446
- * meta: { userId: "123" },
332
+ * // Global scope - share cache across related clients
333
+ * const userClient = createFetchClient({
334
+ * baseURL: "/api/users",
335
+ * dedupeCacheScope: "global",
336
+ * });
337
+ * const postClient = createFetchClient({
338
+ * baseURL: "/api/posts",
339
+ * dedupeCacheScope: "global",
447
340
  * });
341
+ * // These clients will share deduplication state
448
342
  * ```
343
+ *
344
+ * @default "local"
449
345
  */
450
- meta?: InferSchemaOutput<TSchema["meta"], TCallApiContext["Meta"]>;
451
- }>;
452
- type InferAuthOption<TSchema extends CallApiSchema> = MakeSchemaOptionRequiredIfDefined<TSchema["auth"], {
346
+ dedupeCacheScope?: "global" | "local";
453
347
  /**
454
- * Automatically add an Authorization header value.
348
+ * Unique namespace for the global deduplication cache when using `dedupeCacheScope: "global"`.
455
349
  *
456
- * Supports multiple authentication patterns:
457
- * - String: Direct authorization header value
458
- * - Auth object: Structured authentication configuration
350
+ * This creates logical groupings of deduplication caches. All instances with the same key
351
+ * will share the same cache namespace, allowing fine-grained control over which clients
352
+ * share deduplication state.
353
+ *
354
+ * **Best Practices:**
355
+ * - Use descriptive names that reflect the logical grouping (e.g., "user-service", "analytics-api")
356
+ * - Keep scope keys consistent across related API clients
357
+ * - Consider using different scope keys for different environments (dev, staging, prod)
358
+ * - Avoid overly broad scope keys that might cause unintended cache sharing
359
+ *
360
+ * **Cache Management:**
361
+ * - Each scope key maintains its own independent cache
362
+ * - Caches are automatically cleaned up when no references remain
363
+ * - Consider the memory implications of multiple global scopes
459
364
  *
460
365
  * @example
461
366
  * ```ts
462
- * const callMainApi = callApi.create({
463
- * baseURL: "https://main-api.com",
464
- * onRequest: ({ options }) => {
465
- * if (options.auth) {
466
- * options.headers.Authorization = options.auth;
467
- * }
468
- * },
367
+ * // Group related API clients together
368
+ * const userClient = createFetchClient({
369
+ * baseURL: "/api/users",
370
+ * dedupeCacheScope: "global",
371
+ * dedupeCacheScopeKey: "user-service"
372
+ * });
373
+ * const profileClient = createFetchClient({
374
+ * baseURL: "/api/profiles",
375
+ * dedupeCacheScope: "global",
376
+ * dedupeCacheScopeKey: "user-service" // Same scope - will share cache
469
377
  * });
470
378
  *
471
- * const response = await callMainApi({
472
- * url: "https://example.com/api/data",
473
- * auth: "Bearer 123456",
379
+ * // Separate analytics client with its own cache
380
+ * const analyticsClient = createFetchClient({
381
+ * baseURL: "/api/analytics",
382
+ * dedupeCacheScope: "global",
383
+ * dedupeCacheScopeKey: "analytics-service" // Different scope
384
+ * });
385
+ *
386
+ * // Environment-specific scoping
387
+ * const apiClient = createFetchClient({
388
+ * dedupeCacheScope: "global",
389
+ * dedupeCacheScopeKey: `api-${process.env.NODE_ENV}` // "api-development", "api-production", etc.
474
390
  * });
475
391
  * ```
392
+ *
393
+ * @default "default"
476
394
  */
477
- auth?: InferSchemaOutput<TSchema["auth"], AuthOption>;
478
- }>;
479
- type InferQueryOption<TSchema extends CallApiSchema> = MakeSchemaOptionRequiredIfDefined<TSchema["query"], {
395
+ dedupeCacheScopeKey?: "default" | AnyString | ((context: RequestContext) => string | undefined);
480
396
  /**
481
- * Parameters to be appended to the URL (i.e: /:id)
397
+ * Custom key generator for request deduplication.
398
+ *
399
+ * Override the default key generation strategy to control exactly which requests
400
+ * are considered duplicates. The default key combines URL, method, body, and
401
+ * relevant headers (excluding volatile ones like 'Date', 'Authorization', etc.).
402
+ *
403
+ * **Default Key Generation:**
404
+ * The auto-generated key includes:
405
+ * - Full request URL (including query parameters)
406
+ * - HTTP method (GET, POST, etc.)
407
+ * - Request body (for POST/PUT/PATCH requests)
408
+ * - Stable headers (excludes Date, Authorization, User-Agent, etc.)
409
+ *
410
+ * **Custom Key Best Practices:**
411
+ * - Include only the parts of the request that should affect deduplication
412
+ * - Avoid including volatile data (timestamps, random IDs, etc.)
413
+ * - Consider performance - simpler keys are faster to compute and compare
414
+ * - Ensure keys are deterministic for the same logical request
415
+ * - Use consistent key formats across your application
416
+ *
417
+ * **Performance Considerations:**
418
+ * - Function-based keys are computed on every request - keep them lightweight
419
+ * - String keys are fastest but least flexible
420
+ * - Consider caching expensive key computations if needed
421
+ *
422
+ * @example
423
+ * ```ts
424
+ * import { callApi } from "@zayne-labs/callapi";
425
+ *
426
+ * // Simple static key - useful for singleton requests
427
+ * const config = callApi("/api/config", {
428
+ * dedupeKey: "app-config",
429
+ * dedupeStrategy: "defer" // Share the same config across all requests
430
+ * });
431
+ *
432
+ * // URL and method only - ignore headers and body
433
+ * const userData = callApi("/api/user/123", {
434
+ * dedupeKey: (context) => `${context.options.method}:${context.options.fullURL}`
435
+ * });
436
+ *
437
+ * // Include specific headers in deduplication
438
+ * const apiCall = callApi("/api/data", {
439
+ * dedupeKey: (context) => {
440
+ * const authHeader = context.request.headers.get("Authorization");
441
+ * return `${context.options.fullURL}-${authHeader}`;
442
+ * }
443
+ * });
444
+ *
445
+ * // User-specific deduplication
446
+ * const userSpecificCall = callApi("/api/dashboard", {
447
+ * dedupeKey: (context) => {
448
+ * const userId = context.options.fullURL.match(/user\/(\d+)/)?.[1];
449
+ * return `dashboard-${userId}`;
450
+ * }
451
+ * });
452
+ *
453
+ * // Ignore certain query parameters
454
+ * const searchCall = callApi("/api/search?q=test&timestamp=123456", {
455
+ * dedupeKey: (context) => {
456
+ * const url = new URL(context.options.fullURL);
457
+ * url.searchParams.delete("timestamp"); // Remove volatile param
458
+ * return `search:${url.toString()}`;
459
+ * }
460
+ * });
461
+ * ```
462
+ *
463
+ * @default Auto-generated from request details
482
464
  */
483
- query?: InferSchemaOutput<TSchema["query"], Query>;
484
- }>;
485
- type EmptyString = "";
486
- type EmptyTuple = readonly [];
487
- type StringTuple = readonly string[];
488
- type PossibleParamNamePatterns = `${string}:${string}` | `${string}{${string}}${"" | AnyString}`;
489
- 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;
490
- type ConvertParamNamesToRecord<TParamNames extends StringTuple> = Prettify<TParamNames extends (readonly [infer TFirstParamName extends string, ...infer TRemainingParamNames extends StringTuple]) ? Record<TFirstParamName, AllowedQueryParamValues> & ConvertParamNamesToRecord<TRemainingParamNames> : NonNullable<unknown>>;
491
- type ConvertParamNamesToTuple<TParamNames extends StringTuple> = TParamNames extends readonly [string, ...infer TRemainingParamNames extends StringTuple] ? [AllowedQueryParamValues, ...ConvertParamNamesToTuple<TRemainingParamNames>] : [];
492
- type InferParamsFromRoute<TCurrentRoute> = ExtractRouteParamNames<TCurrentRoute> extends StringTuple ? ExtractRouteParamNames<TCurrentRoute> extends EmptyTuple ? Params : ConvertParamNamesToRecord<ExtractRouteParamNames<TCurrentRoute>> | ConvertParamNamesToTuple<ExtractRouteParamNames<TCurrentRoute>> : Params;
493
- type MakeParamsOptionRequired<TParamsSchemaOption extends CallApiSchema["params"], TBaseSchemaRoutes extends BaseCallApiSchemaRoutes, TCurrentRouteSchemaKey extends string, TObject> = MakeSchemaOptionRequiredIfDefined<TParamsSchemaOption, Params extends InferParamsFromRoute<TCurrentRouteSchemaKey> ? TObject : TCurrentRouteSchemaKey extends Extract<keyof TBaseSchemaRoutes, TCurrentRouteSchemaKey> ? undefined extends InferSchemaOutput<TParamsSchemaOption, null> ? TObject : Required<TObject> : TObject>;
494
- type InferParamsOption<TSchema extends CallApiSchema, TBaseSchemaRoutes extends BaseCallApiSchemaRoutes, TCurrentRouteSchemaKey extends string> = MakeParamsOptionRequired<TSchema["params"], TBaseSchemaRoutes, TCurrentRouteSchemaKey, {
465
+ dedupeKey?: string | ((context: RequestContext) => string | undefined);
495
466
  /**
496
- * Parameters to be appended to the URL (i.e: /:id)
467
+ * Strategy for handling duplicate requests. Can be a static string or callback function.
468
+ *
469
+ * **Available Strategies:**
470
+ * - `"cancel"`: Cancel previous request when new one starts (good for search)
471
+ * - `"defer"`: Share response between duplicate requests (good for config loading)
472
+ * - `"none"`: No deduplication, all requests execute independently
473
+ *
474
+ * @example
475
+ * ```ts
476
+ * // Static strategies
477
+ * const searchClient = createFetchClient({
478
+ * dedupeStrategy: "cancel" // Cancel previous searches
479
+ * });
480
+ *
481
+ * const configClient = createFetchClient({
482
+ * dedupeStrategy: "defer" // Share config across components
483
+ * });
484
+ *
485
+ * // Dynamic strategy based on request
486
+ * const smartClient = createFetchClient({
487
+ * dedupeStrategy: (context) => {
488
+ * return context.options.method === "GET" ? "defer" : "cancel";
489
+ * }
490
+ * });
491
+ *
492
+ * // Search-as-you-type with cancel strategy
493
+ * const handleSearch = async (query: string) => {
494
+ * try {
495
+ * const { data } = await callApi("/api/search", {
496
+ * method: "POST",
497
+ * body: { query },
498
+ * dedupeStrategy: "cancel",
499
+ * dedupeKey: "search" // Cancel previous searches, only latest one goes through
500
+ * });
501
+ *
502
+ * updateSearchResults(data);
503
+ * } catch (error) {
504
+ * if (error.name === "AbortError") {
505
+ * // Previous search cancelled - (expected behavior)
506
+ * return;
507
+ * }
508
+ * console.error("Search failed:", error);
509
+ * }
510
+ * };
511
+ *
512
+ * ```
513
+ *
514
+ * @default "cancel"
497
515
  */
498
- params?: InferSchemaOutput<TSchema["params"], InferParamsFromRoute<TCurrentRouteSchemaKey>>;
499
- }>;
500
- type InferExtraOptions<TSchema extends CallApiSchema, TBaseSchemaRoutes extends BaseCallApiSchemaRoutes, TCurrentRouteSchemaKey extends string, TCallApiContext extends CallApiContext> = InferAuthOption<TSchema> & InferMetaOption<TSchema, TCallApiContext> & InferParamsOption<TSchema, TBaseSchemaRoutes, TCurrentRouteSchemaKey> & InferQueryOption<TSchema>;
501
- type InferPluginExtraOptions<TPluginArray extends CallApiPlugin[]> = UnionToIntersection<TPluginArray extends Array<infer TPlugin> ? TPlugin extends CallApiPlugin ? TPlugin["defineExtraOptions"] extends AnyFunction<infer TReturnedSchema> ? InferSchemaOutput<TReturnedSchema> : never : never : never>;
502
- type ResultModeOption<TErrorData, TResultMode extends ResultModeType> = TErrorData extends false ? {
503
- resultMode: "onlyData";
504
- } : TErrorData extends false | undefined ? {
505
- resultMode?: "onlyData";
506
- } : {
507
- resultMode?: TResultMode;
508
- };
509
- type ThrowOnErrorUnion = boolean;
510
- type ThrowOnErrorType<TErrorData, TThrowOnError extends ThrowOnErrorUnion> = TThrowOnError | ((context: ErrorContext<{
511
- ErrorData: TErrorData;
512
- }>) => TThrowOnError);
513
- type ThrowOnErrorOption<TErrorData, TThrowOnError extends ThrowOnErrorUnion> = TErrorData extends false ? {
514
- throwOnError: true;
515
- } : TErrorData extends false | undefined ? {
516
- throwOnError?: true;
517
- } : {
518
- throwOnError?: ThrowOnErrorType<TErrorData, TThrowOnError>;
516
+ dedupeStrategy?: DedupeStrategyUnion | ((context: RequestContext) => DedupeStrategyUnion);
519
517
  };
520
518
  //#endregion
521
519
  //#region src/middlewares.d.ts
522
520
  type FetchImpl = UnmaskType<(input: string | Request | URL, init?: RequestInit) => Promise<Response>>;
521
+ type FetchMiddlewareContext<TCallApiContext extends CallApiContext> = RequestContext<TCallApiContext> & {
522
+ fetchImpl: FetchImpl;
523
+ };
523
524
  interface Middlewares<TCallApiContext extends NoInfer<CallApiContext> = DefaultCallApiContext> {
524
525
  /**
525
526
  * Wraps the fetch implementation to intercept requests at the network layer.
@@ -537,509 +538,478 @@ interface Middlewares<TCallApiContext extends NoInfer<CallApiContext> = DefaultC
537
538
  *
538
539
  * fetchMiddleware: (ctx) => async (input, init) => {
539
540
  * const key = input.toString();
540
- * if (cache.has(key)) return cache.get(key).clone();
541
+ *
542
+ * const cachedResponse = cache.get(key);
543
+ *
544
+ * if (cachedResponse) {
545
+ * return cachedResponse.clone();
546
+ * }
541
547
  *
542
548
  * const response = await ctx.fetchImpl(input, init);
543
549
  * cache.set(key, response.clone());
550
+ *
544
551
  * return response;
545
552
  * }
546
553
  *
547
554
  * // Handle offline
548
- * fetchMiddleware: (ctx) => async (input, init) => {
555
+ * fetchMiddleware: (ctx) => async (...parameters) => {
549
556
  * if (!navigator.onLine) {
550
557
  * return new Response('{"error": "offline"}', { status: 503 });
551
558
  * }
552
- * return ctx.fetchImpl(input, init);
559
+ *
560
+ * return ctx.fetchImpl(...parameters);
553
561
  * }
554
562
  * ```
555
563
  */
556
- fetchMiddleware?: (context: RequestContext<TCallApiContext> & {
557
- fetchImpl: FetchImpl;
558
- }) => FetchImpl;
564
+ fetchMiddleware?: (context: FetchMiddlewareContext<TCallApiContext>) => FetchImpl;
565
+ }
566
+ //#endregion
567
+ //#region src/types/standard-schema.d.ts
568
+ /**
569
+ * The Standard Schema interface.
570
+ * @see https://github.com/standard-schema/standard-schema
571
+ */
572
+ /** The Standard Typed interface. This is a base type extended by other specs. */
573
+ interface StandardTypedV1<Input = unknown, Output = Input> {
574
+ /** The Standard properties. */
575
+ readonly "~standard": StandardTypedV1.Props<Input, Output>;
576
+ }
577
+ declare namespace StandardTypedV1 {
578
+ /** The Standard Typed properties interface. */
579
+ interface Props<Input = unknown, Output = Input> {
580
+ /** Inferred types associated with the schema. */
581
+ readonly types?: Types<Input, Output> | undefined;
582
+ /** The vendor name of the schema library. */
583
+ readonly vendor: string;
584
+ /** The version number of the standard. */
585
+ readonly version: 1;
586
+ }
587
+ /** The Standard Typed types interface. */
588
+ interface Types<Input = unknown, Output = Input> {
589
+ /** The input type of the schema. */
590
+ readonly input: Input;
591
+ /** The output type of the schema. */
592
+ readonly output: Output;
593
+ }
594
+ /** Infers the input type of a Standard Typed. */
595
+ type InferInput<Schema extends StandardTypedV1> = NonNullable<Schema["~standard"]["types"]>["input"];
596
+ /** Infers the output type of a Standard Typed. */
597
+ type InferOutput<Schema extends StandardTypedV1> = NonNullable<Schema["~standard"]["types"]>["output"];
598
+ }
599
+ /** The Standard Schema interface. */
600
+ interface StandardSchemaV1<Input = unknown, Output = Input> {
601
+ /** The Standard Schema properties. */
602
+ readonly "~standard": StandardSchemaV1.Props<Input, Output>;
603
+ }
604
+ declare namespace StandardSchemaV1 {
605
+ /** The Standard Schema properties interface. */
606
+ interface Props<Input = unknown, Output = Input> extends StandardTypedV1.Props<Input, Output> {
607
+ /** Validates unknown input values. */
608
+ readonly validate: (value: unknown, options?: StandardSchemaV1.Options) => Promise<Result<Output>> | Result<Output>;
609
+ }
610
+ /** The result interface of the validate function. */
611
+ type Result<Output> = FailureResult | SuccessResult<Output>;
612
+ /** The result interface if validation succeeds. */
613
+ interface SuccessResult<Output> {
614
+ /** A falsy value for `issues` indicates success. */
615
+ readonly issues?: undefined;
616
+ /** The typed output value. */
617
+ readonly value: Output;
618
+ }
619
+ interface Options {
620
+ /** Explicit support for additional vendor-specific parameters, if needed. */
621
+ readonly libraryOptions?: Record<string, unknown> | undefined;
622
+ }
623
+ /** The result interface if validation fails. */
624
+ interface FailureResult {
625
+ /** The issues of failed validation. */
626
+ readonly issues: readonly Issue[];
627
+ }
628
+ /** The issue interface of the failure output. */
629
+ interface Issue {
630
+ /** The error message of the issue. */
631
+ readonly message: string;
632
+ /** The path of the issue, if any. */
633
+ readonly path?: ReadonlyArray<PathSegment | PropertyKey> | undefined;
634
+ }
635
+ /** The path segment interface of the issue. */
636
+ interface PathSegment {
637
+ /** The key representing a path segment. */
638
+ readonly key: PropertyKey;
639
+ }
640
+ /** The Standard types interface. */
641
+ type Types<Input = unknown, Output = Input> = StandardTypedV1.Types<Input, Output>;
642
+ /** Infers the input type of a Standard. */
643
+ type InferInput<Schema extends StandardTypedV1> = StandardTypedV1.InferInput<Schema>;
644
+ /** Infers the output type of a Standard. */
645
+ type InferOutput<Schema extends StandardTypedV1> = StandardTypedV1.InferOutput<Schema>;
559
646
  }
560
647
  //#endregion
561
- //#region src/plugins.d.ts
562
- type PluginSetupContext<TCallApiContext extends CallApiContext = DefaultCallApiContext> = RequestContext<TCallApiContext> & {
563
- initURL: string;
564
- };
565
- type PluginInitResult<TCallApiContext extends CallApiContext = DefaultCallApiContext> = Partial<Omit<PluginSetupContext<TCallApiContext>, "initURL" | "request"> & {
566
- initURL: InitURLOrURLObject;
567
- request: CallApiRequestOptions;
568
- }>;
569
- type PluginHooks<TCallApiContext extends CallApiContext = DefaultCallApiContext> = HooksOrHooksArray<OverrideCallApiContext<TCallApiContext, {
570
- Data: DefaultDataType extends TCallApiContext["Data"] ? never : TCallApiContext["Data"];
571
- ErrorData: DefaultDataType extends TCallApiContext["ErrorData"] ? never : TCallApiContext["ErrorData"];
572
- }>>;
573
- interface CallApiPlugin<TCallApiContext extends CallApiContext = DefaultCallApiContext> {
574
- /**
575
- * Defines additional options that can be passed to callApi
576
- */
577
- defineExtraOptions?: (...params: never[]) => unknown;
578
- /**
579
- * A description for the plugin
580
- */
581
- description?: string;
582
- /**
583
- * Hooks for the plugin
584
- */
585
- hooks?: PluginHooks<TCallApiContext> | ((context: PluginSetupContext<TCallApiContext>) => Awaitable<PluginHooks<TCallApiContext>>);
586
- /**
587
- * A unique id for the plugin
588
- */
589
- id: string;
590
- /**
591
- * Middlewares that for the plugin
592
- */
593
- middlewares?: Middlewares<TCallApiContext> | ((context: PluginSetupContext<TCallApiContext>) => Awaitable<Middlewares<TCallApiContext>>);
594
- /**
595
- * A name for the plugin
596
- */
597
- name: string;
648
+ //#region src/validation.d.ts
649
+ type ResultVariant = "infer-input" | "infer-output";
650
+ type InferSchemaResult<TSchema, TFallbackResult, TResultVariant extends ResultVariant> = undefined extends TSchema ? TFallbackResult : TSchema extends StandardSchemaV1 ? TResultVariant extends "infer-input" ? StandardSchemaV1.InferInput<TSchema> : StandardSchemaV1.InferOutput<TSchema> : TSchema extends AnyFunction<infer TResult> ? Awaited<TResult> : TFallbackResult;
651
+ type InferSchemaOutput<TSchema, TFallbackResult = unknown> = InferSchemaResult<TSchema, TFallbackResult, "infer-output">;
652
+ type InferSchemaInput<TSchema, TFallbackResult = unknown> = InferSchemaResult<TSchema, TFallbackResult, "infer-input">;
653
+ type BooleanObject = { [Key in keyof CallApiSchema]: boolean };
654
+ interface CallApiSchemaConfig {
598
655
  /**
599
- * Base schema for the client.
656
+ * The base url of the schema. By default it's the baseURL of the callApi instance.
600
657
  */
601
- schema?: BaseCallApiSchemaAndConfig;
658
+ baseURL?: "" | AnyString;
602
659
  /**
603
- * A function that will be called when the plugin is initialized. This will be called before the any of the other internal functions.
660
+ * Disables runtime validation for the schema.
604
661
  */
605
- setup?: (context: PluginSetupContext<TCallApiContext>) => Awaitable<PluginInitResult<TCallApiContext>> | Awaitable<void>;
662
+ disableRuntimeValidation?: boolean | BooleanObject;
606
663
  /**
607
- * A version for the plugin
664
+ * If `true`, the original input value will be used instead of the transformed/validated output.
665
+ *
666
+ * When true, the original input is returned unchanged after validation, ignoring any schema-level
667
+ * transformations such as type coercion, default values, or field mapping. Only the validation
668
+ * step is executed; the resulting value is discarded in favor of the raw input.
608
669
  */
609
- version?: string;
610
- }
611
- //#endregion
612
- //#region src/types/default-types.d.ts
613
- type DefaultDataType = unknown;
614
- type DefaultPluginArray = CallApiPlugin[];
615
- type DefaultThrowOnError = boolean;
616
- type DefaultMetaObject = Record<string, unknown>;
617
- type DefaultCallApiContext = Omit<Required<CallApiContext>, "Meta"> & {
618
- Meta: GlobalMeta;
619
- };
620
- //#endregion
621
- //#region src/hooks.d.ts
622
- interface Hooks<TCallApiContext extends CallApiContext = DefaultCallApiContext> {
670
+ disableRuntimeValidationTransform?: boolean | BooleanObject;
623
671
  /**
624
- * Hook called when any error occurs within the request/response lifecycle.
625
- *
626
- * This is a unified error handler that catches both request errors (network failures,
627
- * timeouts, etc.) and response errors (HTTP error status codes). It's essentially
628
- * a combination of `onRequestError` and `onResponseError` hooks.
672
+ * Optional url prefix that will be substituted for the `baseURL` of the schemaConfig at runtime.
629
673
  *
630
- * @param context - Error context containing error details, request info, and response (if available)
631
- * @returns Promise or void - Hook can be async or sync
674
+ * Enables a short, stable prefix for routes while keeping the full `baseURL` centralized in config.
675
+ * Keeps route definitions concise and shields them from changes to the underlying base URL.
632
676
  */
633
- onError?: (context: ErrorContext<TCallApiContext>) => Awaitable<unknown>;
677
+ prefix?: "" | AnyString;
634
678
  /**
635
- * Hook called before the HTTP request is sent and before any internal processing of the request object begins.
636
- *
637
- * This is the ideal place to modify request headers, add authentication,
638
- * implement request logging, or perform any setup before the network call.
679
+ * Controls the strictness of API route validation.
639
680
  *
640
- * @param context - Request context with mutable request object and configuration
641
- * @returns Promise or void - Hook can be async or sync
681
+ * When true:
682
+ * - Only routes explicitly defined in the schema will be considered valid to typescript and the runtime.
683
+ * - Attempting to call routes not defined in the schema will result in both type errors and runtime validation errors.
684
+ * - Useful for ensuring API calls conform exactly to your schema definition
642
685
  *
686
+ * When false or undefined (default):
687
+ * - All routes will be allowed, whether they are defined in the schema or not
643
688
  */
644
- onRequest?: (context: RequestContext<TCallApiContext>) => Awaitable<unknown>;
689
+ strict?: boolean;
690
+ }
691
+ interface CallApiSchema {
692
+ auth?: StandardSchemaV1<AuthOption | undefined> | ((auth: AuthOption) => Awaitable<AuthOption | undefined>);
645
693
  /**
646
- * Hook called when an error occurs during the fetch request itself.
647
- *
648
- * This handles network-level errors like connection failures, timeouts,
649
- * DNS resolution errors, or other issues that prevent getting an HTTP response.
650
- * Note that HTTP error status codes (4xx, 5xx) are handled by `onResponseError`.
651
- *
652
- * @param context - Request error context with error details and null response
653
- * @returns Promise or void - Hook can be async or sync
694
+ * The schema to use for validating the request body.
654
695
  */
655
- onRequestError?: (context: RequestErrorContext<TCallApiContext>) => Awaitable<unknown>;
696
+ body?: StandardSchemaV1<Body | undefined> | ((body: Body) => Awaitable<Body | undefined>);
656
697
  /**
657
- * Hook called just before the HTTP request is sent and after the request has been processed.
658
- *
659
- * @param context - Request context with mutable request object and configuration
698
+ * The schema to use for validating the response data.
660
699
  */
661
- onRequestReady?: (context: RequestContext<TCallApiContext>) => Awaitable<unknown>;
700
+ data?: StandardSchemaV1 | ((data: unknown) => unknown);
662
701
  /**
663
- * Hook called during upload stream progress tracking.
664
- *
665
- * This hook is triggered when uploading data (like file uploads) and provides
666
- * progress information about the upload. Useful for implementing progress bars
667
- * or upload status indicators.
668
- *
669
- * @param context - Request stream context with progress event and request instance
670
- * @returns Promise or void - Hook can be async or sync
671
- *
702
+ * The schema to use for validating the response error data.
672
703
  */
673
- onRequestStream?: (context: RequestStreamContext<TCallApiContext>) => Awaitable<unknown>;
704
+ errorData?: StandardSchemaV1 | ((errorData: unknown) => unknown);
674
705
  /**
675
- * Hook called when any HTTP response is received from the API.
676
- *
677
- * This hook is triggered for both successful (2xx) and error (4xx, 5xx) responses.
678
- * It's useful for response logging, metrics collection, or any processing that
679
- * should happen regardless of response status.
680
- *
681
- * @param context - Response context with either success data or error information
682
- * @returns Promise or void - Hook can be async or sync
683
- *
706
+ * The schema to use for validating the request headers.
684
707
  */
685
- onResponse?: (context: ResponseContext<TCallApiContext>) => Awaitable<unknown>;
708
+ headers?: StandardSchemaV1<HeadersOption | undefined> | ((headers: HeadersOption) => Awaitable<HeadersOption | undefined>);
686
709
  /**
687
- * Hook called when an HTTP error response (4xx, 5xx) is received from the API.
688
- *
689
- * This handles server-side errors where an HTTP response was successfully received
690
- * but indicates an error condition. Different from `onRequestError` which handles
691
- * network-level failures.
692
- *
693
- * @param context - Response error context with HTTP error details and response
694
- * @returns Promise or void - Hook can be async or sync
710
+ * The schema to use for validating the meta option.
695
711
  */
696
- onResponseError?: (context: ResponseErrorContext<TCallApiContext>) => Awaitable<unknown>;
712
+ meta?: StandardSchemaV1<GlobalMeta | undefined> | ((meta: GlobalMeta) => Awaitable<GlobalMeta | undefined>);
697
713
  /**
698
- * Hook called during download stream progress tracking.
699
- *
700
- * This hook is triggered when downloading data (like file downloads) and provides
701
- * progress information about the download. Useful for implementing progress bars
702
- * or download status indicators.
703
- *
704
- * @param context - Response stream context with progress event and response
705
- * @returns Promise or void - Hook can be async or sync
706
- *
714
+ * The schema to use for validating the request method.
707
715
  */
708
- onResponseStream?: (context: ResponseStreamContext<TCallApiContext>) => Awaitable<unknown>;
716
+ method?: StandardSchemaV1<MethodUnion | undefined> | ((method: MethodUnion) => Awaitable<MethodUnion | undefined>);
709
717
  /**
710
- * Hook called when a request is being retried.
711
- *
712
- * This hook is triggered before each retry attempt, providing information about
713
- * the previous failure and the current retry attempt number. Useful for implementing
714
- * custom retry logic, exponential backoff, or retry logging.
715
- *
716
- * @param context - Retry context with error details and retry attempt count
717
- * @returns Promise or void - Hook can be async or sync
718
- *
718
+ * The schema to use for validating the request url parameters.
719
719
  */
720
- onRetry?: (response: RetryContext<TCallApiContext>) => Awaitable<unknown>;
720
+ params?: StandardSchemaV1<Params | undefined> | ((params: Params) => Awaitable<Params | undefined>);
721
721
  /**
722
- * Hook called when a successful response (2xx status) is received from the API.
723
- *
724
- * This hook is triggered only for successful responses and provides access to
725
- * the parsed response data. Ideal for success logging, caching, or post-processing
726
- * of successful API responses.
727
- *
728
- * @param context - Success context with parsed response data and response object
729
- * @returns Promise or void - Hook can be async or sync
730
- *
722
+ * The schema to use for validating the request url queries.
731
723
  */
732
- onSuccess?: (context: SuccessContext<TCallApiContext>) => Awaitable<unknown>;
724
+ query?: StandardSchemaV1<Query | undefined> | ((query: Query) => Awaitable<Query | undefined>);
725
+ }
726
+ declare const routeKeyMethods: readonly ["delete", "get", "patch", "post", "put"];
727
+ type RouteKeyMethods = (typeof routeKeyMethods)[number];
728
+ type RouteKeyMethodsURLUnion = `@${RouteKeyMethods}/`;
729
+ type BaseSchemaRouteKeyPrefixes = FallBackRouteSchemaKey | RouteKeyMethodsURLUnion;
730
+ type BaseCallApiSchemaRoutes = Partial<Record<AnyString | BaseSchemaRouteKeyPrefixes, CallApiSchema>>;
731
+ type BaseCallApiSchemaAndConfig = {
732
+ config?: CallApiSchemaConfig;
733
+ routes: BaseCallApiSchemaRoutes;
734
+ };
735
+ //#endregion
736
+ //#region src/url.d.ts
737
+ type AllowedQueryParamValues = UnmaskType<boolean | number | string>;
738
+ type RecordStyleParams = UnmaskType<Record<string, AllowedQueryParamValues>>;
739
+ type TupleStyleParams = UnmaskType<AllowedQueryParamValues[]>;
740
+ type Params = UnmaskType<RecordStyleParams | TupleStyleParams>;
741
+ type Query = UnmaskType<Record<string, AllowedQueryParamValues>>;
742
+ type InitURLOrURLObject = AnyString | RouteKeyMethodsURLUnion | URL;
743
+ interface URLOptions {
733
744
  /**
734
- * Hook called when a validation error occurs.
745
+ * Base URL for all API requests. Will only be prepended to relative URLs.
735
746
  *
736
- * This hook is triggered when request or response data fails validation against
737
- * a defined schema. It provides access to the validation error details and can
738
- * be used for custom error handling, logging, or fallback behavior.
747
+ * Absolute URLs (starting with http/https) will not be prepended by the baseURL.
739
748
  *
740
- * @param context - Validation error context with error details and response (if available)
741
- * @returns Promise or void - Hook can be async or sync
749
+ * @example
750
+ * ```ts
751
+ * // Set base URL for all requests
752
+ * baseURL: "https://api.example.com/v1"
753
+ *
754
+ * // Then use relative URLs in requests
755
+ * callApi("/users") // → https://api.example.com/v1/users
756
+ * callApi("/posts/123") // → https://api.example.com/v1/posts/123
742
757
  *
758
+ * // Environment-specific base URLs
759
+ * baseURL: process.env.NODE_ENV === "production"
760
+ * ? "https://api.example.com"
761
+ * : "http://localhost:3000/api"
762
+ * ```
743
763
  */
744
- onValidationError?: (context: ValidationErrorContext<TCallApiContext>) => Awaitable<unknown>;
745
- }
746
- type HooksOrHooksArray<TCallApiContext extends NoInfer<CallApiContext> = DefaultCallApiContext> = { [Key in keyof Hooks<TCallApiContext>]: Hooks<TCallApiContext>[Key] | Array<Hooks<TCallApiContext>[Key]> };
747
- interface HookConfigOptions {
764
+ baseURL?: string;
748
765
  /**
749
- * Controls the execution mode of all composed hooks (main + plugin hooks).
750
- *
751
- * - **"parallel"**: All hooks execute simultaneously via Promise.all() for better performance
752
- * - **"sequential"**: All hooks execute one by one in registration order via await in a loop
766
+ * Resolved request URL after processing baseURL, parameters, and query strings (readonly)
753
767
  *
754
- * This affects how ALL hooks execute together, regardless of their source (main or plugin).
768
+ * This is the final URL that will be used for the HTTP request, computed from
769
+ * baseURL, initURL, params, and query parameters.
755
770
  *
756
- * @default "parallel"
757
771
  */
758
- hooksExecutionMode?: "parallel" | "sequential";
759
- }
760
- type RequestContext<TCallApiContext extends Pick<CallApiContext, "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = {
772
+ readonly fullURL?: string;
761
773
  /**
762
- * Base configuration object passed to createFetchClient.
774
+ * The original URL string passed to the callApi instance (readonly)
763
775
  *
764
- * Contains the foundational configuration that applies to all requests
765
- * made by this client instance, such as baseURL, default headers, and
766
- * global options.
767
- */
768
- baseConfig: Exclude<BaseCallApiConfig, AnyFunction>;
769
- /**
770
- * Instance-specific configuration object passed to the callApi instance.
776
+ * This preserves the original URL as provided, including any method modifiers like "@get/" or "@post/".
771
777
  *
772
- * Contains configuration specific to this particular API call, which
773
- * can override or extend the base configuration.
774
778
  */
775
- config: CallApiConfig;
779
+ readonly initURL?: string;
776
780
  /**
777
- * Merged options combining base config, instance config, and default options.
781
+ * The URL string after normalization, with method modifiers removed(readonly)
778
782
  *
779
- * This is the final resolved configuration that will be used for the request,
780
- * with proper precedence applied (instance > base > defaults).
781
- */
782
- options: CallApiExtraOptionsForHooks<TCallApiContext>;
783
- /**
784
- * Merged request object ready to be sent.
783
+ * Method modifiers like "@get/", "@post/" are stripped to create a clean URL
784
+ * for parameter substitution and final URL construction.
785
785
  *
786
- * Contains the final request configuration including URL, method, headers,
787
- * body, and other fetch options. This object can be modified in onRequest
788
- * hooks to customize the outgoing request.
789
- */
790
- request: CallApiRequestOptionsForHooks;
791
- };
792
- type ValidationErrorContext<TCallApiContext extends Pick<CallApiContext, "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = RequestContext<TCallApiContext> & {
793
- error: PossibleValidationError;
794
- response: Response | null;
795
- };
796
- type SuccessContext<TCallApiContext extends Pick<CallApiContext, "Data" | "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = RequestContext<TCallApiContext> & {
797
- data: TCallApiContext["Data"];
798
- response: Response;
799
- };
800
- type ResponseContext<TCallApiContext extends Pick<CallApiContext, "Data" | "ErrorData" | "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = RequestContext<TCallApiContext> & (Prettify<CallApiResultSuccessVariant<TCallApiContext["Data"]>> | Prettify<Extract<CallApiResultErrorVariant<TCallApiContext["ErrorData"]>, {
801
- error: PossibleHTTPError<TCallApiContext["ErrorData"]>;
802
- }>>);
803
- type RequestErrorContext<TCallApiContext extends Pick<CallApiContext, "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = RequestContext<TCallApiContext> & {
804
- error: PossibleJavaScriptError;
805
- response: null;
806
- };
807
- type ErrorContext<TCallApiContext extends Pick<CallApiContext, "ErrorData" | "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = RequestContext<TCallApiContext> & ({
808
- error: PossibleHTTPError<TCallApiContext["ErrorData"]>;
809
- response: Response;
810
- } | {
811
- error: PossibleJavaScriptOrValidationError;
812
- response: Response | null;
813
- });
814
- type ResponseErrorContext<TCallApiContext extends Pick<CallApiContext, "ErrorData" | "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = Extract<ErrorContext<TCallApiContext>, {
815
- error: PossibleHTTPError<TCallApiContext["ErrorData"]>;
816
- }> & RequestContext<TCallApiContext>;
817
- type RetryContext<TCallApiContext extends Pick<CallApiContext, "ErrorData" | "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = ErrorContext<TCallApiContext> & {
818
- retryAttemptCount: number;
819
- };
820
- type RequestStreamContext<TCallApiContext extends Pick<CallApiContext, "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = RequestContext<TCallApiContext> & {
821
- event: StreamProgressEvent;
822
- requestInstance: Request;
823
- };
824
- type ResponseStreamContext<TCallApiContext extends Pick<CallApiContext, "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = RequestContext<TCallApiContext> & {
825
- event: StreamProgressEvent;
826
- response: Response;
827
- };
828
- //#endregion
829
- //#region src/dedupe.d.ts
830
- type DedupeStrategyUnion = UnmaskType<"cancel" | "defer" | "none">;
831
- type DedupeOptionKeys = Exclude<keyof DedupeOptions, "dedupe">;
832
- type InnerDedupeOptions = { [Key in DedupeOptionKeys as RemovePrefix<"dedupe", Key>]?: DedupeOptions[Key] };
833
- type DedupeOptions = {
834
- /**
835
- * All dedupe options in a single object instead of separate properties
836
786
  */
837
- dedupe?: InnerDedupeOptions;
787
+ readonly initURLNormalized?: string;
838
788
  /**
839
- * Controls the scope of request deduplication caching.
840
- *
841
- * - `"global"`: Shares deduplication cache across all `createFetchClient` instances with the same `dedupeCacheScopeKey`.
842
- * Useful for applications with multiple API clients that should share deduplication state.
843
- * - `"local"`: Limits deduplication to requests within the same `createFetchClient` instance.
844
- * Provides better isolation and is recommended for most use cases.
845
- *
789
+ * Parameters to be substituted into URL path segments.
846
790
  *
847
- * **Real-world Scenarios:**
848
- * - Use `"global"` when you have multiple API clients (user service, auth service, etc.) that might make overlapping requests
849
- * - Use `"local"` (default) for single-purpose clients or when you want strict isolation between different parts of your app
791
+ * Supports both object-style (named parameters) and array-style (positional parameters)
792
+ * for flexible URL parameter substitution.
850
793
  *
851
794
  * @example
852
- * ```ts
853
- * // Local scope - each client has its own deduplication cache
854
- * const userClient = createFetchClient({ baseURL: "/api/users" });
855
- * const postClient = createFetchClient({ baseURL: "/api/posts" });
856
- * // These clients won't share deduplication state
795
+ * ```typescript
796
+ * // Object-style parameters (recommended)
797
+ * const namedParams: URLOptions = {
798
+ * initURL: "/users/:userId/posts/:postId",
799
+ * params: { userId: "123", postId: "456" }
800
+ * };
801
+ * // Results in: /users/123/posts/456
857
802
  *
858
- * // Global scope - share cache across related clients
859
- * const userClient = createFetchClient({
860
- * baseURL: "/api/users",
861
- * dedupeCacheScope: "global",
862
- * });
863
- * const postClient = createFetchClient({
864
- * baseURL: "/api/posts",
865
- * dedupeCacheScope: "global",
866
- * });
867
- * // These clients will share deduplication state
868
- * ```
803
+ * // Array-style parameters (positional)
804
+ * const positionalParams: URLOptions = {
805
+ * initURL: "/users/:userId/posts/:postId",
806
+ * params: ["123", "456"] // Maps in order: userId=123, postId=456
807
+ * };
808
+ * // Results in: /users/123/posts/456
869
809
  *
870
- * @default "local"
810
+ * // Single parameter
811
+ * const singleParam: URLOptions = {
812
+ * initURL: "/users/:id",
813
+ * params: { id: "user-123" }
814
+ * };
815
+ * // Results in: /users/user-123
816
+ * ```
871
817
  */
872
- dedupeCacheScope?: "global" | "local";
818
+ params?: Params;
873
819
  /**
874
- * Unique namespace for the global deduplication cache when using `dedupeCacheScope: "global"`.
875
- *
876
- * This creates logical groupings of deduplication caches. All instances with the same key
877
- * will share the same cache namespace, allowing fine-grained control over which clients
878
- * share deduplication state.
879
- *
880
- * **Best Practices:**
881
- * - Use descriptive names that reflect the logical grouping (e.g., "user-service", "analytics-api")
882
- * - Keep scope keys consistent across related API clients
883
- * - Consider using different scope keys for different environments (dev, staging, prod)
884
- * - Avoid overly broad scope keys that might cause unintended cache sharing
820
+ * Query parameters to append to the URL as search parameters.
885
821
  *
886
- * **Cache Management:**
887
- * - Each scope key maintains its own independent cache
888
- * - Caches are automatically cleaned up when no references remain
889
- * - Consider the memory implications of multiple global scopes
822
+ * These will be serialized into the URL query string using standard
823
+ * URL encoding practices.
890
824
  *
891
825
  * @example
892
- * ```ts
893
- * // Group related API clients together
894
- * const userClient = createFetchClient({
895
- * baseURL: "/api/users",
896
- * dedupeCacheScope: "global",
897
- * dedupeCacheScopeKey: "user-service"
898
- * });
899
- * const profileClient = createFetchClient({
900
- * baseURL: "/api/profiles",
901
- * dedupeCacheScope: "global",
902
- * dedupeCacheScopeKey: "user-service" // Same scope - will share cache
903
- * });
904
- *
905
- * // Separate analytics client with its own cache
906
- * const analyticsClient = createFetchClient({
907
- * baseURL: "/api/analytics",
908
- * dedupeCacheScope: "global",
909
- * dedupeCacheScopeKey: "analytics-service" // Different scope
910
- * });
826
+ * ```typescript
827
+ * // Basic query parameters
828
+ * const queryOptions: URLOptions = {
829
+ * initURL: "/users",
830
+ * query: {
831
+ * page: 1,
832
+ * limit: 10,
833
+ * search: "john doe",
834
+ * active: true
835
+ * }
836
+ * };
837
+ * // Results in: /users?page=1&limit=10&search=john%20doe&active=true
911
838
  *
912
- * // Environment-specific scoping
913
- * const apiClient = createFetchClient({
914
- * dedupeCacheScope: "global",
915
- * dedupeCacheScopeKey: `api-${process.env.NODE_ENV}` // "api-development", "api-production", etc.
916
- * });
839
+ * // Filtering and sorting
840
+ * const filterOptions: URLOptions = {
841
+ * initURL: "/products",
842
+ * query: {
843
+ * category: "electronics",
844
+ * minPrice: 100,
845
+ * maxPrice: 500,
846
+ * sortBy: "price",
847
+ * order: "asc"
848
+ * }
849
+ * };
850
+ * // Results in: /products?category=electronics&minPrice=100&maxPrice=500&sortBy=price&order=asc
917
851
  * ```
918
- *
919
- * @default "default"
920
852
  */
921
- dedupeCacheScopeKey?: "default" | AnyString | ((context: RequestContext) => string | undefined);
853
+ query?: Query;
854
+ }
855
+ //#endregion
856
+ //#region src/types/conditional-types.d.ts
857
+ /**
858
+ * @description Makes a type partial if the output type of TSchema is not provided or has undefined in the union, otherwise makes it required
859
+ */
860
+ type MakeSchemaOptionRequiredIfDefined<TSchemaOption extends CallApiSchema[keyof CallApiSchema], TObject> = undefined extends InferSchemaOutput<TSchemaOption, undefined> ? TObject : Required<TObject>;
861
+ type MergePrefixWithRouteKey<TPrefix extends string, TRouteKey extends string> = TRouteKey extends `@${infer TMethod extends RouteKeyMethods}/${infer TRestOfRoutKey}` ? `@${TMethod}/${TPrefix extends `/${infer TPrefixWithoutSlash}` ? TPrefixWithoutSlash : TPrefix}${TRestOfRoutKey}` : `${TPrefix}${TRouteKey}`;
862
+ type ApplyURLBasedConfig<TSchemaConfig extends CallApiSchemaConfig, TSchemaRouteKeys extends string> = TSchemaConfig["prefix"] extends string ? MergePrefixWithRouteKey<TSchemaConfig["prefix"], TSchemaRouteKeys> : TSchemaConfig["baseURL"] extends string ? MergePrefixWithRouteKey<TSchemaConfig["baseURL"], TSchemaRouteKeys> : TSchemaRouteKeys;
863
+ type ApplyStrictConfig<TSchemaConfig extends CallApiSchemaConfig, TSchemaRouteKeys extends string> = TSchemaConfig["strict"] extends true ? TSchemaRouteKeys :
864
+ // eslint-disable-next-line perfectionist/sort-union-types -- Don't sort union types
865
+ TSchemaRouteKeys | Exclude<InitURLOrURLObject, RouteKeyMethodsURLUnion>;
866
+ type ApplySchemaConfiguration<TSchemaConfig extends CallApiSchemaConfig, TSchemaRouteKeys extends string> = ApplyStrictConfig<TSchemaConfig, ApplyURLBasedConfig<TSchemaConfig, TSchemaRouteKeys>>;
867
+ type InferAllRouteKeys<TBaseSchemaRoutes extends BaseCallApiSchemaRoutes, TSchemaConfig extends CallApiSchemaConfig> = ApplySchemaConfiguration<TSchemaConfig, Exclude<Extract<keyof TBaseSchemaRoutes, string>, FallBackRouteSchemaKey>>;
868
+ type InferInitURL<TBaseSchemaRoutes extends BaseCallApiSchemaRoutes, TSchemaConfig extends CallApiSchemaConfig> = keyof TBaseSchemaRoutes extends never ? InitURLOrURLObject : InferAllRouteKeys<TBaseSchemaRoutes, TSchemaConfig>;
869
+ 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;
870
+ type GetCurrentRouteSchema<TBaseSchemaRoutes extends BaseCallApiSchemaRoutes, TCurrentRouteSchemaKey extends string, TComputedFallBackRouteSchema = TBaseSchemaRoutes[FallBackRouteSchemaKey], TComputedCurrentRouteSchema = TBaseSchemaRoutes[TCurrentRouteSchemaKey], TComputedRouteSchema extends CallApiSchema = NonNullable<Omit<TComputedFallBackRouteSchema, keyof TComputedCurrentRouteSchema> & TComputedCurrentRouteSchema>> = TComputedRouteSchema extends CallApiSchema ? Writeable<TComputedRouteSchema, "deep"> : CallApiSchema;
871
+ type JsonPrimitive = boolean | number | string | null | undefined;
872
+ type SerializableObject = Record<PropertyKey, unknown>;
873
+ type SerializableArray = Array<JsonPrimitive | SerializableObject> | ReadonlyArray<JsonPrimitive | SerializableObject>;
874
+ type Body = UnmaskType<Exclude<RequestInit["body"], undefined> | SerializableArray | SerializableObject>;
875
+ type InferBodyOption<TSchema extends CallApiSchema> = MakeSchemaOptionRequiredIfDefined<TSchema["body"], {
876
+ /**
877
+ * Body of the request, can be a object or any other supported body type.
878
+ */
879
+ body?: InferSchemaOutput<TSchema["body"], Body>;
880
+ }>;
881
+ type MethodUnion = UnmaskType<"CONNECT" | "DELETE" | "GET" | "HEAD" | "OPTIONS" | "PATCH" | "POST" | "PUT" | "TRACE" | AnyString>;
882
+ type InferMethodFromURL<TInitURL> = string extends TInitURL ? MethodUnion : TInitURL extends `@${infer TMethod extends RouteKeyMethods}/${string}` ? Uppercase<TMethod> : MethodUnion;
883
+ type InferMethodOption<TSchema extends CallApiSchema, TInitURL> = MakeSchemaOptionRequiredIfDefined<TSchema["method"], {
922
884
  /**
923
- * Custom key generator for request deduplication.
924
- *
925
- * Override the default key generation strategy to control exactly which requests
926
- * are considered duplicates. The default key combines URL, method, body, and
927
- * relevant headers (excluding volatile ones like 'Date', 'Authorization', etc.).
928
- *
929
- * **Default Key Generation:**
930
- * The auto-generated key includes:
931
- * - Full request URL (including query parameters)
932
- * - HTTP method (GET, POST, etc.)
933
- * - Request body (for POST/PUT/PATCH requests)
934
- * - Stable headers (excludes Date, Authorization, User-Agent, etc.)
935
- *
936
- * **Custom Key Best Practices:**
937
- * - Include only the parts of the request that should affect deduplication
938
- * - Avoid including volatile data (timestamps, random IDs, etc.)
939
- * - Consider performance - simpler keys are faster to compute and compare
940
- * - Ensure keys are deterministic for the same logical request
941
- * - Use consistent key formats across your application
885
+ * HTTP method for the request.
886
+ * @default "GET"
887
+ */
888
+ method?: InferSchemaOutput<TSchema["method"], InferMethodFromURL<TInitURL>>;
889
+ }>;
890
+ type HeadersOption = UnmaskType<Headers | Record<"Authorization", CommonAuthorizationHeaders | undefined> | Record<"Content-Type", CommonContentTypes | undefined> | Record<CommonRequestHeaders, string | undefined> | Record<string, string | undefined> | Array<[string, string]>>;
891
+ type InferHeadersOption<TSchema extends CallApiSchema> = MakeSchemaOptionRequiredIfDefined<TSchema["headers"], {
892
+ /**
893
+ * Headers to be used in the request.
894
+ */
895
+ headers?: InferSchemaOutput<TSchema["headers"], HeadersOption> | ((context: {
896
+ baseHeaders: Extract<HeadersOption, Record<string, unknown>>;
897
+ }) => InferSchemaOutput<TSchema["headers"], HeadersOption>);
898
+ }>;
899
+ type InferRequestOptions<TSchema extends CallApiSchema, TInitURL extends InferInitURL<BaseCallApiSchemaRoutes, CallApiSchemaConfig>> = InferBodyOption<TSchema> & InferHeadersOption<TSchema> & InferMethodOption<TSchema, TInitURL>;
900
+ type InferMetaOption<TSchema extends CallApiSchema, TCallApiContext extends CallApiContext> = MakeSchemaOptionRequiredIfDefined<TSchema["meta"], {
901
+ /**
902
+ * - An optional field you can fill with additional information,
903
+ * to associate with the request, typically used for logging or tracing.
942
904
  *
943
- * **Performance Considerations:**
944
- * - Function-based keys are computed on every request - keep them lightweight
945
- * - String keys are fastest but least flexible
946
- * - Consider caching expensive key computations if needed
905
+ * - A good use case for this, would be to use the info to handle specific cases in any of the shared interceptors.
947
906
  *
948
907
  * @example
949
908
  * ```ts
950
- * import { callApi } from "@zayne-labs/callapi";
951
- *
952
- * // Simple static key - useful for singleton requests
953
- * const config = callApi("/api/config", {
954
- * dedupeKey: "app-config",
955
- * dedupeStrategy: "defer" // Share the same config across all requests
956
- * });
957
- *
958
- * // URL and method only - ignore headers and body
959
- * const userData = callApi("/api/user/123", {
960
- * dedupeKey: (context) => `${context.options.method}:${context.options.fullURL}`
961
- * });
962
- *
963
- * // Include specific headers in deduplication
964
- * const apiCall = callApi("/api/data", {
965
- * dedupeKey: (context) => {
966
- * const authHeader = context.request.headers.get("Authorization");
967
- * return `${context.options.fullURL}-${authHeader}`;
968
- * }
969
- * });
970
- *
971
- * // User-specific deduplication
972
- * const userSpecificCall = callApi("/api/dashboard", {
973
- * dedupeKey: (context) => {
974
- * const userId = context.options.fullURL.match(/user\/(\d+)/)?.[1];
975
- * return `dashboard-${userId}`;
976
- * }
909
+ * const callMainApi = callApi.create({
910
+ * baseURL: "https://main-api.com",
911
+ * onResponseError: ({ response, options }) => {
912
+ * if (options.meta?.userId) {
913
+ * console.error(`User ${options.meta.userId} made an error`);
914
+ * }
915
+ * },
977
916
  * });
978
917
  *
979
- * // Ignore certain query parameters
980
- * const searchCall = callApi("/api/search?q=test&timestamp=123456", {
981
- * dedupeKey: (context) => {
982
- * const url = new URL(context.options.fullURL);
983
- * url.searchParams.delete("timestamp"); // Remove volatile param
984
- * return `search:${url.toString()}`;
985
- * }
918
+ * const response = await callMainApi({
919
+ * url: "https://example.com/api/data",
920
+ * meta: { userId: "123" },
986
921
  * });
987
922
  * ```
988
- *
989
- * @default Auto-generated from request details
990
923
  */
991
- dedupeKey?: string | ((context: RequestContext) => string | undefined);
924
+ meta?: InferSchemaOutput<TSchema["meta"], TCallApiContext["Meta"]>;
925
+ }>;
926
+ type InferAuthOption<TSchema extends CallApiSchema> = MakeSchemaOptionRequiredIfDefined<TSchema["auth"], {
992
927
  /**
993
- * Strategy for handling duplicate requests. Can be a static string or callback function.
928
+ * Automatically add an Authorization header value.
994
929
  *
995
- * **Available Strategies:**
996
- * - `"cancel"`: Cancel previous request when new one starts (good for search)
997
- * - `"defer"`: Share response between duplicate requests (good for config loading)
998
- * - `"none"`: No deduplication, all requests execute independently
930
+ * Supports multiple authentication patterns:
931
+ * - String: Direct authorization header value
932
+ * - Auth object: Structured authentication configuration
999
933
  *
1000
934
  * @example
1001
935
  * ```ts
1002
- * // Static strategies
1003
- * const searchClient = createFetchClient({
1004
- * dedupeStrategy: "cancel" // Cancel previous searches
936
+ * // Bearer auth
937
+ * const response = await callMainApi({
938
+ * url: "https://example.com/api/data",
939
+ * auth: "123456",
1005
940
  * });
1006
941
  *
1007
- * const configClient = createFetchClient({
1008
- * dedupeStrategy: "defer" // Share config across components
1009
- * });
942
+ * // Bearer auth
943
+ * const response = await callMainApi({
944
+ * url: "https://example.com/api/data",
945
+ * auth: {
946
+ * type: "Bearer",
947
+ * value: "123456",
948
+ * },
949
+ })
1010
950
  *
1011
- * // Dynamic strategy based on request
1012
- * const smartClient = createFetchClient({
1013
- * dedupeStrategy: (context) => {
1014
- * return context.options.method === "GET" ? "defer" : "cancel";
1015
- * }
951
+ * // Token auth
952
+ * const response = await callMainApi({
953
+ * url: "https://example.com/api/data",
954
+ * auth: {
955
+ * type: "Token",
956
+ * value: "123456",
957
+ * },
1016
958
  * });
1017
959
  *
1018
- * // Search-as-you-type with cancel strategy
1019
- * const handleSearch = async (query: string) => {
1020
- * try {
1021
- * const { data } = await callApi("/api/search", {
1022
- * method: "POST",
1023
- * body: { query },
1024
- * dedupeStrategy: "cancel",
1025
- * dedupeKey: "search" // Cancel previous searches, only latest one goes through
1026
- * });
1027
- *
1028
- * updateSearchResults(data);
1029
- * } catch (error) {
1030
- * if (error.name === "AbortError") {
1031
- * // Previous search cancelled - (expected behavior)
1032
- * return;
1033
- * }
1034
- * console.error("Search failed:", error);
1035
- * }
1036
- * };
960
+ * // Basic auth
961
+ * const response = await callMainApi({
962
+ * url: "https://example.com/api/data",
963
+ * auth: {
964
+ * type: "Basic",
965
+ * username: "username",
966
+ * password: "password",
967
+ * },
968
+ * });
1037
969
  *
1038
970
  * ```
1039
- *
1040
- * @default "cancel"
1041
971
  */
1042
- dedupeStrategy?: DedupeStrategyUnion | ((context: RequestContext) => DedupeStrategyUnion);
972
+ auth?: InferSchemaOutput<TSchema["auth"], AuthOption>;
973
+ }>;
974
+ type InferQueryOption<TSchema extends CallApiSchema> = MakeSchemaOptionRequiredIfDefined<TSchema["query"], {
975
+ /**
976
+ * Parameters to be appended to the URL (i.e: /:id)
977
+ */
978
+ query?: InferSchemaOutput<TSchema["query"], Query>;
979
+ }>;
980
+ type EmptyString = "";
981
+ type EmptyTuple = readonly [];
982
+ type StringTuple = readonly string[];
983
+ type PossibleParamNamePatterns = `${string}:${string}` | `${string}{${string}}${"" | AnyString}`;
984
+ 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;
985
+ type ConvertParamNamesToRecord<TParamNames extends StringTuple> = Prettify<TParamNames extends (readonly [infer TFirstParamName extends string, ...infer TRemainingParamNames extends StringTuple]) ? Record<TFirstParamName, AllowedQueryParamValues> & ConvertParamNamesToRecord<TRemainingParamNames> : NonNullable<unknown>>;
986
+ type ConvertParamNamesToTuple<TParamNames extends StringTuple> = TParamNames extends readonly [string, ...infer TRemainingParamNames extends StringTuple] ? [AllowedQueryParamValues, ...ConvertParamNamesToTuple<TRemainingParamNames>] : [];
987
+ type InferParamsFromRoute<TCurrentRoute> = ExtractRouteParamNames<TCurrentRoute> extends StringTuple ? ExtractRouteParamNames<TCurrentRoute> extends EmptyTuple ? Params : ConvertParamNamesToRecord<ExtractRouteParamNames<TCurrentRoute>> | ConvertParamNamesToTuple<ExtractRouteParamNames<TCurrentRoute>> : Params;
988
+ type MakeParamsOptionRequired<TParamsSchemaOption extends CallApiSchema["params"], TBaseSchemaRoutes extends BaseCallApiSchemaRoutes, TCurrentRouteSchemaKey extends string, TObject> = MakeSchemaOptionRequiredIfDefined<TParamsSchemaOption, Params extends InferParamsFromRoute<TCurrentRouteSchemaKey> ? TObject : TCurrentRouteSchemaKey extends Extract<keyof TBaseSchemaRoutes, TCurrentRouteSchemaKey> ? undefined extends InferSchemaOutput<TParamsSchemaOption, null> ? TObject : Required<TObject> : TObject>;
989
+ type InferParamsOption<TSchema extends CallApiSchema, TBaseSchemaRoutes extends BaseCallApiSchemaRoutes, TCurrentRouteSchemaKey extends string> = MakeParamsOptionRequired<TSchema["params"], TBaseSchemaRoutes, TCurrentRouteSchemaKey, {
990
+ /**
991
+ * Parameters to be appended to the URL (i.e: /:id)
992
+ */
993
+ params?: InferSchemaOutput<TSchema["params"], InferParamsFromRoute<TCurrentRouteSchemaKey>>;
994
+ }>;
995
+ type InferExtraOptions<TSchema extends CallApiSchema, TBaseSchemaRoutes extends BaseCallApiSchemaRoutes, TCurrentRouteSchemaKey extends string, TCallApiContext extends CallApiContext> = InferAuthOption<TSchema> & InferMetaOption<TSchema, TCallApiContext> & InferParamsOption<TSchema, TBaseSchemaRoutes, TCurrentRouteSchemaKey> & InferQueryOption<TSchema>;
996
+ type ResultModeOption<TErrorData, TResultMode extends ResultModeType> = TErrorData extends false ? {
997
+ resultMode: "onlyData";
998
+ } : TErrorData extends false | undefined ? {
999
+ resultMode?: "onlyData";
1000
+ } : {
1001
+ resultMode?: TResultMode;
1002
+ };
1003
+ type ThrowOnErrorUnion = boolean;
1004
+ type ThrowOnErrorType<TErrorData, TThrowOnError extends ThrowOnErrorUnion> = TThrowOnError | ((context: ErrorContext<{
1005
+ ErrorData: TErrorData;
1006
+ }>) => TThrowOnError);
1007
+ type ThrowOnErrorOption<TErrorData, TThrowOnError extends ThrowOnErrorUnion> = TErrorData extends false ? {
1008
+ throwOnError: true;
1009
+ } : TErrorData extends false | undefined ? {
1010
+ throwOnError?: true;
1011
+ } : {
1012
+ throwOnError?: ThrowOnErrorType<TErrorData, TThrowOnError>;
1043
1013
  };
1044
1014
  //#endregion
1045
1015
  //#region src/retry.d.ts
@@ -1190,7 +1160,7 @@ type CallApiRequestOptions = Prettify<{
1190
1160
  type CallApiRequestOptionsForHooks = Omit<CallApiRequestOptions, "headers"> & {
1191
1161
  headers: Record<string, string | undefined>;
1192
1162
  };
1193
- type SharedExtraOptions<TCallApiContext extends CallApiContext = DefaultCallApiContext, TData = DefaultDataType, TErrorData = DefaultDataType, TResultMode extends ResultModeType = ResultModeType, TThrowOnError extends ThrowOnErrorUnion = DefaultThrowOnError, TResponseType extends ResponseTypeType = ResponseTypeType, TPluginArray extends CallApiPlugin[] = DefaultPluginArray, TComputedMergedPluginExtraOptions = Partial<InferPluginExtraOptions<TPluginArray> & TCallApiContext["InferredExtraOptions"]>, TComputedCallApiContext extends CallApiContext = OverrideCallApiContext<TCallApiContext, {
1163
+ type SharedExtraOptions<TCallApiContext extends CallApiContext = DefaultCallApiContext, TData = DefaultDataType, TErrorData = DefaultDataType, TResultMode extends ResultModeType = ResultModeType, TThrowOnError extends ThrowOnErrorUnion = DefaultThrowOnError, TResponseType extends ResponseTypeType = ResponseTypeType, TPluginArray extends CallApiPlugin[] = DefaultPluginArray, TComputedMergedPluginExtraOptions = Partial<InferPluginExtraOptions<TPluginArray> & InferSchemaOutput<TCallApiContext["InferredExtraOptions"], TCallApiContext["InferredExtraOptions"]>>, TComputedCallApiContext extends CallApiContext = OverrideCallApiContext<TCallApiContext, {
1194
1164
  Data: TData;
1195
1165
  ErrorData: TErrorData;
1196
1166
  InferredExtraOptions: TComputedMergedPluginExtraOptions;
@@ -1783,9 +1753,81 @@ type ResultModeUnion = keyof ResultModeMap;
1783
1753
  type ResultModeType = ResultModePlaceholder | ResultModeUnion;
1784
1754
  type InferCallApiResult<TData, TErrorData, TResultMode extends ResultModeType, TThrowOnError extends ThrowOnErrorUnion, TComputedResultModeMapWithException extends ResultModeMap<TData, TErrorData, true> = ResultModeMap<TData, TErrorData, true>, TComputedResultModeMapWithoutException extends ResultModeMap<TData, TErrorData, TThrowOnError> = ResultModeMap<TData, TErrorData, TThrowOnError>> = TErrorData extends false ? TComputedResultModeMapWithException["onlyData"] : TErrorData extends false | undefined ? TComputedResultModeMapWithException["onlyData"] : ResultModePlaceholder extends TResultMode ? TComputedResultModeMapWithoutException["all"] : TResultMode extends ResultModeUnion ? TComputedResultModeMapWithoutException[TResultMode] : never;
1785
1755
  //#endregion
1756
+ //#region src/plugins.d.ts
1757
+ type PluginSetupContext<TCallApiContext extends CallApiContext = DefaultCallApiContext> = RequestContext<TCallApiContext> & {
1758
+ initURL: string;
1759
+ };
1760
+ type PluginInitResult<TCallApiContext extends CallApiContext = DefaultCallApiContext> = Partial<Omit<PluginSetupContext<TCallApiContext>, "initURL" | "request"> & {
1761
+ initURL: InitURLOrURLObject;
1762
+ request: CallApiRequestOptions;
1763
+ }>;
1764
+ type GetDefaultDataTypeForPlugins<TData> = DefaultDataType extends TData ? never : TData;
1765
+ type PluginHooks<TCallApiContext extends CallApiContext = DefaultCallApiContext> = HooksOrHooksArray<OverrideCallApiContext<TCallApiContext, {
1766
+ Data: GetDefaultDataTypeForPlugins<TCallApiContext["Data"]>;
1767
+ ErrorData: GetDefaultDataTypeForPlugins<TCallApiContext["ErrorData"]>;
1768
+ }>>;
1769
+ type PluginMiddlewares<TCallApiContext extends CallApiContext = DefaultCallApiContext> = Middlewares<OverrideCallApiContext<TCallApiContext, {
1770
+ Data: GetDefaultDataTypeForPlugins<TCallApiContext["Data"]>;
1771
+ ErrorData: GetDefaultDataTypeForPlugins<TCallApiContext["ErrorData"]>;
1772
+ }>>;
1773
+ interface CallApiPlugin<TCallApiContext extends CallApiContext = DefaultCallApiContext> {
1774
+ /**
1775
+ * Defines additional options that can be passed to callApi
1776
+ */
1777
+ defineExtraOptions?: () => TCallApiContext["InferredExtraOptions"];
1778
+ /**
1779
+ * A description for the plugin
1780
+ */
1781
+ description?: string;
1782
+ /**
1783
+ * Hooks for the plugin
1784
+ */
1785
+ hooks?: PluginHooks<TCallApiContext> | ((context: PluginSetupContext<TCallApiContext>) => Awaitable<PluginHooks<TCallApiContext>>);
1786
+ /**
1787
+ * A unique id for the plugin
1788
+ */
1789
+ id: string;
1790
+ /**
1791
+ * Middlewares that for the plugin
1792
+ */
1793
+ middlewares?: PluginMiddlewares<TCallApiContext> | ((context: PluginSetupContext<TCallApiContext>) => Awaitable<PluginMiddlewares<TCallApiContext>>);
1794
+ /**
1795
+ * A name for the plugin
1796
+ */
1797
+ name: string;
1798
+ /**
1799
+ * Base schema for the client.
1800
+ */
1801
+ schema?: BaseCallApiSchemaAndConfig;
1802
+ /**
1803
+ * A function that will be called when the plugin is initialized. This will be called before the any of the other internal functions.
1804
+ */
1805
+ setup?: (context: PluginSetupContext<TCallApiContext>) => Awaitable<PluginInitResult<TCallApiContext>> | Awaitable<void>;
1806
+ /**
1807
+ * A version for the plugin
1808
+ */
1809
+ version?: string;
1810
+ }
1811
+ type InferPluginExtraOptions<TPluginArray extends CallApiPlugin[]> = UnionToIntersection<TPluginArray extends Array<infer TPlugin> ? TPlugin extends CallApiPlugin ? TPlugin["defineExtraOptions"] extends AnyFunction<infer TResult> ? InferSchemaOutput<TResult, TResult> : never : never : never>;
1812
+ //#endregion
1813
+ //#region src/types/default-types.d.ts
1814
+ type DefaultDataType = unknown;
1815
+ type DefaultPluginArray = CallApiPlugin[];
1816
+ type DefaultThrowOnError = boolean;
1817
+ type DefaultMetaObject = Record<string, unknown>;
1818
+ type DefaultCallApiContext = Prettify<Required<Omit<CallApiContext, "Meta">> & {
1819
+ Meta: GlobalMeta;
1820
+ }>;
1821
+ //#endregion
1786
1822
  //#region src/createFetchClient.d.ts
1787
1823
  declare const createFetchClientWithContext: <TOuterCallApiContext extends CallApiContext = DefaultCallApiContext>() => <TBaseCallApiContext extends CallApiContext = TOuterCallApiContext, TBaseData = TBaseCallApiContext["Data"], TBaseErrorData = TBaseCallApiContext["ErrorData"], TBaseResultMode extends ResultModeType = (TBaseCallApiContext["ResultMode"] extends ResultModeType ? TBaseCallApiContext["ResultMode"] : ResultModeType), TBaseThrowOnError extends ThrowOnErrorUnion = boolean, TBaseResponseType extends ResponseTypeType = ResponseTypeType, const TBaseSchemaAndConfig extends BaseCallApiSchemaAndConfig = BaseCallApiSchemaAndConfig, const TBasePluginArray extends CallApiPlugin[] = DefaultPluginArray, TComputedBaseSchemaConfig extends CallApiSchemaConfig = GetBaseSchemaConfig<TBaseSchemaAndConfig>, TComputedBaseSchemaRoutes extends BaseCallApiSchemaRoutes = GetBaseSchemaRoutes<TBaseSchemaAndConfig>>(initBaseConfig?: BaseCallApiConfig<TBaseCallApiContext, TBaseData, TBaseErrorData, TBaseResultMode, TBaseThrowOnError, TBaseResponseType, TBaseSchemaAndConfig, TBasePluginArray>) => <TData = TBaseData, TErrorData = TBaseErrorData, TResultMode extends ResultModeType = TBaseResultMode, TCallApiContext extends CallApiContext = TBaseCallApiContext, TThrowOnError extends ThrowOnErrorUnion = TBaseThrowOnError, TResponseType extends ResponseTypeType = TBaseResponseType, const TSchemaConfig extends CallApiSchemaConfig = TComputedBaseSchemaConfig, TInitURL extends InferInitURL<TComputedBaseSchemaRoutes, TSchemaConfig> = InferInitURL<TComputedBaseSchemaRoutes, TSchemaConfig>, TCurrentRouteSchemaKey extends GetCurrentRouteSchemaKey<TSchemaConfig, TInitURL> = GetCurrentRouteSchemaKey<TSchemaConfig, TInitURL>, const TSchema extends CallApiSchema = GetCurrentRouteSchema<TComputedBaseSchemaRoutes, TCurrentRouteSchemaKey>, const TPluginArray extends CallApiPlugin[] = TBasePluginArray, TComputedData = InferSchemaOutput<TSchema["data"], GetResponseType<TData, TResponseType>>, TComputedErrorData = InferSchemaOutput<TSchema["errorData"], GetResponseType<TErrorData, TResponseType>>, TComputedResult = CallApiResult<TComputedData, TComputedErrorData, TResultMode, TThrowOnError>>(initURL: TInitURL, initConfig?: CallApiConfig<TCallApiContext, TComputedData, TComputedErrorData, TResultMode, TThrowOnError, TResponseType, TComputedBaseSchemaRoutes, TSchema, TComputedBaseSchemaConfig, TSchemaConfig, TInitURL, TCurrentRouteSchemaKey, TBasePluginArray, TPluginArray>) => Promise<TComputedResult>;
1788
- declare const createFetchClient: <TBaseCallApiContext extends CallApiContext = DefaultCallApiContext, TBaseData = TBaseCallApiContext["Data"], TBaseErrorData = TBaseCallApiContext["ErrorData"], TBaseResultMode extends ResultModeType = (TBaseCallApiContext["ResultMode"] extends ResultModeType ? TBaseCallApiContext["ResultMode"] : ResultModeType), TBaseThrowOnError extends ThrowOnErrorUnion = boolean, TBaseResponseType extends ResponseTypeType = ResponseTypeType, const TBaseSchemaAndConfig extends BaseCallApiSchemaAndConfig = BaseCallApiSchemaAndConfig, const TBasePluginArray extends CallApiPlugin[] = DefaultPluginArray, TComputedBaseSchemaConfig extends CallApiSchemaConfig = Writeable<NonNullable<TBaseSchemaAndConfig["config"]>, "deep">, TComputedBaseSchemaRoutes extends BaseCallApiSchemaRoutes = Writeable<TBaseSchemaAndConfig["routes"], "deep">>(initBaseConfig?: BaseCallApiConfig<TBaseCallApiContext, TBaseData, TBaseErrorData, TBaseResultMode, TBaseThrowOnError, TBaseResponseType, TBaseSchemaAndConfig, TBasePluginArray>) => <TData = TBaseData, TErrorData = TBaseErrorData, TResultMode extends ResultModeType = TBaseResultMode, TCallApiContext extends CallApiContext = TBaseCallApiContext, TThrowOnError extends ThrowOnErrorUnion = TBaseThrowOnError, TResponseType extends ResponseTypeType = TBaseResponseType, const TSchemaConfig extends CallApiSchemaConfig = TComputedBaseSchemaConfig, TInitURL extends InferInitURL<TComputedBaseSchemaRoutes, TSchemaConfig> = InferInitURL<TComputedBaseSchemaRoutes, TSchemaConfig>, TCurrentRouteSchemaKey extends GetCurrentRouteSchemaKey<TSchemaConfig, TInitURL> = GetCurrentRouteSchemaKey<TSchemaConfig, TInitURL>, const TSchema extends CallApiSchema = GetCurrentRouteSchema<TComputedBaseSchemaRoutes, TCurrentRouteSchemaKey, TComputedBaseSchemaRoutes["@default"], TComputedBaseSchemaRoutes[TCurrentRouteSchemaKey], NonNullable<Omit<TComputedBaseSchemaRoutes["@default"], keyof TComputedBaseSchemaRoutes[TCurrentRouteSchemaKey]> & TComputedBaseSchemaRoutes[TCurrentRouteSchemaKey]>>, const TPluginArray extends CallApiPlugin[] = TBasePluginArray, TComputedData = InferSchemaResult<TSchema["data"], GetResponseType<TData, TResponseType, ResponseTypeMap<TData>>, "infer-output">, TComputedErrorData = InferSchemaResult<TSchema["errorData"], GetResponseType<TErrorData, TResponseType, ResponseTypeMap<TErrorData>>, "infer-output">, TComputedResult = InferCallApiResult<TComputedData, TComputedErrorData, TResultMode, TThrowOnError, {
1824
+ declare const createFetchClient: <TBaseCallApiContext extends CallApiContext = {
1825
+ Data: DefaultDataType;
1826
+ ErrorData: DefaultDataType;
1827
+ InferredExtraOptions: unknown;
1828
+ ResultMode: ResultModeType;
1829
+ Meta: GlobalMeta;
1830
+ }, TBaseData = TBaseCallApiContext["Data"], TBaseErrorData = TBaseCallApiContext["ErrorData"], TBaseResultMode extends ResultModeType = (TBaseCallApiContext["ResultMode"] extends ResultModeType ? TBaseCallApiContext["ResultMode"] : ResultModeType), TBaseThrowOnError extends ThrowOnErrorUnion = boolean, TBaseResponseType extends ResponseTypeType = ResponseTypeType, const TBaseSchemaAndConfig extends BaseCallApiSchemaAndConfig = BaseCallApiSchemaAndConfig, const TBasePluginArray extends CallApiPlugin[] = DefaultPluginArray, TComputedBaseSchemaConfig extends CallApiSchemaConfig = Writeable<NonNullable<TBaseSchemaAndConfig["config"]>, "deep">, TComputedBaseSchemaRoutes extends BaseCallApiSchemaRoutes = Writeable<TBaseSchemaAndConfig["routes"], "deep">>(initBaseConfig?: BaseCallApiConfig<TBaseCallApiContext, TBaseData, TBaseErrorData, TBaseResultMode, TBaseThrowOnError, TBaseResponseType, TBaseSchemaAndConfig, TBasePluginArray>) => <TData = TBaseData, TErrorData = TBaseErrorData, TResultMode extends ResultModeType = TBaseResultMode, TCallApiContext extends CallApiContext = TBaseCallApiContext, TThrowOnError extends ThrowOnErrorUnion = TBaseThrowOnError, TResponseType extends ResponseTypeType = TBaseResponseType, const TSchemaConfig extends CallApiSchemaConfig = TComputedBaseSchemaConfig, TInitURL extends InferInitURL<TComputedBaseSchemaRoutes, TSchemaConfig> = InferInitURL<TComputedBaseSchemaRoutes, TSchemaConfig>, TCurrentRouteSchemaKey extends GetCurrentRouteSchemaKey<TSchemaConfig, TInitURL> = GetCurrentRouteSchemaKey<TSchemaConfig, TInitURL>, const TSchema extends CallApiSchema = GetCurrentRouteSchema<TComputedBaseSchemaRoutes, TCurrentRouteSchemaKey, TComputedBaseSchemaRoutes["@default"], TComputedBaseSchemaRoutes[TCurrentRouteSchemaKey], NonNullable<Omit<TComputedBaseSchemaRoutes["@default"], keyof TComputedBaseSchemaRoutes[TCurrentRouteSchemaKey]> & TComputedBaseSchemaRoutes[TCurrentRouteSchemaKey]>>, const TPluginArray extends CallApiPlugin[] = TBasePluginArray, TComputedData = InferSchemaResult<TSchema["data"], GetResponseType<TData, TResponseType, ResponseTypeMap<TData>>, "infer-output">, TComputedErrorData = InferSchemaResult<TSchema["errorData"], GetResponseType<TErrorData, TResponseType, ResponseTypeMap<TErrorData>>, "infer-output">, TComputedResult = InferCallApiResult<TComputedData, TComputedErrorData, TResultMode, TThrowOnError, {
1789
1831
  all: CallApiResultSuccessVariant<TComputedData>;
1790
1832
  onlyData: NoInferUnMasked<TComputedData>;
1791
1833
  onlyResponse: Response;
@@ -1799,7 +1841,13 @@ declare const createFetchClient: <TBaseCallApiContext extends CallApiContext = D
1799
1841
  onlyResponse: (TThrowOnError extends true ? CallApiResultSuccessVariant<TComputedData> : CallApiResultSuccessOrErrorVariant<TComputedData, TComputedErrorData>)["response"];
1800
1842
  withoutResponse: DistributiveOmit<TThrowOnError extends true ? CallApiResultSuccessVariant<TComputedData> : CallApiResultSuccessOrErrorVariant<TComputedData, TComputedErrorData>, "response"> extends infer T ? { [Key in keyof T]: T[Key] } : never;
1801
1843
  }>>(initURL: TInitURL, initConfig?: CallApiConfig<TCallApiContext, TComputedData, TComputedErrorData, TResultMode, TThrowOnError, TResponseType, TComputedBaseSchemaRoutes, TSchema, TComputedBaseSchemaConfig, TSchemaConfig, TInitURL, TCurrentRouteSchemaKey, TBasePluginArray, TPluginArray>) => Promise<TComputedResult>;
1802
- declare const callApi: <TData = unknown, TErrorData = unknown, TResultMode extends ResultModeType = ResultModeType, TCallApiContext extends CallApiContext = DefaultCallApiContext, TThrowOnError extends ThrowOnErrorUnion = boolean, TResponseType extends ResponseTypeType = ResponseTypeType, const TSchemaConfig extends CallApiSchemaConfig = CallApiSchemaConfig, TInitURL extends ApplyStrictConfig<TSchemaConfig, ApplyURLBasedConfig<TSchemaConfig, AnyString | "@delete/" | "@get/" | "@patch/" | "@post/" | "@put/">> = ApplyStrictConfig<TSchemaConfig, ApplyURLBasedConfig<TSchemaConfig, AnyString | "@delete/" | "@get/" | "@patch/" | "@post/" | "@put/">>, TCurrentRouteSchemaKey extends GetCurrentRouteSchemaKey<TSchemaConfig, TInitURL> = GetCurrentRouteSchemaKey<TSchemaConfig, TInitURL>, const TSchema extends CallApiSchema = GetCurrentRouteSchema<{
1844
+ declare const callApi: <TData = unknown, TErrorData = unknown, TResultMode extends ResultModeType = ResultModeType, TCallApiContext extends CallApiContext = {
1845
+ Data: DefaultDataType;
1846
+ ErrorData: DefaultDataType;
1847
+ InferredExtraOptions: unknown;
1848
+ ResultMode: ResultModeType;
1849
+ Meta: GlobalMeta;
1850
+ }, TThrowOnError extends ThrowOnErrorUnion = boolean, TResponseType extends ResponseTypeType = ResponseTypeType, const TSchemaConfig extends CallApiSchemaConfig = CallApiSchemaConfig, TInitURL extends ApplyStrictConfig<TSchemaConfig, ApplyURLBasedConfig<TSchemaConfig, AnyString | "@delete/" | "@get/" | "@patch/" | "@post/" | "@put/">> = ApplyStrictConfig<TSchemaConfig, ApplyURLBasedConfig<TSchemaConfig, AnyString | "@delete/" | "@get/" | "@patch/" | "@post/" | "@put/">>, TCurrentRouteSchemaKey extends GetCurrentRouteSchemaKey<TSchemaConfig, TInitURL> = GetCurrentRouteSchemaKey<TSchemaConfig, TInitURL>, const TSchema extends CallApiSchema = GetCurrentRouteSchema<{
1803
1851
  [x: AnyString]: CallApiSchema | undefined;
1804
1852
  "@default"?: CallApiSchema | undefined;
1805
1853
  "@delete/"?: CallApiSchema | undefined;
@@ -1854,5 +1902,5 @@ declare const callApi: <TData = unknown, TErrorData = unknown, TResultMode exten
1854
1902
  "@put/"?: CallApiSchema | undefined;
1855
1903
  }, TSchema, CallApiSchemaConfig, TSchemaConfig, TInitURL, TCurrentRouteSchemaKey, DefaultPluginArray, TPluginArray>) => Promise<TComputedResult>;
1856
1904
  //#endregion
1857
- export { Writeable as $, ErrorContext as A, CallApiPlugin as B, InferExtendSchemaContext as C, ValidationError as D, HTTPError as E, ResponseContext as F, BaseCallApiSchemaRoutes as G, PluginSetupContext as H, ResponseErrorContext as I, CallApiSchemaConfig as J, BaseSchemaRouteKeyPrefixes as K, ResponseStreamContext as L, HooksOrHooksArray as M, RequestContext as N, RetryOptions as O, RequestStreamContext as P, Satisfies as Q, SuccessContext as R, GetExtendSchemaConfigContext as S, Register as T, InferParamsFromRoute as U, PluginHooks as V, URLOptions as W, InferSchemaOutput as X, InferSchemaInput as Y, AnyFunction as Z, CallApiExtraOptionsForHooks as _, CallApiResultSuccessOrErrorVariant as a, CallApiRequestOptionsForHooks as b, PossibleJavaScriptError as c, ResponseTypeType as d, ResultModeType as f, CallApiExtraOptions as g, CallApiConfig as h, CallApiResultErrorVariant as i, Hooks as j, DedupeOptions as k, PossibleJavaScriptOrValidationError as l, BaseCallApiExtraOptions as m, createFetchClient as n, CallApiResultSuccessVariant as o, BaseCallApiConfig as p, CallApiSchema as q, createFetchClientWithContext as r, PossibleHTTPError as s, callApi as t, PossibleValidationError as u, CallApiParameters as v, InstanceContext as w, CallApiResultLoose as x, CallApiRequestOptions as y, DefaultCallApiContext as z };
1858
- //# sourceMappingURL=index-YnlEWnbR.d.ts.map
1905
+ export { ResponseStreamContext as $, Register as A, InferSchemaInput as B, CallApiParameters as C, GetExtendSchemaConfigContext as D, CallApiResultLoose as E, URLOptions as F, DedupeOptions as G, FetchImpl as H, BaseCallApiSchemaRoutes as I, HooksOrHooksArray as J, ErrorContext as K, BaseSchemaRouteKeyPrefixes as L, ValidationError as M, RetryOptions as N, InferExtendSchemaContext as O, InferParamsFromRoute as P, ResponseErrorContext as Q, CallApiSchema as R, CallApiExtraOptionsForHooks as S, CallApiRequestOptionsForHooks as T, FetchMiddlewareContext as U, InferSchemaOutput as V, Middlewares as W, RequestStreamContext as X, RequestContext as Y, ResponseContext as Z, ResultModeType as _, CallApiPlugin as a, CallApiConfig as b, PluginSetupContext as c, CallApiResultSuccessVariant as d, SuccessContext as et, PossibleHTTPError as f, ResponseTypeType as g, PossibleValidationError as h, DefaultCallApiContext as i, HTTPError as j, InstanceContext as k, CallApiResultErrorVariant as l, PossibleJavaScriptOrValidationError as m, createFetchClient as n, Satisfies as nt, PluginHooks as o, PossibleJavaScriptError as p, Hooks as q, createFetchClientWithContext as r, Writeable as rt, PluginMiddlewares as s, callApi as t, AnyFunction as tt, CallApiResultSuccessOrErrorVariant as u, BaseCallApiConfig as v, CallApiRequestOptions as w, CallApiExtraOptions as x, BaseCallApiExtraOptions as y, CallApiSchemaConfig as z };
1906
+ //# sourceMappingURL=index-CGn8h_Ai.d.ts.map