@zayne-labs/callapi-plugins 4.0.35 → 4.0.37

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