@zayne-labs/callapi-plugins 4.0.45 → 5.0.0

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.
@@ -1,6 +1,7 @@
1
+ import * as _zayne_labs_callapi_utils0 from "@zayne-labs/callapi/utils";
1
2
  import { AnyFunction } from "@zayne-labs/toolkit-type-helpers";
2
3
 
3
- //#region ../callapi/dist/index-N6YsZaGN.d.ts
4
+ //#region ../callapi/dist/default-types-BUYTny8V.d.ts
4
5
  //#region src/constants/common.d.ts
5
6
  declare const fetchSpecificKeys: readonly (keyof RequestInit | "duplex")[]; //#endregion
6
7
  //#region src/types/type-helpers.d.ts
@@ -74,979 +75,1017 @@ type CustomAuth = {
74
75
  value: PossibleAuthValueOrGetter;
75
76
  };
76
77
  type AuthOption = PossibleAuthValueOrGetter | BearerAuth | TokenAuth | BasicAuth | CustomAuth; //#endregion
77
- //#region src/stream.d.ts
78
- type StreamProgressEvent = {
78
+ //#region src/constants/validation.d.ts
79
+ declare const fallBackRouteSchemaKey = "@default";
80
+ type FallBackRouteSchemaKey = typeof fallBackRouteSchemaKey; //#endregion
81
+ //#region src/types/standard-schema.d.ts
82
+ /**
83
+ * The Standard Schema interface.
84
+ * @see https://github.com/standard-schema/standard-schema
85
+ */
86
+ /** The Standard Typed interface. This is a base type extended by other specs. */
87
+ interface StandardTypedV1<Input = unknown, Output = Input> {
88
+ /** The Standard properties. */
89
+ readonly "~standard": StandardTypedV1.Props<Input, Output>;
90
+ }
91
+ declare namespace StandardTypedV1 {
92
+ /** The Standard Typed properties interface. */
93
+ interface Props<Input = unknown, Output = Input> {
94
+ /** Inferred types associated with the schema. */
95
+ readonly types?: Types<Input, Output> | undefined;
96
+ /** The vendor name of the schema library. */
97
+ readonly vendor: string;
98
+ /** The version number of the standard. */
99
+ readonly version: 1;
100
+ }
101
+ /** The Standard Typed types interface. */
102
+ interface Types<Input = unknown, Output = Input> {
103
+ /** The input type of the schema. */
104
+ readonly input: Input;
105
+ /** The output type of the schema. */
106
+ readonly output: Output;
107
+ }
108
+ /** Infers the input type of a Standard Typed. */
109
+ type InferInput<Schema extends StandardTypedV1> = NonNullable<Schema["~standard"]["types"]>["input"];
110
+ /** Infers the output type of a Standard Typed. */
111
+ type InferOutput<Schema extends StandardTypedV1> = NonNullable<Schema["~standard"]["types"]>["output"];
112
+ }
113
+ /** The Standard Schema interface. */
114
+ interface StandardSchemaV1<Input = unknown, Output = Input> {
115
+ /** The Standard Schema properties. */
116
+ readonly "~standard": StandardSchemaV1.Props<Input, Output>;
117
+ }
118
+ declare namespace StandardSchemaV1 {
119
+ /** The Standard Schema properties interface. */
120
+ interface Props<Input = unknown, Output = Input> extends StandardTypedV1.Props<Input, Output> {
121
+ /** Validates unknown input values. */
122
+ readonly validate: (value: unknown, options?: StandardSchemaV1.Options) => Promise<Result<Output>> | Result<Output>;
123
+ }
124
+ /** The result interface of the validate function. */
125
+ type Result<Output> = FailureResult | SuccessResult<Output>;
126
+ /** The result interface if validation succeeds. */
127
+ interface SuccessResult<Output> {
128
+ /** A falsy value for `issues` indicates success. */
129
+ readonly issues?: undefined;
130
+ /** The typed output value. */
131
+ readonly value: Output;
132
+ }
133
+ interface Options {
134
+ /** Explicit support for additional vendor-specific parameters, if needed. */
135
+ readonly libraryOptions?: Record<string, unknown> | undefined;
136
+ }
137
+ /** The result interface if validation fails. */
138
+ interface FailureResult {
139
+ /** The issues of failed validation. */
140
+ readonly issues: readonly Issue[];
141
+ }
142
+ /** The issue interface of the failure output. */
143
+ interface Issue {
144
+ /** The error message of the issue. */
145
+ readonly message: string;
146
+ /** The path of the issue, if any. */
147
+ readonly path?: ReadonlyArray<PathSegment | PropertyKey> | undefined;
148
+ }
149
+ /** The path segment interface of the issue. */
150
+ interface PathSegment {
151
+ /** The key representing a path segment. */
152
+ readonly key: PropertyKey;
153
+ }
154
+ /** The Standard types interface. */
155
+ type Types<Input = unknown, Output = Input> = StandardTypedV1.Types<Input, Output>;
156
+ /** Infers the input type of a Standard. */
157
+ type InferInput<Schema extends StandardTypedV1> = StandardTypedV1.InferInput<Schema>;
158
+ /** Infers the output type of a Standard. */
159
+ type InferOutput<Schema extends StandardTypedV1> = StandardTypedV1.InferOutput<Schema>;
160
+ } //#endregion
161
+ //#region src/validation.d.ts
162
+ type ResultVariant = "infer-input" | "infer-output";
163
+ 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$1<infer TResult> ? Awaited<TResult> : TFallbackResult;
164
+ type InferSchemaOutput<TSchema, TFallbackResult = unknown> = InferSchemaResult<TSchema, TFallbackResult, "infer-output">;
165
+ type BooleanObject = { [Key in keyof CallApiSchema]: boolean };
166
+ interface CallApiSchemaConfig {
79
167
  /**
80
- * Current chunk of data being streamed
168
+ * The base url of the schema. By default it's the baseURL of the callApi instance.
81
169
  */
82
- chunk: Uint8Array;
170
+ baseURL?: "" | AnyString;
83
171
  /**
84
- * Progress in percentage
172
+ * Disables runtime validation for the schema.
85
173
  */
86
- progress: number;
174
+ disableRuntimeValidation?: boolean | BooleanObject;
87
175
  /**
88
- * Total size of data in bytes
176
+ * If `true`, the original input value will be used instead of the transformed/validated output.
177
+ *
178
+ * When true, the original input is returned unchanged after validation, ignoring any schema-level
179
+ * transformations such as type coercion, default values, or field mapping. Only the validation
180
+ * step is executed; the resulting value is discarded in favor of the raw input.
89
181
  */
90
- totalBytes: number;
182
+ disableRuntimeValidationTransform?: boolean | BooleanObject;
91
183
  /**
92
- * Amount of data transferred so far
184
+ * Optional url prefix that will be substituted for the `baseURL` of the schemaConfig at runtime.
185
+ *
186
+ * Enables a short, stable prefix for routes while keeping the full `baseURL` centralized in config.
187
+ * Keeps route definitions concise and shields them from changes to the underlying base URL.
93
188
  */
94
- transferredBytes: number;
95
- }; //#endregion
96
- //#region src/hooks.d.ts
97
- interface Hooks<TCallApiContext extends CallApiContext = DefaultCallApiContext> {
189
+ prefix?: "" | AnyString;
98
190
  /**
99
- * Hook called when any error occurs within the request/response lifecycle.
191
+ * Controls the strictness of API route validation.
100
192
  *
101
- * This is a unified error handler that catches both request errors (network failures,
102
- * timeouts, etc.) and response errors (HTTP error status codes). It's essentially
103
- * a combination of `onRequestError` and `onResponseError` hooks.
193
+ * When true:
194
+ * - Only routes explicitly defined in the schema will be considered valid to typescript and the runtime.
195
+ * - Attempting to call routes not defined in the schema will result in both type errors and runtime validation errors.
196
+ * - Useful for ensuring API calls conform exactly to your schema definition
104
197
  *
105
- * @param context - Error context containing error details, request info, and response (if available)
106
- * @returns Promise or void - Hook can be async or sync
198
+ * When false or undefined (default):
199
+ * - All routes will be allowed, whether they are defined in the schema or not
107
200
  */
108
- onError?: (context: ErrorContext<TCallApiContext>) => Awaitable<unknown>;
201
+ strict?: boolean;
202
+ }
203
+ type CallApiSchemaType<TInput> = StandardSchemaV1<TInput | undefined> | ((value: TInput) => Awaitable<TInput | undefined>);
204
+ interface CallApiSchema {
205
+ auth?: CallApiSchemaType<AuthOption>;
109
206
  /**
110
- * Hook called before the HTTP request is sent and before any internal processing of the request object begins.
111
- *
112
- * This is the ideal place to modify request headers, add authentication,
113
- * implement request logging, or perform any setup before the network call.
114
- *
115
- * @param context - Request context with mutable request object and configuration
116
- * @returns Promise or void - Hook can be async or sync
117
- *
207
+ * The schema to use for validating the request body.
118
208
  */
119
- onRequest?: (context: RequestContext<TCallApiContext>) => Awaitable<unknown>;
209
+ body?: CallApiSchemaType<Body>;
120
210
  /**
121
- * Hook called when an error occurs during the fetch request itself.
122
- *
123
- * This handles network-level errors like connection failures, timeouts,
124
- * DNS resolution errors, or other issues that prevent getting an HTTP response.
125
- * Note that HTTP error status codes (4xx, 5xx) are handled by `onResponseError`.
126
- *
127
- * @param context - Request error context with error details and null response
128
- * @returns Promise or void - Hook can be async or sync
211
+ * The schema to use for validating the response data.
129
212
  */
130
- onRequestError?: (context: RequestErrorContext<TCallApiContext>) => Awaitable<unknown>;
213
+ data?: CallApiSchemaType<unknown>;
131
214
  /**
132
- * Hook called just before the HTTP request is sent and after the request has been processed.
133
- *
134
- * @param context - Request context with mutable request object and configuration
215
+ * The schema to use for validating the response error data.
135
216
  */
136
- onRequestReady?: (context: RequestContext<TCallApiContext>) => Awaitable<unknown>;
217
+ errorData?: CallApiSchemaType<unknown>;
137
218
  /**
138
- * Hook called during upload stream progress tracking.
219
+ * The schema to use for validating the request headers.
220
+ */
221
+ headers?: CallApiSchemaType<HeadersOption>;
222
+ /**
223
+ * The schema to use for validating the meta option.
224
+ */
225
+ meta?: CallApiSchemaType<GlobalMeta>;
226
+ /**
227
+ * The schema to use for validating the request method.
228
+ */
229
+ method?: CallApiSchemaType<MethodUnion>;
230
+ /**
231
+ * The schema to use for validating the request url parameters.
232
+ */
233
+ params?: CallApiSchemaType<Params>;
234
+ /**
235
+ * The schema to use for validating the request url queries.
236
+ */
237
+ query?: CallApiSchemaType<Query>;
238
+ }
239
+ declare const routeKeyMethods: readonly ["delete", "get", "patch", "post", "put"];
240
+ type RouteKeyMethods = (typeof routeKeyMethods)[number];
241
+ type RouteKeyMethodsURLUnion = `${AtSymbol}${RouteKeyMethods}/`;
242
+ type BaseSchemaRouteKeyPrefixes = FallBackRouteSchemaKey | RouteKeyMethodsURLUnion;
243
+ type BaseCallApiSchemaRoutes = Partial<Record<AnyString | BaseSchemaRouteKeyPrefixes, CallApiSchema>>;
244
+ type BaseCallApiSchemaAndConfig = {
245
+ config?: CallApiSchemaConfig;
246
+ routes: BaseCallApiSchemaRoutes;
247
+ }; //#endregion
248
+ //#region src/url.d.ts
249
+ declare const atSymbol = "@";
250
+ type AtSymbol = typeof atSymbol;
251
+ type AllowedQueryParamValues = UnmaskType<boolean | number | string>;
252
+ type RecordStyleParams = UnmaskType<Record<string, AllowedQueryParamValues>>;
253
+ type TupleStyleParams = UnmaskType<AllowedQueryParamValues[]>;
254
+ type Params = UnmaskType<RecordStyleParams | TupleStyleParams>;
255
+ type Query = UnmaskType<Record<string, AllowedQueryParamValues> | URLSearchParams>;
256
+ type InitURLOrURLObject = AnyString | RouteKeyMethodsURLUnion | URL;
257
+ interface URLOptions {
258
+ /**
259
+ * Base URL for all API requests. Will only be prepended to relative URLs.
139
260
  *
140
- * This hook is triggered when uploading data (like file uploads) and provides
141
- * progress information about the upload. Useful for implementing progress bars
142
- * or upload status indicators.
261
+ * Absolute URLs (starting with http/https) will not be prepended by the baseURL.
143
262
  *
144
- * @param context - Request stream context with progress event and request instance
145
- * @returns Promise or void - Hook can be async or sync
263
+ * @example
264
+ * ```ts
265
+ * // Set base URL for all requests
266
+ * baseURL: "https://api.example.com/v1"
146
267
  *
268
+ * // Then use relative URLs in requests
269
+ * callApi("/users") // → https://api.example.com/v1/users
270
+ * callApi("/posts/123") // → https://api.example.com/v1/posts/123
271
+ *
272
+ * // Environment-specific base URLs
273
+ * baseURL: process.env.NODE_ENV === "production"
274
+ * ? "https://api.example.com"
275
+ * : "http://localhost:3000/api"
276
+ * ```
147
277
  */
148
- onRequestStream?: (context: RequestStreamContext<TCallApiContext>) => Awaitable<unknown>;
278
+ baseURL?: string;
149
279
  /**
150
- * Hook called when any HTTP response is received from the API.
151
- *
152
- * This hook is triggered for both successful (2xx) and error (4xx, 5xx) responses.
153
- * It's useful for response logging, metrics collection, or any processing that
154
- * should happen regardless of response status.
280
+ * Resolved request URL after processing baseURL, parameters, and query strings (readonly)
155
281
  *
156
- * @param context - Response context with either success data or error information
157
- * @returns Promise or void - Hook can be async or sync
282
+ * This is the final URL that will be used for the HTTP request, computed from
283
+ * baseURL, initURL, params, and query parameters.
158
284
  *
159
285
  */
160
- onResponse?: (context: ResponseContext<TCallApiContext>) => Awaitable<unknown>;
286
+ readonly fullURL?: string;
161
287
  /**
162
- * Hook called when an HTTP error response (4xx, 5xx) is received from the API.
288
+ * The original URL string passed to the callApi instance (readonly)
163
289
  *
164
- * This handles server-side errors where an HTTP response was successfully received
165
- * but indicates an error condition. Different from `onRequestError` which handles
166
- * network-level failures.
290
+ * This preserves the original URL as provided, including any method modifiers like "@get/" or "@post/".
167
291
  *
168
- * @param context - Response error context with HTTP error details and response
169
- * @returns Promise or void - Hook can be async or sync
170
292
  */
171
- onResponseError?: (context: ResponseErrorContext<TCallApiContext>) => Awaitable<unknown>;
293
+ readonly initURL?: string;
172
294
  /**
173
- * Hook called during download stream progress tracking.
295
+ * The URL string after normalization, with method modifiers removed(readonly)
174
296
  *
175
- * This hook is triggered when downloading data (like file downloads) and provides
176
- * progress information about the download. Useful for implementing progress bars
177
- * or download status indicators.
178
- *
179
- * @param context - Response stream context with progress event and response
180
- * @returns Promise or void - Hook can be async or sync
181
- *
182
- */
183
- onResponseStream?: (context: ResponseStreamContext<TCallApiContext>) => Awaitable<unknown>;
184
- /**
185
- * Hook called when a request is being retried.
186
- *
187
- * This hook is triggered before each retry attempt, providing information about
188
- * the previous failure and the current retry attempt number. Useful for implementing
189
- * custom retry logic, exponential backoff, or retry logging.
190
- *
191
- * @param context - Retry context with error details and retry attempt count
192
- * @returns Promise or void - Hook can be async or sync
297
+ * Method modifiers like "@get/", "@post/" are stripped to create a clean URL
298
+ * for parameter substitution and final URL construction.
193
299
  *
194
300
  */
195
- onRetry?: (response: RetryContext<TCallApiContext>) => Awaitable<unknown>;
301
+ readonly initURLNormalized?: string;
196
302
  /**
197
- * Hook called when a successful response (2xx status) is received from the API.
198
- *
199
- * This hook is triggered only for successful responses and provides access to
200
- * the parsed response data. Ideal for success logging, caching, or post-processing
201
- * of successful API responses.
202
- *
203
- * @param context - Success context with parsed response data and response object
204
- * @returns Promise or void - Hook can be async or sync
303
+ * Parameters to be substituted into URL path segments.
205
304
  *
206
- */
207
- onSuccess?: (context: SuccessContext<TCallApiContext>) => Awaitable<unknown>;
208
- /**
209
- * Hook called when a validation error occurs.
305
+ * Supports both object-style (named parameters) and array-style (positional parameters)
306
+ * for flexible URL parameter substitution.
210
307
  *
211
- * This hook is triggered when request or response data fails validation against
212
- * a defined schema. It provides access to the validation error details and can
213
- * be used for custom error handling, logging, or fallback behavior.
308
+ * @example
309
+ * ```typescript
310
+ * // Object-style parameters (recommended)
311
+ * const namedParams: URLOptions = {
312
+ * initURL: "/users/:userId/posts/:postId",
313
+ * params: { userId: "123", postId: "456" }
314
+ * };
315
+ * // Results in: /users/123/posts/456
214
316
  *
215
- * @param context - Validation error context with error details and response (if available)
216
- * @returns Promise or void - Hook can be async or sync
317
+ * // Array-style parameters (positional)
318
+ * const positionalParams: URLOptions = {
319
+ * initURL: "/users/:userId/posts/:postId",
320
+ * params: ["123", "456"] // Maps in order: userId=123, postId=456
321
+ * };
322
+ * // Results in: /users/123/posts/456
217
323
  *
324
+ * // Single parameter
325
+ * const singleParam: URLOptions = {
326
+ * initURL: "/users/:id",
327
+ * params: { id: "user-123" }
328
+ * };
329
+ * // Results in: /users/user-123
330
+ * ```
218
331
  */
219
- onValidationError?: (context: ValidationErrorContext<TCallApiContext>) => Awaitable<unknown>;
220
- }
221
- type HooksOrHooksArray<TCallApiContext extends NoInfer<CallApiContext> = DefaultCallApiContext> = { [Key in keyof Hooks<TCallApiContext>]: Hooks<TCallApiContext>[Key] | Array<Hooks<TCallApiContext>[Key]> };
222
- interface HookConfigOptions {
332
+ params?: Params;
223
333
  /**
224
- * Controls the execution mode of all composed hooks (main + plugin hooks).
225
- *
226
- * - **"parallel"**: All hooks execute simultaneously via Promise.all() for better performance
227
- * - **"sequential"**: All hooks execute one by one in registration order via await in a loop
334
+ * Query parameters to append to the URL as search parameters.
228
335
  *
229
- * This affects how ALL hooks execute together, regardless of their source (main or plugin).
336
+ * These will be serialized into the URL query string using standard
337
+ * URL encoding practices.
230
338
  *
231
- * @default "parallel"
232
- */
233
- hooksExecutionMode?: "parallel" | "sequential";
234
- }
235
- type RequestContext<TCallApiContext extends Pick<CallApiContext, "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = {
236
- /**
237
- * Base configuration object passed to createFetchClient.
339
+ * @example
340
+ * ```typescript
341
+ * // Basic query parameters
342
+ * const queryOptions: URLOptions = {
343
+ * initURL: "/users",
344
+ * query: {
345
+ * page: 1,
346
+ * limit: 10,
347
+ * search: "john doe",
348
+ * active: true
349
+ * }
350
+ * };
351
+ * // Results in: /users?page=1&limit=10&search=john%20doe&active=true
238
352
  *
239
- * Contains the foundational configuration that applies to all requests
240
- * made by this client instance, such as baseURL, default headers, and
241
- * global options.
353
+ * // Filtering and sorting
354
+ * const filterOptions: URLOptions = {
355
+ * initURL: "/products",
356
+ * query: {
357
+ * category: "electronics",
358
+ * minPrice: 100,
359
+ * maxPrice: 500,
360
+ * sortBy: "price",
361
+ * order: "asc"
362
+ * }
363
+ * };
364
+ * // Results in: /products?category=electronics&minPrice=100&maxPrice=500&sortBy=price&order=asc
365
+ * ```
242
366
  */
243
- baseConfig: Exclude<BaseCallApiConfig, AnyFunction$1>;
367
+ query?: Query;
368
+ } //#endregion
369
+ //#region src/types/conditional-types.d.ts
370
+ /**
371
+ * @description Makes a type partial if the output type of TSchema is not provided or has undefined in the union, otherwise makes it required
372
+ */
373
+ type MakeSchemaOptionRequiredIfDefined<TSchemaOption extends CallApiSchema[keyof CallApiSchema], TObject> = undefined extends InferSchemaOutput<TSchemaOption, undefined> ? TObject : Required<TObject>;
374
+ type MergeBaseWithRouteKey<TBaseURLOrPrefix extends string | undefined, TRouteKey extends string> = TBaseURLOrPrefix extends string ? TRouteKey extends `${AtSymbol}${infer TMethod extends RouteKeyMethods}/${infer TRestOfRoutKey}` ? `${AtSymbol}${TMethod}/${RemoveLeadingSlash<RemoveTrailingSlash<TBaseURLOrPrefix>>}/${RemoveLeadingSlash<TRestOfRoutKey>}` : `${TBaseURLOrPrefix}${TRouteKey}` : TRouteKey;
375
+ type ApplyURLBasedConfig<TSchemaConfig extends CallApiSchemaConfig, TSchemaRouteKeys extends string> = TSchemaConfig["prefix"] extends string ? MergeBaseWithRouteKey<TSchemaConfig["prefix"], TSchemaRouteKeys> : TSchemaConfig["baseURL"] extends string ? MergeBaseWithRouteKey<TSchemaConfig["baseURL"], TSchemaRouteKeys> : TSchemaRouteKeys;
376
+ type ApplyStrictConfig<TSchemaConfig extends CallApiSchemaConfig, TSchemaRouteKeys extends string> = TSchemaConfig["strict"] extends true ? TSchemaRouteKeys : // eslint-disable-next-line perfectionist/sort-union-types -- Don't sort union types
377
+ TSchemaRouteKeys | Exclude<InitURLOrURLObject, RouteKeyMethodsURLUnion>;
378
+ type ApplySchemaConfiguration<TSchemaConfig extends CallApiSchemaConfig, TSchemaRouteKeys extends string> = ApplyStrictConfig<TSchemaConfig, ApplyURLBasedConfig<TSchemaConfig, TSchemaRouteKeys>>;
379
+ type InferAllMainRoutes<TBaseSchemaRoutes extends BaseCallApiSchemaRoutes> = Omit<TBaseSchemaRoutes, FallBackRouteSchemaKey>;
380
+ type InferAllMainRouteKeys<TBaseSchemaRoutes extends BaseCallApiSchemaRoutes, TSchemaConfig extends CallApiSchemaConfig> = ApplySchemaConfiguration<TSchemaConfig, Extract<keyof InferAllMainRoutes<TBaseSchemaRoutes>, string>>;
381
+ type InferInitURL<TBaseSchemaRoutes extends BaseCallApiSchemaRoutes, TSchemaConfig extends CallApiSchemaConfig> = keyof TBaseSchemaRoutes extends never ? InitURLOrURLObject : InferAllMainRouteKeys<TBaseSchemaRoutes, TSchemaConfig>;
382
+ 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;
383
+ type JsonPrimitive = boolean | number | string | null | undefined;
384
+ type SerializableObject = Record<PropertyKey, unknown>;
385
+ type SerializableArray = Array<JsonPrimitive | SerializableObject> | ReadonlyArray<JsonPrimitive | SerializableObject>;
386
+ type Body = UnmaskType<Exclude<RequestInit["body"], undefined> | SerializableArray | SerializableObject>;
387
+ type InferBodyOption<TSchema extends CallApiSchema> = MakeSchemaOptionRequiredIfDefined<TSchema["body"], {
244
388
  /**
245
- * Instance-specific configuration object passed to the callApi instance.
246
- *
247
- * Contains configuration specific to this particular API call, which
248
- * can override or extend the base configuration.
389
+ * Body of the request, can be a object or any other supported body type.
249
390
  */
250
- config: CallApiConfig;
391
+ body?: InferSchemaOutput<TSchema["body"], Body>;
392
+ }>;
393
+ type MethodUnion = UnmaskType<"CONNECT" | "DELETE" | "GET" | "HEAD" | "OPTIONS" | "PATCH" | "POST" | "PUT" | "TRACE" | AnyString>;
394
+ type ExtractMethodFromURL<TInitURL> = string extends TInitURL ? MethodUnion : TInitURL extends `${AtSymbol}${infer TMethod extends RouteKeyMethods}/${string}` ? Uppercase<TMethod> : MethodUnion;
395
+ type InferMethodOption<TSchema extends CallApiSchema, TInitURL extends InitURLOrURLObject> = MakeSchemaOptionRequiredIfDefined<TSchema["method"], {
251
396
  /**
252
- * Merged options combining base config, instance config, and default options.
253
- *
254
- * This is the final resolved configuration that will be used for the request,
255
- * with proper precedence applied (instance > base > defaults).
397
+ * HTTP method for the request.
398
+ * @default "GET"
256
399
  */
257
- options: CallApiExtraOptionsForHooks<TCallApiContext>;
400
+ method?: InferSchemaOutput<TSchema["method"], ExtractMethodFromURL<TInitURL>>;
401
+ }>;
402
+ type HeadersOption = UnmaskType<Headers | Record<"Authorization", CommonAuthorizationHeaders | undefined> | Record<"Content-Type", CommonContentTypes | undefined> | Record<CommonRequestHeaders, string | undefined> | Record<string, string | undefined> | Array<[string, string]>>;
403
+ type InferHeadersOption<TSchema extends CallApiSchema> = MakeSchemaOptionRequiredIfDefined<TSchema["headers"], {
258
404
  /**
259
- * Merged request object ready to be sent.
260
- *
261
- * Contains the final request configuration including URL, method, headers,
262
- * body, and other fetch options. This object can be modified in onRequest
263
- * hooks to customize the outgoing request.
405
+ * Headers to be used in the request.
264
406
  */
265
- request: CallApiRequestOptionsForHooks;
266
- };
267
- type ValidationErrorContext<TCallApiContext extends Pick<CallApiContext, "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = DistributiveOmit<Extract<CallApiResultErrorVariant<unknown>, {
268
- error: PossibleValidationError;
269
- }>, "data"> & RequestContext<TCallApiContext>;
270
- type SuccessContext<TCallApiContext extends Pick<CallApiContext, "Data" | "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = DistributiveOmit<CallApiResultSuccessVariant<TCallApiContext["Data"]>, "error"> & RequestContext<TCallApiContext>;
271
- type ResponseContext<TCallApiContext extends Pick<CallApiContext, "Data" | "ErrorData" | "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = RequestContext<TCallApiContext> & (Prettify<CallApiResultSuccessVariant<TCallApiContext["Data"]>> | Prettify<Extract<CallApiResultErrorVariant<TCallApiContext["ErrorData"]>, {
272
- error: PossibleHTTPError<TCallApiContext["ErrorData"]>;
273
- }>>);
274
- type RequestErrorContext<TCallApiContext extends Pick<CallApiContext, "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = DistributiveOmit<Extract<CallApiResultErrorVariant<unknown>, {
275
- error: PossibleJavaScriptError;
276
- }>, "data"> & RequestContext<TCallApiContext>;
277
- type ErrorContext<TCallApiContext extends Pick<CallApiContext, "ErrorData" | "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = DistributiveOmit<CallApiResultErrorVariant<TCallApiContext["ErrorData"]>, "data"> & RequestContext<TCallApiContext>;
278
- type ResponseErrorContext<TCallApiContext extends Pick<CallApiContext, "ErrorData" | "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = Extract<ErrorContext<TCallApiContext>, {
279
- error: PossibleHTTPError<TCallApiContext["ErrorData"]>;
280
- }> & RequestContext<TCallApiContext>;
281
- type RetryContext<TCallApiContext extends Pick<CallApiContext, "ErrorData" | "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = ErrorContext<TCallApiContext> & {
282
- retryAttemptCount: number;
283
- };
284
- type RequestStreamContext<TCallApiContext extends Pick<CallApiContext, "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = RequestContext<TCallApiContext> & {
285
- event: StreamProgressEvent;
286
- requestInstance: Request;
287
- };
288
- type ResponseStreamContext<TCallApiContext extends Pick<CallApiContext, "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = RequestContext<TCallApiContext> & {
289
- event: StreamProgressEvent;
290
- response: Response;
291
- }; //#endregion
292
- //#region src/dedupe.d.ts
293
- type DedupeStrategyUnion = UnmaskType<"cancel" | "defer" | "none">;
294
- type DedupeOptions = {
407
+ headers?: InferSchemaOutput<TSchema["headers"], HeadersOption> | ((context: {
408
+ baseHeaders: Extract<HeadersOption, Record<string, unknown>>;
409
+ }) => InferSchemaOutput<TSchema["headers"], HeadersOption>);
410
+ }>;
411
+ type InferRequestOptions<TSchema extends CallApiSchema, TInitURL extends InferInitURL<BaseCallApiSchemaRoutes, CallApiSchemaConfig>> = InferBodyOption<TSchema> & InferHeadersOption<TSchema> & InferMethodOption<TSchema, TInitURL>;
412
+ type InferMetaOption<TSchema extends CallApiSchema, TCallApiContext extends CallApiContext> = MakeSchemaOptionRequiredIfDefined<TSchema["meta"], {
295
413
  /**
296
- * Controls the scope of request deduplication caching.
297
- *
298
- * - `"global"`: Shares deduplication cache across all `createFetchClient` instances with the same `dedupeCacheScopeKey`.
299
- * Useful for applications with multiple API clients that should share deduplication state.
300
- * - `"local"`: Limits deduplication to requests within the same `createFetchClient` instance.
301
- * Provides better isolation and is recommended for most use cases.
302
- *
414
+ * - An optional field you can fill with additional information,
415
+ * to associate with the request, typically used for logging or tracing.
303
416
  *
304
- * **Real-world Scenarios:**
305
- * - Use `"global"` when you have multiple API clients (user service, auth service, etc.) that might make overlapping requests
306
- * - Use `"local"` (default) for single-purpose clients or when you want strict isolation between different parts of your app
417
+ * - A good use case for this, would be to use the info to handle specific cases in any of the shared interceptors.
307
418
  *
308
419
  * @example
309
420
  * ```ts
310
- * // Local scope - each client has its own deduplication cache
311
- * const userClient = createFetchClient({ baseURL: "/api/users" });
312
- * const postClient = createFetchClient({ baseURL: "/api/posts" });
313
- * // These clients won't share deduplication state
314
- *
315
- * // Global scope - share cache across related clients
316
- * const userClient = createFetchClient({
317
- * baseURL: "/api/users",
318
- * dedupeCacheScope: "global",
421
+ * const callMainApi = callApi.create({
422
+ * baseURL: "https://main-api.com",
423
+ * onResponseError: ({ response, options }) => {
424
+ * if (options.meta?.userId) {
425
+ * console.error(`User ${options.meta.userId} made an error`);
426
+ * }
427
+ * },
319
428
  * });
320
- * const postClient = createFetchClient({
321
- * baseURL: "/api/posts",
322
- * dedupeCacheScope: "global",
429
+ *
430
+ * const response = await callMainApi({
431
+ * url: "https://example.com/api/data",
432
+ * meta: { userId: "123" },
323
433
  * });
324
- * // These clients will share deduplication state
325
434
  * ```
326
- *
327
- * @default "local"
328
435
  */
329
- dedupeCacheScope?: "global" | "local";
436
+ meta?: InferSchemaOutput<TSchema["meta"], TCallApiContext["Meta"]>;
437
+ }>;
438
+ type InferAuthOption<TSchema extends CallApiSchema> = MakeSchemaOptionRequiredIfDefined<TSchema["auth"], {
330
439
  /**
331
- * Unique namespace for the global deduplication cache when using `dedupeCacheScope: "global"`.
332
- *
333
- * This creates logical groupings of deduplication caches. All instances with the same key
334
- * will share the same cache namespace, allowing fine-grained control over which clients
335
- * share deduplication state.
336
- *
337
- * **Best Practices:**
338
- * - Use descriptive names that reflect the logical grouping (e.g., "user-service", "analytics-api")
339
- * - Keep scope keys consistent across related API clients
340
- * - Consider using different scope keys for different environments (dev, staging, prod)
341
- * - Avoid overly broad scope keys that might cause unintended cache sharing
440
+ * Automatically add an Authorization header value.
342
441
  *
343
- * **Cache Management:**
344
- * - Each scope key maintains its own independent cache
345
- * - Caches are automatically cleaned up when no references remain
346
- * - Consider the memory implications of multiple global scopes
442
+ * Supports multiple authentication patterns:
443
+ * - String: Direct authorization header value
444
+ * - Auth object: Structured authentication configuration
347
445
  *
348
446
  * @example
349
447
  * ```ts
350
- * // Group related API clients together
351
- * const userClient = createFetchClient({
352
- * baseURL: "/api/users",
353
- * dedupeCacheScope: "global",
354
- * dedupeCacheScopeKey: "user-service"
448
+ * // Bearer auth
449
+ * const response = await callMainApi({
450
+ * url: "https://example.com/api/data",
451
+ * auth: "123456",
355
452
  * });
356
- * const profileClient = createFetchClient({
357
- * baseURL: "/api/profiles",
358
- * dedupeCacheScope: "global",
359
- * dedupeCacheScopeKey: "user-service" // Same scope - will share cache
360
- * });
361
- *
362
- * // Separate analytics client with its own cache
363
- * const analyticsClient = createFetchClient({
364
- * baseURL: "/api/analytics",
365
- * dedupeCacheScope: "global",
366
- * dedupeCacheScopeKey: "analytics-service" // Different scope
367
- * });
368
- *
369
- * // Environment-specific scoping
370
- * const apiClient = createFetchClient({
371
- * dedupeCacheScope: "global",
372
- * dedupeCacheScopeKey: `api-${process.env.NODE_ENV}` // "api-development", "api-production", etc.
373
- * });
374
- * ```
375
- *
376
- * @default "default"
377
- */
378
- dedupeCacheScopeKey?: "default" | AnyString | ((context: RequestContext) => string | undefined);
379
- /**
380
- * Custom key generator for request deduplication.
381
- *
382
- * Override the default key generation strategy to control exactly which requests
383
- * are considered duplicates. The default key combines URL, method, body, and
384
- * relevant headers (excluding volatile ones like 'Date', 'Authorization', etc.).
385
- *
386
- * **Default Key Generation:**
387
- * The auto-generated key includes:
388
- * - Full request URL (including query parameters)
389
- * - HTTP method (GET, POST, etc.)
390
- * - Request body (for POST/PUT/PATCH requests)
391
- * - Stable headers (excludes Date, Authorization, User-Agent, etc.)
392
- *
393
- * **Custom Key Best Practices:**
394
- * - Include only the parts of the request that should affect deduplication
395
- * - Avoid including volatile data (timestamps, random IDs, etc.)
396
- * - Consider performance - simpler keys are faster to compute and compare
397
- * - Ensure keys are deterministic for the same logical request
398
- * - Use consistent key formats across your application
399
- *
400
- * **Performance Considerations:**
401
- * - Function-based keys are computed on every request - keep them lightweight
402
- * - String keys are fastest but least flexible
403
- * - Consider caching expensive key computations if needed
404
453
  *
405
- * @example
406
- * ```ts
407
- * import { callApi } from "@zayne-labs/callapi";
408
- *
409
- * // Simple static key - useful for singleton requests
410
- * const config = callApi("/api/config", {
411
- * dedupeKey: "app-config",
412
- * dedupeStrategy: "defer" // Share the same config across all requests
413
- * });
414
- *
415
- * // URL and method only - ignore headers and body
416
- * const userData = callApi("/api/user/123", {
417
- * dedupeKey: (context) => `${context.options.method}:${context.options.fullURL}`
418
- * });
454
+ * // Bearer auth
455
+ * const response = await callMainApi({
456
+ * url: "https://example.com/api/data",
457
+ * auth: {
458
+ * type: "Bearer",
459
+ * value: "123456",
460
+ * },
461
+ })
419
462
  *
420
- * // Include specific headers in deduplication
421
- * const apiCall = callApi("/api/data", {
422
- * dedupeKey: (context) => {
423
- * const authHeader = context.request.headers.get("Authorization");
424
- * return `${context.options.fullURL}-${authHeader}`;
425
- * }
463
+ * // Token auth
464
+ * const response = await callMainApi({
465
+ * url: "https://example.com/api/data",
466
+ * auth: {
467
+ * type: "Token",
468
+ * value: "123456",
469
+ * },
426
470
  * });
427
471
  *
428
- * // User-specific deduplication
429
- * const userSpecificCall = callApi("/api/dashboard", {
430
- * dedupeKey: (context) => {
431
- * const userId = context.options.fullURL.match(/user\/(\d+)/)?.[1];
432
- * return `dashboard-${userId}`;
433
- * }
472
+ * // Basic auth
473
+ * const response = await callMainApi({
474
+ * url: "https://example.com/api/data",
475
+ * auth: {
476
+ * type: "Basic",
477
+ * username: "username",
478
+ * password: "password",
479
+ * },
434
480
  * });
435
481
  *
436
- * // Ignore certain query parameters
437
- * const searchCall = callApi("/api/search?q=test&timestamp=123456", {
438
- * dedupeKey: (context) => {
439
- * const url = new URL(context.options.fullURL);
440
- * url.searchParams.delete("timestamp"); // Remove volatile param
441
- * return `search:${url.toString()}`;
442
- * }
443
- * });
444
482
  * ```
445
- *
446
- * @default Auto-generated from request details
447
483
  */
448
- dedupeKey?: string | ((context: RequestContext) => string | undefined);
484
+ auth?: InferSchemaOutput<TSchema["auth"], AuthOption>;
485
+ }>;
486
+ type InferQueryOption<TSchema extends CallApiSchema> = MakeSchemaOptionRequiredIfDefined<TSchema["query"], {
449
487
  /**
450
- * Strategy for handling duplicate requests. Can be a static string or callback function.
451
- *
452
- * **Available Strategies:**
453
- * - `"cancel"`: Cancel previous request when new one starts (good for search)
454
- * - `"defer"`: Share response between duplicate requests (good for config loading)
455
- * - `"none"`: No deduplication, all requests execute independently
456
- *
457
- * @example
458
- * ```ts
459
- * // Static strategies
460
- * const searchClient = createFetchClient({
461
- * dedupeStrategy: "cancel" // Cancel previous searches
462
- * });
463
- *
464
- * const configClient = createFetchClient({
465
- * dedupeStrategy: "defer" // Share config across components
466
- * });
467
- *
468
- * // Dynamic strategy based on request
469
- * const smartClient = createFetchClient({
470
- * dedupeStrategy: (context) => {
471
- * return context.options.method === "GET" ? "defer" : "cancel";
472
- * }
473
- * });
474
- *
475
- * // Search-as-you-type with cancel strategy
476
- * const handleSearch = async (query: string) => {
477
- * try {
478
- * const { data } = await callApi("/api/search", {
479
- * method: "POST",
480
- * body: { query },
481
- * dedupeStrategy: "cancel",
482
- * dedupeKey: "search" // Cancel previous searches, only latest one goes through
483
- * });
484
- *
485
- * updateSearchResults(data);
486
- * } catch (error) {
487
- * if (error.name === "AbortError") {
488
- * // Previous search cancelled - (expected behavior)
489
- * return;
490
- * }
491
- * console.error("Search failed:", error);
492
- * }
493
- * };
494
- *
495
- * ```
496
- *
497
- * @default "cancel"
488
+ * Parameters to be appended to the URL (i.e: /:id)
498
489
  */
499
- dedupeStrategy?: DedupeStrategyUnion | ((context: RequestContext) => DedupeStrategyUnion);
500
- }; //#endregion
501
- //#region src/middlewares.d.ts
502
- type FetchImpl = UnmaskType<(input: string | Request | URL, init?: RequestInit) => Promise<Response>>;
503
- type FetchMiddlewareContext<TCallApiContext extends CallApiContext> = RequestContext<TCallApiContext> & {
504
- fetchImpl: FetchImpl;
490
+ query?: InferSchemaOutput<TSchema["query"], Query>;
491
+ }>;
492
+ type EmptyString = "";
493
+ type EmptyTuple = readonly [];
494
+ type StringTuple = readonly string[];
495
+ type PossibleParamNamePatterns = `${string}:${string}` | `${string}{${string}}${"" | AnyString}`;
496
+ 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;
497
+ type ConvertParamNamesToRecord<TParamNames extends StringTuple> = Prettify<TParamNames extends (readonly [infer TFirstParamName extends string, ...infer TRemainingParamNames extends StringTuple]) ? Record<TFirstParamName, AllowedQueryParamValues> & ConvertParamNamesToRecord<TRemainingParamNames> : NonNullable<unknown>>;
498
+ type ConvertParamNamesToTuple<TParamNames extends StringTuple> = TParamNames extends readonly [string, ...infer TRemainingParamNames extends StringTuple] ? [AllowedQueryParamValues, ...ConvertParamNamesToTuple<TRemainingParamNames>] : [];
499
+ type InferParamsFromRoute<TCurrentRoute> = ExtractRouteParamNames<TCurrentRoute> extends StringTuple ? ExtractRouteParamNames<TCurrentRoute> extends EmptyTuple ? Params : ConvertParamNamesToRecord<ExtractRouteParamNames<TCurrentRoute>> | ConvertParamNamesToTuple<ExtractRouteParamNames<TCurrentRoute>> : Params;
500
+ 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>;
501
+ type InferParamsOption<TSchema extends CallApiSchema, TBaseSchemaRoutes extends BaseCallApiSchemaRoutes, TCurrentRouteSchemaKey extends string> = MakeParamsOptionRequired<TSchema["params"], TBaseSchemaRoutes, TCurrentRouteSchemaKey, {
502
+ /**
503
+ * Parameters to be appended to the URL (i.e: /:id)
504
+ */
505
+ params?: InferSchemaOutput<TSchema["params"], InferParamsFromRoute<TCurrentRouteSchemaKey>>;
506
+ }>;
507
+ 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>;
508
+ type ResultModeOption<TErrorData, TResultMode extends ResultModeType> = TErrorData extends false ? {
509
+ resultMode: "onlyData";
510
+ } : TErrorData extends false | undefined ? {
511
+ resultMode?: "onlyData";
512
+ } : {
513
+ resultMode?: TResultMode;
505
514
  };
506
- interface Middlewares<TCallApiContext extends NoInfer<CallApiContext> = DefaultCallApiContext> {
515
+ type ThrowOnErrorBoolean = boolean;
516
+ type ThrowOnErrorType<TErrorData, TThrowOnError extends ThrowOnErrorBoolean> = TThrowOnError | ((context: ErrorContext<{
517
+ ErrorData: TErrorData;
518
+ }>) => TThrowOnError);
519
+ type ThrowOnErrorOption<TErrorData, TThrowOnError extends ThrowOnErrorBoolean> = TErrorData extends false ? {
520
+ throwOnError: true;
521
+ } : TErrorData extends false | undefined ? {
522
+ throwOnError?: true;
523
+ } : {
524
+ throwOnError?: ThrowOnErrorType<TErrorData, TThrowOnError>;
525
+ }; //#endregion
526
+ //#region src/retry.d.ts
527
+ declare const defaultRetryStatusCodesLookup: () => Readonly<{
528
+ 408: "Request Timeout";
529
+ 409: "Conflict";
530
+ 425: "Too Early";
531
+ 429: "Too Many Requests";
532
+ 500: "Internal Server Error";
533
+ 502: "Bad Gateway";
534
+ 503: "Service Unavailable";
535
+ 504: "Gateway Timeout";
536
+ }>;
537
+ type RetryStatusCodes = UnmaskType<AnyNumber | keyof ReturnType<typeof defaultRetryStatusCodesLookup>>;
538
+ type RetryCondition<TErrorData> = (context: ErrorContext<{
539
+ ErrorData: TErrorData;
540
+ }>) => Awaitable<boolean>;
541
+ interface RetryOptions<TErrorData> {
507
542
  /**
508
- * Wraps the fetch implementation to intercept requests at the network layer.
509
- *
510
- * Takes a context object containing the current fetch function and returns a new fetch function.
511
- * Use it to cache responses, add logging, handle offline mode, or short-circuit requests etc.
512
- * Multiple middleware compose in order: plugins → base config → per-request.
513
- *
514
- * Unlike `customFetchImpl`, middleware can call through to the original fetch.
515
- *
516
- * @example
517
- * ```ts
518
- * // Cache responses
519
- * const cache = new Map();
520
- *
521
- * fetchMiddleware: (ctx) => async (input, init) => {
522
- * const key = input.toString();
523
- *
524
- * const cachedResponse = cache.get(key);
525
- *
526
- * if (cachedResponse) {
527
- * return cachedResponse.clone();
528
- * }
529
- *
530
- * const response = await ctx.fetchImpl(input, init);
531
- * cache.set(key, response.clone());
532
- *
533
- * return response;
534
- * }
535
- *
536
- * // Handle offline
537
- * fetchMiddleware: (ctx) => async (...parameters) => {
538
- * if (!navigator.onLine) {
539
- * return new Response('{"error": "offline"}', { status: 503 });
540
- * }
541
- *
542
- * return ctx.fetchImpl(...parameters);
543
- * }
544
- * ```
543
+ * Keeps track of the number of times the request has already been retried
544
+ * @internal
545
+ * @deprecated **NOTE**: This property is used internally to track retries. Please abstain from modifying it.
545
546
  */
546
- fetchMiddleware?: (context: FetchMiddlewareContext<TCallApiContext>) => FetchImpl;
547
- } //#endregion
548
- //#region src/constants/validation.d.ts
549
- declare const fallBackRouteSchemaKey = "@default";
550
- type FallBackRouteSchemaKey = typeof fallBackRouteSchemaKey; //#endregion
551
- //#region src/types/standard-schema.d.ts
552
- /**
553
- * The Standard Schema interface.
554
- * @see https://github.com/standard-schema/standard-schema
555
- */
556
- /** The Standard Typed interface. This is a base type extended by other specs. */
557
- interface StandardTypedV1<Input = unknown, Output = Input> {
558
- /** The Standard properties. */
559
- readonly "~standard": StandardTypedV1.Props<Input, Output>;
560
- }
561
- declare namespace StandardTypedV1 {
562
- /** The Standard Typed properties interface. */
563
- interface Props<Input = unknown, Output = Input> {
564
- /** Inferred types associated with the schema. */
565
- readonly types?: Types<Input, Output> | undefined;
566
- /** The vendor name of the schema library. */
567
- readonly vendor: string;
568
- /** The version number of the standard. */
569
- readonly version: 1;
570
- }
571
- /** The Standard Typed types interface. */
572
- interface Types<Input = unknown, Output = Input> {
573
- /** The input type of the schema. */
574
- readonly input: Input;
575
- /** The output type of the schema. */
576
- readonly output: Output;
577
- }
578
- /** Infers the input type of a Standard Typed. */
579
- type InferInput<Schema extends StandardTypedV1> = NonNullable<Schema["~standard"]["types"]>["input"];
580
- /** Infers the output type of a Standard Typed. */
581
- type InferOutput<Schema extends StandardTypedV1> = NonNullable<Schema["~standard"]["types"]>["output"];
582
- }
583
- /** The Standard Schema interface. */
584
- interface StandardSchemaV1<Input = unknown, Output = Input> {
585
- /** The Standard Schema properties. */
586
- readonly "~standard": StandardSchemaV1.Props<Input, Output>;
587
- }
588
- declare namespace StandardSchemaV1 {
589
- /** The Standard Schema properties interface. */
590
- interface Props<Input = unknown, Output = Input> extends StandardTypedV1.Props<Input, Output> {
591
- /** Validates unknown input values. */
592
- readonly validate: (value: unknown, options?: StandardSchemaV1.Options) => Promise<Result<Output>> | Result<Output>;
593
- }
594
- /** The result interface of the validate function. */
595
- type Result<Output> = FailureResult | SuccessResult<Output>;
596
- /** The result interface if validation succeeds. */
597
- interface SuccessResult<Output> {
598
- /** A falsy value for `issues` indicates success. */
599
- readonly issues?: undefined;
600
- /** The typed output value. */
601
- readonly value: Output;
602
- }
603
- interface Options {
604
- /** Explicit support for additional vendor-specific parameters, if needed. */
605
- readonly libraryOptions?: Record<string, unknown> | undefined;
606
- }
607
- /** The result interface if validation fails. */
608
- interface FailureResult {
609
- /** The issues of failed validation. */
610
- readonly issues: readonly Issue[];
611
- }
612
- /** The issue interface of the failure output. */
613
- interface Issue {
614
- /** The error message of the issue. */
615
- readonly message: string;
616
- /** The path of the issue, if any. */
617
- readonly path?: ReadonlyArray<PathSegment | PropertyKey> | undefined;
618
- }
619
- /** The path segment interface of the issue. */
620
- interface PathSegment {
621
- /** The key representing a path segment. */
622
- readonly key: PropertyKey;
623
- }
624
- /** The Standard types interface. */
625
- type Types<Input = unknown, Output = Input> = StandardTypedV1.Types<Input, Output>;
626
- /** Infers the input type of a Standard. */
627
- type InferInput<Schema extends StandardTypedV1> = StandardTypedV1.InferInput<Schema>;
628
- /** Infers the output type of a Standard. */
629
- type InferOutput<Schema extends StandardTypedV1> = StandardTypedV1.InferOutput<Schema>;
547
+ readonly ["~retryAttemptCount"]?: number;
548
+ /**
549
+ * Number of allowed retry attempts on HTTP errors
550
+ * @default 0
551
+ */
552
+ retryAttempts?: number;
553
+ /**
554
+ * Callback whose return value determines if a request should be retried or not
555
+ */
556
+ retryCondition?: RetryCondition<TErrorData>;
557
+ /**
558
+ * Delay between retries in milliseconds
559
+ * @default 1000
560
+ */
561
+ retryDelay?: number | ((currentAttemptCount: number) => number);
562
+ /**
563
+ * Maximum delay in milliseconds. Only applies to exponential strategy
564
+ * @default 10000
565
+ */
566
+ retryMaxDelay?: number;
567
+ /**
568
+ * HTTP methods that are allowed to retry
569
+ * @default ["GET", "POST"]
570
+ */
571
+ retryMethods?: MethodUnion[];
572
+ /**
573
+ * HTTP status codes that trigger a retry
574
+ */
575
+ retryStatusCodes?: RetryStatusCodes[];
576
+ /**
577
+ * Strategy to use when retrying
578
+ * @default "linear"
579
+ */
580
+ retryStrategy?: "exponential" | "linear";
630
581
  } //#endregion
631
- //#region src/validation.d.ts
632
- type ResultVariant = "infer-input" | "infer-output";
633
- 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$1<infer TResult> ? Awaited<TResult> : TFallbackResult;
634
- type InferSchemaOutput<TSchema, TFallbackResult = unknown> = InferSchemaResult<TSchema, TFallbackResult, "infer-output">;
635
- type BooleanObject = { [Key in keyof CallApiSchema]: boolean };
636
- interface CallApiSchemaConfig {
582
+ //#region src/refetch.d.ts
583
+ interface RefetchOptions {
637
584
  /**
638
- * The base url of the schema. By default it's the baseURL of the callApi instance.
585
+ * Tracks the number of times refetch has been called
586
+ * @internal
587
+ * @deprecated **NOTE**: This property is used internally to track refetch calls. Please abstain from modifying it.
639
588
  */
640
- baseURL?: "" | AnyString;
589
+ readonly ["~refetchCount"]?: number;
641
590
  /**
642
- * Disables runtime validation for the schema.
591
+ * Maximum number of times refetch can be called from error hooks.
592
+ *
593
+ * Prevents infinite loops when refetch is called repeatedly in error hooks.
594
+ * When the limit is reached, refetch will throw an error instead of retrying.
595
+ *
596
+ * @default 1
597
+ *
598
+ * @example
599
+ * ```ts
600
+ * // Allow up to 5 refetch attempts
601
+ * refetchAttempts: 5
602
+ *
603
+ * // Disable refetch limit
604
+ * refetchAttempts: Infinity
605
+ *
606
+ * // Strict limit of 1 refetch
607
+ * refetchAttempts: 1
608
+ * ```
643
609
  */
644
- disableRuntimeValidation?: boolean | BooleanObject;
610
+ refetchAttempts?: number;
611
+ }
612
+ type RefetchFn = (overrides?: Pick<RefetchOptions, "refetchAttempts">) => Promise<CallApiResultLoose<unknown, unknown> | null>; //#endregion
613
+ //#region src/stream.d.ts
614
+ type StreamProgressEvent = {
645
615
  /**
646
- * If `true`, the original input value will be used instead of the transformed/validated output.
616
+ * Current chunk of data being streamed
617
+ */
618
+ chunk: Uint8Array;
619
+ /**
620
+ * Progress in percentage
621
+ */
622
+ progress: number;
623
+ /**
624
+ * Total size of data in bytes
625
+ */
626
+ totalBytes: number;
627
+ /**
628
+ * Amount of data transferred so far
629
+ */
630
+ transferredBytes: number;
631
+ }; //#endregion
632
+ //#region src/hooks.d.ts
633
+ type CallApiRequestOptionsForHooks = Omit<CallApiRequestOptions, "headers"> & {
634
+ headers: Record<"Authorization" | "Content-Type" | CommonRequestHeaders, string | undefined>;
635
+ };
636
+ type CallApiExtraOptionsForHooks<TCallApiContext extends CallApiContext = DefaultCallApiContext> = Hooks & Omit<CallApiExtraOptions<TCallApiContext>, keyof Hooks> & {
637
+ refetch: RefetchFn;
638
+ };
639
+ interface Hooks<TCallApiContext extends CallApiContext = DefaultCallApiContext> {
640
+ /**
641
+ * Hook called when any error occurs within the request/response lifecycle.
647
642
  *
648
- * When true, the original input is returned unchanged after validation, ignoring any schema-level
649
- * transformations such as type coercion, default values, or field mapping. Only the validation
650
- * step is executed; the resulting value is discarded in favor of the raw input.
643
+ * This is a unified error handler that catches both request errors (network failures,
644
+ * timeouts, etc.) and response errors (HTTP error status codes). It's essentially
645
+ * a combination of `onRequestError` and `onResponseError` hooks.
646
+ *
647
+ * @param context - Error context containing error details, request info, and response (if available)
648
+ * @returns Promise or void - Hook can be async or sync
651
649
  */
652
- disableRuntimeValidationTransform?: boolean | BooleanObject;
650
+ onError?: (context: ErrorContext<TCallApiContext>) => Awaitable<unknown>;
653
651
  /**
654
- * Optional url prefix that will be substituted for the `baseURL` of the schemaConfig at runtime.
652
+ * Hook called before the HTTP request is sent and before any internal processing of the request object begins.
653
+ *
654
+ * This is the ideal place to modify request headers, add authentication,
655
+ * implement request logging, or perform any setup before the network call.
656
+ *
657
+ * @param context - Request context with mutable request object and configuration
658
+ * @returns Promise or void - Hook can be async or sync
655
659
  *
656
- * Enables a short, stable prefix for routes while keeping the full `baseURL` centralized in config.
657
- * Keeps route definitions concise and shields them from changes to the underlying base URL.
658
660
  */
659
- prefix?: "" | AnyString;
661
+ onRequest?: (context: RequestContext<TCallApiContext>) => Awaitable<unknown>;
660
662
  /**
661
- * Controls the strictness of API route validation.
663
+ * Hook called when an error occurs during the fetch request itself.
662
664
  *
663
- * When true:
664
- * - Only routes explicitly defined in the schema will be considered valid to typescript and the runtime.
665
- * - Attempting to call routes not defined in the schema will result in both type errors and runtime validation errors.
666
- * - Useful for ensuring API calls conform exactly to your schema definition
665
+ * This handles network-level errors like connection failures, timeouts,
666
+ * DNS resolution errors, or other issues that prevent getting an HTTP response.
667
+ * Note that HTTP error status codes (4xx, 5xx) are handled by `onResponseError`.
667
668
  *
668
- * When false or undefined (default):
669
- * - All routes will be allowed, whether they are defined in the schema or not
669
+ * @param context - Request error context with error details and null response
670
+ * @returns Promise or void - Hook can be async or sync
670
671
  */
671
- strict?: boolean;
672
- }
673
- type CallApiSchemaType<TInput> = StandardSchemaV1<TInput | undefined> | ((value: TInput) => Awaitable<TInput | undefined>);
674
- interface CallApiSchema {
675
- auth?: CallApiSchemaType<AuthOption>;
672
+ onRequestError?: (context: RequestErrorContext<TCallApiContext>) => Awaitable<unknown>;
676
673
  /**
677
- * The schema to use for validating the request body.
674
+ * Hook called just before the HTTP request is sent and after the request has been processed.
675
+ *
676
+ * @param context - Request context with mutable request object and configuration
678
677
  */
679
- body?: CallApiSchemaType<Body>;
678
+ onRequestReady?: (context: RequestContext<TCallApiContext>) => Awaitable<unknown>;
680
679
  /**
681
- * The schema to use for validating the response data.
680
+ * Hook called during upload stream progress tracking.
681
+ *
682
+ * This hook is triggered when uploading data (like file uploads) and provides
683
+ * progress information about the upload. Useful for implementing progress bars
684
+ * or upload status indicators.
685
+ *
686
+ * @param context - Request stream context with progress event and request instance
687
+ * @returns Promise or void - Hook can be async or sync
688
+ *
682
689
  */
683
- data?: CallApiSchemaType<unknown>;
690
+ onRequestStream?: (context: RequestStreamContext<TCallApiContext>) => Awaitable<unknown>;
684
691
  /**
685
- * The schema to use for validating the response error data.
692
+ * Hook called when any HTTP response is received from the API.
693
+ *
694
+ * This hook is triggered for both successful (2xx) and error (4xx, 5xx) responses.
695
+ * It's useful for response logging, metrics collection, or any processing that
696
+ * should happen regardless of response status.
697
+ *
698
+ * @param context - Response context with either success data or error information
699
+ * @returns Promise or void - Hook can be async or sync
700
+ *
686
701
  */
687
- errorData?: CallApiSchemaType<unknown>;
702
+ onResponse?: (context: ResponseContext<TCallApiContext>) => Awaitable<unknown>;
688
703
  /**
689
- * The schema to use for validating the request headers.
704
+ * Hook called when an HTTP error response (4xx, 5xx) is received from the API.
705
+ *
706
+ * This handles server-side errors where an HTTP response was successfully received
707
+ * but indicates an error condition. Different from `onRequestError` which handles
708
+ * network-level failures.
709
+ *
710
+ * @param context - Response error context with HTTP error details and response
711
+ * @returns Promise or void - Hook can be async or sync
690
712
  */
691
- headers?: CallApiSchemaType<HeadersOption>;
713
+ onResponseError?: (context: ResponseErrorContext<TCallApiContext>) => Awaitable<unknown>;
692
714
  /**
693
- * The schema to use for validating the meta option.
715
+ * Hook called during download stream progress tracking.
716
+ *
717
+ * This hook is triggered when downloading data (like file downloads) and provides
718
+ * progress information about the download. Useful for implementing progress bars
719
+ * or download status indicators.
720
+ *
721
+ * @param context - Response stream context with progress event and response
722
+ * @returns Promise or void - Hook can be async or sync
723
+ *
694
724
  */
695
- meta?: CallApiSchemaType<GlobalMeta>;
725
+ onResponseStream?: (context: ResponseStreamContext<TCallApiContext>) => Awaitable<unknown>;
696
726
  /**
697
- * The schema to use for validating the request method.
727
+ * Hook called when a request is being retried.
728
+ *
729
+ * This hook is triggered before each retry attempt, providing information about
730
+ * the previous failure and the current retry attempt number. Useful for implementing
731
+ * custom retry logic, exponential backoff, or retry logging.
732
+ *
733
+ * @param context - Retry context with error details and retry attempt count
734
+ * @returns Promise or void - Hook can be async or sync
735
+ *
698
736
  */
699
- method?: CallApiSchemaType<MethodUnion>;
737
+ onRetry?: (context: RetryContext<TCallApiContext>) => Awaitable<unknown>;
700
738
  /**
701
- * The schema to use for validating the request url parameters.
739
+ * Hook called when a successful response (2xx status) is received from the API.
740
+ *
741
+ * This hook is triggered only for successful responses and provides access to
742
+ * the parsed response data. Ideal for success logging, caching, or post-processing
743
+ * of successful API responses.
744
+ *
745
+ * @param context - Success context with parsed response data and response object
746
+ * @returns Promise or void - Hook can be async or sync
747
+ *
702
748
  */
703
- params?: CallApiSchemaType<Params>;
749
+ onSuccess?: (context: SuccessContext<TCallApiContext>) => Awaitable<unknown>;
704
750
  /**
705
- * The schema to use for validating the request url queries.
751
+ * Hook called when a validation error occurs.
752
+ *
753
+ * This hook is triggered when request or response data fails validation against
754
+ * a defined schema. It provides access to the validation error details and can
755
+ * be used for custom error handling, logging, or fallback behavior.
756
+ *
757
+ * @param context - Validation error context with error details and response (if available)
758
+ * @returns Promise or void - Hook can be async or sync
759
+ *
706
760
  */
707
- query?: CallApiSchemaType<Query>;
761
+ onValidationError?: (context: ValidationErrorContext<TCallApiContext>) => Awaitable<unknown>;
708
762
  }
709
- declare const routeKeyMethods: readonly ["delete", "get", "patch", "post", "put"];
710
- type RouteKeyMethods = (typeof routeKeyMethods)[number];
711
- type RouteKeyMethodsURLUnion = `${AtSymbol}${RouteKeyMethods}/`;
712
- type BaseSchemaRouteKeyPrefixes = FallBackRouteSchemaKey | RouteKeyMethodsURLUnion;
713
- type BaseCallApiSchemaRoutes = Partial<Record<AnyString | BaseSchemaRouteKeyPrefixes, CallApiSchema>>;
714
- type BaseCallApiSchemaAndConfig = {
715
- config?: CallApiSchemaConfig;
716
- routes: BaseCallApiSchemaRoutes;
763
+ type HooksOrHooksArray<TCallApiContext extends NoInfer<CallApiContext> = DefaultCallApiContext> = { [Key in keyof Hooks<TCallApiContext>]: Hooks<TCallApiContext>[Key] | Array<Hooks<TCallApiContext>[Key]> };
764
+ interface HookConfigOptions {
765
+ /**
766
+ * Controls the execution mode of all composed hooks (main + plugin hooks).
767
+ *
768
+ * - **"parallel"**: All hooks execute simultaneously via Promise.all() for better performance
769
+ * - **"sequential"**: All hooks execute one by one in registration order via await in a loop
770
+ *
771
+ * This affects how ALL hooks execute together, regardless of their source (main or plugin).
772
+ *
773
+ * @default "parallel"
774
+ */
775
+ hooksExecutionMode?: "parallel" | "sequential";
776
+ }
777
+ type RequestContext<TCallApiContext extends Pick<CallApiContext, "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = {
778
+ /**
779
+ * Base configuration object passed to createFetchClient.
780
+ *
781
+ * Contains the foundational configuration that applies to all requests
782
+ * made by this client instance, such as baseURL, default headers, and
783
+ * global options.
784
+ */
785
+ baseConfig: Exclude<BaseCallApiConfig, AnyFunction$1>;
786
+ /**
787
+ * Instance-specific configuration object passed to the callApi instance.
788
+ *
789
+ * Contains configuration specific to this particular API call, which
790
+ * can override or extend the base configuration.
791
+ */
792
+ config: CallApiConfig;
793
+ /**
794
+ * Merged options combining base config, instance config, and default options.
795
+ *
796
+ * This is the final resolved configuration that will be used for the request,
797
+ * with proper precedence applied (instance > base > defaults).
798
+ */
799
+ options: CallApiExtraOptionsForHooks<TCallApiContext>;
800
+ /**
801
+ * Merged request object ready to be sent.
802
+ *
803
+ * Contains the final request configuration including URL, method, headers,
804
+ * body, and other fetch options. This object can be modified in onRequest
805
+ * hooks to customize the outgoing request.
806
+ */
807
+ request: CallApiRequestOptionsForHooks;
808
+ };
809
+ type SuccessContext<TCallApiContext extends Pick<CallApiContext, "Data" | "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = DistributiveOmit<CallApiResultSuccessVariant<TCallApiContext["Data"]>, "error"> & RequestContext<TCallApiContext>;
810
+ type ResponseContext<TCallApiContext extends Pick<CallApiContext, "Data" | "ErrorData" | "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = RequestContext<TCallApiContext> & (Prettify<CallApiResultSuccessVariant<TCallApiContext["Data"]>> | Prettify<Extract<CallApiResultErrorVariant<TCallApiContext["ErrorData"]>, {
811
+ error: PossibleHTTPError<TCallApiContext["ErrorData"]>;
812
+ }>>);
813
+ type RequestStreamContext<TCallApiContext extends Pick<CallApiContext, "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = RequestContext<TCallApiContext> & {
814
+ event: StreamProgressEvent;
815
+ requestInstance: Request;
816
+ };
817
+ type ResponseStreamContext<TCallApiContext extends Pick<CallApiContext, "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = RequestContext<TCallApiContext> & {
818
+ event: StreamProgressEvent;
819
+ response: Response;
820
+ };
821
+ type ErrorContext<TCallApiContext extends Pick<CallApiContext, "ErrorData" | "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = DistributiveOmit<CallApiResultErrorVariant<TCallApiContext["ErrorData"]>, "data"> & RequestContext<TCallApiContext>;
822
+ type ValidationErrorContext<TCallApiContext extends Pick<CallApiContext, "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = Extract<ErrorContext<TCallApiContext>, {
823
+ error: PossibleValidationError;
824
+ }> & RequestContext<TCallApiContext>;
825
+ type RequestErrorContext<TCallApiContext extends Pick<CallApiContext, "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = Extract<ErrorContext<TCallApiContext>, {
826
+ error: PossibleJavaScriptError;
827
+ }> & RequestContext<TCallApiContext>;
828
+ type ResponseErrorContext<TCallApiContext extends Pick<CallApiContext, "ErrorData" | "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = Extract<ErrorContext<TCallApiContext>, {
829
+ error: PossibleHTTPError<TCallApiContext["ErrorData"]>;
830
+ }> & RequestContext<TCallApiContext>;
831
+ type RetryContext<TCallApiContext extends Pick<CallApiContext, "ErrorData" | "InferredExtraOptions" | "Meta"> = DefaultCallApiContext> = ErrorContext<TCallApiContext> & {
832
+ retryAttemptCount: number;
717
833
  }; //#endregion
718
- //#region src/url.d.ts
719
- declare const atSymbol = "@";
720
- type AtSymbol = typeof atSymbol;
721
- type AllowedQueryParamValues = UnmaskType<boolean | number | string>;
722
- type RecordStyleParams = UnmaskType<Record<string, AllowedQueryParamValues>>;
723
- type TupleStyleParams = UnmaskType<AllowedQueryParamValues[]>;
724
- type Params = UnmaskType<RecordStyleParams | TupleStyleParams>;
725
- type Query = UnmaskType<Record<string, AllowedQueryParamValues> | URLSearchParams>;
726
- type InitURLOrURLObject = AnyString | RouteKeyMethodsURLUnion | URL;
727
- interface URLOptions {
834
+ //#region src/dedupe.d.ts
835
+ type DedupeStrategyUnion = UnmaskType<"cancel" | "defer" | "none">;
836
+ type DedupeOptions = {
728
837
  /**
729
- * Base URL for all API requests. Will only be prepended to relative URLs.
838
+ * Controls the scope of request deduplication caching.
730
839
  *
731
- * Absolute URLs (starting with http/https) will not be prepended by the baseURL.
840
+ * - `"global"`: Shares deduplication cache across all `createFetchClient` instances with the same `dedupeCacheScopeKey`.
841
+ * Useful for applications with multiple API clients that should share deduplication state.
842
+ * - `"local"`: Limits deduplication to requests within the same `createFetchClient` instance.
843
+ * Provides better isolation and is recommended for most use cases.
844
+ *
845
+ *
846
+ * **Real-world Scenarios:**
847
+ * - Use `"global"` when you have multiple API clients (user service, auth service, etc.) that might make overlapping requests
848
+ * - Use `"local"` (default) for single-purpose clients or when you want strict isolation between different parts of your app
732
849
  *
733
850
  * @example
734
851
  * ```ts
735
- * // Set base URL for all requests
736
- * baseURL: "https://api.example.com/v1"
737
- *
738
- * // Then use relative URLs in requests
739
- * callApi("/users") // → https://api.example.com/v1/users
740
- * callApi("/posts/123") // → https://api.example.com/v1/posts/123
852
+ * // Local scope - each client has its own deduplication cache
853
+ * const userClient = createFetchClient({ baseURL: "/api/users" });
854
+ * const postClient = createFetchClient({ baseURL: "/api/posts" });
855
+ * // These clients won't share deduplication state
741
856
  *
742
- * // Environment-specific base URLs
743
- * baseURL: process.env.NODE_ENV === "production"
744
- * ? "https://api.example.com"
745
- * : "http://localhost:3000/api"
857
+ * // Global scope - share cache across related clients
858
+ * const userClient = createFetchClient({
859
+ * baseURL: "/api/users",
860
+ * dedupeCacheScope: "global",
861
+ * });
862
+ * const postClient = createFetchClient({
863
+ * baseURL: "/api/posts",
864
+ * dedupeCacheScope: "global",
865
+ * });
866
+ * // These clients will share deduplication state
746
867
  * ```
747
- */
748
- baseURL?: string;
749
- /**
750
- * Resolved request URL after processing baseURL, parameters, and query strings (readonly)
751
- *
752
- * This is the final URL that will be used for the HTTP request, computed from
753
- * baseURL, initURL, params, and query parameters.
754
- *
755
- */
756
- readonly fullURL?: string;
757
- /**
758
- * The original URL string passed to the callApi instance (readonly)
759
- *
760
- * This preserves the original URL as provided, including any method modifiers like "@get/" or "@post/".
761
868
  *
869
+ * @default "local"
762
870
  */
763
- readonly initURL?: string;
871
+ dedupeCacheScope?: "global" | "local";
764
872
  /**
765
- * The URL string after normalization, with method modifiers removed(readonly)
873
+ * Unique namespace for the global deduplication cache when using `dedupeCacheScope: "global"`.
766
874
  *
767
- * Method modifiers like "@get/", "@post/" are stripped to create a clean URL
768
- * for parameter substitution and final URL construction.
875
+ * This creates logical groupings of deduplication caches. All instances with the same key
876
+ * will share the same cache namespace, allowing fine-grained control over which clients
877
+ * share deduplication state.
769
878
  *
770
- */
771
- readonly initURLNormalized?: string;
772
- /**
773
- * Parameters to be substituted into URL path segments.
879
+ * **Best Practices:**
880
+ * - Use descriptive names that reflect the logical grouping (e.g., "user-service", "analytics-api")
881
+ * - Keep scope keys consistent across related API clients
882
+ * - Consider using different scope keys for different environments (dev, staging, prod)
883
+ * - Avoid overly broad scope keys that might cause unintended cache sharing
774
884
  *
775
- * Supports both object-style (named parameters) and array-style (positional parameters)
776
- * for flexible URL parameter substitution.
885
+ * **Cache Management:**
886
+ * - Each scope key maintains its own independent cache
887
+ * - Caches are automatically cleaned up when no references remain
888
+ * - Consider the memory implications of multiple global scopes
777
889
  *
778
890
  * @example
779
- * ```typescript
780
- * // Object-style parameters (recommended)
781
- * const namedParams: URLOptions = {
782
- * initURL: "/users/:userId/posts/:postId",
783
- * params: { userId: "123", postId: "456" }
784
- * };
785
- * // Results in: /users/123/posts/456
891
+ * ```ts
892
+ * // Group related API clients together
893
+ * const userClient = createFetchClient({
894
+ * baseURL: "/api/users",
895
+ * dedupeCacheScope: "global",
896
+ * dedupeCacheScopeKey: "user-service"
897
+ * });
898
+ * const profileClient = createFetchClient({
899
+ * baseURL: "/api/profiles",
900
+ * dedupeCacheScope: "global",
901
+ * dedupeCacheScopeKey: "user-service" // Same scope - will share cache
902
+ * });
786
903
  *
787
- * // Array-style parameters (positional)
788
- * const positionalParams: URLOptions = {
789
- * initURL: "/users/:userId/posts/:postId",
790
- * params: ["123", "456"] // Maps in order: userId=123, postId=456
791
- * };
792
- * // Results in: /users/123/posts/456
904
+ * // Separate analytics client with its own cache
905
+ * const analyticsClient = createFetchClient({
906
+ * baseURL: "/api/analytics",
907
+ * dedupeCacheScope: "global",
908
+ * dedupeCacheScopeKey: "analytics-service" // Different scope
909
+ * });
793
910
  *
794
- * // Single parameter
795
- * const singleParam: URLOptions = {
796
- * initURL: "/users/:id",
797
- * params: { id: "user-123" }
798
- * };
799
- * // Results in: /users/user-123
911
+ * // Environment-specific scoping
912
+ * const apiClient = createFetchClient({
913
+ * dedupeCacheScope: "global",
914
+ * dedupeCacheScopeKey: `api-${process.env.NODE_ENV}` // "api-development", "api-production", etc.
915
+ * });
800
916
  * ```
917
+ *
918
+ * @default "default"
801
919
  */
802
- params?: Params;
920
+ dedupeCacheScopeKey?: "default" | AnyString | ((context: RequestContext) => string | undefined);
803
921
  /**
804
- * Query parameters to append to the URL as search parameters.
922
+ * Custom key generator for request deduplication.
805
923
  *
806
- * These will be serialized into the URL query string using standard
807
- * URL encoding practices.
924
+ * Override the default key generation strategy to control exactly which requests
925
+ * are considered duplicates. The default key combines URL, method, body, and
926
+ * relevant headers (excluding volatile ones like 'Date', 'Authorization', etc.).
927
+ *
928
+ * **Default Key Generation:**
929
+ * The auto-generated key includes:
930
+ * - Full request URL (including query parameters)
931
+ * - HTTP method (GET, POST, etc.)
932
+ * - Request body (for POST/PUT/PATCH requests)
933
+ * - Stable headers (excludes Date, Authorization, User-Agent, etc.)
934
+ *
935
+ * **Custom Key Best Practices:**
936
+ * - Include only the parts of the request that should affect deduplication
937
+ * - Avoid including volatile data (timestamps, random IDs, etc.)
938
+ * - Consider performance - simpler keys are faster to compute and compare
939
+ * - Ensure keys are deterministic for the same logical request
940
+ * - Use consistent key formats across your application
941
+ *
942
+ * **Performance Considerations:**
943
+ * - Function-based keys are computed on every request - keep them lightweight
944
+ * - String keys are fastest but least flexible
945
+ * - Consider caching expensive key computations if needed
808
946
  *
809
947
  * @example
810
- * ```typescript
811
- * // Basic query parameters
812
- * const queryOptions: URLOptions = {
813
- * initURL: "/users",
814
- * query: {
815
- * page: 1,
816
- * limit: 10,
817
- * search: "john doe",
818
- * active: true
948
+ * ```ts
949
+ * import { callApi } from "@zayne-labs/callapi";
950
+ *
951
+ * // Simple static key - useful for singleton requests
952
+ * const config = callApi("/api/config", {
953
+ * dedupeKey: "app-config",
954
+ * dedupeStrategy: "defer" // Share the same config across all requests
955
+ * });
956
+ *
957
+ * // URL and method only - ignore headers and body
958
+ * const userData = callApi("/api/user/123", {
959
+ * dedupeKey: (context) => `${context.options.method}:${context.options.fullURL}`
960
+ * });
961
+ *
962
+ * // Include specific headers in deduplication
963
+ * const apiCall = callApi("/api/data", {
964
+ * dedupeKey: (context) => {
965
+ * const authHeader = context.request.headers.get("Authorization");
966
+ * return `${context.options.fullURL}-${authHeader}`;
819
967
  * }
820
- * };
821
- * // Results in: /users?page=1&limit=10&search=john%20doe&active=true
968
+ * });
822
969
  *
823
- * // Filtering and sorting
824
- * const filterOptions: URLOptions = {
825
- * initURL: "/products",
826
- * query: {
827
- * category: "electronics",
828
- * minPrice: 100,
829
- * maxPrice: 500,
830
- * sortBy: "price",
831
- * order: "asc"
970
+ * // User-specific deduplication
971
+ * const userSpecificCall = callApi("/api/dashboard", {
972
+ * dedupeKey: (context) => {
973
+ * const userId = context.options.fullURL.match(/user\/(\d+)/)?.[1];
974
+ * return `dashboard-${userId}`;
832
975
  * }
833
- * };
834
- * // Results in: /products?category=electronics&minPrice=100&maxPrice=500&sortBy=price&order=asc
835
- * ```
836
- */
837
- query?: Query;
838
- } //#endregion
839
- //#region src/types/conditional-types.d.ts
840
- /**
841
- * @description Makes a type partial if the output type of TSchema is not provided or has undefined in the union, otherwise makes it required
842
- */
843
- type MakeSchemaOptionRequiredIfDefined<TSchemaOption extends CallApiSchema[keyof CallApiSchema], TObject> = undefined extends InferSchemaOutput<TSchemaOption, undefined> ? TObject : Required<TObject>;
844
- type MergeBaseWithRouteKey<TBaseURLOrPrefix extends string | undefined, TRouteKey extends string> = TBaseURLOrPrefix extends string ? TRouteKey extends `${AtSymbol}${infer TMethod extends RouteKeyMethods}/${infer TRestOfRoutKey}` ? `${AtSymbol}${TMethod}/${RemoveLeadingSlash<RemoveTrailingSlash<TBaseURLOrPrefix>>}/${RemoveLeadingSlash<TRestOfRoutKey>}` : `${TBaseURLOrPrefix}${TRouteKey}` : TRouteKey;
845
- type ApplyURLBasedConfig<TSchemaConfig extends CallApiSchemaConfig, TSchemaRouteKeys extends string> = TSchemaConfig["prefix"] extends string ? MergeBaseWithRouteKey<TSchemaConfig["prefix"], TSchemaRouteKeys> : TSchemaConfig["baseURL"] extends string ? MergeBaseWithRouteKey<TSchemaConfig["baseURL"], TSchemaRouteKeys> : TSchemaRouteKeys;
846
- type ApplyStrictConfig<TSchemaConfig extends CallApiSchemaConfig, TSchemaRouteKeys extends string> = TSchemaConfig["strict"] extends true ? TSchemaRouteKeys : // eslint-disable-next-line perfectionist/sort-union-types -- Don't sort union types
847
- TSchemaRouteKeys | Exclude<InitURLOrURLObject, RouteKeyMethodsURLUnion>;
848
- type ApplySchemaConfiguration<TSchemaConfig extends CallApiSchemaConfig, TSchemaRouteKeys extends string> = ApplyStrictConfig<TSchemaConfig, ApplyURLBasedConfig<TSchemaConfig, TSchemaRouteKeys>>;
849
- type InferAllMainRouteKeys<TBaseSchemaRoutes extends BaseCallApiSchemaRoutes, TSchemaConfig extends CallApiSchemaConfig> = ApplySchemaConfiguration<TSchemaConfig, Exclude<Extract<keyof TBaseSchemaRoutes, string>, FallBackRouteSchemaKey>>;
850
- type InferInitURL<TBaseSchemaRoutes extends BaseCallApiSchemaRoutes, TSchemaConfig extends CallApiSchemaConfig> = keyof TBaseSchemaRoutes extends never ? InitURLOrURLObject : InferAllMainRouteKeys<TBaseSchemaRoutes, TSchemaConfig>;
851
- 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;
852
- type JsonPrimitive = boolean | number | string | null | undefined;
853
- type SerializableObject = Record<PropertyKey, unknown>;
854
- type SerializableArray = Array<JsonPrimitive | SerializableObject> | ReadonlyArray<JsonPrimitive | SerializableObject>;
855
- type Body = UnmaskType<Exclude<RequestInit["body"], undefined> | SerializableArray | SerializableObject>;
856
- type InferBodyOption<TSchema extends CallApiSchema> = MakeSchemaOptionRequiredIfDefined<TSchema["body"], {
857
- /**
858
- * Body of the request, can be a object or any other supported body type.
859
- */
860
- body?: InferSchemaOutput<TSchema["body"], Body>;
861
- }>;
862
- type MethodUnion = UnmaskType<"CONNECT" | "DELETE" | "GET" | "HEAD" | "OPTIONS" | "PATCH" | "POST" | "PUT" | "TRACE" | AnyString>;
863
- type InferMethodFromURL<TInitURL> = string extends TInitURL ? MethodUnion : TInitURL extends `${AtSymbol}${infer TMethod extends RouteKeyMethods}/${string}` ? Uppercase<TMethod> : MethodUnion;
864
- type InferMethodOption<TSchema extends CallApiSchema, TInitURL> = MakeSchemaOptionRequiredIfDefined<TSchema["method"], {
865
- /**
866
- * HTTP method for the request.
867
- * @default "GET"
868
- */
869
- method?: InferSchemaOutput<TSchema["method"], InferMethodFromURL<TInitURL>>;
870
- }>;
871
- type HeadersOption = UnmaskType<Headers | Record<"Authorization", CommonAuthorizationHeaders | undefined> | Record<"Content-Type", CommonContentTypes | undefined> | Record<CommonRequestHeaders, string | undefined> | Record<string, string | undefined> | Array<[string, string]>>;
872
- type InferHeadersOption<TSchema extends CallApiSchema> = MakeSchemaOptionRequiredIfDefined<TSchema["headers"], {
873
- /**
874
- * Headers to be used in the request.
976
+ * });
977
+ *
978
+ * // Ignore certain query parameters
979
+ * const searchCall = callApi("/api/search?q=test&timestamp=123456", {
980
+ * dedupeKey: (context) => {
981
+ * const url = new URL(context.options.fullURL);
982
+ * url.searchParams.delete("timestamp"); // Remove volatile param
983
+ * return `search:${url.toString()}`;
984
+ * }
985
+ * });
986
+ * ```
987
+ *
988
+ * @default Auto-generated from request details
875
989
  */
876
- headers?: InferSchemaOutput<TSchema["headers"], HeadersOption> | ((context: {
877
- baseHeaders: Extract<HeadersOption, Record<string, unknown>>;
878
- }) => InferSchemaOutput<TSchema["headers"], HeadersOption>);
879
- }>;
880
- type InferRequestOptions<TSchema extends CallApiSchema, TInitURL extends InferInitURL<BaseCallApiSchemaRoutes, CallApiSchemaConfig>> = InferBodyOption<TSchema> & InferHeadersOption<TSchema> & InferMethodOption<TSchema, TInitURL>;
881
- type InferMetaOption<TSchema extends CallApiSchema, TCallApiContext extends CallApiContext> = MakeSchemaOptionRequiredIfDefined<TSchema["meta"], {
990
+ dedupeKey?: string | ((context: RequestContext) => string | undefined);
882
991
  /**
883
- * - An optional field you can fill with additional information,
884
- * to associate with the request, typically used for logging or tracing.
992
+ * Strategy for handling duplicate requests. Can be a static string or callback function.
885
993
  *
886
- * - A good use case for this, would be to use the info to handle specific cases in any of the shared interceptors.
994
+ * **Available Strategies:**
995
+ * - `"cancel"`: Cancel previous request when new one starts (good for search)
996
+ * - `"defer"`: Share response between duplicate requests (good for config loading)
997
+ * - `"none"`: No deduplication, all requests execute independently
887
998
  *
888
999
  * @example
889
1000
  * ```ts
890
- * const callMainApi = callApi.create({
891
- * baseURL: "https://main-api.com",
892
- * onResponseError: ({ response, options }) => {
893
- * if (options.meta?.userId) {
894
- * console.error(`User ${options.meta.userId} made an error`);
895
- * }
896
- * },
1001
+ * // Static strategies
1002
+ * const searchClient = createFetchClient({
1003
+ * dedupeStrategy: "cancel" // Cancel previous searches
897
1004
  * });
898
1005
  *
899
- * const response = await callMainApi({
900
- * url: "https://example.com/api/data",
901
- * meta: { userId: "123" },
1006
+ * const configClient = createFetchClient({
1007
+ * dedupeStrategy: "defer" // Share config across components
1008
+ * });
1009
+ *
1010
+ * // Dynamic strategy based on request
1011
+ * const smartClient = createFetchClient({
1012
+ * dedupeStrategy: (context) => {
1013
+ * return context.options.method === "GET" ? "defer" : "cancel";
1014
+ * }
902
1015
  * });
1016
+ *
1017
+ * // Search-as-you-type with cancel strategy
1018
+ * const handleSearch = async (query: string) => {
1019
+ * try {
1020
+ * const { data } = await callApi("/api/search", {
1021
+ * method: "POST",
1022
+ * body: { query },
1023
+ * dedupeStrategy: "cancel",
1024
+ * dedupeKey: "search" // Cancel previous searches, only latest one goes through
1025
+ * });
1026
+ *
1027
+ * updateSearchResults(data);
1028
+ * } catch (error) {
1029
+ * if (error.name === "AbortError") {
1030
+ * // Previous search cancelled - (expected behavior)
1031
+ * return;
1032
+ * }
1033
+ * console.error("Search failed:", error);
1034
+ * }
1035
+ * };
1036
+ *
903
1037
  * ```
1038
+ *
1039
+ * @default "cancel"
904
1040
  */
905
- meta?: InferSchemaOutput<TSchema["meta"], TCallApiContext["Meta"]>;
906
- }>;
907
- type InferAuthOption<TSchema extends CallApiSchema> = MakeSchemaOptionRequiredIfDefined<TSchema["auth"], {
1041
+ dedupeStrategy?: DedupeStrategyUnion | ((context: RequestContext) => DedupeStrategyUnion);
1042
+ }; //#endregion
1043
+ //#region src/middlewares.d.ts
1044
+ type FetchImpl = UnmaskType<(input: string | Request | URL, init?: RequestInit) => Promise<Response>>;
1045
+ type FetchMiddlewareContext<TCallApiContext extends CallApiContext> = RequestContext<TCallApiContext> & {
1046
+ fetchImpl: FetchImpl;
1047
+ };
1048
+ interface Middlewares<TCallApiContext extends NoInfer<CallApiContext> = DefaultCallApiContext> {
908
1049
  /**
909
- * Automatically add an Authorization header value.
1050
+ * Wraps the fetch implementation to intercept requests at the network layer.
910
1051
  *
911
- * Supports multiple authentication patterns:
912
- * - String: Direct authorization header value
913
- * - Auth object: Structured authentication configuration
1052
+ * Takes a context object containing the current fetch function and returns a new fetch function.
1053
+ * Use it to cache responses, add logging, handle offline mode, or short-circuit requests etc.
1054
+ * Multiple middleware compose in order: plugins base config → per-request.
1055
+ *
1056
+ * Unlike `customFetchImpl`, middleware can call through to the original fetch.
914
1057
  *
915
1058
  * @example
916
1059
  * ```ts
917
- * // Bearer auth
918
- * const response = await callMainApi({
919
- * url: "https://example.com/api/data",
920
- * auth: "123456",
921
- * });
1060
+ * // Cache responses
1061
+ * const cache = new Map();
922
1062
  *
923
- * // Bearer auth
924
- * const response = await callMainApi({
925
- * url: "https://example.com/api/data",
926
- * auth: {
927
- * type: "Bearer",
928
- * value: "123456",
929
- * },
930
- })
1063
+ * fetchMiddleware: (ctx) => async (input, init) => {
1064
+ * const key = input.toString();
931
1065
  *
932
- * // Token auth
933
- * const response = await callMainApi({
934
- * url: "https://example.com/api/data",
935
- * auth: {
936
- * type: "Token",
937
- * value: "123456",
938
- * },
939
- * });
1066
+ * const cachedResponse = cache.get(key);
940
1067
  *
941
- * // Basic auth
942
- * const response = await callMainApi({
943
- * url: "https://example.com/api/data",
944
- * auth: {
945
- * type: "Basic",
946
- * username: "username",
947
- * password: "password",
948
- * },
949
- * });
1068
+ * if (cachedResponse) {
1069
+ * return cachedResponse.clone();
1070
+ * }
1071
+ *
1072
+ * const response = await ctx.fetchImpl(input, init);
1073
+ * cache.set(key, response.clone());
1074
+ *
1075
+ * return response;
1076
+ * }
1077
+ *
1078
+ * // Handle offline
1079
+ * fetchMiddleware: (ctx) => async (...parameters) => {
1080
+ * if (!navigator.onLine) {
1081
+ * return new Response('{"error": "offline"}', { status: 503 });
1082
+ * }
950
1083
  *
1084
+ * return ctx.fetchImpl(...parameters);
1085
+ * }
951
1086
  * ```
952
1087
  */
953
- auth?: InferSchemaOutput<TSchema["auth"], AuthOption>;
954
- }>;
955
- type InferQueryOption<TSchema extends CallApiSchema> = MakeSchemaOptionRequiredIfDefined<TSchema["query"], {
956
- /**
957
- * Parameters to be appended to the URL (i.e: /:id)
958
- */
959
- query?: InferSchemaOutput<TSchema["query"], Query>;
960
- }>;
961
- type EmptyString = "";
962
- type EmptyTuple = readonly [];
963
- type StringTuple = readonly string[];
964
- type PossibleParamNamePatterns = `${string}:${string}` | `${string}{${string}}${"" | AnyString}`;
965
- 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;
966
- type ConvertParamNamesToRecord<TParamNames extends StringTuple> = Prettify<TParamNames extends (readonly [infer TFirstParamName extends string, ...infer TRemainingParamNames extends StringTuple]) ? Record<TFirstParamName, AllowedQueryParamValues> & ConvertParamNamesToRecord<TRemainingParamNames> : NonNullable<unknown>>;
967
- type ConvertParamNamesToTuple<TParamNames extends StringTuple> = TParamNames extends readonly [string, ...infer TRemainingParamNames extends StringTuple] ? [AllowedQueryParamValues, ...ConvertParamNamesToTuple<TRemainingParamNames>] : [];
968
- type InferParamsFromRoute<TCurrentRoute> = ExtractRouteParamNames<TCurrentRoute> extends StringTuple ? ExtractRouteParamNames<TCurrentRoute> extends EmptyTuple ? Params : ConvertParamNamesToRecord<ExtractRouteParamNames<TCurrentRoute>> | ConvertParamNamesToTuple<ExtractRouteParamNames<TCurrentRoute>> : Params;
969
- 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>;
970
- type InferParamsOption<TSchema extends CallApiSchema, TBaseSchemaRoutes extends BaseCallApiSchemaRoutes, TCurrentRouteSchemaKey extends string> = MakeParamsOptionRequired<TSchema["params"], TBaseSchemaRoutes, TCurrentRouteSchemaKey, {
971
- /**
972
- * Parameters to be appended to the URL (i.e: /:id)
973
- */
974
- params?: InferSchemaOutput<TSchema["params"], InferParamsFromRoute<TCurrentRouteSchemaKey>>;
975
- }>;
976
- 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>;
977
- type ResultModeOption<TErrorData, TResultMode extends ResultModeType> = TErrorData extends false ? {
978
- resultMode: "onlyData";
979
- } : TErrorData extends false | undefined ? {
980
- resultMode?: "onlyData";
981
- } : {
982
- resultMode?: TResultMode;
983
- };
984
- type ThrowOnErrorBoolean = boolean;
985
- type ThrowOnErrorType<TErrorData, TThrowOnError extends ThrowOnErrorBoolean> = TThrowOnError | ((context: ErrorContext<{
986
- ErrorData: TErrorData;
987
- }>) => TThrowOnError);
988
- type ThrowOnErrorOption<TErrorData, TThrowOnError extends ThrowOnErrorBoolean> = TErrorData extends false ? {
989
- throwOnError: true;
990
- } : TErrorData extends false | undefined ? {
991
- throwOnError?: true;
992
- } : {
993
- throwOnError?: ThrowOnErrorType<TErrorData, TThrowOnError>;
994
- }; //#endregion
995
- //#region src/retry.d.ts
996
- declare const defaultRetryStatusCodesLookup: () => Readonly<{
997
- 408: "Request Timeout";
998
- 409: "Conflict";
999
- 425: "Too Early";
1000
- 429: "Too Many Requests";
1001
- 500: "Internal Server Error";
1002
- 502: "Bad Gateway";
1003
- 503: "Service Unavailable";
1004
- 504: "Gateway Timeout";
1005
- }>;
1006
- type RetryStatusCodes = UnmaskType<AnyNumber | keyof ReturnType<typeof defaultRetryStatusCodesLookup>>;
1007
- type RetryCondition<TErrorData> = (context: ErrorContext<{
1008
- ErrorData: TErrorData;
1009
- }>) => Awaitable<boolean>;
1010
- interface RetryOptions<TErrorData> {
1011
- /**
1012
- * Keeps track of the number of times the request has already been retried
1013
- * @internal
1014
- * @deprecated **NOTE**: This property is used internally to track retries. Please abstain from modifying it.
1015
- */
1016
- readonly ["~retryAttemptCount"]?: number;
1017
- /**
1018
- * Number of allowed retry attempts on HTTP errors
1019
- * @default 0
1020
- */
1021
- retryAttempts?: number;
1022
- /**
1023
- * Callback whose return value determines if a request should be retried or not
1024
- */
1025
- retryCondition?: RetryCondition<TErrorData>;
1026
- /**
1027
- * Delay between retries in milliseconds
1028
- * @default 1000
1029
- */
1030
- retryDelay?: number | ((currentAttemptCount: number) => number);
1031
- /**
1032
- * Maximum delay in milliseconds. Only applies to exponential strategy
1033
- * @default 10000
1034
- */
1035
- retryMaxDelay?: number;
1036
- /**
1037
- * HTTP methods that are allowed to retry
1038
- * @default ["GET", "POST"]
1039
- */
1040
- retryMethods?: MethodUnion[];
1041
- /**
1042
- * HTTP status codes that trigger a retry
1043
- */
1044
- retryStatusCodes?: RetryStatusCodes[];
1045
- /**
1046
- * Strategy to use when retrying
1047
- * @default "linear"
1048
- */
1049
- retryStrategy?: "exponential" | "linear";
1088
+ fetchMiddleware?: (context: FetchMiddlewareContext<TCallApiContext>) => FetchImpl;
1050
1089
  } //#endregion
1051
1090
  //#region src/utils/external/error.d.ts
1052
1091
  type HTTPErrorDetails<TErrorData> = Pick<CallApiExtraOptions, "defaultHTTPErrorMessage"> & {
@@ -1129,15 +1168,12 @@ type CallApiRequestOptions = {
1129
1168
  */
1130
1169
  method?: MethodUnion;
1131
1170
  } & Pick<ModifiedRequestInit, FetchSpecificKeysUnion>;
1132
- type CallApiRequestOptionsForHooks = Omit<CallApiRequestOptions, "headers"> & {
1133
- headers: Record<"Authorization" | "Content-Type" | CommonRequestHeaders, string | undefined>;
1134
- };
1135
1171
  type SharedExtraOptions<TCallApiContext extends CallApiContext = DefaultCallApiContext, TData = DefaultDataType, TErrorData = DefaultDataType, TResultMode extends ResultModeType = ResultModeType, TThrowOnError extends ThrowOnErrorBoolean = DefaultThrowOnError, TResponseType extends ResponseTypeType = ResponseTypeType, TPluginArray extends CallApiPlugin[] = DefaultPluginArray, TComputedMergedPluginExtraOptions = Partial<InferPluginExtraOptions<TPluginArray> & InferSchemaOutput<TCallApiContext["InferredExtraOptions"], TCallApiContext["InferredExtraOptions"]>>, TComputedCallApiContext extends CallApiContext = OverrideCallApiContext<TCallApiContext, {
1136
1172
  Data: TData;
1137
1173
  ErrorData: TErrorData;
1138
1174
  InferredExtraOptions: TComputedMergedPluginExtraOptions;
1139
1175
  ResultMode: TResultMode;
1140
- }>> = DedupeOptions & HookConfigOptions & HooksOrHooksArray<NoInferUnMasked<TComputedCallApiContext>> & Middlewares<NoInferUnMasked<TComputedCallApiContext>> & ResultModeOption<TErrorData, TResultMode> & RetryOptions<TErrorData> & TComputedMergedPluginExtraOptions & ThrowOnErrorOption<TErrorData, TThrowOnError> & URLOptions & {
1176
+ }>> = DedupeOptions & HookConfigOptions & HooksOrHooksArray<NoInferUnMasked<TComputedCallApiContext>> & Middlewares<NoInferUnMasked<TComputedCallApiContext>> & RefetchOptions & ResultModeOption<TErrorData, TResultMode> & RetryOptions<TErrorData> & TComputedMergedPluginExtraOptions & ThrowOnErrorOption<TErrorData, TThrowOnError> & URLOptions & {
1141
1177
  /**
1142
1178
  * Automatically add an Authorization header value.
1143
1179
  *
@@ -1612,7 +1648,6 @@ type CallApiExtraOptions<TCallApiContext extends CallApiContext = DefaultCallApi
1612
1648
  */
1613
1649
  schemaConfig?: TSchemaConfig | ((context: GetExtendSchemaConfigContext<TBaseSchemaConfig>) => TSchemaConfig);
1614
1650
  };
1615
- type CallApiExtraOptionsForHooks<TCallApiContext extends CallApiContext = DefaultCallApiContext> = Hooks & Omit<CallApiExtraOptions<TCallApiContext>, keyof Hooks>;
1616
1651
  type InstanceContext = {
1617
1652
  initURL: string;
1618
1653
  options: CallApiExtraOptions;
@@ -1620,7 +1655,7 @@ type InstanceContext = {
1620
1655
  };
1621
1656
  type BaseCallApiConfig<TBaseCallApiContext extends CallApiContext = DefaultCallApiContext, TBaseData = DefaultDataType, TBaseErrorData = DefaultDataType, TBaseResultMode extends ResultModeType = ResultModeType, TBaseThrowOnError extends ThrowOnErrorBoolean = DefaultThrowOnError, TBaseResponseType extends ResponseTypeType = ResponseTypeType, TBaseSchemaAndConfig extends BaseCallApiSchemaAndConfig = BaseCallApiSchemaAndConfig, TBasePluginArray extends CallApiPlugin[] = DefaultPluginArray, TComputedBaseConfig = BaseCallApiExtraOptions<TBaseCallApiContext, TBaseData, TBaseErrorData, TBaseResultMode, TBaseThrowOnError, TBaseResponseType, TBasePluginArray, TBaseSchemaAndConfig>> = (CallApiRequestOptions & TComputedBaseConfig) | ((context: InstanceContext) => CallApiRequestOptions & TComputedBaseConfig);
1622
1657
  type CallApiConfig<TCallApiContext extends CallApiContext = DefaultCallApiContext, TData = DefaultDataType, TErrorData = DefaultDataType, TResultMode extends ResultModeType = ResultModeType, TThrowOnError extends ThrowOnErrorBoolean = DefaultThrowOnError, TResponseType extends ResponseTypeType = ResponseTypeType, TBaseSchemaRoutes extends BaseCallApiSchemaRoutes = BaseCallApiSchemaRoutes, TSchema extends CallApiSchema = CallApiSchema, TBaseSchemaConfig extends CallApiSchemaConfig = CallApiSchemaConfig, TSchemaConfig extends CallApiSchemaConfig = CallApiSchemaConfig, TInitURL extends InitURLOrURLObject = InitURLOrURLObject, TCurrentRouteSchemaKey extends string = string, TBasePluginArray extends CallApiPlugin[] = DefaultPluginArray, TPluginArray extends CallApiPlugin[] = DefaultPluginArray> = InferExtraOptions<TSchema, TBaseSchemaRoutes, TCurrentRouteSchemaKey, TCallApiContext> & InferRequestOptions<TSchema, TInitURL> & Omit<CallApiExtraOptions<TCallApiContext, TData, TErrorData, TResultMode, TThrowOnError, TResponseType, TBasePluginArray, TPluginArray, TBaseSchemaRoutes, TSchema, TBaseSchemaConfig, TSchemaConfig, TCurrentRouteSchemaKey>, keyof InferExtraOptions<CallApiSchema, BaseCallApiSchemaRoutes, string, CallApiContext>> & Omit<CallApiRequestOptions, keyof InferRequestOptions<CallApiSchema, string>>;
1623
- //#endregion
1658
+ type CallApiResultLoose<TData, TErrorData, TResultMode extends ResultModeType = ResultModeType, TThrowOnError extends ThrowOnErrorBoolean = ThrowOnErrorBoolean> = InferCallApiResult<TData, TErrorData, TResultMode, TThrowOnError>; //#endregion
1624
1659
  //#region src/result.d.ts
1625
1660
  type ResponseParser<TData> = (text: string) => Awaitable<TData>;
1626
1661
  declare const getResponseType: <TData>(response: Response, responseParser: ResponseParser<TData>) => {
@@ -1684,7 +1719,7 @@ type ResultModeMap<TData = DefaultDataType, TErrorData = DefaultDataType, TThrow
1684
1719
  type ResultModePlaceholder = null;
1685
1720
  type ResultModeUnion = keyof ResultModeMap;
1686
1721
  type ResultModeType = ResultModePlaceholder | ResultModeUnion;
1687
- //#endregion
1722
+ type InferCallApiResult<TData, TErrorData, TResultMode extends ResultModeType, TThrowOnError extends ThrowOnErrorBoolean, 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; //#endregion
1688
1723
  //#region src/plugins.d.ts
1689
1724
  type PluginSetupContext<TCallApiContext extends CallApiContext = DefaultCallApiContext> = RequestContext<TCallApiContext> & {
1690
1725
  initURL: string;
@@ -1749,7 +1784,6 @@ type DefaultMetaObject = Record<string, unknown>;
1749
1784
  type DefaultCallApiContext = Prettify<OverrideCallApiContext<Required<CallApiContext>, {
1750
1785
  Meta: GlobalMeta;
1751
1786
  }>>; //#endregion
1752
- //#region src/createFetchClient.d.ts
1753
1787
  //#endregion
1754
1788
  //#region src/plugins/logger/logger.d.ts
1755
1789
  type ConsoleLikeObject = {
@@ -1790,53 +1824,63 @@ declare const loggerPlugin: (options?: LoggerOptions) => {
1790
1824
  version: "1.1.0";
1791
1825
  hooks: {
1792
1826
  onRequest: (ctx: RequestContext<Omit<{
1793
- Data: unknown;
1794
- ErrorData: unknown;
1827
+ Data: DefaultDataType;
1828
+ ErrorData: DefaultDataType;
1795
1829
  InferredExtraOptions: unknown;
1796
1830
  ResultMode: ResultModeType;
1797
- Meta: {
1798
- [x: string]: unknown;
1799
- };
1831
+ Meta: GlobalMeta;
1800
1832
  }, "Data" | "ErrorData"> & {
1801
1833
  Data: never;
1802
1834
  ErrorData: never;
1803
1835
  }>) => void;
1804
- onRequestError: (ctx: Omit<{
1836
+ onRequestError: (ctx: (Omit<{
1805
1837
  data: null;
1806
1838
  error: PossibleJavaScriptError;
1807
1839
  response: Response | null;
1808
1840
  }, "data"> & RequestContext<Omit<{
1809
- Data: unknown;
1810
- ErrorData: unknown;
1841
+ Data: DefaultDataType;
1842
+ ErrorData: DefaultDataType;
1811
1843
  InferredExtraOptions: unknown;
1812
1844
  ResultMode: ResultModeType;
1813
- Meta: {
1814
- [x: string]: unknown;
1845
+ Meta: GlobalMeta;
1846
+ }, "Data" | "ErrorData"> & {
1847
+ Data: never;
1848
+ ErrorData: never;
1849
+ }>) | (Omit<{
1850
+ data: null;
1851
+ error: {
1852
+ errorData: never;
1853
+ message: string;
1854
+ name: "HTTPError";
1855
+ originalError: _zayne_labs_callapi_utils0.HTTPError;
1815
1856
  };
1857
+ response: Response;
1858
+ }, "data"> & RequestContext<Omit<{
1859
+ Data: DefaultDataType;
1860
+ ErrorData: DefaultDataType;
1861
+ InferredExtraOptions: unknown;
1862
+ ResultMode: ResultModeType;
1863
+ Meta: GlobalMeta;
1816
1864
  }, "Data" | "ErrorData"> & {
1817
1865
  Data: never;
1818
1866
  ErrorData: never;
1819
- }>) => void;
1867
+ }>)) => void;
1820
1868
  onResponseError: (ctx: ResponseErrorContext<Omit<{
1821
- Data: unknown;
1822
- ErrorData: unknown;
1869
+ Data: DefaultDataType;
1870
+ ErrorData: DefaultDataType;
1823
1871
  InferredExtraOptions: unknown;
1824
1872
  ResultMode: ResultModeType;
1825
- Meta: {
1826
- [x: string]: unknown;
1827
- };
1873
+ Meta: GlobalMeta;
1828
1874
  }, "Data" | "ErrorData"> & {
1829
1875
  Data: never;
1830
1876
  ErrorData: never;
1831
1877
  }>) => void;
1832
1878
  onRetry: (ctx: ErrorContext<Omit<{
1833
- Data: unknown;
1834
- ErrorData: unknown;
1879
+ Data: DefaultDataType;
1880
+ ErrorData: DefaultDataType;
1835
1881
  InferredExtraOptions: unknown;
1836
1882
  ResultMode: ResultModeType;
1837
- Meta: {
1838
- [x: string]: unknown;
1839
- };
1883
+ Meta: GlobalMeta;
1840
1884
  }, "Data" | "ErrorData"> & {
1841
1885
  Data: never;
1842
1886
  ErrorData: never;
@@ -1844,13 +1888,11 @@ declare const loggerPlugin: (options?: LoggerOptions) => {
1844
1888
  retryAttemptCount: number;
1845
1889
  }) => void;
1846
1890
  onSuccess: (ctx: SuccessContext<Omit<{
1847
- Data: unknown;
1848
- ErrorData: unknown;
1891
+ Data: DefaultDataType;
1892
+ ErrorData: DefaultDataType;
1849
1893
  InferredExtraOptions: unknown;
1850
1894
  ResultMode: ResultModeType;
1851
- Meta: {
1852
- [x: string]: unknown;
1853
- };
1895
+ Meta: GlobalMeta;
1854
1896
  }, "Data" | "ErrorData"> & {
1855
1897
  Data: never;
1856
1898
  ErrorData: never;
@@ -1860,13 +1902,11 @@ declare const loggerPlugin: (options?: LoggerOptions) => {
1860
1902
  error: PossibleValidationError;
1861
1903
  response: Response | null;
1862
1904
  }, "data"> & RequestContext<Omit<{
1863
- Data: unknown;
1864
- ErrorData: unknown;
1905
+ Data: DefaultDataType;
1906
+ ErrorData: DefaultDataType;
1865
1907
  InferredExtraOptions: unknown;
1866
1908
  ResultMode: ResultModeType;
1867
- Meta: {
1868
- [x: string]: unknown;
1869
- };
1909
+ Meta: GlobalMeta;
1870
1910
  }, "Data" | "ErrorData"> & {
1871
1911
  Data: never;
1872
1912
  ErrorData: never;
@@ -1875,4 +1915,4 @@ declare const loggerPlugin: (options?: LoggerOptions) => {
1875
1915
  };
1876
1916
  //#endregion
1877
1917
  export { defaultConsoleObject as n, loggerPlugin as r, LoggerOptions as t };
1878
- //# sourceMappingURL=index-BBzD4BfV.d.ts.map
1918
+ //# sourceMappingURL=index-BfWbLAGF.d.ts.map