@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,92 @@
1
+ import { Logger } from '@nu-art/ts-common';
2
+ import { HttpRequest } from './HttpRequest.js';
3
+ import { AxiosRequestConfig as Axios_RequestConfig } from 'axios';
4
+ import { HttpException } from '../exceptions/HttpException.js';
5
+ import { ApiDef, TypedApi } from '../types/api-types.js';
6
+ /**
7
+ * HTTP client configuration.
8
+ */
9
+ export type HttpConfig = {
10
+ origin?: string;
11
+ timeout?: number;
12
+ compress?: boolean;
13
+ };
14
+ /**
15
+ * HTTP client for creating and configuring typed HTTP requests.
16
+ *
17
+ * Manages default configuration (origin, timeout, compression, headers) and provides
18
+ * a factory method for creating typed HttpRequest instances. All requests created
19
+ * from this client inherit the default configuration and callbacks.
20
+ *
21
+ * Extends Logger for built-in logging capabilities.
22
+ */
23
+ export declare class HttpClient_Class extends Logger {
24
+ protected origin?: string;
25
+ protected timeout: number;
26
+ protected compress: boolean;
27
+ private readonly defaultHeaders;
28
+ protected defaultOnComplete?: (response: unknown, input: unknown, request: HttpRequest<any>) => Promise<any>;
29
+ protected defaultOnError?: (errorResponse: HttpException) => Promise<any>;
30
+ private requestOption;
31
+ /**
32
+ * Creates a new HTTP client instance.
33
+ *
34
+ * @param config - Optional configuration (origin, timeout, compress)
35
+ * @param label - Optional label for logging (defaults to 'HttpClient')
36
+ */
37
+ constructor(config?: HttpConfig, label?: string);
38
+ /**
39
+ * Sets the HTTP client configuration.
40
+ *
41
+ * Normalizes the origin URL by removing trailing slashes to ensure consistent
42
+ * URL composition in requests.
43
+ *
44
+ * @param config - Configuration object
45
+ */
46
+ setConfig(config: HttpConfig): void;
47
+ getOrigin(): string | undefined;
48
+ shouldCompress(): boolean;
49
+ setDefaultOnComplete: (defaultOnComplete: (response: unknown, input: unknown, request: HttpRequest<any>) => Promise<any>) => void;
50
+ setDefaultOnError: (defaultOnError: (errorResponse: HttpException) => Promise<any>) => void;
51
+ /**
52
+ * Adds a default header that will be included in all requests created by this client.
53
+ *
54
+ * Headers can be static values (string or array) or functions that return values
55
+ * (evaluated when creating requests). Functions are useful for dynamic headers
56
+ * like authentication tokens.
57
+ *
58
+ * @param key - Header name
59
+ * @param header - Header value (string, array, or function returning string/array)
60
+ */
61
+ addDefaultHeader(key: string, header: (() => string | string[]) | string | string[]): void;
62
+ /**
63
+ * Resolves default headers, evaluating functions and normalizing values.
64
+ *
65
+ * Converts all default header definitions into a flat object of string arrays.
66
+ * Functions are called to get their return values. Throws if header value type
67
+ * is invalid.
68
+ *
69
+ * @returns Object mapping header names to string arrays
70
+ * @throws BadImplementationException if header value type is invalid
71
+ */
72
+ getDefaultHeaders(): {
73
+ [k: string]: string | string[];
74
+ };
75
+ setRequestOption(requestOption: Axios_RequestConfig): this;
76
+ /**
77
+ * Creates a new typed HTTP request with default configuration applied.
78
+ *
79
+ * The request is pre-configured with:
80
+ * - Method, timeout, and request options from client defaults
81
+ * - Default headers (evaluated at creation time)
82
+ * - Default error and completion callbacks (if set)
83
+ * - URL (either fullUrl or composed from baseUrl/origin + path)
84
+ *
85
+ * @template API - Typed API definition
86
+ * @param apiDef - API definition with method, path, and optional URL configuration
87
+ * @param data - Optional request data (used as request key identifier)
88
+ * @returns Configured HttpRequest instance ready for further customization
89
+ */
90
+ createRequest<API extends TypedApi<any, any, any, any>>(apiDef: ApiDef<API>, data?: string): HttpRequest<API>;
91
+ }
92
+ export declare const HttpClient: HttpClient_Class;
@@ -0,0 +1,157 @@
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 { BadImplementationException, Logger } from '@nu-art/ts-common';
8
+ import { HttpRequest } from './HttpRequest.js';
9
+ /**
10
+ * HTTP client for creating and configuring typed HTTP requests.
11
+ *
12
+ * Manages default configuration (origin, timeout, compression, headers) and provides
13
+ * a factory method for creating typed HttpRequest instances. All requests created
14
+ * from this client inherit the default configuration and callbacks.
15
+ *
16
+ * Extends Logger for built-in logging capabilities.
17
+ */
18
+ export class HttpClient_Class extends Logger {
19
+ origin;
20
+ timeout = 10000;
21
+ compress = true;
22
+ defaultHeaders = {};
23
+ defaultOnComplete;
24
+ defaultOnError;
25
+ requestOption = {};
26
+ /**
27
+ * Creates a new HTTP client instance.
28
+ *
29
+ * @param config - Optional configuration (origin, timeout, compress)
30
+ * @param label - Optional label for logging (defaults to 'HttpClient')
31
+ */
32
+ constructor(config, label = 'HttpClient') {
33
+ super(label);
34
+ if (config) {
35
+ this.setConfig(config);
36
+ }
37
+ }
38
+ /**
39
+ * Sets the HTTP client configuration.
40
+ *
41
+ * Normalizes the origin URL by removing trailing slashes to ensure consistent
42
+ * URL composition in requests.
43
+ *
44
+ * @param config - Configuration object
45
+ */
46
+ setConfig(config) {
47
+ if (config.timeout !== undefined)
48
+ this.timeout = config.timeout;
49
+ if (config.compress !== undefined)
50
+ this.compress = config.compress;
51
+ if (config.origin) {
52
+ let origin = config.origin;
53
+ if (origin.endsWith('/'))
54
+ origin = origin.substring(0, origin.length - 1);
55
+ this.origin = origin;
56
+ }
57
+ }
58
+ getOrigin() {
59
+ return this.origin;
60
+ }
61
+ shouldCompress() {
62
+ return this.compress;
63
+ }
64
+ setDefaultOnComplete = (defaultOnComplete) => {
65
+ this.defaultOnComplete = defaultOnComplete;
66
+ };
67
+ setDefaultOnError = (defaultOnError) => {
68
+ this.defaultOnError = defaultOnError;
69
+ };
70
+ /**
71
+ * Adds a default header that will be included in all requests created by this client.
72
+ *
73
+ * Headers can be static values (string or array) or functions that return values
74
+ * (evaluated when creating requests). Functions are useful for dynamic headers
75
+ * like authentication tokens.
76
+ *
77
+ * @param key - Header name
78
+ * @param header - Header value (string, array, or function returning string/array)
79
+ */
80
+ addDefaultHeader(key, header) {
81
+ this.defaultHeaders[key] = header;
82
+ }
83
+ /**
84
+ * Resolves default headers, evaluating functions and normalizing values.
85
+ *
86
+ * Converts all default header definitions into a flat object of string arrays.
87
+ * Functions are called to get their return values. Throws if header value type
88
+ * is invalid.
89
+ *
90
+ * @returns Object mapping header names to string arrays
91
+ * @throws BadImplementationException if header value type is invalid
92
+ */
93
+ getDefaultHeaders() {
94
+ return Object.keys(this.defaultHeaders).reduce((toRet, _key) => {
95
+ const defaultHeader = this.defaultHeaders[_key];
96
+ const key = _key.toLowerCase();
97
+ switch (typeof defaultHeader) {
98
+ case 'string':
99
+ toRet[key] = [defaultHeader];
100
+ break;
101
+ case 'function':
102
+ const functionResult = defaultHeader();
103
+ // Wrap string results in array, keep arrays as-is
104
+ toRet[key] = typeof functionResult === 'string' ? [functionResult] : functionResult;
105
+ break;
106
+ case 'object':
107
+ if (Array.isArray(defaultHeader)) {
108
+ toRet[key] = defaultHeader;
109
+ break;
110
+ }
111
+ // eslint-disable-next-line no-fallthrough
112
+ case 'boolean':
113
+ case 'number':
114
+ case 'symbol':
115
+ case 'bigint':
116
+ case 'undefined':
117
+ throw new BadImplementationException(`Headers values can only be of type: (() => string | string[]) | string | string[], got type ${typeof defaultHeader} `);
118
+ }
119
+ return toRet;
120
+ }, {});
121
+ }
122
+ setRequestOption(requestOption) {
123
+ this.requestOption = requestOption;
124
+ return this;
125
+ }
126
+ /**
127
+ * Creates a new typed HTTP request with default configuration applied.
128
+ *
129
+ * The request is pre-configured with:
130
+ * - Method, timeout, and request options from client defaults
131
+ * - Default headers (evaluated at creation time)
132
+ * - Default error and completion callbacks (if set)
133
+ * - URL (either fullUrl or composed from baseUrl/origin + path)
134
+ *
135
+ * @template API - Typed API definition
136
+ * @param apiDef - API definition with method, path, and optional URL configuration
137
+ * @param data - Optional request data (used as request key identifier)
138
+ * @returns Configured HttpRequest instance ready for further customization
139
+ */
140
+ createRequest(apiDef, data) {
141
+ const request = new HttpRequest(apiDef.path, data, this.shouldCompress())
142
+ .setMethod(apiDef.method)
143
+ .setTimeout(this.timeout)
144
+ .setRequestOption(this.requestOption)
145
+ .addHeaders(this.getDefaultHeaders());
146
+ if (apiDef.fullUrl)
147
+ request.setUrl(apiDef.fullUrl);
148
+ else
149
+ request.setOrigin(apiDef.baseUrl ?? this.origin).setRelativeUrl(apiDef.path);
150
+ if (this.defaultOnError)
151
+ request.setOnError(this.defaultOnError);
152
+ if (this.defaultOnComplete)
153
+ request.setOnCompleted(this.defaultOnComplete);
154
+ return request;
155
+ }
156
+ }
157
+ export const HttpClient = new HttpClient_Class();
@@ -0,0 +1,205 @@
1
+ import { Logger } from '@nu-art/ts-common';
2
+ import { ApiError_GeneralErrorMessage, ApiErrorResponse, ResponseError } from '@nu-art/ts-common/core/exceptions/types';
3
+ import { AxiosRequestConfig as Axios_RequestConfig, AxiosResponse as Axios_Response } from 'axios';
4
+ import { HttpException } from '../exceptions/HttpException.js';
5
+ import { TS_Progress } from '../types/error-types.js';
6
+ import { HttpMethod, TypedApi } from '../types/api-types.js';
7
+ /**
8
+ * Typed HTTP request with fluent builder API and comprehensive logging.
9
+ *
10
+ * Provides a type-safe, chainable interface for building and executing HTTP requests.
11
+ * Extends Logger for built-in request lifecycle logging (verbose, debug, info, warning, error).
12
+ *
13
+ * Features:
14
+ * - Type-safe request/response handling via TypedApi generics
15
+ * - Fluent builder pattern for configuration
16
+ * - Automatic JSON parsing of responses
17
+ * - Request cancellation via AbortController
18
+ * - Callback chaining for error and completion handlers
19
+ * - Comprehensive logging at all stages
20
+ *
21
+ * @template API - Typed API definition specifying method, response, body, params, and error types
22
+ */
23
+ export declare class HttpRequest<API extends TypedApi<any, any, any, any>> extends Logger {
24
+ key: string;
25
+ requestData: any;
26
+ protected origin?: string;
27
+ protected headers: {
28
+ [s: string]: string[];
29
+ };
30
+ protected method: HttpMethod;
31
+ protected timeout: number;
32
+ protected body: API['B'];
33
+ protected url: string;
34
+ protected params: {
35
+ [K in keyof API['P']]?: API['P'][K];
36
+ };
37
+ protected responseType: string;
38
+ protected label: string;
39
+ protected onProgressListener: (ev: TS_Progress) => void;
40
+ protected aborted: boolean;
41
+ protected compress: boolean;
42
+ private onCompleted?;
43
+ private onError?;
44
+ private response?;
45
+ private cancelController;
46
+ protected status?: number;
47
+ private requestOption;
48
+ /**
49
+ * Creates a new HTTP request instance.
50
+ *
51
+ * Automatically extends timeout to 5 minutes in debug mode for development.
52
+ * Initializes AbortController for request cancellation support.
53
+ *
54
+ * @param requestKey - Identifier for this request (used in logging)
55
+ * @param requestData - Optional data associated with the request
56
+ * @param shouldCompress - Whether to enable compression (default: false)
57
+ */
58
+ constructor(requestKey: string, requestData?: any, shouldCompress?: boolean);
59
+ resolveTypedException(exception: HttpException<any> | unknown): API['E'] | undefined;
60
+ getRequestData(): any;
61
+ setOrigin(origin?: string): this;
62
+ setOnProgressListener(onProgressListener: (ev: TS_Progress) => void): this;
63
+ setLabel(label: string): this;
64
+ setMethod(method: HttpMethod): this;
65
+ setResponseType(responseType: string): this;
66
+ setUrlParams(params: API['P']): this;
67
+ setUrlParam<K extends keyof API['P'] = keyof API['P']>(key: K, value: API['P'][K]): this;
68
+ setUrl(url: string): this;
69
+ getUrl(): string;
70
+ /**
71
+ * Sets a relative URL path, composing it with the origin.
72
+ *
73
+ * Automatically removes leading slashes from the relative path and combines
74
+ * with origin. Requires origin to be set first.
75
+ *
76
+ * @param relativeUrl - Relative path (leading slash is removed if present)
77
+ * @returns This instance for chaining
78
+ * @throws BadImplementationException if origin is not set
79
+ */
80
+ setRelativeUrl(relativeUrl: string): this;
81
+ setTimeout(timeout: number): this;
82
+ setHeaders(headers: {
83
+ [s: string]: string | string[];
84
+ }): this;
85
+ addHeaders(headers: {
86
+ [s: string]: string | string[];
87
+ }): this;
88
+ /**
89
+ * Sets a header, replacing any existing values for the key.
90
+ *
91
+ * Header keys are normalized to lowercase. Multiple calls to setHeader
92
+ * for the same key will replace previous values.
93
+ *
94
+ * @param _key - Header name (case-insensitive, normalized to lowercase)
95
+ * @param value - Header value(s)
96
+ * @returns This instance for chaining
97
+ */
98
+ setHeader(_key: string, value: string | string[]): this;
99
+ /**
100
+ * Adds a header value, appending to existing values if the key already exists.
101
+ *
102
+ * Header keys are normalized to lowercase. Multiple values for the same header
103
+ * are joined with '; ' when the request is executed.
104
+ *
105
+ * @param _key - Header name (case-insensitive, normalized to lowercase)
106
+ * @param value - Header value(s) to append
107
+ * @returns This instance for chaining
108
+ */
109
+ addHeader(_key: string, value: string | string[]): this;
110
+ removeHeader(key: string): this;
111
+ protected _addHeaderImpl(key: string, value: string | string[]): this;
112
+ protected prepareJsonBody(bodyObject: API['B']): any;
113
+ setBodyAsJson(bodyObject: API['B'], compress?: boolean): this;
114
+ /**
115
+ * Sets the request body.
116
+ *
117
+ * If compression is enabled and body is a string, automatically adds
118
+ * 'Content-encoding: gzip' header.
119
+ *
120
+ * @param bodyAsString - Request body (any type)
121
+ * @param _compress - Override compression setting (optional)
122
+ * @returns This instance for chaining
123
+ */
124
+ setBody(bodyAsString: any, _compress?: boolean): this;
125
+ isValidStatus(statusCode: number): boolean;
126
+ protected print(): void;
127
+ /**
128
+ * Executes the HTTP request and returns the typed response.
129
+ *
130
+ * Process:
131
+ * 1. Validates request isn't aborted
132
+ * 2. Composes full URL with query parameters
133
+ * 3. Prepares headers (joins multiple values with '; ')
134
+ * 4. Sends request via Axios
135
+ * 5. Validates response status (200-299 considered success)
136
+ * 6. Attempts JSON parsing (falls back to raw response if not JSON)
137
+ * 7. Calls completion/error callbacks
138
+ *
139
+ * Automatically handles:
140
+ * - Request cancellation (AbortController)
141
+ * - Error response parsing
142
+ * - JSON response parsing
143
+ * - Callback execution (onError for failures, onCompleted for success)
144
+ *
145
+ * @param print - If true, logs request details (URL, params, headers) before execution
146
+ * @returns Typed response data (API['R'])
147
+ * @throws HttpException if request fails or returns non-2xx status
148
+ */
149
+ execute(print?: boolean): Promise<API['R']>;
150
+ clearOnCompleted: () => void;
151
+ /**
152
+ * Generic callback chaining helper.
153
+ *
154
+ * Chains multiple callbacks of the same type, executing them in order.
155
+ * If an existing callback exists, both are executed (existing first, then new).
156
+ *
157
+ * @template T - Callback function type
158
+ * @param existingCallback - Previously set callback (if any)
159
+ * @param newCallback - New callback to add
160
+ * @param setter - Function to set the final callback
161
+ * @returns This instance for chaining
162
+ */
163
+ private setCallback;
164
+ setOnCompleted: (onCompleted?: (response: API["R"], input: API["P"] | API["B"], request: HttpRequest<API>) => Promise<any>) => this;
165
+ setOnError(onError?: (errorResponse: HttpException<API['E']>) => Promise<any>): this;
166
+ getStatus(): number;
167
+ getResponse(): any;
168
+ /**
169
+ * Gets the full Axios response object.
170
+ *
171
+ * Returns the complete Axios response including:
172
+ * - status, statusText
173
+ * - headers (full object)
174
+ * - data (response body)
175
+ * - config (request configuration)
176
+ *
177
+ * Useful when you need access to response metadata beyond just the body,
178
+ * such as response headers, status text, or the full request configuration.
179
+ *
180
+ * @returns Full Axios response object
181
+ * @throws BadImplementationException if response is not yet available (execute() hasn't been called)
182
+ */
183
+ getRawResponse(): Axios_Response<API['R']>;
184
+ /**
185
+ * Extracts error response from the HTTP response.
186
+ *
187
+ * Returns a basic error response structure with the raw response data
188
+ * as the debug message. Used when status validation fails.
189
+ *
190
+ * @returns Error response structure with debug message
191
+ */
192
+ getErrorResponse(): ApiErrorResponse<ResponseError | ApiError_GeneralErrorMessage>;
193
+ private abortImpl;
194
+ setRequestOption(requestOption: Axios_RequestConfig): this;
195
+ _getResponseHeader(headerKey: string): string | string[] | undefined;
196
+ getResponseHeader(headerKey: string): string | string[] | undefined;
197
+ /**
198
+ * Aborts the HTTP request.
199
+ *
200
+ * Marks the request as aborted and triggers the AbortController signal,
201
+ * which cancels the underlying Axios request. If executed before the request
202
+ * completes, the execute() method will throw an HttpException with status 0.
203
+ */
204
+ abort(): void;
205
+ }