@adatechnology/http-client 0.0.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.
Files changed (32) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +596 -0
  3. package/dist/http.interface.js +27 -0
  4. package/dist/http.module.js +37 -0
  5. package/dist/http.provider.js +137 -0
  6. package/dist/http.token.js +6 -0
  7. package/dist/implementations/axios/axios.http.module.js +41 -0
  8. package/dist/implementations/axios/axios.http.provider.js +424 -0
  9. package/dist/implementations/axios/axios.http.token.js +4 -0
  10. package/dist/implementations/http.implementation.module.js +20 -0
  11. package/dist/index.js +11 -0
  12. package/dist/types/http.interface.d.ts +175 -0
  13. package/dist/types/http.module.d.ts +9 -0
  14. package/dist/types/http.provider.d.ts +43 -0
  15. package/dist/types/http.token.d.ts +3 -0
  16. package/dist/types/implementations/axios/axios.http.module.d.ts +5 -0
  17. package/dist/types/implementations/axios/axios.http.provider.d.ts +226 -0
  18. package/dist/types/implementations/axios/axios.http.token.d.ts +1 -0
  19. package/dist/types/implementations/http.implementation.module.d.ts +2 -0
  20. package/dist/types/index.d.ts +4 -0
  21. package/package.json +19 -0
  22. package/src/http.interface.ts +259 -0
  23. package/src/http.module.ts +27 -0
  24. package/src/http.provider.ts +219 -0
  25. package/src/http.token.ts +3 -0
  26. package/src/implementations/axios/axios.http.module.ts +33 -0
  27. package/src/implementations/axios/axios.http.provider.ts +603 -0
  28. package/src/implementations/axios/axios.http.token.ts +1 -0
  29. package/src/implementations/http.implementation.module.ts +9 -0
  30. package/src/index.ts +8 -0
  31. package/tsconfig.json +16 -0
  32. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,603 @@
1
+ import axios, { AxiosInstance, AxiosResponse } from "axios";
2
+ import { from, Observable } from "rxjs";
3
+
4
+ import {
5
+ ErrorInterceptor,
6
+ HttpProviderInterface,
7
+ HttpRequestConfig,
8
+ HttpResponse,
9
+ } from "../../http.interface";
10
+
11
+ /**
12
+ * Interface for Axios HTTP Provider
13
+ */
14
+ export interface AxiosHttpProviderInterface extends HttpProviderInterface {}
15
+
16
+ /**
17
+ * Simple cache entry
18
+ */
19
+ interface CacheEntry<T> {
20
+ data: T;
21
+ timestamp: number;
22
+ ttl: number;
23
+ }
24
+
25
+ /**
26
+ * Axios-based implementation of the HTTP provider interface.
27
+ * Provides HTTP client functionality with both Promise and Observable APIs,
28
+ * plus basic caching capabilities.
29
+ */
30
+ export class AxiosHttpProvider implements AxiosHttpProviderInterface {
31
+ private axiosInstance: AxiosInstance;
32
+ private errorInterceptors: Map<number, ErrorInterceptor> = new Map();
33
+ private nextErrorInterceptorId = 0;
34
+ private requestInterceptorIds: Set<number> = new Set();
35
+ private responseInterceptorIds: Set<number> = new Set();
36
+ private cache = new Map<string, CacheEntry<any>>();
37
+ private cacheCleanupInterval?: ReturnType<typeof setInterval>;
38
+
39
+ constructor(axiosInstance?: AxiosInstance) {
40
+ this.axiosInstance = axiosInstance || axios.create();
41
+ this.startCacheCleanup();
42
+ }
43
+
44
+ /**
45
+ * Starts automatic cache cleanup
46
+ */
47
+ private startCacheCleanup(): void {
48
+ // Clean expired cache entries every 5 minutes
49
+ this.cacheCleanupInterval = setInterval(() => {
50
+ this.cleanupExpiredCache();
51
+ }, 300000);
52
+ }
53
+
54
+ /**
55
+ * Stops automatic cache cleanup
56
+ */
57
+ private stopCacheCleanup(): void {
58
+ if (this.cacheCleanupInterval) {
59
+ clearInterval(this.cacheCleanupInterval);
60
+ this.cacheCleanupInterval = undefined;
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Cleans up expired cache entries
66
+ */
67
+ private cleanupExpiredCache(): void {
68
+ const now = Date.now();
69
+ const keysToDelete: string[] = [];
70
+
71
+ for (const [key, entry] of this.cache.entries()) {
72
+ if (now - entry.timestamp > entry.ttl) {
73
+ keysToDelete.push(key);
74
+ }
75
+ }
76
+
77
+ keysToDelete.forEach((key) => this.cache.delete(key));
78
+ }
79
+
80
+ /**
81
+ * Generates a cache key from URL and config
82
+ */
83
+ private generateCacheKey(url: string, config?: HttpRequestConfig): string {
84
+ const params = config?.params ? JSON.stringify(config.params) : "";
85
+ return `${url}${params}`;
86
+ }
87
+
88
+ /**
89
+ * Gets cached data if valid
90
+ */
91
+ private getCached<T>(key: string): T | null {
92
+ const entry = this.cache.get(key);
93
+ if (!entry) return null;
94
+
95
+ const now = Date.now();
96
+ if (now - entry.timestamp > entry.ttl) {
97
+ this.cache.delete(key);
98
+ return null;
99
+ }
100
+
101
+ return entry.data;
102
+ }
103
+
104
+ /**
105
+ * Sets data in cache
106
+ */
107
+ private setCache<T>(key: string, data: T, ttl: number = 300000): void {
108
+ // 5 minutes default
109
+ this.cache.set(key, {
110
+ data,
111
+ timestamp: Date.now(),
112
+ ttl,
113
+ });
114
+ }
115
+
116
+ /**
117
+ * Clears cache for a specific key or all cache
118
+ */
119
+ clearCache(key?: string): void {
120
+ if (key) {
121
+ this.cache.delete(key);
122
+ } else {
123
+ this.cache.clear();
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Gets cache statistics
129
+ */
130
+ getCacheStats(): { size: number; keys: string[] } {
131
+ return {
132
+ size: this.cache.size,
133
+ keys: Array.from(this.cache.keys()),
134
+ };
135
+ }
136
+
137
+ /**
138
+ * Sets a global header that will be included in all requests.
139
+ */
140
+ setGlobalHeader(key: string, value: string): void {
141
+ this.axiosInstance.defaults.headers.common[key] = value;
142
+ }
143
+
144
+ /**
145
+ * Removes a global header.
146
+ */
147
+ removeGlobalHeader(key: string): void {
148
+ delete this.axiosInstance.defaults.headers.common[key];
149
+ }
150
+
151
+ /**
152
+ * Gets all global headers currently set.
153
+ */
154
+ getGlobalHeaders(): Record<string, string> {
155
+ return { ...this.axiosInstance.defaults.headers.common } as Record<
156
+ string,
157
+ string
158
+ >;
159
+ }
160
+
161
+ /**
162
+ * Sets the base URL for all requests.
163
+ */
164
+ setBaseUrl(baseUrl: string): void {
165
+ this.axiosInstance.defaults.baseURL = baseUrl;
166
+ }
167
+
168
+ /**
169
+ * Gets the current base URL.
170
+ */
171
+ getBaseUrl(): string {
172
+ return this.axiosInstance.defaults.baseURL || "";
173
+ }
174
+
175
+ /**
176
+ * Sets the default timeout for all requests.
177
+ */
178
+ setDefaultTimeout(timeout: number): void {
179
+ this.axiosInstance.defaults.timeout = timeout;
180
+ }
181
+
182
+ /**
183
+ * Adds an error interceptor for handling errors globally.
184
+ */
185
+ addErrorInterceptor(interceptor: ErrorInterceptor): number {
186
+ const id = this.nextErrorInterceptorId++;
187
+ this.errorInterceptors.set(id, interceptor);
188
+ return id;
189
+ }
190
+
191
+ /**
192
+ * Adds a request interceptor to the underlying Axios instance.
193
+ */
194
+ addRequestInterceptor(
195
+ onFulfilled: (config: any) => any,
196
+ onRejected?: (error: any) => any,
197
+ ): number {
198
+ const id = this.axiosInstance.interceptors.request.use(
199
+ onFulfilled,
200
+ onRejected,
201
+ );
202
+ this.requestInterceptorIds.add(id);
203
+ return id;
204
+ }
205
+
206
+ /**
207
+ * Removes a request interceptor by id.
208
+ */
209
+ removeRequestInterceptor(id: number): void {
210
+ this.axiosInstance.interceptors.request.eject(id);
211
+ this.requestInterceptorIds.delete(id);
212
+ }
213
+
214
+ /**
215
+ * Adds a response interceptor to the underlying Axios instance.
216
+ */
217
+ addResponseInterceptor(
218
+ onFulfilled: (res: any) => any,
219
+ onRejected?: (error: any) => any,
220
+ ): number {
221
+ const id = this.axiosInstance.interceptors.response.use(
222
+ onFulfilled,
223
+ onRejected,
224
+ );
225
+ this.responseInterceptorIds.add(id);
226
+ return id;
227
+ }
228
+
229
+ /**
230
+ * Removes a response interceptor by id.
231
+ */
232
+ removeResponseInterceptor(id: number): void {
233
+ this.axiosInstance.interceptors.response.eject(id);
234
+ this.responseInterceptorIds.delete(id);
235
+ }
236
+
237
+ /**
238
+ * Removes an error interceptor by its ID.
239
+ */
240
+ removeErrorInterceptor(id: number): void {
241
+ this.errorInterceptors.delete(id);
242
+ }
243
+
244
+ /**
245
+ * Performs a GET request to the specified URL.
246
+ */
247
+ async get<T>(
248
+ url: string,
249
+ config?: HttpRequestConfig,
250
+ ): Promise<HttpResponse<T>> {
251
+ const cacheKey = this.generateCacheKey(url, config);
252
+ const cached = this.getCached<T>(cacheKey);
253
+ if (cached) {
254
+ return {
255
+ data: cached,
256
+ status: 200,
257
+ statusText: "OK",
258
+ headers: {},
259
+ config: config || { url },
260
+ } as HttpResponse<T>;
261
+ }
262
+
263
+ return this.wrapWithErrorInterceptors(() =>
264
+ this.performGet<T>(url, config, cacheKey),
265
+ );
266
+ }
267
+
268
+ /**
269
+ * Internal method to perform the actual GET request.
270
+ */
271
+ private async performGet<T>(
272
+ url: string,
273
+ config?: HttpRequestConfig,
274
+ cacheKey?: string,
275
+ ): Promise<HttpResponse<T>> {
276
+ const response = await this.axiosInstance.get<T>(url, config);
277
+ const transformed = this.transformResponse<T>(response);
278
+
279
+ if (cacheKey && config?.cache !== false) {
280
+ this.setCache(cacheKey, transformed.data, config?.cacheTtl);
281
+ }
282
+
283
+ return transformed;
284
+ }
285
+
286
+ /**
287
+ * Performs a GET request and returns an Observable.
288
+ */
289
+ get$<T>(
290
+ url: string,
291
+ config?: HttpRequestConfig,
292
+ ): Observable<HttpResponse<T>> {
293
+ return from(this.get<T>(url, config));
294
+ }
295
+
296
+ /**
297
+ * Performs a POST request.
298
+ */
299
+ async post<T>(
300
+ url: string,
301
+ data?: unknown,
302
+ config?: HttpRequestConfig,
303
+ ): Promise<HttpResponse<T>> {
304
+ return this.wrapWithErrorInterceptors(() =>
305
+ this.performPost<T>(url, data, config),
306
+ );
307
+ }
308
+
309
+ /**
310
+ * Internal method to perform the actual POST request.
311
+ */
312
+ private async performPost<T>(
313
+ url: string,
314
+ data?: unknown,
315
+ config?: HttpRequestConfig,
316
+ ): Promise<HttpResponse<T>> {
317
+ const response = await this.axiosInstance.post<T>(url, data, config);
318
+ return this.transformResponse<T>(response);
319
+ }
320
+
321
+ /**
322
+ * Performs a POST request and returns an Observable.
323
+ */
324
+ post$<T>(
325
+ url: string,
326
+ data?: unknown,
327
+ config?: HttpRequestConfig,
328
+ ): Observable<HttpResponse<T>> {
329
+ return from(this.post<T>(url, data, config));
330
+ }
331
+
332
+ /**
333
+ * Performs a PUT request.
334
+ */
335
+ async put<T>(
336
+ url: string,
337
+ data?: unknown,
338
+ config?: HttpRequestConfig,
339
+ ): Promise<HttpResponse<T>> {
340
+ return this.wrapWithErrorInterceptors(() =>
341
+ this.performPut<T>(url, data, config),
342
+ );
343
+ }
344
+
345
+ /**
346
+ * Internal method to perform the actual PUT request.
347
+ */
348
+ private async performPut<T>(
349
+ url: string,
350
+ data?: unknown,
351
+ config?: HttpRequestConfig,
352
+ ): Promise<HttpResponse<T>> {
353
+ const response = await this.axiosInstance.put<T>(url, data, config);
354
+ return this.transformResponse<T>(response);
355
+ }
356
+
357
+ /**
358
+ * Performs a PUT request and returns an Observable.
359
+ */
360
+ put$<T>(
361
+ url: string,
362
+ data?: unknown,
363
+ config?: HttpRequestConfig,
364
+ ): Observable<HttpResponse<T>> {
365
+ return from(this.put<T>(url, data, config));
366
+ }
367
+
368
+ /**
369
+ * Performs a PATCH request.
370
+ */
371
+ async patch<T>(
372
+ url: string,
373
+ data?: unknown,
374
+ config?: HttpRequestConfig,
375
+ ): Promise<HttpResponse<T>> {
376
+ return this.wrapWithErrorInterceptors(() =>
377
+ this.performPatch<T>(url, data, config),
378
+ );
379
+ }
380
+
381
+ /**
382
+ * Internal method to perform the actual PATCH request.
383
+ */
384
+ private async performPatch<T>(
385
+ url: string,
386
+ data?: unknown,
387
+ config?: HttpRequestConfig,
388
+ ): Promise<HttpResponse<T>> {
389
+ const response = await this.axiosInstance.patch<T>(url, data, config);
390
+ return this.transformResponse<T>(response);
391
+ }
392
+
393
+ /**
394
+ * Performs a PATCH request and returns an Observable.
395
+ */
396
+ patch$<T>(
397
+ url: string,
398
+ data?: unknown,
399
+ config?: HttpRequestConfig,
400
+ ): Observable<HttpResponse<T>> {
401
+ return from(this.patch<T>(url, data, config));
402
+ }
403
+
404
+ /**
405
+ * Performs a DELETE request.
406
+ */
407
+ async delete<T>(
408
+ url: string,
409
+ config?: HttpRequestConfig,
410
+ ): Promise<HttpResponse<T>> {
411
+ return this.wrapWithErrorInterceptors(() =>
412
+ this.performDelete<T>(url, config),
413
+ );
414
+ }
415
+
416
+ /**
417
+ * Internal method to perform the actual DELETE request.
418
+ */
419
+ private async performDelete<T>(
420
+ url: string,
421
+ config?: HttpRequestConfig,
422
+ ): Promise<HttpResponse<T>> {
423
+ const response = await this.axiosInstance.delete<T>(url, config);
424
+ return this.transformResponse<T>(response);
425
+ }
426
+
427
+ /**
428
+ * Performs a DELETE request and returns an Observable.
429
+ */
430
+ delete$<T>(
431
+ url: string,
432
+ config?: HttpRequestConfig,
433
+ ): Observable<HttpResponse<T>> {
434
+ return from(this.delete<T>(url, config));
435
+ }
436
+
437
+ /**
438
+ * Performs a HEAD request.
439
+ */
440
+ async head<T>(
441
+ url: string,
442
+ config?: HttpRequestConfig,
443
+ ): Promise<HttpResponse<T>> {
444
+ return this.wrapWithErrorInterceptors(() =>
445
+ this.performHead<T>(url, config),
446
+ );
447
+ }
448
+
449
+ /**
450
+ * Internal method to perform the actual HEAD request.
451
+ */
452
+ private async performHead<T>(
453
+ url: string,
454
+ config?: HttpRequestConfig,
455
+ ): Promise<HttpResponse<T>> {
456
+ const response = await this.axiosInstance.head<T>(url, config);
457
+ return this.transformResponse<T>(response);
458
+ }
459
+
460
+ /**
461
+ * Performs a HEAD request and returns an Observable.
462
+ */
463
+ head$<T>(
464
+ url: string,
465
+ config?: HttpRequestConfig,
466
+ ): Observable<HttpResponse<T>> {
467
+ return from(this.head<T>(url, config));
468
+ }
469
+
470
+ /**
471
+ * Performs an OPTIONS request.
472
+ */
473
+ async options<T>(
474
+ url: string,
475
+ config?: HttpRequestConfig,
476
+ ): Promise<HttpResponse<T>> {
477
+ return this.wrapWithErrorInterceptors(() =>
478
+ this.performOptions<T>(url, config),
479
+ );
480
+ }
481
+
482
+ /**
483
+ * Internal method to perform the actual OPTIONS request.
484
+ */
485
+ private async performOptions<T>(
486
+ url: string,
487
+ config?: HttpRequestConfig,
488
+ ): Promise<HttpResponse<T>> {
489
+ const response = await this.axiosInstance.options<T>(url, config);
490
+ return this.transformResponse<T>(response);
491
+ }
492
+
493
+ /**
494
+ * Performs an OPTIONS request and returns an Observable.
495
+ */
496
+ options$<T>(
497
+ url: string,
498
+ config?: HttpRequestConfig,
499
+ ): Observable<HttpResponse<T>> {
500
+ return from(this.options<T>(url, config));
501
+ }
502
+
503
+ /**
504
+ * Performs a custom HTTP request.
505
+ */
506
+ async request<T>(config: HttpRequestConfig): Promise<HttpResponse<T>> {
507
+ return this.wrapWithErrorInterceptors(() => this.performRequest<T>(config));
508
+ }
509
+
510
+ /**
511
+ * Internal method to perform the actual custom request.
512
+ */
513
+ private async performRequest<T>(
514
+ config: HttpRequestConfig,
515
+ ): Promise<HttpResponse<T>> {
516
+ const response = await this.axiosInstance.request<T>(config);
517
+ return this.transformResponse<T>(response);
518
+ }
519
+
520
+ /**
521
+ * Performs a custom HTTP request and returns an Observable.
522
+ */
523
+ request$<T>(config: HttpRequestConfig): Observable<HttpResponse<T>> {
524
+ return from(this.request<T>(config));
525
+ }
526
+
527
+ /**
528
+ * Sets the authorization token for requests.
529
+ */
530
+ setAuthToken(token: string, type: string = "Bearer"): void {
531
+ this.axiosInstance.defaults.headers.common["Authorization"] =
532
+ `${type} ${token}`;
533
+ // Log only the initial part of the token to avoid exposing the full secret in logs
534
+ try {
535
+ const masked = AxiosHttpProvider.maskToken(token);
536
+
537
+ console.debug(`[AxiosHttpProvider] setAuthToken ${type} ${masked}`);
538
+ } catch (err) {
539
+ // swallow logging errors
540
+ }
541
+ }
542
+
543
+ /**
544
+ * Clears the authorization token.
545
+ */
546
+ clearAuthToken(): void {
547
+ delete this.axiosInstance.defaults.headers.common["Authorization"];
548
+ }
549
+
550
+ /**
551
+ * Processes an error through all registered error interceptors.
552
+ */
553
+ private async processErrorInterceptors(error: unknown): Promise<unknown> {
554
+ let processedError = error;
555
+
556
+ for (const interceptor of this.errorInterceptors.values()) {
557
+ try {
558
+ processedError = await interceptor(processedError);
559
+ } catch (interceptorError) {
560
+ console.warn("Error interceptor failed:", interceptorError);
561
+ }
562
+ }
563
+
564
+ return processedError;
565
+ }
566
+
567
+ /**
568
+ * Returns a masked version of the token showing only the initial characters.
569
+ */
570
+ private static maskToken(token: string, visibleChars = 8): string {
571
+ if (!token || typeof token !== "string") return "";
572
+ return token.length <= visibleChars
573
+ ? token
574
+ : `${token.slice(0, visibleChars)}...`;
575
+ }
576
+
577
+ /**
578
+ * Wraps a promise-returning HTTP method with error interceptor processing.
579
+ */
580
+ private async wrapWithErrorInterceptors<T>(
581
+ method: () => Promise<HttpResponse<T>>,
582
+ ): Promise<HttpResponse<T>> {
583
+ try {
584
+ return await method();
585
+ } catch (error) {
586
+ const processedError = await this.processErrorInterceptors(error);
587
+ throw processedError;
588
+ }
589
+ }
590
+
591
+ /**
592
+ * Transforms an Axios response to the standardized HTTP response format.
593
+ */
594
+ private transformResponse<T>(response: AxiosResponse<T>): HttpResponse<T> {
595
+ return {
596
+ data: response.data,
597
+ status: response.status,
598
+ statusText: response.statusText,
599
+ headers: response.headers as Record<string, string>,
600
+ config: response.config as HttpRequestConfig,
601
+ };
602
+ }
603
+ }
@@ -0,0 +1 @@
1
+ export const AXIOS_HTTP_PROVIDER = 'AXIOS_HTTP_PROVIDER';
@@ -0,0 +1,9 @@
1
+ import { Module } from "@nestjs/common";
2
+
3
+ import { HttpImplementationAxiosModule } from "./axios/axios.http.module";
4
+
5
+ @Module({
6
+ imports: [HttpImplementationAxiosModule],
7
+ exports: [HttpImplementationAxiosModule],
8
+ })
9
+ export class HttpImplementationModule {}
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ export { HttpModule } from "./http.module";
2
+ export { HttpImplementationAxiosModule } from "./implementations/axios/axios.http.module";
3
+ export {
4
+ HTTP_PROVIDER,
5
+ HTTP_AXIOS_PROVIDER,
6
+ HTTP_AXIOS_CONNECTION,
7
+ } from "./http.token";
8
+ export type { HttpProviderInterface } from "./http.interface";
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "module": "CommonJS",
5
+ "moduleResolution": "node",
6
+ "outDir": "dist",
7
+ "rootDir": "src",
8
+ "declarationDir": "dist/types",
9
+ "declaration": true,
10
+ "composite": true
11
+ },
12
+ "include": [
13
+ "./src/**/*.ts"
14
+ ],
15
+ "exclude": ["node_modules", "dist"]
16
+ }