aspi 1.1.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +292 -0
- package/dist/index.cjs +1237 -0
- package/dist/index.d.cts +1285 -0
- package/dist/index.d.ts +1285 -0
- package/dist/index.js +1210 -0
- package/package.json +56 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1237 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
Aspi: () => Aspi,
|
|
24
|
+
AspiError: () => AspiError,
|
|
25
|
+
CustomError: () => CustomError,
|
|
26
|
+
Request: () => Request,
|
|
27
|
+
Result: () => result_exports,
|
|
28
|
+
getHttpErrorStatus: () => getHttpErrorStatus,
|
|
29
|
+
httpErrors: () => httpErrors
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(index_exports);
|
|
32
|
+
|
|
33
|
+
// src/http.ts
|
|
34
|
+
var httpErrors = {
|
|
35
|
+
BAD_REQUEST: 400,
|
|
36
|
+
UNAUTHORIZED: 401,
|
|
37
|
+
FORBIDDEN: 403,
|
|
38
|
+
NOT_FOUND: 404,
|
|
39
|
+
METHOD_NOT_ALLOWED: 405,
|
|
40
|
+
CONFLICT: 409,
|
|
41
|
+
PAYLOAD_TOO_LARGE: 413,
|
|
42
|
+
UNSUPPORTED_MEDIA_TYPE: 415,
|
|
43
|
+
UNPROCESSABLE_ENTITY: 422,
|
|
44
|
+
TOO_MANY_REQUESTS: 429,
|
|
45
|
+
INTERNAL_SERVER_ERROR: 500,
|
|
46
|
+
NOT_IMPLEMENTED: 501,
|
|
47
|
+
BAD_GATEWAY: 502,
|
|
48
|
+
SERVICE_UNAVAILABLE: 503,
|
|
49
|
+
GATEWAY_TIMEOUT: 504
|
|
50
|
+
};
|
|
51
|
+
var getHttpErrorStatus = (status) => {
|
|
52
|
+
return Object.keys(httpErrors).find(
|
|
53
|
+
(key) => httpErrors[key] === status
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// src/error.ts
|
|
58
|
+
var AspiError = class extends Error {
|
|
59
|
+
tag = "aspiError";
|
|
60
|
+
request;
|
|
61
|
+
response;
|
|
62
|
+
/**
|
|
63
|
+
* Creates an instance of AspiError
|
|
64
|
+
* @param {string} message - The error message
|
|
65
|
+
* @param {AspiRe} request - The request configuration
|
|
66
|
+
* @param {AspiResponse} response - The error response
|
|
67
|
+
*/
|
|
68
|
+
constructor(message, request, response) {
|
|
69
|
+
super(message);
|
|
70
|
+
this.request = request;
|
|
71
|
+
this.response = response;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Conditionally executes callback if status matches
|
|
75
|
+
* @template T
|
|
76
|
+
* @param {HttpErrorStatus} status - The status to match against
|
|
77
|
+
* @param {(args: { request: AspiRequest; response: AspiResponse }) => T} cb - Callback to execute on match
|
|
78
|
+
* @returns {T | undefined} Result of callback if status matches, undefined otherwise
|
|
79
|
+
*/
|
|
80
|
+
ifMatch(status, cb) {
|
|
81
|
+
if (this.response.statusText === status) {
|
|
82
|
+
return cb({
|
|
83
|
+
request: this.request,
|
|
84
|
+
response: this.response
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
var CustomError = class extends Error {
|
|
90
|
+
tag;
|
|
91
|
+
data;
|
|
92
|
+
/**
|
|
93
|
+
* Creates an instance of CustomError
|
|
94
|
+
* @param {Tag} tag - Tag identifying the error type
|
|
95
|
+
* @param {A} data - Additional error data
|
|
96
|
+
*/
|
|
97
|
+
constructor(tag, data) {
|
|
98
|
+
super(tag);
|
|
99
|
+
this.tag = tag;
|
|
100
|
+
this.data = data;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// src/result.ts
|
|
105
|
+
var result_exports = {};
|
|
106
|
+
__export(result_exports, {
|
|
107
|
+
catchAllErrors: () => catchAllErrors,
|
|
108
|
+
catchError: () => catchError,
|
|
109
|
+
catchErrors: () => catchErrors,
|
|
110
|
+
err: () => err,
|
|
111
|
+
getErrorOrNull: () => getErrorOrNull,
|
|
112
|
+
getOrElse: () => getOrElse,
|
|
113
|
+
getOrNull: () => getOrNull,
|
|
114
|
+
getOrThrow: () => getOrThrow,
|
|
115
|
+
getOrThrowWith: () => getOrThrowWith,
|
|
116
|
+
isErr: () => isErr,
|
|
117
|
+
isOk: () => isOk,
|
|
118
|
+
map: () => map,
|
|
119
|
+
mapErr: () => mapErr,
|
|
120
|
+
match: () => match,
|
|
121
|
+
ok: () => ok,
|
|
122
|
+
pipe: () => pipe
|
|
123
|
+
});
|
|
124
|
+
var ok = (value) => ({ __tag: "ok", value });
|
|
125
|
+
var err = (error) => ({ __tag: "err", error });
|
|
126
|
+
var isOk = (result) => result.__tag === "ok";
|
|
127
|
+
var isErr = (result) => !isOk(result);
|
|
128
|
+
function map(selfOrFn, fn) {
|
|
129
|
+
if (fn) {
|
|
130
|
+
const self = selfOrFn;
|
|
131
|
+
return isOk(self) ? ok(fn(self.value)) : self;
|
|
132
|
+
} else {
|
|
133
|
+
const mappingFn = selfOrFn;
|
|
134
|
+
return (self) => isOk(self) ? ok(mappingFn(self.value)) : self;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function mapErr(selfOrFn, fn) {
|
|
138
|
+
if (fn) {
|
|
139
|
+
const self = selfOrFn;
|
|
140
|
+
return isErr(self) ? err(fn(self.error)) : self;
|
|
141
|
+
} else {
|
|
142
|
+
const mappingFn = selfOrFn;
|
|
143
|
+
return (self) => isErr(self) ? err(mappingFn(self.error)) : self;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function match(selfOrHandlers, handlers) {
|
|
147
|
+
if (handlers) {
|
|
148
|
+
const self = selfOrHandlers;
|
|
149
|
+
return isOk(self) ? handlers.onOk(self.value) : handlers.onErr(self.error);
|
|
150
|
+
} else {
|
|
151
|
+
const handlersObj = selfOrHandlers;
|
|
152
|
+
return (self) => isOk(self) ? handlersObj.onOk(self.value) : handlersObj.onErr(self.error);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
var getOrNull = (result) => {
|
|
156
|
+
if (isOk(result)) {
|
|
157
|
+
return result.value;
|
|
158
|
+
} else {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
var getErrorOrNull = (result) => {
|
|
163
|
+
if (isErr(result)) {
|
|
164
|
+
return result.error;
|
|
165
|
+
} else {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
var getOrElse = (result, fallback) => isOk(result) ? result.value : fallback;
|
|
170
|
+
var getOrThrow = (result) => {
|
|
171
|
+
if (isOk(result)) {
|
|
172
|
+
return result.value;
|
|
173
|
+
} else {
|
|
174
|
+
throw result.error;
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
function getOrThrowWith(selfOrFn, fn) {
|
|
178
|
+
if (fn) {
|
|
179
|
+
const self = selfOrFn;
|
|
180
|
+
if (isOk(self)) {
|
|
181
|
+
return self.value;
|
|
182
|
+
} else {
|
|
183
|
+
throw fn(self.error);
|
|
184
|
+
}
|
|
185
|
+
} else {
|
|
186
|
+
const throwFn = selfOrFn;
|
|
187
|
+
return (self) => {
|
|
188
|
+
if (isOk(self)) {
|
|
189
|
+
return self.value;
|
|
190
|
+
} else {
|
|
191
|
+
throw throwFn(self.error);
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function catchError(resultOrErrorOrTag, tagOrFn, fnOrUndefined) {
|
|
197
|
+
if (typeof resultOrErrorOrTag === "string") {
|
|
198
|
+
const tag = resultOrErrorOrTag;
|
|
199
|
+
const fn = tagOrFn;
|
|
200
|
+
return (resultOrError) => {
|
|
201
|
+
if ("__tag" in resultOrError) {
|
|
202
|
+
const result = resultOrError;
|
|
203
|
+
if (isErr(result) && result.error.tag === tag) {
|
|
204
|
+
fn(result.error);
|
|
205
|
+
return ok(null);
|
|
206
|
+
}
|
|
207
|
+
return result;
|
|
208
|
+
}
|
|
209
|
+
const error = resultOrError;
|
|
210
|
+
if (error.tag === tag) {
|
|
211
|
+
fn(error);
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
} else {
|
|
215
|
+
const resultOrError = resultOrErrorOrTag;
|
|
216
|
+
const tag = tagOrFn;
|
|
217
|
+
const fn = fnOrUndefined;
|
|
218
|
+
if ("__tag" in resultOrError) {
|
|
219
|
+
const result = resultOrError;
|
|
220
|
+
if (isErr(result) && result.error.tag === tag) {
|
|
221
|
+
fn(result.error);
|
|
222
|
+
return ok(null);
|
|
223
|
+
}
|
|
224
|
+
return result;
|
|
225
|
+
}
|
|
226
|
+
const error = resultOrError;
|
|
227
|
+
if (error.tag === tag) {
|
|
228
|
+
fn(error);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function catchAllErrors(resultOrFn, fnOrUndefined) {
|
|
233
|
+
if ("__tag" in resultOrFn) {
|
|
234
|
+
const result = resultOrFn;
|
|
235
|
+
if (isErr(result)) {
|
|
236
|
+
fnOrUndefined(result.error);
|
|
237
|
+
}
|
|
238
|
+
return result;
|
|
239
|
+
}
|
|
240
|
+
return (result) => {
|
|
241
|
+
if (isErr(result)) {
|
|
242
|
+
fnOrUndefined(result.error);
|
|
243
|
+
}
|
|
244
|
+
return result;
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function catchErrors(resultOrHandlers, handlersOrUndefined) {
|
|
248
|
+
if ("__tag" in resultOrHandlers) {
|
|
249
|
+
const result = resultOrHandlers;
|
|
250
|
+
if (isErr(result)) {
|
|
251
|
+
const handler = handlersOrUndefined[result.error.tag];
|
|
252
|
+
if (handler) {
|
|
253
|
+
handler(result.error);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return result;
|
|
257
|
+
} else {
|
|
258
|
+
return (result) => {
|
|
259
|
+
if (isErr(result)) {
|
|
260
|
+
const handler = handlersOrUndefined[result.error.tag];
|
|
261
|
+
if (handler) {
|
|
262
|
+
handler(result.error);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return result;
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
var pipe = (result, ab) => {
|
|
270
|
+
function run(a) {
|
|
271
|
+
return ab(a);
|
|
272
|
+
}
|
|
273
|
+
run.pipe = (bc) => pipe(result, (a) => bc(ab(a)));
|
|
274
|
+
run.execute = () => run(result);
|
|
275
|
+
return run;
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
// src/request.ts
|
|
279
|
+
var Request = class {
|
|
280
|
+
#path;
|
|
281
|
+
#localRequestInit;
|
|
282
|
+
#customErrorCbs = {};
|
|
283
|
+
#queryParams;
|
|
284
|
+
#middlewares;
|
|
285
|
+
#schema = null;
|
|
286
|
+
#bodySchema = null;
|
|
287
|
+
#retryConfig;
|
|
288
|
+
#shouldBeResult = false;
|
|
289
|
+
#bodySchemaIssues = [];
|
|
290
|
+
constructor(method, path, {
|
|
291
|
+
requestConfig,
|
|
292
|
+
retryConfig,
|
|
293
|
+
middlewares,
|
|
294
|
+
errorCbs
|
|
295
|
+
}) {
|
|
296
|
+
this.#path = path;
|
|
297
|
+
this.#middlewares = middlewares || [];
|
|
298
|
+
this.#localRequestInit = { ...requestConfig, method };
|
|
299
|
+
this.#retryConfig = retryConfig;
|
|
300
|
+
this.#customErrorCbs = errorCbs || {};
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Sets the base URL for the request.
|
|
304
|
+
* @param url The base URL to set
|
|
305
|
+
* @returns The request instance for chaining
|
|
306
|
+
* @example
|
|
307
|
+
* const request = new Request('/users', config);
|
|
308
|
+
* request.setBaseUrl('https://api.example.com');
|
|
309
|
+
*/
|
|
310
|
+
setBaseUrl(url) {
|
|
311
|
+
this.#localRequestInit.baseUrl = url;
|
|
312
|
+
return this;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Sets the retry configuration for the request.
|
|
316
|
+
* @param retry The retry configuration object
|
|
317
|
+
* @returns The request instance for chaining
|
|
318
|
+
* @example
|
|
319
|
+
* const request = new Request('/users', config);
|
|
320
|
+
* request.setRetry({
|
|
321
|
+
* retries: 3, // Number of retry attempts
|
|
322
|
+
* retryDelay: 1000, // Delay between retries in ms
|
|
323
|
+
* retryOn: [500, 502, 503], // Status codes to retry on
|
|
324
|
+
* retryWhile: (request, response) => response.status >= 500 // Custom retry condition
|
|
325
|
+
* });
|
|
326
|
+
*/
|
|
327
|
+
setRetry(retry) {
|
|
328
|
+
this.#retryConfig = {
|
|
329
|
+
...this.#retryConfig,
|
|
330
|
+
...retry
|
|
331
|
+
};
|
|
332
|
+
return this;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Sets multiple headers for the request.
|
|
336
|
+
* @param headers An object containing header key-value pairs
|
|
337
|
+
* @returns The request instance for chaining
|
|
338
|
+
* @example
|
|
339
|
+
* const request = new Request('/users', config);
|
|
340
|
+
* request.setHeaders({
|
|
341
|
+
* 'Content-Type': 'application/json',
|
|
342
|
+
* 'Accept': 'application/json'
|
|
343
|
+
* });
|
|
344
|
+
*/
|
|
345
|
+
setHeaders(headers) {
|
|
346
|
+
this.#localRequestInit.headers = headers;
|
|
347
|
+
return this;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Sets a single header for the request.
|
|
351
|
+
* @param key The header key
|
|
352
|
+
* @param value The header value
|
|
353
|
+
* @returns The request instance for chaining
|
|
354
|
+
* @example
|
|
355
|
+
* const request = new Request('/users', config);
|
|
356
|
+
* request.setHeader('Content-Type', 'application/json');
|
|
357
|
+
*/
|
|
358
|
+
setHeader(key, value) {
|
|
359
|
+
this.#localRequestInit.headers = {
|
|
360
|
+
...this.#localRequestInit.headers,
|
|
361
|
+
[key]: value
|
|
362
|
+
};
|
|
363
|
+
return this;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Sets the Authorization header with a bearer token.
|
|
367
|
+
* @param token The bearer token to set
|
|
368
|
+
* @returns The request instance for chaining
|
|
369
|
+
* @example
|
|
370
|
+
* const request = new Request('/users', config);
|
|
371
|
+
* request.setBearer('my-auth-token');
|
|
372
|
+
*/
|
|
373
|
+
setBearer(token) {
|
|
374
|
+
return this.setHeader("Authorization", `Bearer ${token}`);
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Sets a validation schema for the request body using a StandardSchemaV1 schema.
|
|
378
|
+
* @template TSchema Type parameter extending StandardSchemaV1
|
|
379
|
+
* @param schema The schema to validate the body against
|
|
380
|
+
* @returns The request instance for chaining with updated error type
|
|
381
|
+
* @example
|
|
382
|
+
* const userSchema = z.object({
|
|
383
|
+
* name: z.string(),
|
|
384
|
+
* email: z.string().email()
|
|
385
|
+
* });
|
|
386
|
+
*
|
|
387
|
+
* const request = new Request('/users', config);
|
|
388
|
+
* request
|
|
389
|
+
* .bodySchema(userSchema)
|
|
390
|
+
* .bodyJson({
|
|
391
|
+
* name: 'John',
|
|
392
|
+
* email: 'john@example.com'
|
|
393
|
+
* });
|
|
394
|
+
*/
|
|
395
|
+
bodySchema(schema) {
|
|
396
|
+
this.#bodySchema = schema;
|
|
397
|
+
return this;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Sets the request body as JSON by automatically stringifying the provided object.
|
|
401
|
+
* @param body The object to be stringified and sent as the request body
|
|
402
|
+
* @returns The request instance for chaining
|
|
403
|
+
* @example
|
|
404
|
+
* const request = new Request('/users', config);
|
|
405
|
+
* request.bodyJson({
|
|
406
|
+
* name: 'John Doe',
|
|
407
|
+
* email: 'john@example.com',
|
|
408
|
+
* age: 30
|
|
409
|
+
* });
|
|
410
|
+
*/
|
|
411
|
+
bodyJson(body) {
|
|
412
|
+
if (this.#bodySchema) {
|
|
413
|
+
const data = this.#bodySchema["~standard"].validate(body);
|
|
414
|
+
if (data instanceof Promise) {
|
|
415
|
+
throw new Error("Schema validation should not return a promise");
|
|
416
|
+
}
|
|
417
|
+
if (data.issues) {
|
|
418
|
+
this.#bodySchemaIssues = data.issues;
|
|
419
|
+
} else {
|
|
420
|
+
this.#localRequestInit.body = JSON.stringify(body);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return this;
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Sets the raw request body (unsafe, use with caution).
|
|
427
|
+
* @param body The body content to send with the request
|
|
428
|
+
* @returns The request instance for chaining
|
|
429
|
+
* @example
|
|
430
|
+
* const request = new Request('/users', config);
|
|
431
|
+
* request.unsafeBody(new FormData());
|
|
432
|
+
*
|
|
433
|
+
* // or with raw text
|
|
434
|
+
* request.unsafeBody('Hello World');
|
|
435
|
+
*
|
|
436
|
+
* // or with URLSearchParams
|
|
437
|
+
* request.unsafeBody(new URLSearchParams({ key: 'value' }));
|
|
438
|
+
*/
|
|
439
|
+
unsafeBody(body) {
|
|
440
|
+
this.#localRequestInit.body = body;
|
|
441
|
+
return this;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Handles 404 Not Found errors with a custom callback.
|
|
445
|
+
* @param cb The callback function to handle the error
|
|
446
|
+
* @returns The request instance for chaining
|
|
447
|
+
* @example
|
|
448
|
+
* const request = new Request('/users', config);
|
|
449
|
+
* request.notFound((error) => {
|
|
450
|
+
* console.log('Resource not found:', error);
|
|
451
|
+
* return { message: 'Custom not found message' };
|
|
452
|
+
* });
|
|
453
|
+
*/
|
|
454
|
+
notFound(cb) {
|
|
455
|
+
return this.error("notFoundError", "NOT_FOUND", cb);
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Handles 429 Too Many Requests errors with a custom callback.
|
|
459
|
+
* @param cb The callback function to handle the error
|
|
460
|
+
* @returns The request instance for chaining
|
|
461
|
+
* @example
|
|
462
|
+
* const request = new Request('/users', config);
|
|
463
|
+
* request.tooManyRequests((error) => {
|
|
464
|
+
* console.log('Rate limited:', error);
|
|
465
|
+
* return { message: 'Please try again later' };
|
|
466
|
+
* });
|
|
467
|
+
*/
|
|
468
|
+
tooManyRequests(cb) {
|
|
469
|
+
return this.error("tooManyRequestsError", "TOO_MANY_REQUESTS", cb);
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Handles 400 Bad Request errors with a custom callback.
|
|
473
|
+
* @param cb The callback function to handle the error
|
|
474
|
+
* @returns The request instance for chaining
|
|
475
|
+
* @example
|
|
476
|
+
* const request = new Request('/users', config);
|
|
477
|
+
* request.badRequest((error) => {
|
|
478
|
+
* console.log('Bad request error:', error);
|
|
479
|
+
* return { message: 'Invalid request parameters' };
|
|
480
|
+
* });
|
|
481
|
+
*/
|
|
482
|
+
badRequest(cb) {
|
|
483
|
+
return this.error("badRequestError", "BAD_REQUEST", cb);
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Handles 409 Conflict errors with a custom callback.
|
|
487
|
+
* @param cb The callback function to handle the error
|
|
488
|
+
* @returns The request instance for chaining
|
|
489
|
+
* @example
|
|
490
|
+
* const request = new Request('/users', config);
|
|
491
|
+
* request.conflict((error) => {
|
|
492
|
+
* console.log('Conflict error:', error);
|
|
493
|
+
* return { message: 'Resource conflict detected' };
|
|
494
|
+
* });
|
|
495
|
+
*/
|
|
496
|
+
conflict(cb) {
|
|
497
|
+
return this.error("conflictError", "CONFLICT", cb);
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Handles 401 Unauthorized errors with a custom callback.
|
|
501
|
+
* @param cb The callback function to handle the error
|
|
502
|
+
* @returns The request instance for chaining
|
|
503
|
+
* @example
|
|
504
|
+
* const request = new Request('/users', config);
|
|
505
|
+
* request.unauthorised((error) => {
|
|
506
|
+
* console.log('Unauthorized access:', error);
|
|
507
|
+
* return { message: 'Please login first' };
|
|
508
|
+
* });
|
|
509
|
+
*/
|
|
510
|
+
unauthorised(cb) {
|
|
511
|
+
return this.error("unauthorisedError", "UNAUTHORIZED", cb);
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Handles 403 Forbidden errors with a custom callback.
|
|
515
|
+
* @param cb The callback function to handle the error
|
|
516
|
+
* @returns The request instance for chaining
|
|
517
|
+
* @example
|
|
518
|
+
* const request = new Request('/users', config);
|
|
519
|
+
* request.forbidden((error) => {
|
|
520
|
+
* console.log('Access forbidden:', error);
|
|
521
|
+
* return { message: 'You do not have permission' };
|
|
522
|
+
* });
|
|
523
|
+
*/
|
|
524
|
+
forbidden(cb) {
|
|
525
|
+
return this.error("forbiddenError", "FORBIDDEN", cb);
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Handles 501 Not Implemented errors with a custom callback.
|
|
529
|
+
* @param cb The callback function to handle the error
|
|
530
|
+
* @returns The request instance for chaining
|
|
531
|
+
* @example
|
|
532
|
+
* const request = new Request('/users', config);
|
|
533
|
+
* request.notImplemented((error) => {
|
|
534
|
+
* console.log('Not implemented:', error);
|
|
535
|
+
* return { message: 'This feature is not implemented yet' };
|
|
536
|
+
* });
|
|
537
|
+
*/
|
|
538
|
+
notImplemented(cb) {
|
|
539
|
+
return this.error("notImplementedError", "NOT_IMPLEMENTED", cb);
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Handles 500 Internal Server Error errors with a custom callback.
|
|
543
|
+
* @param cb The callback function to handle the error
|
|
544
|
+
* @returns The request instance for chaining
|
|
545
|
+
* @example
|
|
546
|
+
* const request = new Request('/users', config);
|
|
547
|
+
* request.internalServerError((error) => {
|
|
548
|
+
* console.log('Server error:', error);
|
|
549
|
+
* return { message: 'An unexpected error occurred' };
|
|
550
|
+
* });
|
|
551
|
+
*/
|
|
552
|
+
internalServerError(cb) {
|
|
553
|
+
return this.error("internalServerError", "INTERNAL_SERVER_ERROR", cb);
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Sets a custom error handler for a specific HTTP status code.
|
|
557
|
+
* @param tag A string identifier for the error type
|
|
558
|
+
* @param status The HTTP error status to handle
|
|
559
|
+
* @param cb The callback function to handle the error
|
|
560
|
+
* @returns The request instance for chaining
|
|
561
|
+
* @example
|
|
562
|
+
* const request = new Request('/users', config);
|
|
563
|
+
* request
|
|
564
|
+
.withResult()
|
|
565
|
+
.error('customError', 'BAD_REQUEST', (error) => {
|
|
566
|
+
* console.log('Bad request error:', error);
|
|
567
|
+
* return {
|
|
568
|
+
* message: 'Invalid input',
|
|
569
|
+
* details: error.response.responseData
|
|
570
|
+
* };
|
|
571
|
+
* });
|
|
572
|
+
*
|
|
573
|
+
* // Later when making the request:
|
|
574
|
+
* const result = await request.json();
|
|
575
|
+
* if (Result.isErr(result)) {
|
|
576
|
+
* if(result.tag === 'customError') {
|
|
577
|
+
* console.log(result.error.data.message); // 'Invalid input'
|
|
578
|
+
* }
|
|
579
|
+
*/
|
|
580
|
+
error(tag, status, cb) {
|
|
581
|
+
this.#customErrorCbs[httpErrors[status]] = {
|
|
582
|
+
cb,
|
|
583
|
+
tag
|
|
584
|
+
};
|
|
585
|
+
return this;
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Sets query parameters for the request URL.
|
|
589
|
+
* @param params An object containing query parameter key-value pairs
|
|
590
|
+
* @returns The request instance for chaining
|
|
591
|
+
* @example
|
|
592
|
+
* const request = new Request('/users', config);
|
|
593
|
+
* request.setQueryParams({
|
|
594
|
+
* page: '1',
|
|
595
|
+
* limit: '10',
|
|
596
|
+
* sort: 'desc'
|
|
597
|
+
* });
|
|
598
|
+
*/
|
|
599
|
+
setQueryParams(params) {
|
|
600
|
+
this.#queryParams = new URLSearchParams(params);
|
|
601
|
+
return this;
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Sets the output schema for validating the response data using Zod.
|
|
605
|
+
* @param schema The Zod schema to validate the response
|
|
606
|
+
* @returns The request instance for chaining
|
|
607
|
+
* @example
|
|
608
|
+
* import { z } from 'zod';
|
|
609
|
+
*
|
|
610
|
+
* const userSchema = z.object({
|
|
611
|
+
* id: z.number(),
|
|
612
|
+
* name: z.string(),
|
|
613
|
+
* email: z.string().email()
|
|
614
|
+
* });
|
|
615
|
+
*
|
|
616
|
+
* const request = new Request('/users', config);
|
|
617
|
+
* const result = await request
|
|
618
|
+
* .withResult()
|
|
619
|
+
* .output(userSchema)
|
|
620
|
+
* .json();
|
|
621
|
+
*
|
|
622
|
+
* if (Result.isOk(result)) {
|
|
623
|
+
* const user = result.value; // Typed and validated user data
|
|
624
|
+
* }
|
|
625
|
+
*/
|
|
626
|
+
schema(schema) {
|
|
627
|
+
this.#schema = schema;
|
|
628
|
+
return this;
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Executes the request and returns the JSON response.
|
|
632
|
+
* @returns A Promise containing the Result type with either successful data or error information
|
|
633
|
+
* @example
|
|
634
|
+
* const request = new Request('/users', config);
|
|
635
|
+
* const result = await request
|
|
636
|
+
* .setQueryParams({ id: '123' })
|
|
637
|
+
* .withResult()
|
|
638
|
+
* .notFound((error) => ({ message: 'User not found' }))
|
|
639
|
+
* .json<User>();
|
|
640
|
+
*
|
|
641
|
+
* if (Result.isOk(result)) {
|
|
642
|
+
* const user = result.value; // User data
|
|
643
|
+
* } else {
|
|
644
|
+
* console.error(result.error); // Error handling
|
|
645
|
+
* }
|
|
646
|
+
*/
|
|
647
|
+
async json() {
|
|
648
|
+
const output = await this.#makeRequest(
|
|
649
|
+
async (response) => response.json().catch(
|
|
650
|
+
(e) => new CustomError("jsonParseError", {
|
|
651
|
+
message: e instanceof Error ? e.message : "Failed to parse JSON"
|
|
652
|
+
})
|
|
653
|
+
),
|
|
654
|
+
true
|
|
655
|
+
);
|
|
656
|
+
return this.#mapResponse(output);
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Executes the request and returns the response as plain text.
|
|
660
|
+
* @returns A Promise containing the Result type with either successful text data or error information
|
|
661
|
+
* @example
|
|
662
|
+
* const request = new Request('/data.txt', config);
|
|
663
|
+
* const result = await request
|
|
664
|
+
* .setQueryParams({ version: '1' })
|
|
665
|
+
* .withResult()
|
|
666
|
+
* .notFound((error) => ({ message: 'Text file not found' }))
|
|
667
|
+
* .text();
|
|
668
|
+
*
|
|
669
|
+
* if (Result.isOk(result)) {
|
|
670
|
+
* const text = result.value; // Plain text content
|
|
671
|
+
* } else {
|
|
672
|
+
* console.error(result.error); // Error handling
|
|
673
|
+
* }
|
|
674
|
+
*/
|
|
675
|
+
async text() {
|
|
676
|
+
const output = await this.#makeRequest(
|
|
677
|
+
(response) => response.text()
|
|
678
|
+
);
|
|
679
|
+
return this.#mapResponse(output);
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Executes the request and returns the response as a Blob.
|
|
683
|
+
* @returns A Promise containing the Result type with either successful Blob data or error information
|
|
684
|
+
* @example
|
|
685
|
+
* const request = new Request('/image.jpg', config);
|
|
686
|
+
* const result = await request
|
|
687
|
+
* .setQueryParams({ size: 'large' })
|
|
688
|
+
* .withResult()
|
|
689
|
+
* .notFound((error) => ({ message: 'Image not found' }))
|
|
690
|
+
* .blob();
|
|
691
|
+
*
|
|
692
|
+
* if (Result.isOk(result)) {
|
|
693
|
+
* const imageBlob = result.value; // Blob data
|
|
694
|
+
* } else {
|
|
695
|
+
* console.error(result.error); // Error handling
|
|
696
|
+
* }
|
|
697
|
+
*/
|
|
698
|
+
async blob() {
|
|
699
|
+
const output = await this.#makeRequest((response) => response.blob());
|
|
700
|
+
return this.#mapResponse(output);
|
|
701
|
+
}
|
|
702
|
+
#url() {
|
|
703
|
+
const baseUrl = this.#localRequestInit.baseUrl?.replace(/\/+$/, "") ?? "";
|
|
704
|
+
const path = this.#path.replace(/^\/+/, "/");
|
|
705
|
+
const queryString = this.#queryParams ? `?${this.#queryParams.toString()}` : "";
|
|
706
|
+
const url = [baseUrl, path, queryString].filter(Boolean).join("");
|
|
707
|
+
return url;
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Returns the complete URL for the request including base URL, path, and query parameters.
|
|
711
|
+
* @returns The complete URL string
|
|
712
|
+
* @example
|
|
713
|
+
* const request = new Request('/users', config);
|
|
714
|
+
* request.setBaseUrl('https://api.example.com');
|
|
715
|
+
* request.setQueryParams({ id: '123' });
|
|
716
|
+
* console.log(request.url()); // 'https://api.example.com/users?id=123'
|
|
717
|
+
*/
|
|
718
|
+
url() {
|
|
719
|
+
return this.#url();
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Configures the request to return a Result type instead of a tuple.
|
|
723
|
+
* @returns The request instance for chaining with Result type return value
|
|
724
|
+
* @example
|
|
725
|
+
* const request = new Request('/users', config);
|
|
726
|
+
* const result = await request
|
|
727
|
+
* .withResult()
|
|
728
|
+
* .json<User>();
|
|
729
|
+
*
|
|
730
|
+
* // Returns Result type instead of tuple
|
|
731
|
+
* if (Result.isOk(result)) {
|
|
732
|
+
* const user = result.value;
|
|
733
|
+
* }
|
|
734
|
+
*/
|
|
735
|
+
withResult() {
|
|
736
|
+
this.#shouldBeResult = true;
|
|
737
|
+
return this;
|
|
738
|
+
}
|
|
739
|
+
#mapResponse(value) {
|
|
740
|
+
if (this.#shouldBeResult) {
|
|
741
|
+
return value;
|
|
742
|
+
}
|
|
743
|
+
if (isOk(value)) {
|
|
744
|
+
return [getOrNull(value), null];
|
|
745
|
+
} else {
|
|
746
|
+
return [null, getErrorOrNull(value)];
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
async #makeRequest(responseParser, isJson = false) {
|
|
750
|
+
if (this.#bodySchemaIssues.length) {
|
|
751
|
+
return err(new CustomError("parseError", this.#bodySchemaIssues));
|
|
752
|
+
}
|
|
753
|
+
const request = this.#request();
|
|
754
|
+
const { retries, retryDelay, retryOn, retryWhile, onRetry } = this.#sanitisedRetryConfig();
|
|
755
|
+
try {
|
|
756
|
+
const requestInit = request.requestInit;
|
|
757
|
+
const url = this.#url();
|
|
758
|
+
let attempts = 1;
|
|
759
|
+
let response;
|
|
760
|
+
let responseData;
|
|
761
|
+
while (attempts <= retries) {
|
|
762
|
+
try {
|
|
763
|
+
response = await fetch(url, requestInit);
|
|
764
|
+
responseData = await responseParser(response);
|
|
765
|
+
if (responseData instanceof CustomError) {
|
|
766
|
+
return err(responseData);
|
|
767
|
+
}
|
|
768
|
+
const retryWhileCondition = retryWhile ? await retryWhile(
|
|
769
|
+
request,
|
|
770
|
+
this.#makeResponse(response, responseData)
|
|
771
|
+
) : false;
|
|
772
|
+
if (response.ok || !retryOn.includes(response.status) && !retryWhileCondition) {
|
|
773
|
+
break;
|
|
774
|
+
}
|
|
775
|
+
if (response.status in this.#customErrorCbs && attempts === retries) {
|
|
776
|
+
const result = this.#customErrorCbs[response.status].cb({
|
|
777
|
+
request,
|
|
778
|
+
response: this.#makeResponse(response, responseData)
|
|
779
|
+
});
|
|
780
|
+
return err(
|
|
781
|
+
new CustomError(
|
|
782
|
+
// @ts-ignore
|
|
783
|
+
this.#customErrorCbs[response.status].tag,
|
|
784
|
+
result
|
|
785
|
+
)
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
if (attempts < retries) {
|
|
789
|
+
const delay = typeof retryDelay === "function" ? await retryDelay(
|
|
790
|
+
retries - attempts - 1,
|
|
791
|
+
retries,
|
|
792
|
+
request,
|
|
793
|
+
this.#makeResponse(response, responseData)
|
|
794
|
+
) : retryDelay;
|
|
795
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
796
|
+
}
|
|
797
|
+
} catch (e) {
|
|
798
|
+
if (attempts === retries) throw e;
|
|
799
|
+
const delay = typeof retryDelay === "function" ? await retryDelay(
|
|
800
|
+
retries - attempts - 1,
|
|
801
|
+
retries,
|
|
802
|
+
request,
|
|
803
|
+
this.#makeResponse(response, responseData)
|
|
804
|
+
) : retryDelay;
|
|
805
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
806
|
+
}
|
|
807
|
+
if (onRetry) {
|
|
808
|
+
onRetry(request, this.#makeResponse(response, responseData));
|
|
809
|
+
}
|
|
810
|
+
attempts++;
|
|
811
|
+
}
|
|
812
|
+
if (!response.ok) {
|
|
813
|
+
if (response.status in this.#customErrorCbs) {
|
|
814
|
+
const result = this.#customErrorCbs[response.status].cb({
|
|
815
|
+
request,
|
|
816
|
+
response: this.#makeResponse(response, responseData)
|
|
817
|
+
});
|
|
818
|
+
return err(
|
|
819
|
+
new CustomError(
|
|
820
|
+
// @ts-ignore
|
|
821
|
+
this.#customErrorCbs[response.status].tag,
|
|
822
|
+
result
|
|
823
|
+
)
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
return err(
|
|
827
|
+
new AspiError(
|
|
828
|
+
response.statusText,
|
|
829
|
+
this.#request(),
|
|
830
|
+
this.#makeResponse(response, responseData)
|
|
831
|
+
)
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
if (isJson && this.#schema) {
|
|
835
|
+
const data = this.#schema["~standard"].validate(responseData);
|
|
836
|
+
if (data instanceof Promise) {
|
|
837
|
+
throw new Error("Schema validation should not return a promise");
|
|
838
|
+
}
|
|
839
|
+
if (data.issues) {
|
|
840
|
+
return err(new CustomError("parseError", data.issues));
|
|
841
|
+
}
|
|
842
|
+
return ok({
|
|
843
|
+
data: data.value,
|
|
844
|
+
request,
|
|
845
|
+
response: this.#makeResponse(response, responseData)
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
return ok({
|
|
849
|
+
data: responseData,
|
|
850
|
+
request,
|
|
851
|
+
response: this.#makeResponse(response, responseData)
|
|
852
|
+
});
|
|
853
|
+
} catch (error) {
|
|
854
|
+
if (500 in this.#customErrorCbs) {
|
|
855
|
+
const result = this.#customErrorCbs[500].cb({
|
|
856
|
+
request,
|
|
857
|
+
response: {
|
|
858
|
+
status: 500,
|
|
859
|
+
statusText: "INTERNAL_SERVER_ERROR"
|
|
860
|
+
}
|
|
861
|
+
});
|
|
862
|
+
return err(
|
|
863
|
+
new CustomError(
|
|
864
|
+
// @ts-ignore
|
|
865
|
+
this.#customErrorCbs[500].tag,
|
|
866
|
+
result
|
|
867
|
+
)
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
return err(
|
|
871
|
+
new AspiError(
|
|
872
|
+
error instanceof Error ? error.message : "Something went wrong",
|
|
873
|
+
request,
|
|
874
|
+
{
|
|
875
|
+
status: 500,
|
|
876
|
+
statusText: "INTERNAL_SERVER_ERROR"
|
|
877
|
+
}
|
|
878
|
+
)
|
|
879
|
+
);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
#request() {
|
|
883
|
+
let requestInit = this.#localRequestInit;
|
|
884
|
+
for (const middleware of this.#middlewares) {
|
|
885
|
+
requestInit = middleware(this.#localRequestInit);
|
|
886
|
+
}
|
|
887
|
+
return {
|
|
888
|
+
requestInit,
|
|
889
|
+
baseUrl: this.#localRequestInit.baseUrl,
|
|
890
|
+
path: this.#path,
|
|
891
|
+
queryParams: this.#queryParams || null,
|
|
892
|
+
retryConfig: this.#sanitisedRetryConfig()
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
#sanitisedRetryConfig() {
|
|
896
|
+
const retries = this.#retryConfig?.retries || 1;
|
|
897
|
+
const retryDelay = this.#retryConfig?.retryDelay || 0;
|
|
898
|
+
const retryOn = this.#retryConfig?.retryOn || [];
|
|
899
|
+
const retryWhile = this.#retryConfig?.retryWhile;
|
|
900
|
+
const onRetry = this.#retryConfig?.onRetry;
|
|
901
|
+
return {
|
|
902
|
+
retries,
|
|
903
|
+
retryDelay,
|
|
904
|
+
retryOn,
|
|
905
|
+
retryWhile,
|
|
906
|
+
onRetry
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
#makeResponse(response, responseData) {
|
|
910
|
+
return {
|
|
911
|
+
response,
|
|
912
|
+
status: response.status,
|
|
913
|
+
statusText: getHttpErrorStatus(response.status),
|
|
914
|
+
responseData
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
};
|
|
918
|
+
|
|
919
|
+
// src/aspi.ts
|
|
920
|
+
var Aspi = class {
|
|
921
|
+
#globalRequestInit;
|
|
922
|
+
#middlewares = [];
|
|
923
|
+
#retryConfig;
|
|
924
|
+
#customErrorCbs = {};
|
|
925
|
+
constructor(config) {
|
|
926
|
+
const { retryConfig, ...requestConfig } = config;
|
|
927
|
+
this.#globalRequestInit = requestConfig;
|
|
928
|
+
this.#retryConfig = retryConfig;
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Sets the base URL for all API requests
|
|
932
|
+
* @param {string} url - The base URL to be set
|
|
933
|
+
* @returns {Aspi} The Aspi instance for chaining
|
|
934
|
+
* @example
|
|
935
|
+
* const api = new Aspi({ baseUrl: 'https://api.example.com' });
|
|
936
|
+
* api.setBaseUrl('https://api.newdomain.com');
|
|
937
|
+
*/
|
|
938
|
+
setBaseUrl(url) {
|
|
939
|
+
this.#globalRequestInit.baseUrl = url;
|
|
940
|
+
return this;
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* Sets the retry configuration for failed requests
|
|
944
|
+
* @param {AspiRetryConfig<TRequest>} retry - The retry configuration object
|
|
945
|
+
* @returns {Aspi} The Aspi instance for chaining
|
|
946
|
+
* @example
|
|
947
|
+
* const api = new Aspi({ baseUrl: 'https://api.example.com' });
|
|
948
|
+
* api.setRetry({
|
|
949
|
+
* retries: 3,
|
|
950
|
+
* retryDelay: 1000,
|
|
951
|
+
* retryOn: [500, 502],
|
|
952
|
+
* retryWhile: (req, res) => res.status === 500
|
|
953
|
+
* });
|
|
954
|
+
*/
|
|
955
|
+
setRetry(retry) {
|
|
956
|
+
this.#retryConfig = {
|
|
957
|
+
...this.#retryConfig,
|
|
958
|
+
...retry
|
|
959
|
+
};
|
|
960
|
+
return this;
|
|
961
|
+
}
|
|
962
|
+
#createRequest(method, path, body) {
|
|
963
|
+
return new Request(method, path, {
|
|
964
|
+
requestConfig: {
|
|
965
|
+
...this.#globalRequestInit,
|
|
966
|
+
method,
|
|
967
|
+
body
|
|
968
|
+
},
|
|
969
|
+
retryConfig: this.#retryConfig,
|
|
970
|
+
middlewares: this.#middlewares,
|
|
971
|
+
errorCbs: this.#customErrorCbs
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
/**
|
|
975
|
+
* Makes a GET request to the specified path
|
|
976
|
+
* @param {string} path - The path to make the request to
|
|
977
|
+
* @returns {Request} A Request instance for chaining
|
|
978
|
+
* @example
|
|
979
|
+
* const api = new Aspi({ baseUrl: 'https://api.example.com' });
|
|
980
|
+
* const response = await api.get('/users').json();
|
|
981
|
+
*/
|
|
982
|
+
get(path) {
|
|
983
|
+
return this.#createRequest("GET", path);
|
|
984
|
+
}
|
|
985
|
+
/**
|
|
986
|
+
* Makes a POST request to the specified path with an optional body
|
|
987
|
+
* @param {string} path - The path to make the request to
|
|
988
|
+
* @param {BodyInit} [body] - The body of the request
|
|
989
|
+
* @returns {Request} A Request instance for chaining
|
|
990
|
+
* @example
|
|
991
|
+
* const api = new Aspi({ baseUrl: 'https://api.example.com' });
|
|
992
|
+
* const response = await api.post('/users', { name: 'John' }).json();
|
|
993
|
+
*/
|
|
994
|
+
post(path, body) {
|
|
995
|
+
return this.#createRequest("POST", path, body);
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Makes a PUT request to the specified path with an optional body
|
|
999
|
+
* @param {string} path - The path to make the request to
|
|
1000
|
+
* @param {BodyInit} [body] - The body of the request
|
|
1001
|
+
* @returns {Request} A Request instance for chaining
|
|
1002
|
+
* @example
|
|
1003
|
+
* const api = new Aspi({ baseUrl: 'https://api.example.com' });
|
|
1004
|
+
* const response = await api.put('/users/1', { name: 'John' }).json();
|
|
1005
|
+
*/
|
|
1006
|
+
put(path, body) {
|
|
1007
|
+
return this.#createRequest("PUT", path, body);
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Makes a PATCH request to the specified path with an optional body
|
|
1011
|
+
* @param {string} path - The path to make the request to
|
|
1012
|
+
* @param {BodyInit} [body] - The body of the request
|
|
1013
|
+
* @returns {Request} A Request instance for chaining
|
|
1014
|
+
* @example
|
|
1015
|
+
* const api = new Aspi({ baseUrl: 'https://api.example.com' });
|
|
1016
|
+
* const response = await api.patch('/users/1', { name: 'John' }).json();
|
|
1017
|
+
*/
|
|
1018
|
+
patch(path, body) {
|
|
1019
|
+
return this.#createRequest("PATCH", path, body);
|
|
1020
|
+
}
|
|
1021
|
+
/**
|
|
1022
|
+
* Makes a DELETE request to the specified path
|
|
1023
|
+
* @param {string} path - The path to make the request to
|
|
1024
|
+
* @returns {Request} A Request instance for chaining
|
|
1025
|
+
* @example
|
|
1026
|
+
* const api = new Aspi({ baseUrl: 'https://api.example.com' });
|
|
1027
|
+
* const response = await api.delete('/users/1').json();
|
|
1028
|
+
*/
|
|
1029
|
+
delete(path) {
|
|
1030
|
+
return this.#createRequest("DELETE", path);
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Makes a HEAD request to the specified path
|
|
1034
|
+
* @param {string} path - The path to make the request to
|
|
1035
|
+
* @returns {Request} A Request instance for chaining
|
|
1036
|
+
* @example
|
|
1037
|
+
* const api = new Aspi({ baseUrl: 'https://api.example.com' });
|
|
1038
|
+
* const response = await api.head('/users').json();
|
|
1039
|
+
*/
|
|
1040
|
+
head(path) {
|
|
1041
|
+
return this.#createRequest("HEAD", path);
|
|
1042
|
+
}
|
|
1043
|
+
/**
|
|
1044
|
+
* Makes an OPTIONS request to the specified path
|
|
1045
|
+
* @param {string} path - The path to make the request to
|
|
1046
|
+
* @returns {Request} A Request instance for chaining
|
|
1047
|
+
* @example
|
|
1048
|
+
* const api = new Aspi({ baseUrl: 'https://api.example.com' });
|
|
1049
|
+
* const response = await api.options('/users').json();
|
|
1050
|
+
*/
|
|
1051
|
+
options(path) {
|
|
1052
|
+
return this.#createRequest("OPTIONS", path);
|
|
1053
|
+
}
|
|
1054
|
+
/**
|
|
1055
|
+
* Sets multiple headers for all API requests
|
|
1056
|
+
* @param {Record<string, string>} headers - An object containing header key-value pairs
|
|
1057
|
+
* @returns {Aspi} The Aspi instance for chaining
|
|
1058
|
+
* @example
|
|
1059
|
+
* const api = new Aspi({ baseUrl: 'https://api.example.com' });
|
|
1060
|
+
* api.setHeaders({
|
|
1061
|
+
* 'Content-Type': 'application/json',
|
|
1062
|
+
* 'Accept': 'application/json'
|
|
1063
|
+
* });
|
|
1064
|
+
*/
|
|
1065
|
+
setHeaders(headers) {
|
|
1066
|
+
this.#globalRequestInit.headers = headers;
|
|
1067
|
+
return this;
|
|
1068
|
+
}
|
|
1069
|
+
/**
|
|
1070
|
+
* Sets a single header for all API requests
|
|
1071
|
+
* @param {string} key - The header key
|
|
1072
|
+
* @param {string} value - The header value
|
|
1073
|
+
* @returns {Aspi} The Aspi instance for chaining
|
|
1074
|
+
* @example
|
|
1075
|
+
* const api = new Aspi({ baseUrl: 'https://api.example.com' });
|
|
1076
|
+
* api.setHeader('Content-Type', 'application/json');
|
|
1077
|
+
*/
|
|
1078
|
+
setHeader(key, value) {
|
|
1079
|
+
this.#globalRequestInit.headers = {
|
|
1080
|
+
...this.#globalRequestInit.headers,
|
|
1081
|
+
[key]: value
|
|
1082
|
+
};
|
|
1083
|
+
return this;
|
|
1084
|
+
}
|
|
1085
|
+
/**
|
|
1086
|
+
* Sets the Authorization header with a Bearer token
|
|
1087
|
+
* @param {string} token - The Bearer token
|
|
1088
|
+
* @returns {Aspi} The Aspi instance for chaining
|
|
1089
|
+
* @example
|
|
1090
|
+
* const api = new Aspi({ baseUrl: 'https://api.example.com' });
|
|
1091
|
+
* api.setBearer('myAuthToken123');
|
|
1092
|
+
*/
|
|
1093
|
+
setBearer(token) {
|
|
1094
|
+
return this.setHeader("Authorization", `Bearer ${token}`);
|
|
1095
|
+
}
|
|
1096
|
+
/**
|
|
1097
|
+
* Use a middleware function to transform requests
|
|
1098
|
+
* @param {Middleware<T, U>} fn - The middleware function to apply
|
|
1099
|
+
* @returns {Aspi<U>} A new Aspi instance with the applied middleware
|
|
1100
|
+
* @example
|
|
1101
|
+
* const api = new Aspi({ baseUrl: 'https://api.example.com' });
|
|
1102
|
+
* api.use((req) => {
|
|
1103
|
+
* // Add custom headers
|
|
1104
|
+
* req.headers = {
|
|
1105
|
+
* ...req.headers,
|
|
1106
|
+
* 'x-custom-header': 'custom-value'
|
|
1107
|
+
* };
|
|
1108
|
+
* return req;
|
|
1109
|
+
* });
|
|
1110
|
+
*/
|
|
1111
|
+
use(fn) {
|
|
1112
|
+
this.#middlewares = [...this.#middlewares, fn];
|
|
1113
|
+
return this;
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Registers a custom error handler for a specific HTTP status
|
|
1117
|
+
* @param {string} tag - The error tag identifier
|
|
1118
|
+
* @param {HttpErrorStatus} status - The HTTP status code to handle
|
|
1119
|
+
* @param {CustomErrorCb<TRequest, A>} cb - The callback function to handle the error
|
|
1120
|
+
* @returns {Aspi} The Aspi instance for chaining
|
|
1121
|
+
* @example
|
|
1122
|
+
* const api = new Aspi({ baseUrl: 'https://api.example.com' });
|
|
1123
|
+
* api.error('customError', 'BAD_REQUEST', (req, res) => {
|
|
1124
|
+
* console.log('Bad request error occurred');
|
|
1125
|
+
* return { message: 'Invalid input' };
|
|
1126
|
+
* });
|
|
1127
|
+
*/
|
|
1128
|
+
error(tag, status, cb) {
|
|
1129
|
+
this.#customErrorCbs[httpErrors[status]] = {
|
|
1130
|
+
cb,
|
|
1131
|
+
tag
|
|
1132
|
+
};
|
|
1133
|
+
return this;
|
|
1134
|
+
}
|
|
1135
|
+
/**
|
|
1136
|
+
* Handles 404 Not Found errors with a custom callback.
|
|
1137
|
+
* @param cb The callback function to handle the error
|
|
1138
|
+
* @returns The request instance for chaining
|
|
1139
|
+
* @example
|
|
1140
|
+
* const request = new Request('/users', config);
|
|
1141
|
+
* request.notFound((error) => {
|
|
1142
|
+
* console.log('Not found:', error);
|
|
1143
|
+
* return { message: 'Resource not found' };
|
|
1144
|
+
* });
|
|
1145
|
+
*/
|
|
1146
|
+
notFound(cb) {
|
|
1147
|
+
return this.error("notFoundError", "NOT_FOUND", cb);
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* Handles 429 Too Many Requests errors with a custom callback.
|
|
1151
|
+
* @param cb The callback function to handle the error
|
|
1152
|
+
* @returns The request instance for chaining
|
|
1153
|
+
* @example
|
|
1154
|
+
* const request = new Request('/users', config);
|
|
1155
|
+
* request.tooManyRequests((error) => {
|
|
1156
|
+
* console.log('Rate limited:', error);
|
|
1157
|
+
* return { message: 'Please try again later' };
|
|
1158
|
+
* });
|
|
1159
|
+
*/
|
|
1160
|
+
tooManyRequests(cb) {
|
|
1161
|
+
return this.error("tooManyRequestsError", "TOO_MANY_REQUESTS", cb);
|
|
1162
|
+
}
|
|
1163
|
+
/**
|
|
1164
|
+
* Handles 409 Conflict errors with a custom callback.
|
|
1165
|
+
* @param cb The callback function to handle the error
|
|
1166
|
+
* @returns The request instance for chaining
|
|
1167
|
+
* @example
|
|
1168
|
+
* const request = new Request('/users', config);
|
|
1169
|
+
* request.conflict((error) => {
|
|
1170
|
+
* console.log('Conflict error:', error);
|
|
1171
|
+
* return { message: 'Resource conflict detected' };
|
|
1172
|
+
* });
|
|
1173
|
+
*/
|
|
1174
|
+
conflict(cb) {
|
|
1175
|
+
return this.error("conflictError", "CONFLICT", cb);
|
|
1176
|
+
}
|
|
1177
|
+
/**
|
|
1178
|
+
* Registers a handler for 400 Bad Request errors
|
|
1179
|
+
* @param {CustomErrorCb<TRequest, A>} cb - The callback function to handle the error
|
|
1180
|
+
* @returns {Aspi} The Aspi instance for chaining
|
|
1181
|
+
* @example
|
|
1182
|
+
* api.badRequest((req, res) => ({ message: 'Invalid request parameters' }));
|
|
1183
|
+
*/
|
|
1184
|
+
badRequest(cb) {
|
|
1185
|
+
return this.error("badRequestError", "BAD_REQUEST", cb);
|
|
1186
|
+
}
|
|
1187
|
+
/**
|
|
1188
|
+
* Registers a handler for 401 Unauthorized errors
|
|
1189
|
+
* @param {CustomErrorCb<TRequest, A>} cb - The callback function to handle the error
|
|
1190
|
+
* @returns {Aspi} The Aspi instance for chaining
|
|
1191
|
+
* @example
|
|
1192
|
+
* api.unauthorized((req, res) => ({ message: 'Authentication required' }));
|
|
1193
|
+
*/
|
|
1194
|
+
unauthorized(cb) {
|
|
1195
|
+
return this.error("unauthorizedError", "UNAUTHORIZED", cb);
|
|
1196
|
+
}
|
|
1197
|
+
/**
|
|
1198
|
+
* Registers a handler for 403 Forbidden errors
|
|
1199
|
+
* @param {CustomErrorCb<TRequest, A>} cb - The callback function to handle the error
|
|
1200
|
+
* @returns {Aspi} The Aspi instance for chaining
|
|
1201
|
+
* @example
|
|
1202
|
+
* api.forbidden((req, res) => ({ message: 'Access denied' }));
|
|
1203
|
+
*/
|
|
1204
|
+
forbidden(cb) {
|
|
1205
|
+
return this.error("forbiddenError", "FORBIDDEN", cb);
|
|
1206
|
+
}
|
|
1207
|
+
/**
|
|
1208
|
+
* Registers a handler for 501 Not Implemented errors
|
|
1209
|
+
* @param {CustomErrorCb<TRequest, A>} cb - The callback function to handle the error
|
|
1210
|
+
* @returns {Aspi} The Aspi instance for chaining
|
|
1211
|
+
* @example
|
|
1212
|
+
* api.notImplemented((req, res) => ({ message: 'Feature not implemented' }));
|
|
1213
|
+
*/
|
|
1214
|
+
notImplemented(cb) {
|
|
1215
|
+
return this.error("notImplementedError", "NOT_IMPLEMENTED", cb);
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* Registers a handler for 500 Internal Server errors
|
|
1219
|
+
* @param {CustomErrorCb<TRequest, A>} cb - The callback function to handle the error
|
|
1220
|
+
* @returns {Aspi} The Aspi instance for chaining
|
|
1221
|
+
* @example
|
|
1222
|
+
* api.internalServerError((req, res) => ({ message: 'Server error occurred' }));
|
|
1223
|
+
*/
|
|
1224
|
+
internalServerError(cb) {
|
|
1225
|
+
return this.error("internalServerErrorError", "INTERNAL_SERVER_ERROR", cb);
|
|
1226
|
+
}
|
|
1227
|
+
};
|
|
1228
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1229
|
+
0 && (module.exports = {
|
|
1230
|
+
Aspi,
|
|
1231
|
+
AspiError,
|
|
1232
|
+
CustomError,
|
|
1233
|
+
Request,
|
|
1234
|
+
Result,
|
|
1235
|
+
getHttpErrorStatus,
|
|
1236
|
+
httpErrors
|
|
1237
|
+
});
|