@beyonk/http 12.1.1 → 12.2.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/dist/index.cjs ADDED
@@ -0,0 +1,489 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+
19
+ // lib/index.js
20
+ var index_exports = {};
21
+ __export(index_exports, {
22
+ Api: () => Api,
23
+ HttpError: () => HttpError,
24
+ default: () => index_default
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+ var HttpError = class extends Error {
28
+ /**
29
+ * Create a new HTTP error
30
+ * @param {string} message - Error message
31
+ * @param {any} body - Error response body
32
+ */
33
+ constructor(message, body) {
34
+ super(message);
35
+ this.body = body;
36
+ }
37
+ };
38
+ var AccessDeniedError = class extends HttpError {
39
+ };
40
+ var PaymentRequiredError = class extends HttpError {
41
+ };
42
+ var ForbiddenError = class extends HttpError {
43
+ };
44
+ var NotFoundError = class extends HttpError {
45
+ };
46
+ var NotAcceptableError = class extends HttpError {
47
+ };
48
+ var ConflictError = class extends HttpError {
49
+ };
50
+ var GoneError = class extends HttpError {
51
+ };
52
+ var PreconditionFailedError = class extends HttpError {
53
+ };
54
+ var ExpectationFailedError = class extends HttpError {
55
+ };
56
+ var BadDataError = class extends HttpError {
57
+ };
58
+ var TooManyRequestsError = class extends HttpError {
59
+ };
60
+ var errorMapping = {
61
+ 401: AccessDeniedError,
62
+ 402: PaymentRequiredError,
63
+ 403: ForbiddenError,
64
+ 404: NotFoundError,
65
+ 406: NotAcceptableError,
66
+ 409: ConflictError,
67
+ 410: GoneError,
68
+ 412: PreconditionFailedError,
69
+ 417: ExpectationFailedError,
70
+ 422: BadDataError,
71
+ 429: TooManyRequestsError
72
+ };
73
+ function simpleClone(obj) {
74
+ return JSON.parse(JSON.stringify(obj));
75
+ }
76
+ function getErrorByCode(code) {
77
+ return errorMapping[code] || HttpError;
78
+ }
79
+ var DEFAULT_CONFIG = {
80
+ endpoint: null,
81
+ method: "GET",
82
+ payload: null,
83
+ query: null,
84
+ headers: {},
85
+ overrides: {}
86
+ };
87
+ var Api = class {
88
+ config = simpleClone(DEFAULT_CONFIG);
89
+ /** @type {Record<string, ErrorHandler>} */
90
+ handlers = {};
91
+ /** @type {FetchClient|null} */
92
+ client = null;
93
+ /** @type {ApiContext|null} */
94
+ ctx = null;
95
+ /** @type {ErrorHandler|null} */
96
+ defaultHandler = null;
97
+ /**
98
+ * Creates a new API instance
99
+ * @param {ApiOptions} options - API configuration options
100
+ */
101
+ constructor(options) {
102
+ this.options = Object.assign({
103
+ retry: false,
104
+ parseErrors: true,
105
+ handlers: {}
106
+ }, options);
107
+ }
108
+ /**
109
+ * Reset the request configuration to defaults
110
+ */
111
+ resetRequest() {
112
+ this.config = simpleClone(DEFAULT_CONFIG);
113
+ }
114
+ /**
115
+ * Get the HTTP client to use for requests
116
+ * @returns {FetchClient} HTTP client
117
+ * @throws {Error} If no client is available
118
+ */
119
+ getClient() {
120
+ if (this.options.mock) {
121
+ console.warn("@beyonk/http: Using mocked http client");
122
+ return this.options.mock;
123
+ }
124
+ if (this.client) {
125
+ return this.client;
126
+ }
127
+ if (config?.fetch) {
128
+ return config.fetch;
129
+ }
130
+ if ("fetch" in globalThis) {
131
+ return globalThis.fetch;
132
+ }
133
+ throw Error("No client provided and can't find one automatically");
134
+ }
135
+ /**
136
+ * Handle an error
137
+ * @param {HttpError} e - Error instance
138
+ * @param {ApiContext} [ctx] - API context
139
+ * @returns {any} Result of error handler
140
+ */
141
+ handle(e, ctx) {
142
+ const constructorName = Object.getPrototypeOf(e).constructor.name;
143
+ const globalHandlerName = `${constructorName[0].toLowerCase()}${constructorName.slice(1, -5)}`;
144
+ const handler = this.handlers[constructorName] || this.options.handlers && this.options.handlers[globalHandlerName] || this.defaultHandler || ((e2) => {
145
+ console.error(constructorName, e2.message, e2);
146
+ });
147
+ return handler(e, ctx);
148
+ }
149
+ /**
150
+ * Send the HTTP request
151
+ * @template T
152
+ * @param {ResponseTransformer<T>} [fn] - Function to transform the response
153
+ * @returns {Promise<T>} Response data
154
+ */
155
+ async send(fn) {
156
+ const endpoint = this.config.endpoint?.includes("://") ? this.config.endpoint : `${this.options.baseUrl}/${this.config.endpoint}`;
157
+ const hasPayload = !!this.config.payload;
158
+ const options = Object.assign(
159
+ {
160
+ method: this.config.method,
161
+ cors: true,
162
+ credentials: "include",
163
+ headers: Object.assign(
164
+ { Accept: "application/json" },
165
+ hasPayload ? { "Content-Type": "application/json" } : {},
166
+ this.config.headers
167
+ )
168
+ },
169
+ hasPayload ? { body: JSON.stringify(this.config.payload) } : {},
170
+ this.config.overrides
171
+ );
172
+ const client = this.getClient();
173
+ const ep = this.config.query ? `${endpoint}?${this.config.query}` : `${endpoint}`;
174
+ let result;
175
+ try {
176
+ result = await this.#doQuery(1, client, ep, options);
177
+ } catch (e) {
178
+ console.log(e);
179
+ return this.handle(e, this.ctx);
180
+ } finally {
181
+ this.resetRequest();
182
+ }
183
+ const { json, httpStatus } = result;
184
+ return fn ? fn(json, httpStatus) : json;
185
+ }
186
+ /**
187
+ * Check if the response has content
188
+ * @param {import('./types.js').Response} response - HTTP response
189
+ * @returns {boolean} Whether the response has content
190
+ */
191
+ #hasContent(response) {
192
+ const contentLength = parseInt(response.headers.get("content-length") ?? "", 10);
193
+ const isNoContentResponse = response.status === 204;
194
+ if (isNoContentResponse) {
195
+ return false;
196
+ }
197
+ const headerExists = !isNaN(contentLength);
198
+ return !headerExists || contentLength > 0;
199
+ }
200
+ /**
201
+ * Perform the HTTP request with retry logic
202
+ * @param {number} attempt - Current attempt number
203
+ * @param {FetchClient} client - HTTP client
204
+ * @param {string} endpoint - API endpoint URL
205
+ * @param {Record<string, any>} options - Request options
206
+ * @returns {Promise<QueryResult>} Response data
207
+ * @throws {HttpError} If the request fails
208
+ */
209
+ async #doQuery(attempt, client, endpoint, options) {
210
+ const retry = this.options.retry || { attempts: 1 };
211
+ try {
212
+ const r = await client(endpoint, options);
213
+ if (r.status >= 200 && r.status < 400) {
214
+ let json;
215
+ if (this.#hasContent(r)) {
216
+ try {
217
+ json = await r.json();
218
+ } catch (e) {
219
+ console.error("Unable to parse response json", e.message);
220
+ }
221
+ }
222
+ return { httpStatus: r.status, json };
223
+ }
224
+ let content = "";
225
+ try {
226
+ content = this.options.parseErrors ? await r.json() : await r.text();
227
+ } catch (e) {
228
+ console.log("Failed to parse error body when asked.");
229
+ }
230
+ const ClientError = getErrorByCode(r.status);
231
+ throw new ClientError(r.statusText, content);
232
+ } catch (e) {
233
+ if (retry.attempts && retry.errors && attempt < retry.attempts && retry.errors.includes(e.code)) {
234
+ console.warn(`Got ${e.code} when calling ${endpoint}. Retrying request (${attempt}/${retry.attempts})`);
235
+ return this.#doQuery(++attempt, client, endpoint, options);
236
+ }
237
+ throw e;
238
+ }
239
+ }
240
+ /**
241
+ * Set the context for the request
242
+ * @param {ApiContext} ctx - Request context
243
+ * @returns {this} Current instance
244
+ */
245
+ context(ctx) {
246
+ if (ctx.fetch) {
247
+ this.client = ctx.fetch;
248
+ }
249
+ this.ctx = ctx;
250
+ return this;
251
+ }
252
+ /**
253
+ * Set request overrides
254
+ * @param {Record<string, any>} override - Request overrides
255
+ * @returns {this} Current instance
256
+ */
257
+ override(override) {
258
+ this.config.overrides = override;
259
+ return this;
260
+ }
261
+ /**
262
+ * Set request headers
263
+ * @param {Record<string, string>} headers - Request headers
264
+ * @returns {this} Current instance
265
+ */
266
+ headers(headers) {
267
+ this.config.headers = headers;
268
+ return this;
269
+ }
270
+ /**
271
+ * Perform a GET request
272
+ * @template T
273
+ * @param {ResponseTransformer<T>} [fn] - Function to transform the response
274
+ * @returns {Promise<T>} Response data
275
+ */
276
+ async get(fn) {
277
+ this.config.method = "GET";
278
+ return this.send(fn);
279
+ }
280
+ /**
281
+ * Perform a POST request
282
+ * @template T
283
+ * @param {ResponseTransformer<T>} [fn] - Function to transform the response
284
+ * @returns {Promise<T>} Response data
285
+ */
286
+ async post(fn) {
287
+ this.config.method = "POST";
288
+ return this.send(fn);
289
+ }
290
+ /**
291
+ * Perform a PATCH request
292
+ * @template T
293
+ * @param {ResponseTransformer<T>} [fn] - Function to transform the response
294
+ * @returns {Promise<T>} Response data
295
+ */
296
+ async patch(fn) {
297
+ this.config.method = "PATCH";
298
+ return this.send(fn);
299
+ }
300
+ /**
301
+ * Perform a PUT request
302
+ * @template T
303
+ * @param {ResponseTransformer<T>} [fn] - Function to transform the response
304
+ * @returns {Promise<T>} Response data
305
+ */
306
+ async put(fn) {
307
+ this.config.method = "PUT";
308
+ return this.send(fn);
309
+ }
310
+ /**
311
+ * Perform a DELETE request
312
+ * @template T
313
+ * @param {ResponseTransformer<T>} [fn] - Function to transform the response
314
+ * @returns {Promise<T>} Response data
315
+ */
316
+ async del(fn) {
317
+ this.config.method = "DELETE";
318
+ return this.send(fn);
319
+ }
320
+ /**
321
+ * Set the API endpoint
322
+ * @param {string} endpoint - API endpoint
323
+ * @returns {this} Current instance
324
+ */
325
+ endpoint(endpoint) {
326
+ this.config.endpoint = endpoint;
327
+ return this;
328
+ }
329
+ /**
330
+ * Set query parameters
331
+ * @param {Record<string, any>} query - Query parameters
332
+ * @returns {this} Current instance
333
+ */
334
+ query(query) {
335
+ const q = Object.entries(query).reduce(
336
+ (curr, [k, v]) => {
337
+ if (typeof v === "undefined") {
338
+ return curr;
339
+ }
340
+ if (Array.isArray(v)) {
341
+ curr.push(...v.map((n) => `${k}=${encodeURIComponent(n)}`));
342
+ } else {
343
+ curr.push(`${k}=${encodeURIComponent(v)}`);
344
+ }
345
+ return curr;
346
+ },
347
+ /** @type {string[]} */
348
+ []
349
+ );
350
+ this.config.query = q.join("&");
351
+ return this;
352
+ }
353
+ /**
354
+ * Set request payload
355
+ * @param {any} payload - Request payload
356
+ * @returns {this} Current instance
357
+ */
358
+ payload(payload) {
359
+ this.config.payload = payload;
360
+ return this;
361
+ }
362
+ /**
363
+ * Register a default error handler
364
+ * @param {ErrorHandler} fn - Error handler function
365
+ * @returns {this} Current instance
366
+ */
367
+ default(fn) {
368
+ this.defaultHandler = fn;
369
+ return this;
370
+ }
371
+ /**
372
+ * Register a handler for AccessDenied (401) errors
373
+ * @param {ErrorHandler} fn - Error handler function
374
+ * @returns {this} Current instance
375
+ */
376
+ accessDenied(fn) {
377
+ this.handlers[AccessDeniedError.name] = fn;
378
+ return this;
379
+ }
380
+ /**
381
+ * Register a handler for PaymentRequired (402) errors
382
+ * @param {ErrorHandler} fn - Error handler function
383
+ * @returns {this} Current instance
384
+ */
385
+ paymentRequired(fn) {
386
+ this.handlers[PaymentRequiredError.name] = fn;
387
+ return this;
388
+ }
389
+ /**
390
+ * Register a handler for Forbidden (403) errors
391
+ * @param {ErrorHandler} fn - Error handler function
392
+ * @returns {this} Current instance
393
+ */
394
+ forbidden(fn) {
395
+ this.handlers[ForbiddenError.name] = fn;
396
+ return this;
397
+ }
398
+ /**
399
+ * Register a handler for NotFound (404) errors
400
+ * @param {ErrorHandler} fn - Error handler function
401
+ * @returns {this} Current instance
402
+ */
403
+ notFound(fn) {
404
+ this.handlers[NotFoundError.name] = fn;
405
+ return this;
406
+ }
407
+ /**
408
+ * Register a handler for NotAcceptable (406) errors
409
+ * @param {ErrorHandler} fn - Error handler function
410
+ * @returns {this} Current instance
411
+ */
412
+ notAcceptable(fn) {
413
+ this.handlers[NotAcceptableError.name] = fn;
414
+ return this;
415
+ }
416
+ /**
417
+ * Register a handler for Conflict (409) errors
418
+ * @param {ErrorHandler} fn - Error handler function
419
+ * @returns {this} Current instance
420
+ */
421
+ conflict(fn) {
422
+ this.handlers[ConflictError.name] = fn;
423
+ return this;
424
+ }
425
+ /**
426
+ * Register a handler for Gone (410) errors
427
+ * @param {ErrorHandler} fn - Error handler function
428
+ * @returns {this} Current instance
429
+ */
430
+ gone(fn) {
431
+ this.handlers[GoneError.name] = fn;
432
+ return this;
433
+ }
434
+ /**
435
+ * Register a handler for PreconditionFailed (412) errors
436
+ * @param {ErrorHandler} fn - Error handler function
437
+ * @returns {this} Current instance
438
+ */
439
+ preconditionFailed(fn) {
440
+ this.handlers[PreconditionFailedError.name] = fn;
441
+ return this;
442
+ }
443
+ /**
444
+ * Register a handler for ExpectationFailed (417) errors
445
+ * @param {ErrorHandler} fn - Error handler function
446
+ * @returns {this} Current instance
447
+ */
448
+ expectationFailed(fn) {
449
+ this.handlers[ExpectationFailedError.name] = fn;
450
+ return this;
451
+ }
452
+ /**
453
+ * Register a handler for BadData (422) errors
454
+ * @param {ErrorHandler} fn - Error handler function
455
+ * @returns {this} Current instance
456
+ */
457
+ badData(fn) {
458
+ this.handlers[BadDataError.name] = fn;
459
+ return this;
460
+ }
461
+ /**
462
+ * Register a handler for TooManyRequests (429) errors
463
+ * @param {ErrorHandler} fn - Error handler function
464
+ * @returns {this} Current instance
465
+ */
466
+ tooManyRequests(fn) {
467
+ this.handlers[TooManyRequestsError.name] = fn;
468
+ return this;
469
+ }
470
+ };
471
+ var config;
472
+ function configure(options) {
473
+ config = options;
474
+ }
475
+ function create() {
476
+ if (!config) {
477
+ throw new Error("Api client must be configured");
478
+ }
479
+ return new Api(config);
480
+ }
481
+ var index_default = {
482
+ create,
483
+ configure
484
+ };
485
+ // Annotate the CommonJS export names for ESM import in node:
486
+ 0 && (module.exports = {
487
+ Api,
488
+ HttpError
489
+ });