@zayne-labs/callapi-plugins 4.0.8 → 4.0.10
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.
- package/dist/esm/index.d.ts +1740 -16
- package/package.json +3 -3
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,5 +1,1719 @@
|
|
|
1
1
|
import { AnyFunction } from "@zayne-labs/toolkit-type-helpers";
|
|
2
2
|
|
|
3
|
+
//#region ../callapi/dist/esm/common-DvPxUh-h.d.ts
|
|
4
|
+
//#region src/types/type-helpers.d.ts
|
|
5
|
+
type AnyString = string & NonNullable<unknown>;
|
|
6
|
+
type AnyNumber = number & NonNullable<unknown>;
|
|
7
|
+
type AnyFunction$1<TResult$1 = unknown> = (...args: any[]) => TResult$1;
|
|
8
|
+
type Prettify<TObject> = NonNullable<unknown> & { [Key in keyof TObject]: TObject[Key] };
|
|
9
|
+
type WriteableLevel = "deep" | "shallow";
|
|
10
|
+
/**
|
|
11
|
+
* Makes all properties in an object type writeable (removes readonly modifiers).
|
|
12
|
+
* Supports both shallow and deep modes, and handles special cases like arrays, tuples, and unions.
|
|
13
|
+
* @template TObject - The object type to make writeable
|
|
14
|
+
* @template TVariant - The level of writeable transformation ("shallow" | "deep")
|
|
15
|
+
*/
|
|
16
|
+
type ArrayOrObject = Record<number | string | symbol, unknown> | unknown[] | readonly unknown[];
|
|
17
|
+
type Writeable<TObject, TLevel extends WriteableLevel = "shallow"> = TObject extends ArrayOrObject ? { -readonly [Key in keyof TObject]: TLevel extends "deep" ? NonNullable<TObject[Key]> extends ArrayOrObject ? Writeable<TObject[Key], "deep"> : TObject[Key] : TObject[Key] } : TObject;
|
|
18
|
+
type UnionToIntersection<TUnion> = (TUnion extends unknown ? (param: TUnion) => void : never) extends ((param: infer TParam) => void) ? TParam : never;
|
|
19
|
+
type UnmaskType<TValue$1> = {
|
|
20
|
+
_: TValue$1;
|
|
21
|
+
}["_"];
|
|
22
|
+
type RemovePrefix<TPrefix extends "dedupe" | "retry", TKey extends string> = TKey extends `${TPrefix}${infer TRest}` ? Uncapitalize<TRest> : TKey;
|
|
23
|
+
type Awaitable<TValue$1> = Promise<TValue$1> | TValue$1;
|
|
24
|
+
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;
|
|
25
|
+
type CommonAuthorizationHeaders = `${"Basic" | "Bearer" | "Token"} ${string}`;
|
|
26
|
+
type CommonContentTypes = "application/epub+zip" | "application/gzip" | "application/json" | "application/ld+json" | "application/octet-stream" | "application/ogg" | "application/pdf" | "application/rtf" | "application/vnd.ms-fontobject" | "application/wasm" | "application/xhtml+xml" | "application/xml" | "application/zip" | "audio/aac" | "audio/mpeg" | "audio/ogg" | "audio/opus" | "audio/webm" | "audio/x-midi" | "font/otf" | "font/ttf" | "font/woff" | "font/woff2" | "image/avif" | "image/bmp" | "image/gif" | "image/jpeg" | "image/png" | "image/svg+xml" | "image/tiff" | "image/webp" | "image/x-icon" | "model/gltf-binary" | "model/gltf+json" | "text/calendar" | "text/css" | "text/csv" | "text/html" | "text/javascript" | "text/plain" | "video/3gpp" | "video/3gpp2" | "video/av1" | "video/mp2t" | "video/mp4" | "video/mpeg" | "video/ogg" | "video/webm" | "video/x-msvideo" | AnyString;
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/auth.d.ts
|
|
29
|
+
type PossibleAuthValue = Awaitable<string | null | undefined>;
|
|
30
|
+
type PossibleAuthValueOrGetter = PossibleAuthValue | (() => PossibleAuthValue);
|
|
31
|
+
type BearerOrTokenAuth = {
|
|
32
|
+
type?: "Bearer";
|
|
33
|
+
bearer?: PossibleAuthValueOrGetter;
|
|
34
|
+
token?: never;
|
|
35
|
+
} | {
|
|
36
|
+
type?: "Token";
|
|
37
|
+
bearer?: never;
|
|
38
|
+
token?: PossibleAuthValueOrGetter;
|
|
39
|
+
};
|
|
40
|
+
type BasicAuth = {
|
|
41
|
+
type: "Basic";
|
|
42
|
+
username: PossibleAuthValueOrGetter;
|
|
43
|
+
password: PossibleAuthValueOrGetter;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Custom auth
|
|
47
|
+
*
|
|
48
|
+
* @param prefix - prefix of the header
|
|
49
|
+
* @param authValue - value of the header
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* {
|
|
54
|
+
* type: "Custom",
|
|
55
|
+
* prefix: "Token",
|
|
56
|
+
* authValue: "token"
|
|
57
|
+
* }
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
type CustomAuth = {
|
|
61
|
+
type: "Custom";
|
|
62
|
+
prefix: PossibleAuthValueOrGetter;
|
|
63
|
+
value: PossibleAuthValueOrGetter;
|
|
64
|
+
};
|
|
65
|
+
type Auth = PossibleAuthValueOrGetter | BearerOrTokenAuth | BasicAuth | CustomAuth;
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region src/constants/common.d.ts
|
|
68
|
+
declare const fetchSpecificKeys: readonly (keyof RequestInit | "duplex")[];
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/types/standard-schema.d.ts
|
|
71
|
+
/**
|
|
72
|
+
* The Standard Schema interface.
|
|
73
|
+
* @see https://github.com/standard-schema/standard-schema
|
|
74
|
+
*/
|
|
75
|
+
interface StandardSchemaV1<Input$1 = unknown, Output$1 = Input$1> {
|
|
76
|
+
/**
|
|
77
|
+
* The Standard Schema properties.
|
|
78
|
+
*/
|
|
79
|
+
readonly "~standard": StandardSchemaV1.Props<Input$1, Output$1>;
|
|
80
|
+
}
|
|
81
|
+
declare namespace StandardSchemaV1 {
|
|
82
|
+
/**
|
|
83
|
+
* The Standard Schema properties interface.
|
|
84
|
+
*/
|
|
85
|
+
interface Props<Input = unknown, Output = Input> {
|
|
86
|
+
/**
|
|
87
|
+
* Inferred types associated with the schema.
|
|
88
|
+
*/
|
|
89
|
+
readonly types?: Types<Input, Output> | undefined;
|
|
90
|
+
/**
|
|
91
|
+
* Validates unknown input values.
|
|
92
|
+
*/
|
|
93
|
+
readonly validate: (value: unknown) => Promise<Result<Output>> | Result<Output>;
|
|
94
|
+
/**
|
|
95
|
+
* The vendor name of the schema library.
|
|
96
|
+
*/
|
|
97
|
+
readonly vendor: string;
|
|
98
|
+
/**
|
|
99
|
+
* The version number of the standard.
|
|
100
|
+
*/
|
|
101
|
+
readonly version: 1;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The result interface of the validate function.
|
|
105
|
+
*/
|
|
106
|
+
type Result<Output> = FailureResult | SuccessResult<Output>;
|
|
107
|
+
/**
|
|
108
|
+
* The result interface if validation succeeds.
|
|
109
|
+
*/
|
|
110
|
+
interface SuccessResult<Output> {
|
|
111
|
+
/**
|
|
112
|
+
* The non-existent issues.
|
|
113
|
+
*/
|
|
114
|
+
readonly issues?: undefined;
|
|
115
|
+
/**
|
|
116
|
+
* The typed output value.
|
|
117
|
+
*/
|
|
118
|
+
readonly value: Output;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* The result interface if validation fails.
|
|
122
|
+
*/
|
|
123
|
+
interface FailureResult {
|
|
124
|
+
/**
|
|
125
|
+
* The issues of failed validation.
|
|
126
|
+
*/
|
|
127
|
+
readonly issues: readonly Issue[];
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* The issue interface of the failure output.
|
|
131
|
+
*/
|
|
132
|
+
interface Issue {
|
|
133
|
+
/**
|
|
134
|
+
* The error message of the issue.
|
|
135
|
+
*/
|
|
136
|
+
readonly message: string;
|
|
137
|
+
/**
|
|
138
|
+
* The path of the issue, if any.
|
|
139
|
+
*/
|
|
140
|
+
readonly path?: ReadonlyArray<PathSegment | PropertyKey> | undefined;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* The path segment interface of the issue.
|
|
144
|
+
*/
|
|
145
|
+
interface PathSegment {
|
|
146
|
+
/**
|
|
147
|
+
* The key representing a path segment.
|
|
148
|
+
*/
|
|
149
|
+
readonly key: PropertyKey;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* The Standard Schema types interface.
|
|
153
|
+
*/
|
|
154
|
+
interface Types<Input = unknown, Output = Input> {
|
|
155
|
+
/** The input type of the schema. */
|
|
156
|
+
readonly input: Input;
|
|
157
|
+
/** The output type of the schema. */
|
|
158
|
+
readonly output: Output;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Infers the input type of a Standard Schema.
|
|
162
|
+
*/
|
|
163
|
+
type InferInput<Schema extends StandardSchemaV1> = NonNullable<Schema["~standard"]["types"]>["input"];
|
|
164
|
+
/**
|
|
165
|
+
* Infers the output type of a Standard Schema.
|
|
166
|
+
*/
|
|
167
|
+
type InferOutput<Schema extends StandardSchemaV1> = NonNullable<Schema["~standard"]["types"]>["output"];
|
|
168
|
+
}
|
|
169
|
+
//#endregion
|
|
170
|
+
//#region src/url.d.ts
|
|
171
|
+
type AllowedQueryParamValues = UnmaskType<boolean | number | string>;
|
|
172
|
+
type RecordStyleParams = UnmaskType<Record<string, AllowedQueryParamValues>>;
|
|
173
|
+
type TupleStyleParams = UnmaskType<AllowedQueryParamValues[]>;
|
|
174
|
+
type Params = UnmaskType<RecordStyleParams | TupleStyleParams>;
|
|
175
|
+
type Query = UnmaskType<Record<string, AllowedQueryParamValues>>;
|
|
176
|
+
type InitURLOrURLObject = AnyString | RouteKeyMethodsURLUnion | URL;
|
|
177
|
+
interface URLOptions {
|
|
178
|
+
/**
|
|
179
|
+
* Base URL for all API requests. Will only be prepended to relative URLs.
|
|
180
|
+
*
|
|
181
|
+
* Absolute URLs (starting with http/https) will not be prepended by the baseURL.
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* ```ts
|
|
185
|
+
* // Set base URL for all requests
|
|
186
|
+
* baseURL: "https://api.example.com/v1"
|
|
187
|
+
*
|
|
188
|
+
* // Then use relative URLs in requests
|
|
189
|
+
* callApi("/users") // → https://api.example.com/v1/users
|
|
190
|
+
* callApi("/posts/123") // → https://api.example.com/v1/posts/123
|
|
191
|
+
*
|
|
192
|
+
* // Environment-specific base URLs
|
|
193
|
+
* baseURL: process.env.NODE_ENV === "production"
|
|
194
|
+
* ? "https://api.example.com"
|
|
195
|
+
* : "http://localhost:3000/api"
|
|
196
|
+
* ```
|
|
197
|
+
*/
|
|
198
|
+
baseURL?: string;
|
|
199
|
+
/**
|
|
200
|
+
* Resolved request URL after processing baseURL, parameters, and query strings (readonly)
|
|
201
|
+
*
|
|
202
|
+
* This is the final URL that will be used for the HTTP request, computed from
|
|
203
|
+
* baseURL, initURL, params, and query parameters.
|
|
204
|
+
*
|
|
205
|
+
*/
|
|
206
|
+
readonly fullURL?: string;
|
|
207
|
+
/**
|
|
208
|
+
* The original URL string passed to the callApi instance (readonly)
|
|
209
|
+
*
|
|
210
|
+
* This preserves the original URL as provided, including any method modifiers like "@get/" or "@post/".
|
|
211
|
+
*
|
|
212
|
+
*/
|
|
213
|
+
readonly initURL?: string;
|
|
214
|
+
/**
|
|
215
|
+
* The URL string after normalization, with method modifiers removed(readonly)
|
|
216
|
+
*
|
|
217
|
+
* Method modifiers like "@get/", "@post/" are stripped to create a clean URL
|
|
218
|
+
* for parameter substitution and final URL construction.
|
|
219
|
+
*
|
|
220
|
+
*/
|
|
221
|
+
readonly initURLNormalized?: string;
|
|
222
|
+
/**
|
|
223
|
+
* Parameters to be substituted into URL path segments.
|
|
224
|
+
*
|
|
225
|
+
* Supports both object-style (named parameters) and array-style (positional parameters)
|
|
226
|
+
* for flexible URL parameter substitution.
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* ```typescript
|
|
230
|
+
* // Object-style parameters (recommended)
|
|
231
|
+
* const namedParams: URLOptions = {
|
|
232
|
+
* initURL: "/users/:userId/posts/:postId",
|
|
233
|
+
* params: { userId: "123", postId: "456" }
|
|
234
|
+
* };
|
|
235
|
+
* // Results in: /users/123/posts/456
|
|
236
|
+
*
|
|
237
|
+
* // Array-style parameters (positional)
|
|
238
|
+
* const positionalParams: URLOptions = {
|
|
239
|
+
* initURL: "/users/:userId/posts/:postId",
|
|
240
|
+
* params: ["123", "456"] // Maps in order: userId=123, postId=456
|
|
241
|
+
* };
|
|
242
|
+
* // Results in: /users/123/posts/456
|
|
243
|
+
*
|
|
244
|
+
* // Single parameter
|
|
245
|
+
* const singleParam: URLOptions = {
|
|
246
|
+
* initURL: "/users/:id",
|
|
247
|
+
* params: { id: "user-123" }
|
|
248
|
+
* };
|
|
249
|
+
* // Results in: /users/user-123
|
|
250
|
+
* ```
|
|
251
|
+
*/
|
|
252
|
+
params?: Params;
|
|
253
|
+
/**
|
|
254
|
+
* Query parameters to append to the URL as search parameters.
|
|
255
|
+
*
|
|
256
|
+
* These will be serialized into the URL query string using standard
|
|
257
|
+
* URL encoding practices.
|
|
258
|
+
*
|
|
259
|
+
* @example
|
|
260
|
+
* ```typescript
|
|
261
|
+
* // Basic query parameters
|
|
262
|
+
* const queryOptions: URLOptions = {
|
|
263
|
+
* initURL: "/users",
|
|
264
|
+
* query: {
|
|
265
|
+
* page: 1,
|
|
266
|
+
* limit: 10,
|
|
267
|
+
* search: "john doe",
|
|
268
|
+
* active: true
|
|
269
|
+
* }
|
|
270
|
+
* };
|
|
271
|
+
* // Results in: /users?page=1&limit=10&search=john%20doe&active=true
|
|
272
|
+
*
|
|
273
|
+
* // Filtering and sorting
|
|
274
|
+
* const filterOptions: URLOptions = {
|
|
275
|
+
* initURL: "/products",
|
|
276
|
+
* query: {
|
|
277
|
+
* category: "electronics",
|
|
278
|
+
* minPrice: 100,
|
|
279
|
+
* maxPrice: 500,
|
|
280
|
+
* sortBy: "price",
|
|
281
|
+
* order: "asc"
|
|
282
|
+
* }
|
|
283
|
+
* };
|
|
284
|
+
* // Results in: /products?category=electronics&minPrice=100&maxPrice=500&sortBy=price&order=asc
|
|
285
|
+
* ```
|
|
286
|
+
*/
|
|
287
|
+
query?: Query;
|
|
288
|
+
}
|
|
289
|
+
//#endregion
|
|
290
|
+
//#region src/validation.d.ts
|
|
291
|
+
type InferSchemaOutputResult<TSchema$1, TFallbackResult = unknown> = undefined extends TSchema$1 ? TFallbackResult : TSchema$1 extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<TSchema$1> : TSchema$1 extends AnyFunction$1<infer TResult> ? Awaited<TResult> : TFallbackResult;
|
|
292
|
+
interface CallApiSchemaConfig {
|
|
293
|
+
/**
|
|
294
|
+
* The base url of the schema. By default it's the baseURL of the callApi instance.
|
|
295
|
+
*/
|
|
296
|
+
baseURL?: string;
|
|
297
|
+
/**
|
|
298
|
+
* Disables runtime validation for the schema.
|
|
299
|
+
*/
|
|
300
|
+
disableRuntimeValidation?: boolean;
|
|
301
|
+
/**
|
|
302
|
+
* If `true`, the original input value will be used instead of the transformed/validated output.
|
|
303
|
+
*
|
|
304
|
+
* This is useful when you want to validate the input but don't want any transformations
|
|
305
|
+
* applied by the validation schema (e.g., type coercion, default values, etc).
|
|
306
|
+
*/
|
|
307
|
+
disableValidationOutputApplication?: boolean;
|
|
308
|
+
/**
|
|
309
|
+
* Optional url prefix that will be substituted for the `baseURL` of the schemaConfig at runtime.
|
|
310
|
+
*
|
|
311
|
+
* This allows you to reuse the same schema against different base URLs (for example,
|
|
312
|
+
* swapping between `/api/v1` and `/api/v2`) without redefining the entire schema.
|
|
313
|
+
*/
|
|
314
|
+
prefix?: string;
|
|
315
|
+
/**
|
|
316
|
+
* Controls the strictness of API route validation.
|
|
317
|
+
*
|
|
318
|
+
* When true:
|
|
319
|
+
* - Only routes explicitly defined in the schema will be considered valid to typescript and the runtime.
|
|
320
|
+
* - Attempting to call routes not defined in the schema will result in both type errors and runtime validation errors.
|
|
321
|
+
* - Useful for ensuring API calls conform exactly to your schema definition
|
|
322
|
+
*
|
|
323
|
+
* When false or undefined (default):
|
|
324
|
+
* - All routes will be allowed, whether they are defined in the schema or not
|
|
325
|
+
*/
|
|
326
|
+
strict?: boolean;
|
|
327
|
+
}
|
|
328
|
+
interface CallApiSchema {
|
|
329
|
+
/**
|
|
330
|
+
* The schema to use for validating the request body.
|
|
331
|
+
*/
|
|
332
|
+
body?: StandardSchemaV1<Body> | ((body: Body) => Awaitable<Body>);
|
|
333
|
+
/**
|
|
334
|
+
* The schema to use for validating the response data.
|
|
335
|
+
*/
|
|
336
|
+
data?: StandardSchemaV1 | ((data: unknown) => unknown);
|
|
337
|
+
/**
|
|
338
|
+
* The schema to use for validating the response error data.
|
|
339
|
+
*/
|
|
340
|
+
errorData?: StandardSchemaV1 | ((errorData: unknown) => unknown);
|
|
341
|
+
/**
|
|
342
|
+
* The schema to use for validating the request headers.
|
|
343
|
+
*/
|
|
344
|
+
headers?: StandardSchemaV1<HeadersOption | undefined> | ((headers: HeadersOption) => Awaitable<HeadersOption | undefined>);
|
|
345
|
+
/**
|
|
346
|
+
* The schema to use for validating the meta option.
|
|
347
|
+
*/
|
|
348
|
+
meta?: StandardSchemaV1<GlobalMeta | undefined> | ((meta: GlobalMeta) => Awaitable<GlobalMeta | undefined>);
|
|
349
|
+
/**
|
|
350
|
+
* The schema to use for validating the request method.
|
|
351
|
+
*/
|
|
352
|
+
method?: StandardSchemaV1<MethodUnion | undefined> | ((method: MethodUnion) => Awaitable<MethodUnion | undefined>);
|
|
353
|
+
/**
|
|
354
|
+
* The schema to use for validating the request url parameters.
|
|
355
|
+
*/
|
|
356
|
+
params?: StandardSchemaV1<Params | undefined> | ((params: Params) => Awaitable<Params | undefined>);
|
|
357
|
+
/**
|
|
358
|
+
* The schema to use for validating the request url queries.
|
|
359
|
+
*/
|
|
360
|
+
query?: StandardSchemaV1<Query | undefined> | ((query: Query) => Awaitable<Query | undefined>);
|
|
361
|
+
}
|
|
362
|
+
declare const routeKeyMethods: readonly ["delete", "get", "patch", "post", "put"];
|
|
363
|
+
type RouteKeyMethods = (typeof routeKeyMethods)[number];
|
|
364
|
+
type RouteKeyMethodsURLUnion = `@${RouteKeyMethods}/`;
|
|
365
|
+
type BaseCallApiSchemaRoutes = Partial<Record<AnyString | RouteKeyMethodsURLUnion, CallApiSchema>>;
|
|
366
|
+
type BaseCallApiSchemaAndConfig = {
|
|
367
|
+
config?: CallApiSchemaConfig;
|
|
368
|
+
routes: BaseCallApiSchemaRoutes;
|
|
369
|
+
};
|
|
370
|
+
declare const fallBackRouteSchemaKey = ".";
|
|
371
|
+
type FallBackRouteSchemaKey = typeof fallBackRouteSchemaKey;
|
|
372
|
+
//#endregion
|
|
373
|
+
//#region src/error.d.ts
|
|
374
|
+
type HTTPErrorDetails<TErrorData> = Pick<CallApiExtraOptions, "defaultHTTPErrorMessage"> & {
|
|
375
|
+
errorData: TErrorData;
|
|
376
|
+
response: Response;
|
|
377
|
+
};
|
|
378
|
+
declare class HTTPError<TErrorData = Record<string, unknown>> extends Error {
|
|
379
|
+
errorData: HTTPErrorDetails<TErrorData>["errorData"];
|
|
380
|
+
readonly httpErrorSymbol: symbol;
|
|
381
|
+
name: "HTTPError";
|
|
382
|
+
response: HTTPErrorDetails<TErrorData>["response"];
|
|
383
|
+
constructor(errorDetails: HTTPErrorDetails<TErrorData>, errorOptions?: ErrorOptions);
|
|
384
|
+
/**
|
|
385
|
+
* @description Checks if the given error is an instance of HTTPError
|
|
386
|
+
* @param error - The error to check
|
|
387
|
+
* @returns true if the error is an instance of HTTPError, false otherwise
|
|
388
|
+
*/
|
|
389
|
+
static isError<TErrorData>(error: unknown): error is HTTPError<TErrorData>;
|
|
390
|
+
}
|
|
391
|
+
type SafeExtract<TUnion, TKey extends TUnion> = Extract<TUnion, TKey>;
|
|
392
|
+
type ValidationErrorDetails = {
|
|
393
|
+
/**
|
|
394
|
+
* The cause of the validation error.
|
|
395
|
+
*
|
|
396
|
+
* It's either the name the schema for which validation failed, or the name of the schema config option that led to the validation error.
|
|
397
|
+
*/
|
|
398
|
+
issueCause: "unknown" | `schemaConfig-(${SafeExtract<keyof CallApiSchemaConfig, "strict">})` | keyof CallApiSchema;
|
|
399
|
+
/**
|
|
400
|
+
* The issues that caused the validation error.
|
|
401
|
+
*/
|
|
402
|
+
issues: readonly StandardSchemaV1.Issue[];
|
|
403
|
+
/**
|
|
404
|
+
* The response from server, if any.
|
|
405
|
+
*/
|
|
406
|
+
response: Response | null;
|
|
407
|
+
};
|
|
408
|
+
declare class ValidationError extends Error {
|
|
409
|
+
errorData: ValidationErrorDetails["issues"];
|
|
410
|
+
issueCause: ValidationErrorDetails["issueCause"];
|
|
411
|
+
name: "ValidationError";
|
|
412
|
+
response: ValidationErrorDetails["response"];
|
|
413
|
+
readonly validationErrorSymbol: symbol;
|
|
414
|
+
constructor(details: ValidationErrorDetails, errorOptions?: ErrorOptions);
|
|
415
|
+
/**
|
|
416
|
+
* @description Checks if the given error is an instance of ValidationError
|
|
417
|
+
* @param error - The error to check
|
|
418
|
+
* @returns true if the error is an instance of ValidationError, false otherwise
|
|
419
|
+
*/
|
|
420
|
+
static isError(error: unknown): error is ValidationError;
|
|
421
|
+
}
|
|
422
|
+
//#endregion
|
|
423
|
+
//#region src/middlewares.d.ts
|
|
424
|
+
type FetchImpl = UnmaskType<(input: string | Request | URL, init?: RequestInit) => Promise<Response>>;
|
|
425
|
+
interface Middlewares {
|
|
426
|
+
/**
|
|
427
|
+
* Wraps the fetch implementation to intercept requests at the network layer.
|
|
428
|
+
*
|
|
429
|
+
* Takes a context object containing the current fetch function and returns a new fetch function.
|
|
430
|
+
* Use it to cache responses, add logging, handle offline mode, or short-circuit requests etc.
|
|
431
|
+
* Multiple middleware compose in order: plugins → base config → per-request.
|
|
432
|
+
*
|
|
433
|
+
* Unlike `customFetchImpl`, middleware can call through to the original fetch.
|
|
434
|
+
*
|
|
435
|
+
* @example
|
|
436
|
+
* ```ts
|
|
437
|
+
* // Cache responses
|
|
438
|
+
* const cache = new Map();
|
|
439
|
+
* fetchMiddleware: (ctx) => async (input, init) => {
|
|
440
|
+
* const key = input.toString();
|
|
441
|
+
* if (cache.has(key)) return cache.get(key).clone();
|
|
442
|
+
*
|
|
443
|
+
* const response = await ctx.fetchImpl(input, init);
|
|
444
|
+
* cache.set(key, response.clone());
|
|
445
|
+
* return response;
|
|
446
|
+
* }
|
|
447
|
+
*
|
|
448
|
+
* // Handle offline
|
|
449
|
+
* fetchMiddleware: (ctx) => async (input, init) => {
|
|
450
|
+
* if (!navigator.onLine) {
|
|
451
|
+
* return new Response('{"error": "offline"}', { status: 503 });
|
|
452
|
+
* }
|
|
453
|
+
* return ctx.fetchImpl(input, init);
|
|
454
|
+
* }
|
|
455
|
+
* ```
|
|
456
|
+
*/
|
|
457
|
+
fetchMiddleware?: (context: RequestContext & {
|
|
458
|
+
fetchImpl: FetchImpl;
|
|
459
|
+
}) => FetchImpl;
|
|
460
|
+
}
|
|
461
|
+
//#endregion
|
|
462
|
+
//#region src/plugins.d.ts
|
|
463
|
+
type PluginSetupContext<TPluginExtraOptions = unknown> = RequestContext & PluginExtraOptions<TPluginExtraOptions> & {
|
|
464
|
+
initURL: string;
|
|
465
|
+
};
|
|
466
|
+
type PluginInitResult = Partial<Omit<PluginSetupContext, "initURL" | "request"> & {
|
|
467
|
+
initURL: InitURLOrURLObject;
|
|
468
|
+
request: CallApiRequestOptions;
|
|
469
|
+
}>;
|
|
470
|
+
type PluginHooks<TData = never, TErrorData = never, TMoreOptions = unknown> = HooksOrHooksArray<TData, TErrorData, TMoreOptions>;
|
|
471
|
+
interface CallApiPlugin {
|
|
472
|
+
/**
|
|
473
|
+
* Defines additional options that can be passed to callApi
|
|
474
|
+
*/
|
|
475
|
+
defineExtraOptions?: (...params: never[]) => unknown;
|
|
476
|
+
/**
|
|
477
|
+
* A description for the plugin
|
|
478
|
+
*/
|
|
479
|
+
description?: string;
|
|
480
|
+
/**
|
|
481
|
+
* Hooks for the plugin
|
|
482
|
+
*/
|
|
483
|
+
hooks?: PluginHooks | ((context: PluginSetupContext) => Awaitable<PluginHooks>);
|
|
484
|
+
/**
|
|
485
|
+
* A unique id for the plugin
|
|
486
|
+
*/
|
|
487
|
+
id: string;
|
|
488
|
+
/**
|
|
489
|
+
* Middlewares that for the plugin
|
|
490
|
+
*/
|
|
491
|
+
middlewares?: Middlewares | ((context: PluginSetupContext) => Awaitable<Middlewares>);
|
|
492
|
+
/**
|
|
493
|
+
* A name for the plugin
|
|
494
|
+
*/
|
|
495
|
+
name: string;
|
|
496
|
+
/**
|
|
497
|
+
* Base schema for the client.
|
|
498
|
+
*/
|
|
499
|
+
schema?: BaseCallApiSchemaAndConfig;
|
|
500
|
+
/**
|
|
501
|
+
* A function that will be called when the plugin is initialized. This will be called before the any of the other internal functions.
|
|
502
|
+
*/
|
|
503
|
+
setup?: (context: PluginSetupContext) => Awaitable<PluginInitResult> | Awaitable<void>;
|
|
504
|
+
/**
|
|
505
|
+
* A version for the plugin
|
|
506
|
+
*/
|
|
507
|
+
version?: string;
|
|
508
|
+
}
|
|
509
|
+
//#endregion
|
|
510
|
+
//#region src/types/default-types.d.ts
|
|
511
|
+
type DefaultDataType = unknown;
|
|
512
|
+
type DefaultPluginArray = CallApiPlugin[];
|
|
513
|
+
type DefaultThrowOnError = boolean;
|
|
514
|
+
//#endregion
|
|
515
|
+
//#region src/result.d.ts
|
|
516
|
+
type Parser<TData> = (responseString: string) => Awaitable<TData>;
|
|
517
|
+
declare const getResponseType: <TResponse>(response: Response, parser: Parser<TResponse>) => {
|
|
518
|
+
arrayBuffer: () => Promise<ArrayBuffer>;
|
|
519
|
+
blob: () => Promise<Blob>;
|
|
520
|
+
formData: () => Promise<FormData>;
|
|
521
|
+
json: () => Promise<TResponse>;
|
|
522
|
+
stream: () => ReadableStream<Uint8Array<ArrayBuffer>> | null;
|
|
523
|
+
text: () => Promise<string>;
|
|
524
|
+
};
|
|
525
|
+
type InitResponseTypeMap<TResponse$1 = unknown> = ReturnType<typeof getResponseType<TResponse$1>>;
|
|
526
|
+
type ResponseTypeUnion = keyof InitResponseTypeMap | null;
|
|
527
|
+
type ResponseTypeMap<TResponse$1> = { [Key in keyof InitResponseTypeMap<TResponse$1>]: Awaited<ReturnType<InitResponseTypeMap<TResponse$1>[Key]>> };
|
|
528
|
+
type GetResponseType<TResponse$1, TResponseType extends ResponseTypeUnion, TComputedResponseTypeMap extends ResponseTypeMap<TResponse$1> = ResponseTypeMap<TResponse$1>> = null extends TResponseType ? TComputedResponseTypeMap["json"] : TResponseType extends NonNullable<ResponseTypeUnion> ? TComputedResponseTypeMap[TResponseType] : never;
|
|
529
|
+
type CallApiResultSuccessVariant<TData> = {
|
|
530
|
+
data: NoInfer<TData>;
|
|
531
|
+
error: null;
|
|
532
|
+
response: Response;
|
|
533
|
+
};
|
|
534
|
+
type PossibleJavaScriptError = UnmaskType<{
|
|
535
|
+
errorData: false;
|
|
536
|
+
message: string;
|
|
537
|
+
name: "AbortError" | "Error" | "SyntaxError" | "TimeoutError" | "TypeError" | AnyString;
|
|
538
|
+
originalError: DOMException | Error | SyntaxError | TypeError;
|
|
539
|
+
}>;
|
|
540
|
+
type PossibleHTTPError<TErrorData> = UnmaskType<{
|
|
541
|
+
errorData: NoInfer<TErrorData>;
|
|
542
|
+
message: string;
|
|
543
|
+
name: "HTTPError";
|
|
544
|
+
originalError: HTTPError;
|
|
545
|
+
}>;
|
|
546
|
+
type PossibleValidationError = UnmaskType<{
|
|
547
|
+
errorData: ValidationError["errorData"];
|
|
548
|
+
issueCause: ValidationError["issueCause"];
|
|
549
|
+
message: string;
|
|
550
|
+
name: "ValidationError";
|
|
551
|
+
originalError: ValidationError;
|
|
552
|
+
}>;
|
|
553
|
+
type PossibleJavaScriptOrValidationError = UnmaskType<PossibleJavaScriptError | PossibleValidationError>;
|
|
554
|
+
type CallApiResultErrorVariant<TErrorData> = {
|
|
555
|
+
data: null;
|
|
556
|
+
error: PossibleHTTPError<TErrorData>;
|
|
557
|
+
response: Response;
|
|
558
|
+
} | {
|
|
559
|
+
data: null;
|
|
560
|
+
error: PossibleJavaScriptOrValidationError;
|
|
561
|
+
response: Response | null;
|
|
562
|
+
};
|
|
563
|
+
type CallApiSuccessOrErrorVariant<TData, TError> = CallApiResultErrorVariant<TError> | CallApiResultSuccessVariant<TData>;
|
|
564
|
+
type ResultModeMapWithoutException<TData, TErrorData, TResponseType extends ResponseTypeUnion, TComputedData = GetResponseType<TData, TResponseType>, TComputedErrorData = GetResponseType<TErrorData, TResponseType>, TComputedResult extends CallApiSuccessOrErrorVariant<TComputedData, TComputedErrorData> = CallApiSuccessOrErrorVariant<TComputedData, TComputedErrorData>> = UnmaskType<{
|
|
565
|
+
all: TComputedResult;
|
|
566
|
+
onlyData: TComputedResult["data"];
|
|
567
|
+
onlyResponse: TComputedResult["response"];
|
|
568
|
+
}>;
|
|
569
|
+
type ResultModeMapWithException<TData, TResponseType extends ResponseTypeUnion, TComputedData = GetResponseType<TData, TResponseType>, TComputedResult extends CallApiResultSuccessVariant<TComputedData> = CallApiResultSuccessVariant<TComputedData>> = {
|
|
570
|
+
all: TComputedResult;
|
|
571
|
+
onlyData: TComputedResult["data"];
|
|
572
|
+
onlyResponse: TComputedResult["response"];
|
|
573
|
+
};
|
|
574
|
+
type ResultModeMap<TData = DefaultDataType, TErrorData = DefaultDataType, TResponseType extends ResponseTypeUnion = ResponseTypeUnion, TThrowOnError extends ThrowOnErrorUnion = DefaultThrowOnError> = TThrowOnError extends true ? ResultModeMapWithException<TData, TResponseType> : ResultModeMapWithoutException<TData, TErrorData, TResponseType>;
|
|
575
|
+
type ResultModePlaceholder = null;
|
|
576
|
+
type ResultModeUnion = keyof ResultModeMap | ResultModePlaceholder;
|
|
577
|
+
//#endregion
|
|
578
|
+
//#region src/stream.d.ts
|
|
579
|
+
type StreamProgressEvent = {
|
|
580
|
+
/**
|
|
581
|
+
* Current chunk of data being streamed
|
|
582
|
+
*/
|
|
583
|
+
chunk: Uint8Array;
|
|
584
|
+
/**
|
|
585
|
+
* Progress in percentage
|
|
586
|
+
*/
|
|
587
|
+
progress: number;
|
|
588
|
+
/**
|
|
589
|
+
* Total size of data in bytes
|
|
590
|
+
*/
|
|
591
|
+
totalBytes: number;
|
|
592
|
+
/**
|
|
593
|
+
* Amount of data transferred so far
|
|
594
|
+
*/
|
|
595
|
+
transferredBytes: number;
|
|
596
|
+
};
|
|
597
|
+
declare global {
|
|
598
|
+
interface ReadableStream<R> {
|
|
599
|
+
[Symbol.asyncIterator]: () => AsyncIterableIterator<R>;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
//#endregion
|
|
603
|
+
//#region src/hooks.d.ts
|
|
604
|
+
type PluginExtraOptions<TPluginOptions = unknown> = {
|
|
605
|
+
/** Plugin-specific options passed to the plugin configuration */
|
|
606
|
+
options: Partial<TPluginOptions>;
|
|
607
|
+
};
|
|
608
|
+
interface Hooks<TData = DefaultDataType, TErrorData = DefaultDataType, TPluginOptions = unknown> {
|
|
609
|
+
/**
|
|
610
|
+
* Hook called when any error occurs within the request/response lifecycle.
|
|
611
|
+
*
|
|
612
|
+
* This is a unified error handler that catches both request errors (network failures,
|
|
613
|
+
* timeouts, etc.) and response errors (HTTP error status codes). It's essentially
|
|
614
|
+
* a combination of `onRequestError` and `onResponseError` hooks.
|
|
615
|
+
*
|
|
616
|
+
* @param context - Error context containing error details, request info, and response (if available)
|
|
617
|
+
* @returns Promise or void - Hook can be async or sync
|
|
618
|
+
*/
|
|
619
|
+
onError?: (context: ErrorContext<TErrorData> & PluginExtraOptions<TPluginOptions>) => Awaitable<unknown>;
|
|
620
|
+
/**
|
|
621
|
+
* Hook called before the HTTP request is sent and before any internal processing of the request object begins.
|
|
622
|
+
*
|
|
623
|
+
* This is the ideal place to modify request headers, add authentication,
|
|
624
|
+
* implement request logging, or perform any setup before the network call.
|
|
625
|
+
*
|
|
626
|
+
* @param context - Request context with mutable request object and configuration
|
|
627
|
+
* @returns Promise or void - Hook can be async or sync
|
|
628
|
+
*
|
|
629
|
+
*/
|
|
630
|
+
onRequest?: (context: RequestContext & PluginExtraOptions<TPluginOptions>) => Awaitable<unknown>;
|
|
631
|
+
/**
|
|
632
|
+
* Hook called when an error occurs during the fetch request itself.
|
|
633
|
+
*
|
|
634
|
+
* This handles network-level errors like connection failures, timeouts,
|
|
635
|
+
* DNS resolution errors, or other issues that prevent getting an HTTP response.
|
|
636
|
+
* Note that HTTP error status codes (4xx, 5xx) are handled by `onResponseError`.
|
|
637
|
+
*
|
|
638
|
+
* @param context - Request error context with error details and null response
|
|
639
|
+
* @returns Promise or void - Hook can be async or sync
|
|
640
|
+
*/
|
|
641
|
+
onRequestError?: (context: RequestErrorContext & PluginExtraOptions<TPluginOptions>) => Awaitable<unknown>;
|
|
642
|
+
/**
|
|
643
|
+
* Hook called just before the HTTP request is sent and after the request has been processed.
|
|
644
|
+
*
|
|
645
|
+
* @param context - Request context with mutable request object and configuration
|
|
646
|
+
*/
|
|
647
|
+
onRequestReady?: (context: RequestContext & PluginExtraOptions<TPluginOptions>) => Awaitable<unknown>;
|
|
648
|
+
/**
|
|
649
|
+
* Hook called during upload stream progress tracking.
|
|
650
|
+
*
|
|
651
|
+
* This hook is triggered when uploading data (like file uploads) and provides
|
|
652
|
+
* progress information about the upload. Useful for implementing progress bars
|
|
653
|
+
* or upload status indicators.
|
|
654
|
+
*
|
|
655
|
+
* @param context - Request stream context with progress event and request instance
|
|
656
|
+
* @returns Promise or void - Hook can be async or sync
|
|
657
|
+
*
|
|
658
|
+
*/
|
|
659
|
+
onRequestStream?: (context: RequestStreamContext & PluginExtraOptions<TPluginOptions>) => Awaitable<unknown>;
|
|
660
|
+
/**
|
|
661
|
+
* Hook called when any HTTP response is received from the API.
|
|
662
|
+
*
|
|
663
|
+
* This hook is triggered for both successful (2xx) and error (4xx, 5xx) responses.
|
|
664
|
+
* It's useful for response logging, metrics collection, or any processing that
|
|
665
|
+
* should happen regardless of response status.
|
|
666
|
+
*
|
|
667
|
+
* @param context - Response context with either success data or error information
|
|
668
|
+
* @returns Promise or void - Hook can be async or sync
|
|
669
|
+
*
|
|
670
|
+
*/
|
|
671
|
+
onResponse?: (context: ResponseContext<TData, TErrorData> & PluginExtraOptions<TPluginOptions>) => Awaitable<unknown>;
|
|
672
|
+
/**
|
|
673
|
+
* Hook called when an HTTP error response (4xx, 5xx) is received from the API.
|
|
674
|
+
*
|
|
675
|
+
* This handles server-side errors where an HTTP response was successfully received
|
|
676
|
+
* but indicates an error condition. Different from `onRequestError` which handles
|
|
677
|
+
* network-level failures.
|
|
678
|
+
*
|
|
679
|
+
* @param context - Response error context with HTTP error details and response
|
|
680
|
+
* @returns Promise or void - Hook can be async or sync
|
|
681
|
+
*/
|
|
682
|
+
onResponseError?: (context: ResponseErrorContext<TErrorData> & PluginExtraOptions<TPluginOptions>) => Awaitable<unknown>;
|
|
683
|
+
/**
|
|
684
|
+
* Hook called during download stream progress tracking.
|
|
685
|
+
*
|
|
686
|
+
* This hook is triggered when downloading data (like file downloads) and provides
|
|
687
|
+
* progress information about the download. Useful for implementing progress bars
|
|
688
|
+
* or download status indicators.
|
|
689
|
+
*
|
|
690
|
+
* @param context - Response stream context with progress event and response
|
|
691
|
+
* @returns Promise or void - Hook can be async or sync
|
|
692
|
+
*
|
|
693
|
+
*/
|
|
694
|
+
onResponseStream?: (context: ResponseStreamContext & PluginExtraOptions<TPluginOptions>) => Awaitable<unknown>;
|
|
695
|
+
/**
|
|
696
|
+
* Hook called when a request is being retried.
|
|
697
|
+
*
|
|
698
|
+
* This hook is triggered before each retry attempt, providing information about
|
|
699
|
+
* the previous failure and the current retry attempt number. Useful for implementing
|
|
700
|
+
* custom retry logic, exponential backoff, or retry logging.
|
|
701
|
+
*
|
|
702
|
+
* @param context - Retry context with error details and retry attempt count
|
|
703
|
+
* @returns Promise or void - Hook can be async or sync
|
|
704
|
+
*
|
|
705
|
+
*/
|
|
706
|
+
onRetry?: (response: RetryContext<TErrorData> & PluginExtraOptions<TPluginOptions>) => Awaitable<unknown>;
|
|
707
|
+
/**
|
|
708
|
+
* Hook called when a successful response (2xx status) is received from the API.
|
|
709
|
+
*
|
|
710
|
+
* This hook is triggered only for successful responses and provides access to
|
|
711
|
+
* the parsed response data. Ideal for success logging, caching, or post-processing
|
|
712
|
+
* of successful API responses.
|
|
713
|
+
*
|
|
714
|
+
* @param context - Success context with parsed response data and response object
|
|
715
|
+
* @returns Promise or void - Hook can be async or sync
|
|
716
|
+
*
|
|
717
|
+
*/
|
|
718
|
+
onSuccess?: (context: SuccessContext<TData> & PluginExtraOptions<TPluginOptions>) => Awaitable<unknown>;
|
|
719
|
+
/**
|
|
720
|
+
* Hook called when a validation error occurs.
|
|
721
|
+
*
|
|
722
|
+
* This hook is triggered when request or response data fails validation against
|
|
723
|
+
* a defined schema. It provides access to the validation error details and can
|
|
724
|
+
* be used for custom error handling, logging, or fallback behavior.
|
|
725
|
+
*
|
|
726
|
+
* @param context - Validation error context with error details and response (if available)
|
|
727
|
+
* @returns Promise or void - Hook can be async or sync
|
|
728
|
+
*
|
|
729
|
+
*/
|
|
730
|
+
onValidationError?: (context: ValidationErrorContext & PluginExtraOptions<TPluginOptions>) => Awaitable<unknown>;
|
|
731
|
+
}
|
|
732
|
+
type HooksOrHooksArray<TData = DefaultDataType, TErrorData = DefaultDataType, TMoreOptions = unknown> = { [Key in keyof Hooks<TData, TErrorData, TMoreOptions>]: Hooks<TData, TErrorData, TMoreOptions>[Key] | Array<Hooks<TData, TErrorData, TMoreOptions>[Key]> };
|
|
733
|
+
interface HookConfigOptions {
|
|
734
|
+
/**
|
|
735
|
+
* Controls the execution mode of all composed hooks (main + plugin hooks).
|
|
736
|
+
*
|
|
737
|
+
* - **"parallel"**: All hooks execute simultaneously via Promise.all() for better performance
|
|
738
|
+
* - **"sequential"**: All hooks execute one by one in registration order via await in a loop
|
|
739
|
+
*
|
|
740
|
+
* This affects how ALL hooks execute together, regardless of their source (main or plugin).
|
|
741
|
+
*
|
|
742
|
+
* @default "parallel"
|
|
743
|
+
*
|
|
744
|
+
* @example
|
|
745
|
+
* ```ts
|
|
746
|
+
* // Parallel execution (default) - all hooks run simultaneously
|
|
747
|
+
* hooksExecutionMode: "parallel"
|
|
748
|
+
*
|
|
749
|
+
* // Sequential execution - hooks run one after another
|
|
750
|
+
* hooksExecutionMode: "sequential"
|
|
751
|
+
*
|
|
752
|
+
* // Use case: Hooks have dependencies and must run in order
|
|
753
|
+
* const client = callApi.create({
|
|
754
|
+
* hooksExecutionMode: "sequential",
|
|
755
|
+
* plugins: [transformPlugin],
|
|
756
|
+
* onRequest: (ctx) => {
|
|
757
|
+
* // This runs first, then transform plugin runs
|
|
758
|
+
* ctx.request.headers["x-request-id"] = generateId();
|
|
759
|
+
* }
|
|
760
|
+
* });
|
|
761
|
+
*
|
|
762
|
+
* // Use case: Independent operations can run in parallel for speed
|
|
763
|
+
* const client = callApi.create({
|
|
764
|
+
* hooksExecutionMode: "parallel", // Default
|
|
765
|
+
* plugins: [metricsPlugin, cachePlugin, loggingPlugin],
|
|
766
|
+
* onRequest: (ctx) => {
|
|
767
|
+
* // All hooks (main + plugins) run simultaneously
|
|
768
|
+
* addRequestTimestamp(ctx.request);
|
|
769
|
+
* }
|
|
770
|
+
* });
|
|
771
|
+
*
|
|
772
|
+
* // Use case: Error handling hooks that need sequential processing
|
|
773
|
+
* const client = callApi.create({
|
|
774
|
+
* hooksExecutionMode: "sequential",
|
|
775
|
+
* onError: [
|
|
776
|
+
* (ctx) => logError(ctx.error), // Log first
|
|
777
|
+
* (ctx) => reportError(ctx.error), // Then report
|
|
778
|
+
* (ctx) => cleanupResources(ctx) // Finally cleanup
|
|
779
|
+
* ]
|
|
780
|
+
* });
|
|
781
|
+
* ```
|
|
782
|
+
*/
|
|
783
|
+
hooksExecutionMode?: "parallel" | "sequential";
|
|
784
|
+
}
|
|
785
|
+
type RequestContext = {
|
|
786
|
+
/**
|
|
787
|
+
* Base configuration object passed to createFetchClient.
|
|
788
|
+
*
|
|
789
|
+
* Contains the foundational configuration that applies to all requests
|
|
790
|
+
* made by this client instance, such as baseURL, default headers, and
|
|
791
|
+
* global options.
|
|
792
|
+
*/
|
|
793
|
+
baseConfig: BaseCallApiExtraOptions & CallApiRequestOptions;
|
|
794
|
+
/**
|
|
795
|
+
* Instance-specific configuration object passed to the callApi instance.
|
|
796
|
+
*
|
|
797
|
+
* Contains configuration specific to this particular API call, which
|
|
798
|
+
* can override or extend the base configuration.
|
|
799
|
+
*/
|
|
800
|
+
config: CallApiExtraOptions & CallApiRequestOptions;
|
|
801
|
+
/**
|
|
802
|
+
* Merged options combining base config, instance config, and default options.
|
|
803
|
+
*
|
|
804
|
+
* This is the final resolved configuration that will be used for the request,
|
|
805
|
+
* with proper precedence applied (instance > base > defaults).
|
|
806
|
+
*/
|
|
807
|
+
options: CallApiExtraOptionsForHooks;
|
|
808
|
+
/**
|
|
809
|
+
* Merged request object ready to be sent.
|
|
810
|
+
*
|
|
811
|
+
* Contains the final request configuration including URL, method, headers,
|
|
812
|
+
* body, and other fetch options. This object can be modified in onRequest
|
|
813
|
+
* hooks to customize the outgoing request.
|
|
814
|
+
*/
|
|
815
|
+
request: CallApiRequestOptionsForHooks;
|
|
816
|
+
};
|
|
817
|
+
type ValidationErrorContext = UnmaskType<RequestContext & {
|
|
818
|
+
/** Validation error containing details about what failed validation */
|
|
819
|
+
error: PossibleValidationError;
|
|
820
|
+
/** HTTP response object if validation failed on response, null if on request */
|
|
821
|
+
response: Response | null;
|
|
822
|
+
}>;
|
|
823
|
+
type SuccessContext<TData> = UnmaskType<RequestContext & {
|
|
824
|
+
/** Parsed response data with the expected success type */
|
|
825
|
+
data: TData;
|
|
826
|
+
/** HTTP response object for the successful request */
|
|
827
|
+
response: Response;
|
|
828
|
+
}>;
|
|
829
|
+
type ResponseContext<TData, TErrorData> = UnmaskType<RequestContext & (Prettify<CallApiResultSuccessVariant<TData>> | Prettify<Extract<CallApiResultErrorVariant<TErrorData>, {
|
|
830
|
+
error: PossibleHTTPError<TErrorData>;
|
|
831
|
+
}>>)>;
|
|
832
|
+
type RequestErrorContext = RequestContext & {
|
|
833
|
+
/** Error that occurred during the request (network, timeout, etc.) */
|
|
834
|
+
error: PossibleJavaScriptError;
|
|
835
|
+
/** Always null for request errors since no response was received */
|
|
836
|
+
response: null;
|
|
837
|
+
};
|
|
838
|
+
type ErrorContext<TErrorData> = UnmaskType<RequestContext & ({
|
|
839
|
+
/** HTTP error with response data */
|
|
840
|
+
error: PossibleHTTPError<TErrorData>;
|
|
841
|
+
/** HTTP response object containing error status */
|
|
842
|
+
response: Response;
|
|
843
|
+
} | {
|
|
844
|
+
/** Request-level error (network, timeout, validation, etc.) */
|
|
845
|
+
error: PossibleJavaScriptOrValidationError;
|
|
846
|
+
/** Response object if available, null for request errors */
|
|
847
|
+
response: Response | null;
|
|
848
|
+
})>;
|
|
849
|
+
type ResponseErrorContext<TErrorData> = UnmaskType<Extract<ErrorContext<TErrorData>, {
|
|
850
|
+
error: PossibleHTTPError<TErrorData>;
|
|
851
|
+
}> & RequestContext>;
|
|
852
|
+
type RetryContext<TErrorData> = UnmaskType<ErrorContext<TErrorData> & {
|
|
853
|
+
/** Current retry attempt number (1-based, so 1 = first retry) */
|
|
854
|
+
retryAttemptCount: number;
|
|
855
|
+
}>;
|
|
856
|
+
type RequestStreamContext = UnmaskType<RequestContext & {
|
|
857
|
+
/** Progress event containing loaded/total bytes information */
|
|
858
|
+
event: StreamProgressEvent;
|
|
859
|
+
/** The actual Request instance being uploaded */
|
|
860
|
+
requestInstance: Request;
|
|
861
|
+
}>;
|
|
862
|
+
type ResponseStreamContext = UnmaskType<RequestContext & {
|
|
863
|
+
/** Progress event containing loaded/total bytes information */
|
|
864
|
+
event: StreamProgressEvent;
|
|
865
|
+
/** HTTP response object being downloaded */
|
|
866
|
+
response: Response;
|
|
867
|
+
}>;
|
|
868
|
+
//#endregion
|
|
869
|
+
//#region src/dedupe.d.ts
|
|
870
|
+
type DedupeStrategyUnion = UnmaskType<"cancel" | "defer" | "none">;
|
|
871
|
+
type DedupeOptionKeys = Exclude<keyof DedupeOptions, "dedupe">;
|
|
872
|
+
type InnerDedupeOptions = { [Key in DedupeOptionKeys as RemovePrefix<"dedupe", Key>]?: DedupeOptions[Key] };
|
|
873
|
+
type DedupeOptions = {
|
|
874
|
+
/**
|
|
875
|
+
* All dedupe options in a single object instead of separate properties
|
|
876
|
+
*/
|
|
877
|
+
dedupe?: InnerDedupeOptions;
|
|
878
|
+
/**
|
|
879
|
+
* Controls the scope of request deduplication caching.
|
|
880
|
+
*
|
|
881
|
+
* - `"global"`: Shares deduplication cache across all `createFetchClient` instances with the same `dedupeCacheScopeKey`.
|
|
882
|
+
* Useful for applications with multiple API clients that should share deduplication state.
|
|
883
|
+
* - `"local"`: Limits deduplication to requests within the same `createFetchClient` instance.
|
|
884
|
+
* Provides better isolation and is recommended for most use cases.
|
|
885
|
+
*
|
|
886
|
+
*
|
|
887
|
+
* **Real-world Scenarios:**
|
|
888
|
+
* - Use `"global"` when you have multiple API clients (user service, auth service, etc.) that might make overlapping requests
|
|
889
|
+
* - Use `"local"` (default) for single-purpose clients or when you want strict isolation between different parts of your app
|
|
890
|
+
*
|
|
891
|
+
* @example
|
|
892
|
+
* ```ts
|
|
893
|
+
* // Local scope - each client has its own deduplication cache
|
|
894
|
+
* const userClient = createFetchClient({ baseURL: "/api/users" });
|
|
895
|
+
* const postClient = createFetchClient({ baseURL: "/api/posts" });
|
|
896
|
+
* // These clients won't share deduplication state
|
|
897
|
+
*
|
|
898
|
+
* // Global scope - share cache across related clients
|
|
899
|
+
* const userClient = createFetchClient({
|
|
900
|
+
* baseURL: "/api/users",
|
|
901
|
+
* dedupeCacheScope: "global",
|
|
902
|
+
* });
|
|
903
|
+
* const postClient = createFetchClient({
|
|
904
|
+
* baseURL: "/api/posts",
|
|
905
|
+
* dedupeCacheScope: "global",
|
|
906
|
+
* });
|
|
907
|
+
* // These clients will share deduplication state
|
|
908
|
+
* ```
|
|
909
|
+
*
|
|
910
|
+
* @default "local"
|
|
911
|
+
*/
|
|
912
|
+
dedupeCacheScope?: "global" | "local";
|
|
913
|
+
/**
|
|
914
|
+
* Unique namespace for the global deduplication cache when using `dedupeCacheScope: "global"`.
|
|
915
|
+
*
|
|
916
|
+
* This creates logical groupings of deduplication caches. All instances with the same key
|
|
917
|
+
* will share the same cache namespace, allowing fine-grained control over which clients
|
|
918
|
+
* share deduplication state.
|
|
919
|
+
*
|
|
920
|
+
* **Best Practices:**
|
|
921
|
+
* - Use descriptive names that reflect the logical grouping (e.g., "user-service", "analytics-api")
|
|
922
|
+
* - Keep scope keys consistent across related API clients
|
|
923
|
+
* - Consider using different scope keys for different environments (dev, staging, prod)
|
|
924
|
+
* - Avoid overly broad scope keys that might cause unintended cache sharing
|
|
925
|
+
*
|
|
926
|
+
* **Cache Management:**
|
|
927
|
+
* - Each scope key maintains its own independent cache
|
|
928
|
+
* - Caches are automatically cleaned up when no references remain
|
|
929
|
+
* - Consider the memory implications of multiple global scopes
|
|
930
|
+
*
|
|
931
|
+
* @example
|
|
932
|
+
* ```ts
|
|
933
|
+
* // Group related API clients together
|
|
934
|
+
* const userClient = createFetchClient({
|
|
935
|
+
* baseURL: "/api/users",
|
|
936
|
+
* dedupeCacheScope: "global",
|
|
937
|
+
* dedupeCacheScopeKey: "user-service"
|
|
938
|
+
* });
|
|
939
|
+
* const profileClient = createFetchClient({
|
|
940
|
+
* baseURL: "/api/profiles",
|
|
941
|
+
* dedupeCacheScope: "global",
|
|
942
|
+
* dedupeCacheScopeKey: "user-service" // Same scope - will share cache
|
|
943
|
+
* });
|
|
944
|
+
*
|
|
945
|
+
* // Separate analytics client with its own cache
|
|
946
|
+
* const analyticsClient = createFetchClient({
|
|
947
|
+
* baseURL: "/api/analytics",
|
|
948
|
+
* dedupeCacheScope: "global",
|
|
949
|
+
* dedupeCacheScopeKey: "analytics-service" // Different scope
|
|
950
|
+
* });
|
|
951
|
+
*
|
|
952
|
+
* // Environment-specific scoping
|
|
953
|
+
* const apiClient = createFetchClient({
|
|
954
|
+
* dedupeCacheScope: "global",
|
|
955
|
+
* dedupeCacheScopeKey: `api-${process.env.NODE_ENV}` // "api-development", "api-production", etc.
|
|
956
|
+
* });
|
|
957
|
+
* ```
|
|
958
|
+
*
|
|
959
|
+
* @default "default"
|
|
960
|
+
*/
|
|
961
|
+
dedupeCacheScopeKey?: "default" | AnyString | ((context: RequestContext) => string | undefined);
|
|
962
|
+
/**
|
|
963
|
+
* Custom key generator for request deduplication.
|
|
964
|
+
*
|
|
965
|
+
* Override the default key generation strategy to control exactly which requests
|
|
966
|
+
* are considered duplicates. The default key combines URL, method, body, and
|
|
967
|
+
* relevant headers (excluding volatile ones like 'Date', 'Authorization', etc.).
|
|
968
|
+
*
|
|
969
|
+
* **Default Key Generation:**
|
|
970
|
+
* The auto-generated key includes:
|
|
971
|
+
* - Full request URL (including query parameters)
|
|
972
|
+
* - HTTP method (GET, POST, etc.)
|
|
973
|
+
* - Request body (for POST/PUT/PATCH requests)
|
|
974
|
+
* - Stable headers (excludes Date, Authorization, User-Agent, etc.)
|
|
975
|
+
*
|
|
976
|
+
* **Custom Key Best Practices:**
|
|
977
|
+
* - Include only the parts of the request that should affect deduplication
|
|
978
|
+
* - Avoid including volatile data (timestamps, random IDs, etc.)
|
|
979
|
+
* - Consider performance - simpler keys are faster to compute and compare
|
|
980
|
+
* - Ensure keys are deterministic for the same logical request
|
|
981
|
+
* - Use consistent key formats across your application
|
|
982
|
+
*
|
|
983
|
+
* **Performance Considerations:**
|
|
984
|
+
* - Function-based keys are computed on every request - keep them lightweight
|
|
985
|
+
* - String keys are fastest but least flexible
|
|
986
|
+
* - Consider caching expensive key computations if needed
|
|
987
|
+
*
|
|
988
|
+
* @example
|
|
989
|
+
* ```ts
|
|
990
|
+
* import { callApi } from "@zayne-labs/callapi";
|
|
991
|
+
*
|
|
992
|
+
* // Simple static key - useful for singleton requests
|
|
993
|
+
* const config = callApi("/api/config", {
|
|
994
|
+
* dedupeKey: "app-config",
|
|
995
|
+
* dedupeStrategy: "defer" // Share the same config across all requests
|
|
996
|
+
* });
|
|
997
|
+
*
|
|
998
|
+
* // URL and method only - ignore headers and body
|
|
999
|
+
* const userData = callApi("/api/user/123", {
|
|
1000
|
+
* dedupeKey: (context) => `${context.options.method}:${context.options.fullURL}`
|
|
1001
|
+
* });
|
|
1002
|
+
*
|
|
1003
|
+
* // Include specific headers in deduplication
|
|
1004
|
+
* const apiCall = callApi("/api/data", {
|
|
1005
|
+
* dedupeKey: (context) => {
|
|
1006
|
+
* const authHeader = context.request.headers.get("Authorization");
|
|
1007
|
+
* return `${context.options.fullURL}-${authHeader}`;
|
|
1008
|
+
* }
|
|
1009
|
+
* });
|
|
1010
|
+
*
|
|
1011
|
+
* // User-specific deduplication
|
|
1012
|
+
* const userSpecificCall = callApi("/api/dashboard", {
|
|
1013
|
+
* dedupeKey: (context) => {
|
|
1014
|
+
* const userId = context.options.fullURL.match(/user\/(\d+)/)?.[1];
|
|
1015
|
+
* return `dashboard-${userId}`;
|
|
1016
|
+
* }
|
|
1017
|
+
* });
|
|
1018
|
+
*
|
|
1019
|
+
* // Ignore certain query parameters
|
|
1020
|
+
* const searchCall = callApi("/api/search?q=test×tamp=123456", {
|
|
1021
|
+
* dedupeKey: (context) => {
|
|
1022
|
+
* const url = new URL(context.options.fullURL);
|
|
1023
|
+
* url.searchParams.delete("timestamp"); // Remove volatile param
|
|
1024
|
+
* return `search:${url.toString()}`;
|
|
1025
|
+
* }
|
|
1026
|
+
* });
|
|
1027
|
+
* ```
|
|
1028
|
+
*
|
|
1029
|
+
* @default Auto-generated from request details
|
|
1030
|
+
*/
|
|
1031
|
+
dedupeKey?: string | ((context: RequestContext) => string | undefined);
|
|
1032
|
+
/**
|
|
1033
|
+
* Strategy for handling duplicate requests. Can be a static string or callback function.
|
|
1034
|
+
*
|
|
1035
|
+
* **Available Strategies:**
|
|
1036
|
+
* - `"cancel"`: Cancel previous request when new one starts (good for search)
|
|
1037
|
+
* - `"defer"`: Share response between duplicate requests (good for config loading)
|
|
1038
|
+
* - `"none"`: No deduplication, all requests execute independently
|
|
1039
|
+
*
|
|
1040
|
+
* @example
|
|
1041
|
+
* ```ts
|
|
1042
|
+
* // Static strategies
|
|
1043
|
+
* const searchClient = createFetchClient({
|
|
1044
|
+
* dedupeStrategy: "cancel" // Cancel previous searches
|
|
1045
|
+
* });
|
|
1046
|
+
*
|
|
1047
|
+
* const configClient = createFetchClient({
|
|
1048
|
+
* dedupeStrategy: "defer" // Share config across components
|
|
1049
|
+
* });
|
|
1050
|
+
*
|
|
1051
|
+
* // Dynamic strategy based on request
|
|
1052
|
+
* const smartClient = createFetchClient({
|
|
1053
|
+
* dedupeStrategy: (context) => {
|
|
1054
|
+
* return context.options.method === "GET" ? "defer" : "cancel";
|
|
1055
|
+
* }
|
|
1056
|
+
* });
|
|
1057
|
+
*
|
|
1058
|
+
* // Search-as-you-type with cancel strategy
|
|
1059
|
+
* const handleSearch = async (query: string) => {
|
|
1060
|
+
* try {
|
|
1061
|
+
* const { data } = await callApi("/api/search", {
|
|
1062
|
+
* method: "POST",
|
|
1063
|
+
* body: { query },
|
|
1064
|
+
* dedupeStrategy: "cancel",
|
|
1065
|
+
* dedupeKey: "search" // Cancel previous searches, only latest one goes through
|
|
1066
|
+
* });
|
|
1067
|
+
*
|
|
1068
|
+
* updateSearchResults(data);
|
|
1069
|
+
* } catch (error) {
|
|
1070
|
+
* if (error.name === "AbortError") {
|
|
1071
|
+
* // Previous search cancelled - (expected behavior)
|
|
1072
|
+
* return;
|
|
1073
|
+
* }
|
|
1074
|
+
* console.error("Search failed:", error);
|
|
1075
|
+
* }
|
|
1076
|
+
* };
|
|
1077
|
+
*
|
|
1078
|
+
* ```
|
|
1079
|
+
*
|
|
1080
|
+
* @default "cancel"
|
|
1081
|
+
*/
|
|
1082
|
+
dedupeStrategy?: DedupeStrategyUnion | ((context: RequestContext) => DedupeStrategyUnion);
|
|
1083
|
+
};
|
|
1084
|
+
//#endregion
|
|
1085
|
+
//#region src/retry.d.ts
|
|
1086
|
+
declare const defaultRetryStatusCodesLookup: () => Readonly<{
|
|
1087
|
+
408: "Request Timeout";
|
|
1088
|
+
409: "Conflict";
|
|
1089
|
+
425: "Too Early";
|
|
1090
|
+
429: "Too Many Requests";
|
|
1091
|
+
500: "Internal Server Error";
|
|
1092
|
+
502: "Bad Gateway";
|
|
1093
|
+
503: "Service Unavailable";
|
|
1094
|
+
504: "Gateway Timeout";
|
|
1095
|
+
}>;
|
|
1096
|
+
type RetryStatusCodes = UnmaskType<AnyNumber | keyof ReturnType<typeof defaultRetryStatusCodesLookup>>;
|
|
1097
|
+
type RetryCondition<TErrorData> = (context: ErrorContext<TErrorData>) => Awaitable<boolean>;
|
|
1098
|
+
type RetryOptionKeys<TErrorData> = Exclude<keyof RetryOptions<TErrorData>, "~retryAttemptCount" | "retry">;
|
|
1099
|
+
type InnerRetryOptions<TErrorData> = { [Key in RetryOptionKeys<TErrorData> as RemovePrefix<"retry", Key>]?: RetryOptions<TErrorData>[Key] };
|
|
1100
|
+
interface RetryOptions<TErrorData> {
|
|
1101
|
+
/**
|
|
1102
|
+
* Keeps track of the number of times the request has already been retried
|
|
1103
|
+
* @internal
|
|
1104
|
+
* @deprecated **NOTE**: This property is used internally to track retries. Please abstain from modifying it.
|
|
1105
|
+
*/
|
|
1106
|
+
readonly ["~retryAttemptCount"]?: number;
|
|
1107
|
+
/**
|
|
1108
|
+
* All retry options in a single object instead of separate properties
|
|
1109
|
+
*/
|
|
1110
|
+
retry?: InnerRetryOptions<TErrorData>;
|
|
1111
|
+
/**
|
|
1112
|
+
* Number of allowed retry attempts on HTTP errors
|
|
1113
|
+
* @default 0
|
|
1114
|
+
*/
|
|
1115
|
+
retryAttempts?: number;
|
|
1116
|
+
/**
|
|
1117
|
+
* Callback whose return value determines if a request should be retried or not
|
|
1118
|
+
*/
|
|
1119
|
+
retryCondition?: RetryCondition<TErrorData>;
|
|
1120
|
+
/**
|
|
1121
|
+
* Delay between retries in milliseconds
|
|
1122
|
+
* @default 1000
|
|
1123
|
+
*/
|
|
1124
|
+
retryDelay?: number | ((currentAttemptCount: number) => number);
|
|
1125
|
+
/**
|
|
1126
|
+
* Maximum delay in milliseconds. Only applies to exponential strategy
|
|
1127
|
+
* @default 10000
|
|
1128
|
+
*/
|
|
1129
|
+
retryMaxDelay?: number;
|
|
1130
|
+
/**
|
|
1131
|
+
* HTTP methods that are allowed to retry
|
|
1132
|
+
* @default ["GET", "POST"]
|
|
1133
|
+
*/
|
|
1134
|
+
retryMethods?: MethodUnion[];
|
|
1135
|
+
/**
|
|
1136
|
+
* HTTP status codes that trigger a retry
|
|
1137
|
+
*/
|
|
1138
|
+
retryStatusCodes?: RetryStatusCodes[];
|
|
1139
|
+
/**
|
|
1140
|
+
* Strategy to use when retrying
|
|
1141
|
+
* @default "linear"
|
|
1142
|
+
*/
|
|
1143
|
+
retryStrategy?: "exponential" | "linear";
|
|
1144
|
+
}
|
|
1145
|
+
//#endregion
|
|
1146
|
+
//#region src/types/conditional-types.d.ts
|
|
1147
|
+
/**
|
|
1148
|
+
* @description Makes a type partial if the output type of TSchema is not provided or has undefined in the union, otherwise makes it required
|
|
1149
|
+
*/
|
|
1150
|
+
|
|
1151
|
+
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;
|
|
1152
|
+
type JsonPrimitive = boolean | number | string | null | undefined;
|
|
1153
|
+
type SerializableObject = Record<keyof object, unknown>;
|
|
1154
|
+
type SerializableArray = Array<JsonPrimitive | SerializableArray | SerializableObject> | ReadonlyArray<JsonPrimitive | SerializableArray | SerializableObject>;
|
|
1155
|
+
type Body = UnmaskType<RequestInit["body"] | SerializableArray | SerializableObject>;
|
|
1156
|
+
type MethodUnion = UnmaskType<"CONNECT" | "DELETE" | "GET" | "HEAD" | "OPTIONS" | "PATCH" | "POST" | "PUT" | "TRACE" | AnyString>;
|
|
1157
|
+
type HeadersOption = UnmaskType<Record<"Authorization", CommonAuthorizationHeaders | undefined> | Record<"Content-Type", CommonContentTypes | undefined> | Record<CommonRequestHeaders, string | undefined> | Record<string, string | undefined> | Array<[string, string]>>;
|
|
1158
|
+
interface Register {}
|
|
1159
|
+
type GlobalMeta = Register extends {
|
|
1160
|
+
meta?: infer TMeta extends Record<string, unknown>;
|
|
1161
|
+
} ? TMeta : never;
|
|
1162
|
+
type InferPluginOptions<TPluginArray extends CallApiPlugin[]> = UnionToIntersection<TPluginArray extends Array<infer TPlugin> ? TPlugin extends CallApiPlugin ? TPlugin["defineExtraOptions"] extends AnyFunction$1<infer TReturnedSchema> ? InferSchemaOutputResult<TReturnedSchema> : never : never : never>;
|
|
1163
|
+
type ResultModeOption<TErrorData, TResultMode extends ResultModeUnion> = TErrorData extends false ? {
|
|
1164
|
+
resultMode: "onlyData";
|
|
1165
|
+
} : TErrorData extends false | undefined ? {
|
|
1166
|
+
resultMode?: "onlyData";
|
|
1167
|
+
} : {
|
|
1168
|
+
resultMode?: TResultMode;
|
|
1169
|
+
};
|
|
1170
|
+
type ThrowOnErrorUnion = boolean;
|
|
1171
|
+
type ThrowOnErrorType<TErrorData, TThrowOnError extends ThrowOnErrorUnion> = TThrowOnError | ((context: ErrorContext<TErrorData>) => TThrowOnError);
|
|
1172
|
+
type ThrowOnErrorOption<TErrorData, TThrowOnError extends ThrowOnErrorUnion> = TErrorData extends false ? {
|
|
1173
|
+
throwOnError: true;
|
|
1174
|
+
} : TErrorData extends false | undefined ? {
|
|
1175
|
+
throwOnError?: true;
|
|
1176
|
+
} : {
|
|
1177
|
+
throwOnError?: ThrowOnErrorType<TErrorData, TThrowOnError>;
|
|
1178
|
+
};
|
|
1179
|
+
//#endregion
|
|
1180
|
+
//#region src/types/common.d.ts
|
|
1181
|
+
type FetchSpecificKeysUnion = Exclude<(typeof fetchSpecificKeys)[number], "body" | "headers" | "method">;
|
|
1182
|
+
type ModifiedRequestInit = RequestInit & {
|
|
1183
|
+
duplex?: "half";
|
|
1184
|
+
};
|
|
1185
|
+
type CallApiRequestOptions = Prettify<{
|
|
1186
|
+
/**
|
|
1187
|
+
* Body of the request, can be a object or any other supported body type.
|
|
1188
|
+
*/
|
|
1189
|
+
body?: Body;
|
|
1190
|
+
/**
|
|
1191
|
+
* Headers to be used in the request.
|
|
1192
|
+
*/
|
|
1193
|
+
headers?: HeadersOption;
|
|
1194
|
+
/**
|
|
1195
|
+
* HTTP method for the request.
|
|
1196
|
+
* @default "GET"
|
|
1197
|
+
*/
|
|
1198
|
+
method?: MethodUnion;
|
|
1199
|
+
} & Pick<ModifiedRequestInit, FetchSpecificKeysUnion>>;
|
|
1200
|
+
type CallApiRequestOptionsForHooks = Omit<CallApiRequestOptions, "headers"> & {
|
|
1201
|
+
headers: Record<string, string | undefined>;
|
|
1202
|
+
};
|
|
1203
|
+
type SharedExtraOptions<TData = DefaultDataType, TErrorData = DefaultDataType, TResultMode extends ResultModeUnion = ResultModeUnion, TThrowOnError extends ThrowOnErrorUnion = DefaultThrowOnError, TResponseType extends ResponseTypeUnion = ResponseTypeUnion, TPluginArray extends CallApiPlugin[] = DefaultPluginArray> = DedupeOptions & HookConfigOptions & HooksOrHooksArray<TData, TErrorData, Partial<InferPluginOptions<TPluginArray>>> & Middlewares & Partial<InferPluginOptions<TPluginArray>> & ResultModeOption<TErrorData, TResultMode> & RetryOptions<TErrorData> & ThrowOnErrorOption<TErrorData, TThrowOnError> & URLOptions & {
|
|
1204
|
+
/**
|
|
1205
|
+
* Automatically add an Authorization header value.
|
|
1206
|
+
*
|
|
1207
|
+
* Supports multiple authentication patterns:
|
|
1208
|
+
* - String: Direct authorization header value
|
|
1209
|
+
* - Auth object: Structured authentication configuration
|
|
1210
|
+
*
|
|
1211
|
+
* ```
|
|
1212
|
+
*/
|
|
1213
|
+
auth?: Auth;
|
|
1214
|
+
/**
|
|
1215
|
+
* Custom function to serialize request body objects into strings.
|
|
1216
|
+
*
|
|
1217
|
+
* Useful for custom serialization formats or when the default JSON
|
|
1218
|
+
* serialization doesn't meet your needs.
|
|
1219
|
+
*
|
|
1220
|
+
* @example
|
|
1221
|
+
* ```ts
|
|
1222
|
+
* // Custom form data serialization
|
|
1223
|
+
* bodySerializer: (data) => {
|
|
1224
|
+
* const formData = new URLSearchParams();
|
|
1225
|
+
* Object.entries(data).forEach(([key, value]) => {
|
|
1226
|
+
* formData.append(key, String(value));
|
|
1227
|
+
* });
|
|
1228
|
+
* return formData.toString();
|
|
1229
|
+
* }
|
|
1230
|
+
*
|
|
1231
|
+
* // XML serialization
|
|
1232
|
+
* bodySerializer: (data) => {
|
|
1233
|
+
* return `<request>${Object.entries(data)
|
|
1234
|
+
* .map(([key, value]) => `<${key}>${value}</${key}>`)
|
|
1235
|
+
* .join('')}</request>`;
|
|
1236
|
+
* }
|
|
1237
|
+
*
|
|
1238
|
+
* // Custom JSON with specific formatting
|
|
1239
|
+
* bodySerializer: (data) => JSON.stringify(data, null, 2)
|
|
1240
|
+
* ```
|
|
1241
|
+
*/
|
|
1242
|
+
bodySerializer?: (bodyData: Record<string, unknown>) => string;
|
|
1243
|
+
/**
|
|
1244
|
+
* Whether to clone the response so it can be read multiple times.
|
|
1245
|
+
*
|
|
1246
|
+
* By default, response streams can only be consumed once. Enable this when you need
|
|
1247
|
+
* to read the response in multiple places (e.g., in hooks and main code).
|
|
1248
|
+
*
|
|
1249
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Response/clone
|
|
1250
|
+
* @default false
|
|
1251
|
+
*/
|
|
1252
|
+
cloneResponse?: boolean;
|
|
1253
|
+
/**
|
|
1254
|
+
* Custom fetch implementation to replace the default fetch function.
|
|
1255
|
+
*
|
|
1256
|
+
* Useful for testing, adding custom behavior, or using alternative HTTP clients
|
|
1257
|
+
* that implement the fetch API interface.
|
|
1258
|
+
*
|
|
1259
|
+
* @example
|
|
1260
|
+
* ```ts
|
|
1261
|
+
* // Use node-fetch in Node.js environments
|
|
1262
|
+
* import fetch from 'node-fetch';
|
|
1263
|
+
*
|
|
1264
|
+
* // Mock fetch for testing
|
|
1265
|
+
* customFetchImpl: async (url, init) => {
|
|
1266
|
+
* return new Response(JSON.stringify({ mocked: true }), {
|
|
1267
|
+
* status: 200,
|
|
1268
|
+
* headers: { 'Content-Type': 'application/json' }
|
|
1269
|
+
* });
|
|
1270
|
+
* }
|
|
1271
|
+
*
|
|
1272
|
+
* // Add custom logging to all requests
|
|
1273
|
+
* customFetchImpl: async (url, init) => {
|
|
1274
|
+
* console.log(`Fetching: ${url}`);
|
|
1275
|
+
* const response = await fetch(url, init);
|
|
1276
|
+
* console.log(`Response: ${response.status}`);
|
|
1277
|
+
* return response;
|
|
1278
|
+
* }
|
|
1279
|
+
*
|
|
1280
|
+
* // Use with custom HTTP client
|
|
1281
|
+
* customFetchImpl: async (url, init) => {
|
|
1282
|
+
* // Convert to your preferred HTTP client format
|
|
1283
|
+
* return await customHttpClient.request({
|
|
1284
|
+
* url: url.toString(),
|
|
1285
|
+
* method: init?.method || 'GET',
|
|
1286
|
+
* headers: init?.headers,
|
|
1287
|
+
* body: init?.body
|
|
1288
|
+
* });
|
|
1289
|
+
* }
|
|
1290
|
+
* ```
|
|
1291
|
+
*/
|
|
1292
|
+
customFetchImpl?: FetchImpl;
|
|
1293
|
+
/**
|
|
1294
|
+
* Default HTTP error message when server doesn't provide one.
|
|
1295
|
+
*
|
|
1296
|
+
* Can be a static string or a function that receives error context
|
|
1297
|
+
* to generate dynamic error messages based on the response.
|
|
1298
|
+
*
|
|
1299
|
+
* @default "Failed to fetch data from server!"
|
|
1300
|
+
*
|
|
1301
|
+
* @example
|
|
1302
|
+
* ```ts
|
|
1303
|
+
* // Static error message
|
|
1304
|
+
* defaultHTTPErrorMessage: "API request failed. Please try again."
|
|
1305
|
+
*
|
|
1306
|
+
* // Dynamic error message based on status code
|
|
1307
|
+
* defaultHTTPErrorMessage: ({ response }) => {
|
|
1308
|
+
* switch (response.status) {
|
|
1309
|
+
* case 401: return "Authentication required. Please log in.";
|
|
1310
|
+
* case 403: return "Access denied. Insufficient permissions.";
|
|
1311
|
+
* case 404: return "Resource not found.";
|
|
1312
|
+
* case 429: return "Too many requests. Please wait and try again.";
|
|
1313
|
+
* case 500: return "Server error. Please contact support.";
|
|
1314
|
+
* default: return `Request failed with status ${response.status}`;
|
|
1315
|
+
* }
|
|
1316
|
+
* }
|
|
1317
|
+
*
|
|
1318
|
+
* // Include error data in message
|
|
1319
|
+
* defaultHTTPErrorMessage: ({ errorData, response }) => {
|
|
1320
|
+
* const userMessage = errorData?.message || "Unknown error occurred";
|
|
1321
|
+
* return `${userMessage} (Status: ${response.status})`;
|
|
1322
|
+
* }
|
|
1323
|
+
* ```
|
|
1324
|
+
*/
|
|
1325
|
+
defaultHTTPErrorMessage?: string | ((context: Pick<HTTPError<TErrorData>, "errorData" | "response">) => string);
|
|
1326
|
+
/**
|
|
1327
|
+
* Forces calculation of total byte size from request/response body streams.
|
|
1328
|
+
*
|
|
1329
|
+
* Useful when the Content-Length header is missing or incorrect, and you need
|
|
1330
|
+
* accurate size information for progress tracking or bandwidth monitoring.
|
|
1331
|
+
*
|
|
1332
|
+
* @default false
|
|
1333
|
+
*
|
|
1334
|
+
*/
|
|
1335
|
+
forcefullyCalculateStreamSize?: boolean | {
|
|
1336
|
+
request?: boolean;
|
|
1337
|
+
response?: boolean;
|
|
1338
|
+
};
|
|
1339
|
+
/**
|
|
1340
|
+
* Optional metadata field for associating additional information with requests.
|
|
1341
|
+
*
|
|
1342
|
+
* Useful for logging, tracing, or handling specific cases in shared interceptors.
|
|
1343
|
+
* The meta object is passed through to all hooks and can be accessed in error handlers.
|
|
1344
|
+
*
|
|
1345
|
+
* @example
|
|
1346
|
+
* ```ts
|
|
1347
|
+
* const callMainApi = callApi.create({
|
|
1348
|
+
* baseURL: "https://main-api.com",
|
|
1349
|
+
* onResponseError: ({ response, options }) => {
|
|
1350
|
+
* if (options.meta?.userId) {
|
|
1351
|
+
* console.error(`User ${options.meta.userId} made an error`);
|
|
1352
|
+
* }
|
|
1353
|
+
* },
|
|
1354
|
+
* });
|
|
1355
|
+
*
|
|
1356
|
+
* const response = await callMainApi({
|
|
1357
|
+
* url: "https://example.com/api/data",
|
|
1358
|
+
* meta: { userId: "123" },
|
|
1359
|
+
* });
|
|
1360
|
+
*
|
|
1361
|
+
* // Use case: Request tracking
|
|
1362
|
+
* const result = await callMainApi({
|
|
1363
|
+
* url: "https://example.com/api/data",
|
|
1364
|
+
* meta: {
|
|
1365
|
+
* requestId: generateId(),
|
|
1366
|
+
* source: "user-dashboard",
|
|
1367
|
+
* priority: "high"
|
|
1368
|
+
* }
|
|
1369
|
+
* });
|
|
1370
|
+
*
|
|
1371
|
+
* // Use case: Feature flags
|
|
1372
|
+
* const client = callApi.create({
|
|
1373
|
+
* baseURL: "https://api.example.com",
|
|
1374
|
+
* meta: {
|
|
1375
|
+
* features: ["newUI", "betaFeature"],
|
|
1376
|
+
* experiment: "variantA"
|
|
1377
|
+
* }
|
|
1378
|
+
* });
|
|
1379
|
+
* ```
|
|
1380
|
+
*/
|
|
1381
|
+
meta?: GlobalMeta;
|
|
1382
|
+
/**
|
|
1383
|
+
* Custom function to parse response strings into actual value instead of the default response.json().
|
|
1384
|
+
*
|
|
1385
|
+
* Useful when you need custom parsing logic for specific response formats.
|
|
1386
|
+
*
|
|
1387
|
+
* @example
|
|
1388
|
+
* ```ts
|
|
1389
|
+
* responseParser: (responseString) => {
|
|
1390
|
+
* return JSON.parse(responseString);
|
|
1391
|
+
* }
|
|
1392
|
+
*
|
|
1393
|
+
* // Parse XML responses
|
|
1394
|
+
* responseParser: (responseString) => {
|
|
1395
|
+
* const parser = new DOMParser();
|
|
1396
|
+
* const doc = parser.parseFromString(responseString, "text/xml");
|
|
1397
|
+
* return xmlToObject(doc);
|
|
1398
|
+
* }
|
|
1399
|
+
*
|
|
1400
|
+
* // Parse CSV responses
|
|
1401
|
+
* responseParser: (responseString) => {
|
|
1402
|
+
* const lines = responseString.split('\n');
|
|
1403
|
+
* const headers = lines[0].split(',');
|
|
1404
|
+
* const data = lines.slice(1).map(line => {
|
|
1405
|
+
* const values = line.split(',');
|
|
1406
|
+
* return headers.reduce((obj, header, index) => {
|
|
1407
|
+
* obj[header] = values[index];
|
|
1408
|
+
* return obj;
|
|
1409
|
+
* }, {});
|
|
1410
|
+
* });
|
|
1411
|
+
* return data;
|
|
1412
|
+
* }
|
|
1413
|
+
*
|
|
1414
|
+
* ```
|
|
1415
|
+
*/
|
|
1416
|
+
responseParser?: (responseString: string) => Awaitable<TData>;
|
|
1417
|
+
/**
|
|
1418
|
+
* Expected response type, determines how the response body is parsed.
|
|
1419
|
+
*
|
|
1420
|
+
* Different response types trigger different parsing methods:
|
|
1421
|
+
* - **"json"**: Parses as JSON using response.json()
|
|
1422
|
+
* - **"text"**: Returns as plain text using response.text()
|
|
1423
|
+
* - **"blob"**: Returns as Blob using response.blob()
|
|
1424
|
+
* - **"arrayBuffer"**: Returns as ArrayBuffer using response.arrayBuffer()
|
|
1425
|
+
* - **"stream"**: Returns the response body stream directly
|
|
1426
|
+
*
|
|
1427
|
+
* @default "json"
|
|
1428
|
+
*
|
|
1429
|
+
* @example
|
|
1430
|
+
* ```ts
|
|
1431
|
+
* // JSON API responses (default)
|
|
1432
|
+
* responseType: "json"
|
|
1433
|
+
*
|
|
1434
|
+
* // Plain text responses
|
|
1435
|
+
* responseType: "text"
|
|
1436
|
+
* // Usage: const csvData = await callApi("/export.csv", { responseType: "text" });
|
|
1437
|
+
*
|
|
1438
|
+
* // File downloads
|
|
1439
|
+
* responseType: "blob"
|
|
1440
|
+
* // Usage: const file = await callApi("/download/file.pdf", { responseType: "blob" });
|
|
1441
|
+
*
|
|
1442
|
+
* // Binary data
|
|
1443
|
+
* responseType: "arrayBuffer"
|
|
1444
|
+
* // Usage: const buffer = await callApi("/binary-data", { responseType: "arrayBuffer" });
|
|
1445
|
+
*
|
|
1446
|
+
* // Streaming responses
|
|
1447
|
+
* responseType: "stream"
|
|
1448
|
+
* // Usage: const stream = await callApi("/large-dataset", { responseType: "stream" });
|
|
1449
|
+
* ```
|
|
1450
|
+
*/
|
|
1451
|
+
responseType?: TResponseType;
|
|
1452
|
+
/**
|
|
1453
|
+
* Controls what data is included in the returned result object.
|
|
1454
|
+
*
|
|
1455
|
+
* Different modes return different combinations of data, error, and response:
|
|
1456
|
+
* - **"all"**: Returns { data, error, response } - complete result information
|
|
1457
|
+
* - **"onlyData"**: Returns only data (null for errors)
|
|
1458
|
+
*
|
|
1459
|
+
* When combined with throwOnError: true, null/error variants are automatically removed:
|
|
1460
|
+
* - **"all" + throwOnError: true**: Returns { data, error: null, response } (error property is null, throws instead)
|
|
1461
|
+
* - **"onlyData" + throwOnError: true**: Returns data (never null, throws on error)
|
|
1462
|
+
*
|
|
1463
|
+
* @default "all"
|
|
1464
|
+
*
|
|
1465
|
+
* @example
|
|
1466
|
+
* ```ts
|
|
1467
|
+
* // Complete result with all information (default)
|
|
1468
|
+
* const { data, error, response } = await callApi("/users", { resultMode: "all" });
|
|
1469
|
+
* if (error) {
|
|
1470
|
+
* console.error("Request failed:", error);
|
|
1471
|
+
* } else {
|
|
1472
|
+
* console.log("Users:", data);
|
|
1473
|
+
* }
|
|
1474
|
+
*
|
|
1475
|
+
* // Complete result but throws on errors (throwOnError removes error from type)
|
|
1476
|
+
* try {
|
|
1477
|
+
* const { data, response } = await callApi("/users", {
|
|
1478
|
+
* resultMode: "all",
|
|
1479
|
+
* throwOnError: true
|
|
1480
|
+
* });
|
|
1481
|
+
* console.log("Users:", data); // data is never null here
|
|
1482
|
+
* } catch (error) {
|
|
1483
|
+
* console.error("Request failed:", error);
|
|
1484
|
+
* }
|
|
1485
|
+
*
|
|
1486
|
+
* // Only data, returns null on errors
|
|
1487
|
+
* const users = await callApi("/users", { resultMode: "onlyData" });
|
|
1488
|
+
* if (users) {
|
|
1489
|
+
* console.log("Users:", users);
|
|
1490
|
+
* } else {
|
|
1491
|
+
* console.log("Request failed");
|
|
1492
|
+
* }
|
|
1493
|
+
*
|
|
1494
|
+
* // Only data, throws on errors (throwOnError removes null from type)
|
|
1495
|
+
* try {
|
|
1496
|
+
* const users = await callApi("/users", {
|
|
1497
|
+
* resultMode: "onlyData",
|
|
1498
|
+
* throwOnError: true
|
|
1499
|
+
* });
|
|
1500
|
+
* console.log("Users:", users); // users is never null here
|
|
1501
|
+
* } catch (error) {
|
|
1502
|
+
* console.error("Request failed:", error);
|
|
1503
|
+
* }
|
|
1504
|
+
* ```
|
|
1505
|
+
*/
|
|
1506
|
+
resultMode?: TResultMode;
|
|
1507
|
+
/**
|
|
1508
|
+
* Controls whether errors are thrown as exceptions or returned in the result.
|
|
1509
|
+
*
|
|
1510
|
+
* Can be a boolean or a function that receives the error and decides whether to throw.
|
|
1511
|
+
* When true, errors are thrown as exceptions instead of being returned in the result object.
|
|
1512
|
+
*
|
|
1513
|
+
* @default false
|
|
1514
|
+
*
|
|
1515
|
+
* @example
|
|
1516
|
+
* ```ts
|
|
1517
|
+
* // Always throw errors
|
|
1518
|
+
* throwOnError: true
|
|
1519
|
+
* try {
|
|
1520
|
+
* const data = await callApi("/users");
|
|
1521
|
+
* console.log("Users:", data);
|
|
1522
|
+
* } catch (error) {
|
|
1523
|
+
* console.error("Request failed:", error);
|
|
1524
|
+
* }
|
|
1525
|
+
*
|
|
1526
|
+
* // Never throw errors (default)
|
|
1527
|
+
* throwOnError: false
|
|
1528
|
+
* const { data, error } = await callApi("/users");
|
|
1529
|
+
* if (error) {
|
|
1530
|
+
* console.error("Request failed:", error);
|
|
1531
|
+
* }
|
|
1532
|
+
*
|
|
1533
|
+
* // Conditionally throw based on error type
|
|
1534
|
+
* throwOnError: (error) => {
|
|
1535
|
+
* // Throw on client errors (4xx) but not server errors (5xx)
|
|
1536
|
+
* return error.response?.status >= 400 && error.response?.status < 500;
|
|
1537
|
+
* }
|
|
1538
|
+
*
|
|
1539
|
+
* // Throw only on specific status codes
|
|
1540
|
+
* throwOnError: (error) => {
|
|
1541
|
+
* const criticalErrors = [401, 403, 404];
|
|
1542
|
+
* return criticalErrors.includes(error.response?.status);
|
|
1543
|
+
* }
|
|
1544
|
+
*
|
|
1545
|
+
* // Throw on validation errors but not network errors
|
|
1546
|
+
* throwOnError: (error) => {
|
|
1547
|
+
* return error.type === "validation";
|
|
1548
|
+
* }
|
|
1549
|
+
* ```
|
|
1550
|
+
*/
|
|
1551
|
+
throwOnError?: ThrowOnErrorType<TErrorData, TThrowOnError>;
|
|
1552
|
+
/**
|
|
1553
|
+
* Request timeout in milliseconds. Request will be aborted if it takes longer.
|
|
1554
|
+
*
|
|
1555
|
+
* Useful for preventing requests from hanging indefinitely and providing
|
|
1556
|
+
* better user experience with predictable response times.
|
|
1557
|
+
*
|
|
1558
|
+
* @example
|
|
1559
|
+
* ```ts
|
|
1560
|
+
* // 5 second timeout
|
|
1561
|
+
* timeout: 5000
|
|
1562
|
+
*
|
|
1563
|
+
* // Different timeouts for different endpoints
|
|
1564
|
+
* const quickApi = createFetchClient({ timeout: 3000 }); // 3s for fast endpoints
|
|
1565
|
+
* const slowApi = createFetchClient({ timeout: 30000 }); // 30s for slow operations
|
|
1566
|
+
*
|
|
1567
|
+
* // Per-request timeout override
|
|
1568
|
+
* await callApi("/quick-data", { timeout: 1000 });
|
|
1569
|
+
* await callApi("/slow-report", { timeout: 60000 });
|
|
1570
|
+
*
|
|
1571
|
+
* // No timeout (use with caution)
|
|
1572
|
+
* timeout: 0
|
|
1573
|
+
* ```
|
|
1574
|
+
*/
|
|
1575
|
+
timeout?: number;
|
|
1576
|
+
};
|
|
1577
|
+
type BaseCallApiExtraOptions<TBaseData = DefaultDataType, TBaseErrorData = DefaultDataType, TBaseResultMode extends ResultModeUnion = ResultModeUnion, TBaseThrowOnError extends ThrowOnErrorUnion = DefaultThrowOnError, TBaseResponseType extends ResponseTypeUnion = ResponseTypeUnion, TBasePluginArray extends CallApiPlugin[] = DefaultPluginArray, TBaseSchemaAndConfig extends BaseCallApiSchemaAndConfig = BaseCallApiSchemaAndConfig> = SharedExtraOptions<TBaseData, TBaseErrorData, TBaseResultMode, TBaseThrowOnError, TBaseResponseType, TBasePluginArray> & {
|
|
1578
|
+
/**
|
|
1579
|
+
* Array of base CallApi plugins to extend library functionality.
|
|
1580
|
+
*
|
|
1581
|
+
* Base plugins are applied to all instances created from this base configuration
|
|
1582
|
+
* and provide foundational functionality like authentication, logging, or caching.
|
|
1583
|
+
*
|
|
1584
|
+
* @example
|
|
1585
|
+
* ```ts
|
|
1586
|
+
* // Add logging plugin
|
|
1587
|
+
*
|
|
1588
|
+
* // Create base client with common plugins
|
|
1589
|
+
* const callApi = createFetchClient({
|
|
1590
|
+
* baseURL: "https://api.example.com",
|
|
1591
|
+
* plugins: [loggerPlugin({ enabled: true })]
|
|
1592
|
+
* });
|
|
1593
|
+
*
|
|
1594
|
+
* // All requests inherit base plugins
|
|
1595
|
+
* await callApi("/users");
|
|
1596
|
+
* await callApi("/posts");
|
|
1597
|
+
*
|
|
1598
|
+
* ```
|
|
1599
|
+
*/
|
|
1600
|
+
plugins?: TBasePluginArray;
|
|
1601
|
+
/**
|
|
1602
|
+
* Base validation schemas for the client configuration.
|
|
1603
|
+
*
|
|
1604
|
+
* Defines validation rules for requests and responses that apply to all
|
|
1605
|
+
* instances created from this base configuration. Provides type safety
|
|
1606
|
+
* and runtime validation for API interactions.
|
|
1607
|
+
*/
|
|
1608
|
+
schema?: TBaseSchemaAndConfig;
|
|
1609
|
+
/**
|
|
1610
|
+
* Controls which configuration parts skip automatic merging between base and instance configs.
|
|
1611
|
+
*
|
|
1612
|
+
* By default, CallApi automatically merges base configuration with instance configuration.
|
|
1613
|
+
* This option allows you to disable automatic merging for specific parts when you need
|
|
1614
|
+
* manual control over how configurations are combined.
|
|
1615
|
+
*
|
|
1616
|
+
* @enum
|
|
1617
|
+
* - **"all"**: Disables automatic merging for both request options and extra options
|
|
1618
|
+
* - **"options"**: Disables automatic merging of extra options only (hooks, plugins, etc.)
|
|
1619
|
+
* - **"request"**: Disables automatic merging of request options only (headers, body, etc.)
|
|
1620
|
+
*
|
|
1621
|
+
* @example
|
|
1622
|
+
* ```ts
|
|
1623
|
+
* // Skip all automatic merging - full manual control
|
|
1624
|
+
* const client = callApi.create((ctx) => ({
|
|
1625
|
+
* skipAutoMergeFor: "all",
|
|
1626
|
+
*
|
|
1627
|
+
* // Manually decide what to merge
|
|
1628
|
+
* baseURL: ctx.options.baseURL, // Keep base URL
|
|
1629
|
+
* timeout: 5000, // Override timeout
|
|
1630
|
+
* headers: {
|
|
1631
|
+
* ...ctx.request.headers, // Merge headers manually
|
|
1632
|
+
* "X-Custom": "value" // Add custom header
|
|
1633
|
+
* }
|
|
1634
|
+
* }));
|
|
1635
|
+
*
|
|
1636
|
+
* // Skip options merging - manual plugin/hook control
|
|
1637
|
+
* const client = callApi.create((ctx) => ({
|
|
1638
|
+
* skipAutoMergeFor: "options",
|
|
1639
|
+
*
|
|
1640
|
+
* // Manually control which plugins to use
|
|
1641
|
+
* plugins: [
|
|
1642
|
+
* ...ctx.options.plugins?.filter(p => p.name !== "unwanted") || [],
|
|
1643
|
+
* customPlugin
|
|
1644
|
+
* ],
|
|
1645
|
+
*
|
|
1646
|
+
* // Request options still auto-merge
|
|
1647
|
+
* method: "POST"
|
|
1648
|
+
* }));
|
|
1649
|
+
*
|
|
1650
|
+
* // Skip request merging - manual request control
|
|
1651
|
+
* const client = callApi.create((ctx) => ({
|
|
1652
|
+
* skipAutoMergeFor: "request",
|
|
1653
|
+
*
|
|
1654
|
+
* // Extra options still auto-merge (plugins, hooks, etc.)
|
|
1655
|
+
*
|
|
1656
|
+
* // Manually control request options
|
|
1657
|
+
* headers: {
|
|
1658
|
+
* "Content-Type": "application/json",
|
|
1659
|
+
* // Don't merge base headers
|
|
1660
|
+
* },
|
|
1661
|
+
* method: ctx.request.method || "GET"
|
|
1662
|
+
* }));
|
|
1663
|
+
*
|
|
1664
|
+
* // Use case: Conditional merging based on request
|
|
1665
|
+
* const client = createFetchClient((ctx) => ({
|
|
1666
|
+
* skipAutoMergeFor: "options",
|
|
1667
|
+
*
|
|
1668
|
+
* // Only use auth plugin for protected routes
|
|
1669
|
+
* plugins: ctx.initURL.includes("/protected/")
|
|
1670
|
+
* ? [...(ctx.options.plugins || []), authPlugin]
|
|
1671
|
+
* : ctx.options.plugins?.filter(p => p.name !== "auth") || []
|
|
1672
|
+
* }));
|
|
1673
|
+
* ```
|
|
1674
|
+
*/
|
|
1675
|
+
skipAutoMergeFor?: "all" | "options" | "request";
|
|
1676
|
+
};
|
|
1677
|
+
type InferExtendSchemaContext<TBaseSchemaRoutes extends BaseCallApiSchemaRoutes, TCurrentRouteSchemaKey extends string> = {
|
|
1678
|
+
baseSchemaRoutes: TBaseSchemaRoutes;
|
|
1679
|
+
currentRouteSchema: GetCurrentRouteSchema<TBaseSchemaRoutes, TCurrentRouteSchemaKey>;
|
|
1680
|
+
};
|
|
1681
|
+
type InferExtendSchemaConfigContext<TBaseSchemaConfig extends CallApiSchemaConfig> = {
|
|
1682
|
+
baseSchemaConfig: TBaseSchemaConfig;
|
|
1683
|
+
};
|
|
1684
|
+
type InferExtendPluginContext<TBasePluginArray extends CallApiPlugin[]> = {
|
|
1685
|
+
basePlugins: TBasePluginArray;
|
|
1686
|
+
};
|
|
1687
|
+
type CallApiExtraOptions<TData = DefaultDataType, TErrorData = DefaultDataType, TResultMode extends ResultModeUnion = ResultModeUnion, TThrowOnError extends ThrowOnErrorUnion = DefaultThrowOnError, TResponseType extends ResponseTypeUnion = ResponseTypeUnion, TBasePluginArray extends CallApiPlugin[] = DefaultPluginArray, TPluginArray extends CallApiPlugin[] = DefaultPluginArray, TBaseSchemaRoutes extends BaseCallApiSchemaRoutes = BaseCallApiSchemaRoutes, TSchema$1 extends CallApiSchema = CallApiSchema, TBaseSchemaConfig extends CallApiSchemaConfig = CallApiSchemaConfig, TSchemaConfig extends CallApiSchemaConfig = CallApiSchemaConfig, TCurrentRouteSchemaKey extends string = string, TComputedPluginContext = InferExtendPluginContext<TBasePluginArray>, TComputedSchemaContext = InferExtendSchemaContext<TBaseSchemaRoutes, TCurrentRouteSchemaKey>, TComputedSchemaConfigContext = InferExtendSchemaConfigContext<TBaseSchemaConfig>> = SharedExtraOptions<TData, TErrorData, TResultMode, TThrowOnError, TResponseType, TPluginArray> & {
|
|
1688
|
+
/**
|
|
1689
|
+
* Array of instance-specific CallApi plugins or a function to configure plugins.
|
|
1690
|
+
*
|
|
1691
|
+
* Instance plugins are added to the base plugins and provide functionality
|
|
1692
|
+
* specific to this particular API instance. Can be a static array or a function
|
|
1693
|
+
* that receives base plugins and returns the instance plugins.
|
|
1694
|
+
*
|
|
1695
|
+
*/
|
|
1696
|
+
plugins?: TPluginArray | ((context: TComputedPluginContext) => TPluginArray);
|
|
1697
|
+
/**
|
|
1698
|
+
* For instance-specific validation schemas
|
|
1699
|
+
*
|
|
1700
|
+
* Defines validation rules specific to this API instance, extending or overriding the base schema.
|
|
1701
|
+
*
|
|
1702
|
+
* Can be a static schema object or a function that receives base schema context and returns instance schemas.
|
|
1703
|
+
*
|
|
1704
|
+
*/
|
|
1705
|
+
schema?: TSchema$1 | ((context: TComputedSchemaContext) => TSchema$1);
|
|
1706
|
+
/**
|
|
1707
|
+
* Instance-specific schema configuration or a function to configure schema behavior.
|
|
1708
|
+
*
|
|
1709
|
+
* Controls how validation schemas are applied and behave for this specific API instance.
|
|
1710
|
+
* Can override base schema configuration or extend it with instance-specific validation rules.
|
|
1711
|
+
*
|
|
1712
|
+
*/
|
|
1713
|
+
schemaConfig?: TSchemaConfig | ((context: TComputedSchemaConfigContext) => TSchemaConfig);
|
|
1714
|
+
};
|
|
1715
|
+
type CallApiExtraOptionsForHooks = Hooks & Omit<CallApiExtraOptions, keyof Hooks>;
|
|
1716
|
+
//#endregion
|
|
3
1717
|
//#region src/logger/logger.d.ts
|
|
4
1718
|
type ConsoleLikeObject = {
|
|
5
1719
|
error: AnyFunction<void>;
|
|
@@ -38,32 +1752,42 @@ declare const loggerPlugin: (options?: LoggerOptions) => {
|
|
|
38
1752
|
name: "Logger";
|
|
39
1753
|
version: "1.1.0";
|
|
40
1754
|
hooks: {
|
|
41
|
-
onRequest: (ctx:
|
|
42
|
-
onRequestError: (ctx:
|
|
43
|
-
error:
|
|
1755
|
+
onRequest: (ctx: RequestContext & PluginExtraOptions<unknown>) => void;
|
|
1756
|
+
onRequestError: (ctx: RequestContext & {
|
|
1757
|
+
error: PossibleJavaScriptError;
|
|
44
1758
|
response: null;
|
|
45
|
-
} &
|
|
46
|
-
onResponseError: (ctx:
|
|
47
|
-
error:
|
|
1759
|
+
} & PluginExtraOptions<unknown>) => void;
|
|
1760
|
+
onResponseError: (ctx: RequestContext & {
|
|
1761
|
+
error: {
|
|
1762
|
+
errorData: never;
|
|
1763
|
+
message: string;
|
|
1764
|
+
name: "HTTPError";
|
|
1765
|
+
originalError: HTTPError;
|
|
1766
|
+
};
|
|
48
1767
|
response: Response;
|
|
49
|
-
} &
|
|
50
|
-
onRetry: (ctx: ((
|
|
51
|
-
error:
|
|
1768
|
+
} & PluginExtraOptions<unknown>) => void;
|
|
1769
|
+
onRetry: (ctx: ((RequestContext & ({
|
|
1770
|
+
error: PossibleJavaScriptOrValidationError;
|
|
52
1771
|
response: Response | null;
|
|
53
1772
|
} | {
|
|
54
|
-
error:
|
|
1773
|
+
error: {
|
|
1774
|
+
errorData: never;
|
|
1775
|
+
message: string;
|
|
1776
|
+
name: "HTTPError";
|
|
1777
|
+
originalError: HTTPError;
|
|
1778
|
+
};
|
|
55
1779
|
response: Response;
|
|
56
1780
|
})) & {
|
|
57
1781
|
retryAttemptCount: number;
|
|
58
|
-
}) &
|
|
59
|
-
onSuccess: (ctx:
|
|
1782
|
+
}) & PluginExtraOptions<unknown>) => void;
|
|
1783
|
+
onSuccess: (ctx: RequestContext & {
|
|
60
1784
|
data: never;
|
|
61
1785
|
response: Response;
|
|
62
|
-
} &
|
|
63
|
-
onValidationError: (ctx:
|
|
64
|
-
error:
|
|
1786
|
+
} & PluginExtraOptions<unknown>) => void;
|
|
1787
|
+
onValidationError: (ctx: RequestContext & {
|
|
1788
|
+
error: PossibleValidationError;
|
|
65
1789
|
response: Response | null;
|
|
66
|
-
} &
|
|
1790
|
+
} & PluginExtraOptions<unknown>) => void;
|
|
67
1791
|
};
|
|
68
1792
|
};
|
|
69
1793
|
//#endregion
|