@beyonk/http 12.1.1 → 12.1.2

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,485 @@
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
+ } else if (typeof window !== "undefined") {
127
+ return window.fetch.bind(window);
128
+ }
129
+ throw Error("No client provided and can't find one automatically");
130
+ }
131
+ /**
132
+ * Handle an error
133
+ * @param {HttpError} e - Error instance
134
+ * @param {ApiContext} [ctx] - API context
135
+ * @returns {any} Result of error handler
136
+ */
137
+ handle(e, ctx) {
138
+ const constructorName = Object.getPrototypeOf(e).constructor.name;
139
+ const globalHandlerName = `${constructorName[0].toLowerCase()}${constructorName.slice(1, -5)}`;
140
+ const handler = this.handlers[constructorName] || this.options.handlers && this.options.handlers[globalHandlerName] || this.defaultHandler || ((e2) => {
141
+ console.error(constructorName, e2.message, e2);
142
+ });
143
+ return handler(e, ctx);
144
+ }
145
+ /**
146
+ * Send the HTTP request
147
+ * @template T
148
+ * @param {ResponseTransformer<T>} [fn] - Function to transform the response
149
+ * @returns {Promise<T>} Response data
150
+ */
151
+ async send(fn) {
152
+ const endpoint = this.config.endpoint?.includes("://") ? this.config.endpoint : `${this.options.baseUrl}/${this.config.endpoint}`;
153
+ const hasPayload = !!this.config.payload;
154
+ const options = Object.assign(
155
+ {
156
+ method: this.config.method,
157
+ cors: true,
158
+ credentials: "include",
159
+ headers: Object.assign(
160
+ { Accept: "application/json" },
161
+ hasPayload ? { "Content-Type": "application/json" } : {},
162
+ this.config.headers
163
+ )
164
+ },
165
+ hasPayload ? { body: JSON.stringify(this.config.payload) } : {},
166
+ this.config.overrides
167
+ );
168
+ const client = this.getClient();
169
+ const ep = this.config.query ? `${endpoint}?${this.config.query}` : `${endpoint}`;
170
+ let result;
171
+ try {
172
+ result = await this.#doQuery(1, client, ep, options);
173
+ } catch (e) {
174
+ console.log(e);
175
+ return this.handle(e, this.ctx);
176
+ } finally {
177
+ this.resetRequest();
178
+ }
179
+ const { json, httpStatus } = result;
180
+ return fn ? fn(json, httpStatus) : json;
181
+ }
182
+ /**
183
+ * Check if the response has content
184
+ * @param {import('./types.js').Response} response - HTTP response
185
+ * @returns {boolean} Whether the response has content
186
+ */
187
+ #hasContent(response) {
188
+ const contentLength = parseInt(response.headers.get("content-length") ?? "", 10);
189
+ const isNoContentResponse = response.status === 204;
190
+ if (isNoContentResponse) {
191
+ return false;
192
+ }
193
+ const headerExists = !isNaN(contentLength);
194
+ return !headerExists || contentLength > 0;
195
+ }
196
+ /**
197
+ * Perform the HTTP request with retry logic
198
+ * @param {number} attempt - Current attempt number
199
+ * @param {FetchClient} client - HTTP client
200
+ * @param {string} endpoint - API endpoint URL
201
+ * @param {Record<string, any>} options - Request options
202
+ * @returns {Promise<QueryResult>} Response data
203
+ * @throws {HttpError} If the request fails
204
+ */
205
+ async #doQuery(attempt, client, endpoint, options) {
206
+ const retry = this.options.retry || { attempts: 1 };
207
+ try {
208
+ const r = await client(endpoint, options);
209
+ if (r.status >= 200 && r.status < 400) {
210
+ let json;
211
+ if (this.#hasContent(r)) {
212
+ try {
213
+ json = await r.json();
214
+ } catch (e) {
215
+ console.error("Unable to parse response json", e.message);
216
+ }
217
+ }
218
+ return { httpStatus: r.status, json };
219
+ }
220
+ let content = "";
221
+ try {
222
+ content = this.options.parseErrors ? await r.json() : await r.text();
223
+ } catch (e) {
224
+ console.log("Failed to parse error body when asked.");
225
+ }
226
+ const ClientError = getErrorByCode(r.status);
227
+ throw new ClientError(r.statusText, content);
228
+ } catch (e) {
229
+ if (retry.attempts && retry.errors && attempt < retry.attempts && retry.errors.includes(e.code)) {
230
+ console.warn(`Got ${e.code} when calling ${endpoint}. Retrying request (${attempt}/${retry.attempts})`);
231
+ return this.#doQuery(++attempt, client, endpoint, options);
232
+ }
233
+ throw e;
234
+ }
235
+ }
236
+ /**
237
+ * Set the context for the request
238
+ * @param {ApiContext} ctx - Request context
239
+ * @returns {this} Current instance
240
+ */
241
+ context(ctx) {
242
+ if (ctx.fetch) {
243
+ this.client = ctx.fetch;
244
+ }
245
+ this.ctx = ctx;
246
+ return this;
247
+ }
248
+ /**
249
+ * Set request overrides
250
+ * @param {Record<string, any>} override - Request overrides
251
+ * @returns {this} Current instance
252
+ */
253
+ override(override) {
254
+ this.config.overrides = override;
255
+ return this;
256
+ }
257
+ /**
258
+ * Set request headers
259
+ * @param {Record<string, string>} headers - Request headers
260
+ * @returns {this} Current instance
261
+ */
262
+ headers(headers) {
263
+ this.config.headers = headers;
264
+ return this;
265
+ }
266
+ /**
267
+ * Perform a GET request
268
+ * @template T
269
+ * @param {ResponseTransformer<T>} [fn] - Function to transform the response
270
+ * @returns {Promise<T>} Response data
271
+ */
272
+ async get(fn) {
273
+ this.config.method = "GET";
274
+ return this.send(fn);
275
+ }
276
+ /**
277
+ * Perform a POST request
278
+ * @template T
279
+ * @param {ResponseTransformer<T>} [fn] - Function to transform the response
280
+ * @returns {Promise<T>} Response data
281
+ */
282
+ async post(fn) {
283
+ this.config.method = "POST";
284
+ return this.send(fn);
285
+ }
286
+ /**
287
+ * Perform a PATCH request
288
+ * @template T
289
+ * @param {ResponseTransformer<T>} [fn] - Function to transform the response
290
+ * @returns {Promise<T>} Response data
291
+ */
292
+ async patch(fn) {
293
+ this.config.method = "PATCH";
294
+ return this.send(fn);
295
+ }
296
+ /**
297
+ * Perform a PUT request
298
+ * @template T
299
+ * @param {ResponseTransformer<T>} [fn] - Function to transform the response
300
+ * @returns {Promise<T>} Response data
301
+ */
302
+ async put(fn) {
303
+ this.config.method = "PUT";
304
+ return this.send(fn);
305
+ }
306
+ /**
307
+ * Perform a DELETE request
308
+ * @template T
309
+ * @param {ResponseTransformer<T>} [fn] - Function to transform the response
310
+ * @returns {Promise<T>} Response data
311
+ */
312
+ async del(fn) {
313
+ this.config.method = "DELETE";
314
+ return this.send(fn);
315
+ }
316
+ /**
317
+ * Set the API endpoint
318
+ * @param {string} endpoint - API endpoint
319
+ * @returns {this} Current instance
320
+ */
321
+ endpoint(endpoint) {
322
+ this.config.endpoint = endpoint;
323
+ return this;
324
+ }
325
+ /**
326
+ * Set query parameters
327
+ * @param {Record<string, any>} query - Query parameters
328
+ * @returns {this} Current instance
329
+ */
330
+ query(query) {
331
+ const q = Object.entries(query).reduce(
332
+ (curr, [k, v]) => {
333
+ if (typeof v === "undefined") {
334
+ return curr;
335
+ }
336
+ if (Array.isArray(v)) {
337
+ curr.push(...v.map((n) => `${k}=${encodeURIComponent(n)}`));
338
+ } else {
339
+ curr.push(`${k}=${encodeURIComponent(v)}`);
340
+ }
341
+ return curr;
342
+ },
343
+ /** @type {string[]} */
344
+ []
345
+ );
346
+ this.config.query = q.join("&");
347
+ return this;
348
+ }
349
+ /**
350
+ * Set request payload
351
+ * @param {any} payload - Request payload
352
+ * @returns {this} Current instance
353
+ */
354
+ payload(payload) {
355
+ this.config.payload = payload;
356
+ return this;
357
+ }
358
+ /**
359
+ * Register a default error handler
360
+ * @param {ErrorHandler} fn - Error handler function
361
+ * @returns {this} Current instance
362
+ */
363
+ default(fn) {
364
+ this.defaultHandler = fn;
365
+ return this;
366
+ }
367
+ /**
368
+ * Register a handler for AccessDenied (401) errors
369
+ * @param {ErrorHandler} fn - Error handler function
370
+ * @returns {this} Current instance
371
+ */
372
+ accessDenied(fn) {
373
+ this.handlers[AccessDeniedError.name] = fn;
374
+ return this;
375
+ }
376
+ /**
377
+ * Register a handler for PaymentRequired (402) errors
378
+ * @param {ErrorHandler} fn - Error handler function
379
+ * @returns {this} Current instance
380
+ */
381
+ paymentRequired(fn) {
382
+ this.handlers[PaymentRequiredError.name] = fn;
383
+ return this;
384
+ }
385
+ /**
386
+ * Register a handler for Forbidden (403) errors
387
+ * @param {ErrorHandler} fn - Error handler function
388
+ * @returns {this} Current instance
389
+ */
390
+ forbidden(fn) {
391
+ this.handlers[ForbiddenError.name] = fn;
392
+ return this;
393
+ }
394
+ /**
395
+ * Register a handler for NotFound (404) errors
396
+ * @param {ErrorHandler} fn - Error handler function
397
+ * @returns {this} Current instance
398
+ */
399
+ notFound(fn) {
400
+ this.handlers[NotFoundError.name] = fn;
401
+ return this;
402
+ }
403
+ /**
404
+ * Register a handler for NotAcceptable (406) errors
405
+ * @param {ErrorHandler} fn - Error handler function
406
+ * @returns {this} Current instance
407
+ */
408
+ notAcceptable(fn) {
409
+ this.handlers[NotAcceptableError.name] = fn;
410
+ return this;
411
+ }
412
+ /**
413
+ * Register a handler for Conflict (409) errors
414
+ * @param {ErrorHandler} fn - Error handler function
415
+ * @returns {this} Current instance
416
+ */
417
+ conflict(fn) {
418
+ this.handlers[ConflictError.name] = fn;
419
+ return this;
420
+ }
421
+ /**
422
+ * Register a handler for Gone (410) errors
423
+ * @param {ErrorHandler} fn - Error handler function
424
+ * @returns {this} Current instance
425
+ */
426
+ gone(fn) {
427
+ this.handlers[GoneError.name] = fn;
428
+ return this;
429
+ }
430
+ /**
431
+ * Register a handler for PreconditionFailed (412) errors
432
+ * @param {ErrorHandler} fn - Error handler function
433
+ * @returns {this} Current instance
434
+ */
435
+ preconditionFailed(fn) {
436
+ this.handlers[PreconditionFailedError.name] = fn;
437
+ return this;
438
+ }
439
+ /**
440
+ * Register a handler for ExpectationFailed (417) errors
441
+ * @param {ErrorHandler} fn - Error handler function
442
+ * @returns {this} Current instance
443
+ */
444
+ expectationFailed(fn) {
445
+ this.handlers[ExpectationFailedError.name] = fn;
446
+ return this;
447
+ }
448
+ /**
449
+ * Register a handler for BadData (422) errors
450
+ * @param {ErrorHandler} fn - Error handler function
451
+ * @returns {this} Current instance
452
+ */
453
+ badData(fn) {
454
+ this.handlers[BadDataError.name] = fn;
455
+ return this;
456
+ }
457
+ /**
458
+ * Register a handler for TooManyRequests (429) errors
459
+ * @param {ErrorHandler} fn - Error handler function
460
+ * @returns {this} Current instance
461
+ */
462
+ tooManyRequests(fn) {
463
+ this.handlers[TooManyRequestsError.name] = fn;
464
+ return this;
465
+ }
466
+ };
467
+ var config;
468
+ function configure(options) {
469
+ config = options;
470
+ }
471
+ function create() {
472
+ if (!config) {
473
+ throw new Error("Api client must be configured");
474
+ }
475
+ return new Api(config);
476
+ }
477
+ var index_default = {
478
+ create,
479
+ configure
480
+ };
481
+ // Annotate the CommonJS export names for ESM import in node:
482
+ 0 && (module.exports = {
483
+ Api,
484
+ HttpError
485
+ });