@nu-art/http-client 0.401.1

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.
@@ -0,0 +1,494 @@
1
+ /*
2
+ * @nu-art/thunderstorm-http - A robust and type-safe HTTP client for Thunderstorm applications
3
+ * Copyright (C) 2024 Adam van der Kruk aka TacB0sS
4
+ * Licensed under the Apache License, Version 2.0
5
+ */
6
+ // noinspection TypeScriptPreferShortImport
7
+ import { _keys, asArray, BadImplementationException, exists, isErrorOfType, Logger, MimeType_json } from '@nu-art/ts-common';
8
+ import { composeUrl } from '../utils/utils.js';
9
+ // Axios v1+ import style
10
+ import axios, { CanceledError } from 'axios';
11
+ import { HttpException } from '../exceptions/HttpException.js';
12
+ import { HttpMethod } from '../types/api-types.js';
13
+ /**
14
+ * Typed HTTP request with fluent builder API and comprehensive logging.
15
+ *
16
+ * Provides a type-safe, chainable interface for building and executing HTTP requests.
17
+ * Extends Logger for built-in request lifecycle logging (verbose, debug, info, warning, error).
18
+ *
19
+ * Features:
20
+ * - Type-safe request/response handling via TypedApi generics
21
+ * - Fluent builder pattern for configuration
22
+ * - Automatic JSON parsing of responses
23
+ * - Request cancellation via AbortController
24
+ * - Callback chaining for error and completion handlers
25
+ * - Comprehensive logging at all stages
26
+ *
27
+ * @template API - Typed API definition specifying method, response, body, params, and error types
28
+ */
29
+ export class HttpRequest extends Logger {
30
+ key;
31
+ requestData;
32
+ origin;
33
+ headers = {};
34
+ method = HttpMethod.GET;
35
+ timeout = 10000;
36
+ body;
37
+ url;
38
+ params = {};
39
+ responseType;
40
+ label;
41
+ onProgressListener;
42
+ aborted = false;
43
+ compress;
44
+ onCompleted;
45
+ onError;
46
+ response;
47
+ cancelController;
48
+ status;
49
+ requestOption = {};
50
+ /**
51
+ * Creates a new HTTP request instance.
52
+ *
53
+ * Automatically extends timeout to 5 minutes in debug mode for development.
54
+ * Initializes AbortController for request cancellation support.
55
+ *
56
+ * @param requestKey - Identifier for this request (used in logging)
57
+ * @param requestData - Optional data associated with the request
58
+ * @param shouldCompress - Whether to enable compression (default: false)
59
+ */
60
+ constructor(requestKey, requestData, shouldCompress) {
61
+ const label = `http request: ${requestKey}${requestData ? ` ${requestData}` : ''}`;
62
+ super(label);
63
+ this.key = requestKey;
64
+ this.requestData = requestData;
65
+ this.label = label;
66
+ this.compress = shouldCompress === undefined ? false : shouldCompress;
67
+ this.cancelController = new AbortController();
68
+ this.logVerbose('HttpRequest created', { key: requestKey, requestData, compress: this.compress, timeout: this.timeout });
69
+ }
70
+ resolveTypedException(exception) {
71
+ if (isErrorOfType(exception, HttpException))
72
+ return exception.errorResponse?.error;
73
+ }
74
+ getRequestData() {
75
+ return this.requestData;
76
+ }
77
+ setOrigin(origin) {
78
+ this.origin = origin;
79
+ return this;
80
+ }
81
+ setOnProgressListener(onProgressListener) {
82
+ this.onProgressListener = onProgressListener;
83
+ return this;
84
+ }
85
+ setLabel(label) {
86
+ this.label = label;
87
+ return this;
88
+ }
89
+ setMethod(method) {
90
+ this.method = method;
91
+ return this;
92
+ }
93
+ setResponseType(responseType) {
94
+ this.responseType = responseType;
95
+ return this;
96
+ }
97
+ setUrlParams(params) {
98
+ if (!params)
99
+ return this;
100
+ _keys(params).forEach((key) => {
101
+ const param = params[key];
102
+ return param && typeof param === 'string' && this.setUrlParam(key, param);
103
+ });
104
+ return this;
105
+ }
106
+ setUrlParam(key, value) {
107
+ delete this.params[key];
108
+ this.params[key] = value;
109
+ return this;
110
+ }
111
+ setUrl(url) {
112
+ this.url = url;
113
+ return this;
114
+ }
115
+ getUrl() {
116
+ return this.url;
117
+ }
118
+ /**
119
+ * Sets a relative URL path, composing it with the origin.
120
+ *
121
+ * Automatically removes leading slashes from the relative path and combines
122
+ * with origin. Requires origin to be set first.
123
+ *
124
+ * @param relativeUrl - Relative path (leading slash is removed if present)
125
+ * @returns This instance for chaining
126
+ * @throws BadImplementationException if origin is not set
127
+ */
128
+ setRelativeUrl(relativeUrl) {
129
+ if (!this.origin)
130
+ throw new BadImplementationException('if you want to use relative urls, you need to set an origin');
131
+ if (relativeUrl.startsWith('/'))
132
+ relativeUrl = relativeUrl.substring(1);
133
+ this.url = `${this.origin}/${relativeUrl}`;
134
+ return this;
135
+ }
136
+ setTimeout(timeout) {
137
+ this.timeout = timeout;
138
+ return this;
139
+ }
140
+ setHeaders(headers) {
141
+ if (!headers)
142
+ return this;
143
+ Object.keys(headers).forEach((key) => this.setHeader(key, headers[key]));
144
+ return this;
145
+ }
146
+ addHeaders(headers) {
147
+ if (!headers)
148
+ return this;
149
+ Object.keys(headers).forEach((key) => this.addHeader(key, headers[key]));
150
+ return this;
151
+ }
152
+ /**
153
+ * Sets a header, replacing any existing values for the key.
154
+ *
155
+ * Header keys are normalized to lowercase. Multiple calls to setHeader
156
+ * for the same key will replace previous values.
157
+ *
158
+ * @param _key - Header name (case-insensitive, normalized to lowercase)
159
+ * @param value - Header value(s)
160
+ * @returns This instance for chaining
161
+ */
162
+ setHeader(_key, value) {
163
+ const key = _key.toLowerCase();
164
+ delete this.headers[key];
165
+ return this.addHeader(key, value);
166
+ }
167
+ /**
168
+ * Adds a header value, appending to existing values if the key already exists.
169
+ *
170
+ * Header keys are normalized to lowercase. Multiple values for the same header
171
+ * are joined with '; ' when the request is executed.
172
+ *
173
+ * @param _key - Header name (case-insensitive, normalized to lowercase)
174
+ * @param value - Header value(s) to append
175
+ * @returns This instance for chaining
176
+ */
177
+ addHeader(_key, value) {
178
+ const key = _key.toLowerCase();
179
+ return this._addHeaderImpl(key, value);
180
+ }
181
+ removeHeader(key) {
182
+ delete this.headers[key];
183
+ return this;
184
+ }
185
+ _addHeaderImpl(key, value) {
186
+ const values = asArray(value);
187
+ if (!this.headers[key])
188
+ this.headers[key] = values;
189
+ else
190
+ this.headers[key].push(...values);
191
+ this.logVerbose(`Added ${value} header`, value);
192
+ return this;
193
+ }
194
+ prepareJsonBody(bodyObject) {
195
+ return bodyObject;
196
+ }
197
+ setBodyAsJson(bodyObject, compress) {
198
+ this.setHeader('content-type', MimeType_json);
199
+ this.setBody(this.prepareJsonBody(bodyObject), compress);
200
+ return this;
201
+ }
202
+ /**
203
+ * Sets the request body.
204
+ *
205
+ * If compression is enabled and body is a string, automatically adds
206
+ * 'Content-encoding: gzip' header.
207
+ *
208
+ * @param bodyAsString - Request body (any type)
209
+ * @param _compress - Override compression setting (optional)
210
+ * @returns This instance for chaining
211
+ */
212
+ setBody(bodyAsString, _compress) {
213
+ this.body = bodyAsString;
214
+ this.compress = _compress === undefined ? this.compress : _compress;
215
+ if (typeof bodyAsString === 'string') {
216
+ if (this.compress)
217
+ this.setHeader('Content-encoding', 'gzip');
218
+ // Set content-type for plain text if not already set
219
+ if (!this.headers['content-type'] && !this.headers['Content-Type'])
220
+ this.setHeader('Content-Type', 'text/plain');
221
+ }
222
+ return this;
223
+ }
224
+ isValidStatus(statusCode) {
225
+ return statusCode >= 200 && statusCode < 300;
226
+ }
227
+ print() {
228
+ this.logInfo(`Url: ${this.url}`);
229
+ this.logInfo(`Params:`, this.params);
230
+ this.logInfo(`Headers:`, this.headers);
231
+ }
232
+ /**
233
+ * Executes the HTTP request and returns the typed response.
234
+ *
235
+ * Process:
236
+ * 1. Validates request isn't aborted
237
+ * 2. Composes full URL with query parameters
238
+ * 3. Prepares headers (joins multiple values with '; ')
239
+ * 4. Sends request via Axios
240
+ * 5. Validates response status (200-299 considered success)
241
+ * 6. Attempts JSON parsing (falls back to raw response if not JSON)
242
+ * 7. Calls completion/error callbacks
243
+ *
244
+ * Automatically handles:
245
+ * - Request cancellation (AbortController)
246
+ * - Error response parsing
247
+ * - JSON response parsing
248
+ * - Callback execution (onError for failures, onCompleted for success)
249
+ *
250
+ * @param print - If true, logs request details (URL, params, headers) before execution
251
+ * @returns Typed response data (API['R'])
252
+ * @throws HttpException if request fails or returns non-2xx status
253
+ */
254
+ async execute(print = false) {
255
+ this.logVerbose('Executing HTTP request', { method: this.method, key: this.key });
256
+ if (print)
257
+ this.print();
258
+ if (this.aborted) {
259
+ this.logWarning('Request was aborted before execution');
260
+ const httpException = new HttpException(0, this);
261
+ await this.onError?.(httpException);
262
+ throw httpException;
263
+ }
264
+ const fullUrl = composeUrl(this.url, this.params);
265
+ const body = this.body;
266
+ this.logDebug('Composed URL', { url: this.url, params: this.params, fullUrl });
267
+ if (typeof body === 'string') {
268
+ this.addHeader('Content-Length', `${body.length}`);
269
+ }
270
+ const headers = Object.keys(this.headers).reduce((carry, headerKey) => {
271
+ carry[headerKey] = this.headers[headerKey].join('; ');
272
+ return carry;
273
+ }, {});
274
+ this.logDebug('Prepared headers', headers);
275
+ const options = {
276
+ ...this.requestOption,
277
+ url: fullUrl,
278
+ method: this.method,
279
+ headers,
280
+ timeout: this.timeout,
281
+ signal: this.cancelController.signal,
282
+ };
283
+ if (body) {
284
+ options.data = body;
285
+ this.logVerbose('Request body set', { bodyType: typeof body, bodyLength: typeof body === 'string' ? body.length : 'object' });
286
+ }
287
+ if (this.responseType) {
288
+ options.responseType = this.responseType;
289
+ this.logVerbose(`Response type set: ${this.responseType}`);
290
+ }
291
+ // Set up progress listener if configured
292
+ if (this.onProgressListener) {
293
+ options.onDownloadProgress = (progressEvent) => {
294
+ this.onProgressListener({
295
+ loaded: progressEvent.loaded || 0,
296
+ total: progressEvent.total || 0,
297
+ lengthComputable: progressEvent.lengthComputable !== false,
298
+ target: progressEvent
299
+ });
300
+ };
301
+ options.onUploadProgress = (progressEvent) => {
302
+ this.onProgressListener({
303
+ loaded: progressEvent.loaded || 0,
304
+ total: progressEvent.total || 0,
305
+ lengthComputable: progressEvent.lengthComputable !== false,
306
+ target: progressEvent
307
+ });
308
+ };
309
+ }
310
+ this.logDebug('Request options', options);
311
+ this.logInfo(`Calling: ${this.method} - ${fullUrl}`);
312
+ try {
313
+ this.response = await axios.request(options);
314
+ this.status = this.response?.status ?? 200;
315
+ this.logVerbose('Response received', { status: this.status, headers: this.response.headers });
316
+ }
317
+ catch (e) {
318
+ // cancellation path in v1
319
+ if (e instanceof CanceledError || e?.code === 'ERR_CANCELED') {
320
+ this.aborted = true;
321
+ this.logWarning('Request cancelled', e.message);
322
+ const httpException = new HttpException(0, this);
323
+ await this.onError?.(httpException);
324
+ throw httpException;
325
+ }
326
+ // Extract response and status from Axios error
327
+ this.response = e?.response;
328
+ this.status = this.response?.status ?? 500;
329
+ this.logError('Request failed', { status: this.status, error: e.message });
330
+ // Convert AxiosError to HttpException for non-2xx status codes
331
+ if (!this.isValidStatus(this.status)) {
332
+ const errorResponse = this.getErrorResponse();
333
+ const httpException = new HttpException(this.status, this, errorResponse);
334
+ await this.onError?.(httpException);
335
+ throw httpException;
336
+ }
337
+ // For other errors (network, timeout, etc.), still throw HttpException
338
+ const errorResponse = this.getErrorResponse();
339
+ const httpException = new HttpException(this.status, this, errorResponse);
340
+ await this.onError?.(httpException);
341
+ throw httpException;
342
+ }
343
+ const status = this.getStatus();
344
+ this.logDebug('Response status', { status });
345
+ if (this.aborted) {
346
+ this.logWarning('Request was aborted after response');
347
+ const httpException = new HttpException(status, this);
348
+ await this.onError?.(httpException);
349
+ throw httpException;
350
+ }
351
+ // Status validation is already handled in catch block for errors
352
+ // This check is for successful responses that might have invalid status (shouldn't happen)
353
+ if (!this.isValidStatus(status)) {
354
+ this.logWarning('Invalid response status', { status, expectedRange: '200-299' });
355
+ const errorResponse = this.getErrorResponse();
356
+ const httpException = new HttpException(status, this, errorResponse);
357
+ await this.onError?.(httpException);
358
+ throw httpException;
359
+ }
360
+ let response = this.getResponse();
361
+ const requestData = this.body || this.params;
362
+ if (!exists(response)) {
363
+ this.logVerbose('Empty response received');
364
+ await this.onCompleted?.(response, requestData, this);
365
+ return response;
366
+ }
367
+ // Convert Buffer to ArrayBuffer for arraybuffer response type (Node.js compatibility)
368
+ if (this.responseType === 'arraybuffer' && Buffer.isBuffer(response)) {
369
+ const buffer = response;
370
+ const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
371
+ response = arrayBuffer;
372
+ this.logVerbose('Converted Buffer to ArrayBuffer for arraybuffer response type');
373
+ }
374
+ try {
375
+ response = JSON.parse(response);
376
+ this.logVerbose('Response parsed as JSON');
377
+ }
378
+ catch (ignore) {
379
+ this.logVerbose('Response is not JSON, returning as-is');
380
+ }
381
+ this.logInfo(`Request completed successfully`, { status, method: this.method, url: fullUrl });
382
+ await this.onCompleted?.(response, requestData, this);
383
+ return response;
384
+ }
385
+ clearOnCompleted = () => {
386
+ delete this.onCompleted;
387
+ };
388
+ /**
389
+ * Generic callback chaining helper.
390
+ *
391
+ * Chains multiple callbacks of the same type, executing them in order.
392
+ * If an existing callback exists, both are executed (existing first, then new).
393
+ *
394
+ * @template T - Callback function type
395
+ * @param existingCallback - Previously set callback (if any)
396
+ * @param newCallback - New callback to add
397
+ * @param setter - Function to set the final callback
398
+ * @returns This instance for chaining
399
+ */
400
+ setCallback(existingCallback, newCallback, setter) {
401
+ if (!newCallback)
402
+ return this;
403
+ if (existingCallback && newCallback) {
404
+ const _existing = existingCallback;
405
+ setter((async (...args) => {
406
+ await _existing(...args);
407
+ await newCallback(...args);
408
+ }));
409
+ }
410
+ else
411
+ setter(newCallback);
412
+ return this;
413
+ }
414
+ setOnCompleted = (onCompleted) => {
415
+ return this.setCallback(this.onCompleted, onCompleted, (callback) => {
416
+ this.onCompleted = callback;
417
+ });
418
+ };
419
+ setOnError(onError) {
420
+ return this.setCallback(this.onError, onError, (callback) => {
421
+ this.onError = callback;
422
+ });
423
+ }
424
+ getStatus() {
425
+ if (!this.status)
426
+ throw new BadImplementationException('Missing status..');
427
+ return this.status;
428
+ }
429
+ getResponse() {
430
+ return this.response?.data;
431
+ }
432
+ /**
433
+ * Gets the full Axios response object.
434
+ *
435
+ * Returns the complete Axios response including:
436
+ * - status, statusText
437
+ * - headers (full object)
438
+ * - data (response body)
439
+ * - config (request configuration)
440
+ *
441
+ * Useful when you need access to response metadata beyond just the body,
442
+ * such as response headers, status text, or the full request configuration.
443
+ *
444
+ * @returns Full Axios response object
445
+ * @throws BadImplementationException if response is not yet available (execute() hasn't been called)
446
+ */
447
+ getRawResponse() {
448
+ if (!this.response)
449
+ throw new BadImplementationException('Response not available. Call execute() first.');
450
+ return this.response;
451
+ }
452
+ /**
453
+ * Extracts error response from the HTTP response.
454
+ *
455
+ * Returns a basic error response structure with the raw response data
456
+ * as the debug message. Used when status validation fails.
457
+ *
458
+ * @returns Error response structure with debug message
459
+ */
460
+ getErrorResponse() {
461
+ return { debugMessage: this.getResponse() };
462
+ }
463
+ abortImpl() {
464
+ this.cancelController.abort();
465
+ }
466
+ setRequestOption(requestOption) {
467
+ this.requestOption = requestOption;
468
+ return this;
469
+ }
470
+ _getResponseHeader(headerKey) {
471
+ if (!this.response)
472
+ throw new BadImplementationException(`axios didn't return yet`);
473
+ return this.response.headers?.[headerKey];
474
+ }
475
+ getResponseHeader(headerKey) {
476
+ try {
477
+ return this._getResponseHeader(headerKey);
478
+ }
479
+ catch (e) {
480
+ this.logError(`Response headers not available yet. Request may not have completed.`, e);
481
+ }
482
+ }
483
+ /**
484
+ * Aborts the HTTP request.
485
+ *
486
+ * Marks the request as aborted and triggers the AbortController signal,
487
+ * which cancels the underlying Axios request. If executed before the request
488
+ * completes, the execute() method will throw an HttpException with status 0.
489
+ */
490
+ abort() {
491
+ this.aborted = true;
492
+ this.abortImpl();
493
+ }
494
+ }
@@ -0,0 +1,79 @@
1
+ import { CustomException } from '@nu-art/ts-common';
2
+ import type { HttpRequest } from '../core/HttpRequest.js';
3
+ import type { ApiError_GeneralErrorMessage, ApiErrorResponse, ResponseError } from '../types/error-types.js';
4
+ /**
5
+ * HTTP exception containing error details and the original request.
6
+ *
7
+ * Provides complete context for error handling including the request that failed,
8
+ * allowing error handlers to retry, inspect request details, or perform recovery operations.
9
+ *
10
+ * @template E - Response error type
11
+ */
12
+ export declare class HttpException<E extends ResponseError = ResponseError> extends CustomException {
13
+ /** HTTP status code */
14
+ responseCode: number;
15
+ /** Error response from server (if available) */
16
+ errorResponse?: ApiErrorResponse<E>;
17
+ /** The HTTP request that failed - provides access to method, headers, URL, body, params, etc. */
18
+ request: HttpRequest<any>;
19
+ constructor(responseCode: number, request: HttpRequest<any>, errorResponse?: ApiErrorResponse<E>);
20
+ }
21
+ /**
22
+ * Exception class for API errors with HTTP response codes and structured error bodies.
23
+ *
24
+ * Used for errors that need to be returned to API clients with:
25
+ * - HTTP status code
26
+ * - Structured error response body
27
+ * - Debug message for server-side logging
28
+ *
29
+ * The constructor accepts `causeOrMessage` as either a string (message) or Error (cause),
30
+ * allowing flexible error construction. If both `causeOrMessage` (as Error) and `cause`
31
+ * are provided, `causeOrMessage` takes precedence.
32
+ *
33
+ * @template Err - Type of the error body (must extend ResponseError)
34
+ *
35
+ * @category Exceptions
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * // With message string
40
+ * throw new ApiException(404, 'Resource not found');
41
+ *
42
+ * // With cause Error
43
+ * throw new ApiException(500, originalError);
44
+ *
45
+ * // With both message and cause
46
+ * throw new ApiException(400, 'Invalid input', validationError);
47
+ * ```
48
+ */
49
+ export declare class ApiException<Err extends ResponseError = ApiError_GeneralErrorMessage> extends CustomException {
50
+ /** Structured error response body for API clients */
51
+ readonly responseBody: ApiErrorResponse<Err>;
52
+ /** HTTP status code for the error response */
53
+ readonly responseCode: number;
54
+ /**
55
+ * Sets the error body and returns this instance for method chaining.
56
+ *
57
+ * @param errorBody - Error body object to include in the response
58
+ * @returns This instance for chaining
59
+ */
60
+ readonly setErrorBody: (errorBody: Err) => this;
61
+ /**
62
+ * Creates a new ApiException.
63
+ *
64
+ * @param responseCode - HTTP status code (e.g., 404, 500)
65
+ * @param causeOrMessage - Either a message string or an Error object (as cause)
66
+ * @param cause - Optional additional cause Error (only used if causeOrMessage is a string)
67
+ */
68
+ constructor(responseCode: number, causeOrMessage?: string | Error, cause?: Error);
69
+ /**
70
+ * Extracts message string from causeOrMessage if it's a string.
71
+ */
72
+ private static getMessage;
73
+ /**
74
+ * Extracts cause Error from parameters.
75
+ * If causeOrMessage is an Error, it's used as the cause.
76
+ * Otherwise, the cause parameter is used.
77
+ */
78
+ private static getCause;
79
+ }
@@ -0,0 +1,99 @@
1
+ /*
2
+ * @nu-art/thunderstorm-http - A robust and type-safe HTTP client for Thunderstorm applications
3
+ * Copyright (C) 2024 Adam van der Kruk aka TacB0sS
4
+ * Licensed under the Apache License, Version 2.0
5
+ */
6
+ import { _logger_logException, CustomException } from '@nu-art/ts-common';
7
+ /**
8
+ * HTTP exception containing error details and the original request.
9
+ *
10
+ * Provides complete context for error handling including the request that failed,
11
+ * allowing error handlers to retry, inspect request details, or perform recovery operations.
12
+ *
13
+ * @template E - Response error type
14
+ */
15
+ export class HttpException extends CustomException {
16
+ /** HTTP status code */
17
+ responseCode;
18
+ /** Error response from server (if available) */
19
+ errorResponse;
20
+ /** The HTTP request that failed - provides access to method, headers, URL, body, params, etc. */
21
+ request;
22
+ constructor(responseCode, request, errorResponse) {
23
+ const url = request.getUrl();
24
+ super(HttpException, `${responseCode} - ${url}`);
25
+ this.responseCode = responseCode;
26
+ this.errorResponse = errorResponse;
27
+ this.request = request;
28
+ }
29
+ }
30
+ /**
31
+ * Exception class for API errors with HTTP response codes and structured error bodies.
32
+ *
33
+ * Used for errors that need to be returned to API clients with:
34
+ * - HTTP status code
35
+ * - Structured error response body
36
+ * - Debug message for server-side logging
37
+ *
38
+ * The constructor accepts `causeOrMessage` as either a string (message) or Error (cause),
39
+ * allowing flexible error construction. If both `causeOrMessage` (as Error) and `cause`
40
+ * are provided, `causeOrMessage` takes precedence.
41
+ *
42
+ * @template Err - Type of the error body (must extend ResponseError)
43
+ *
44
+ * @category Exceptions
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * // With message string
49
+ * throw new ApiException(404, 'Resource not found');
50
+ *
51
+ * // With cause Error
52
+ * throw new ApiException(500, originalError);
53
+ *
54
+ * // With both message and cause
55
+ * throw new ApiException(400, 'Invalid input', validationError);
56
+ * ```
57
+ */
58
+ export class ApiException extends CustomException {
59
+ /** Structured error response body for API clients */
60
+ responseBody = {};
61
+ /** HTTP status code for the error response */
62
+ responseCode;
63
+ /**
64
+ * Sets the error body and returns this instance for method chaining.
65
+ *
66
+ * @param errorBody - Error body object to include in the response
67
+ * @returns This instance for chaining
68
+ */
69
+ setErrorBody = (errorBody) => {
70
+ this.responseBody.error = errorBody;
71
+ return this;
72
+ };
73
+ /**
74
+ * Creates a new ApiException.
75
+ *
76
+ * @param responseCode - HTTP status code (e.g., 404, 500)
77
+ * @param causeOrMessage - Either a message string or an Error object (as cause)
78
+ * @param cause - Optional additional cause Error (only used if causeOrMessage is a string)
79
+ */
80
+ constructor(responseCode, causeOrMessage, cause) {
81
+ super(ApiException, `${responseCode}${ApiException.getMessage(causeOrMessage)}`, ApiException.getCause(causeOrMessage, cause));
82
+ this.responseCode = responseCode;
83
+ this.responseBody.debugMessage = _logger_logException(this);
84
+ }
85
+ /**
86
+ * Extracts message string from causeOrMessage if it's a string.
87
+ */
88
+ static getMessage(causeOrMessage) {
89
+ return typeof causeOrMessage === 'string' ? `-${JSON.stringify(causeOrMessage)}` : '';
90
+ }
91
+ /**
92
+ * Extracts cause Error from parameters.
93
+ * If causeOrMessage is an Error, it's used as the cause.
94
+ * Otherwise, the cause parameter is used.
95
+ */
96
+ static getCause(causeOrMessage, cause) {
97
+ return typeof causeOrMessage != 'string' ? causeOrMessage : cause;
98
+ }
99
+ }
package/index.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ export * from './core/HttpClient.js';
2
+ export * from './core/HttpRequest.js';
3
+ export * from './types/api-types.js';
4
+ export * from './types/error-types.js';
5
+ export * from './types/types.js';
6
+ export * from './exceptions/HttpException.js';
7
+ export * from './utils/http-codes.js';
8
+ export * from './utils/utils.js';
package/index.js ADDED
@@ -0,0 +1,17 @@
1
+ /*
2
+ * @nu-art/thunderstorm-http - A robust and type-safe HTTP client for Thunderstorm applications
3
+ * Copyright (C) 2024 Adam van der Kruk aka TacB0sS
4
+ * Licensed under the Apache License, Version 2.0
5
+ */
6
+ // Core classes
7
+ export * from './core/HttpClient.js';
8
+ export * from './core/HttpRequest.js';
9
+ // Types
10
+ export * from './types/api-types.js';
11
+ export * from './types/error-types.js';
12
+ export * from './types/types.js';
13
+ // Exceptions
14
+ export * from './exceptions/HttpException.js';
15
+ // Utils
16
+ export * from './utils/http-codes.js';
17
+ export * from './utils/utils.js';