lambda-essentials-ts 6.1.0 → 7.0.0-beta

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/CHANGELOG.md CHANGED
@@ -4,7 +4,28 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
6
6
 
7
- ## [6.0.0] - 2022-02-22
7
+ ## [7.0.0-beta] - 2025-11-14
8
+
9
+ ### Changed
10
+
11
+ - Add 429 status code mapping
12
+ - Changed logging format. Removed the inner `message` object.
13
+ - Update packages
14
+ - Use axios-cache-interceptor instead of axios-cache-adapter
15
+
16
+ ## [6.1.1] - 2025-10-27
17
+
18
+ ### Changed
19
+
20
+ - TokenProviderHttpClient now uses the http client with retries as default.
21
+
22
+ ## [6.1.0] - 2024-09-30
23
+
24
+ ### Changed
25
+
26
+ - Preserve the original message in ClientException
27
+
28
+ ## [6.0.0] - 2024-02-22
8
29
 
9
30
  ### Changed
10
31
 
@@ -4,7 +4,7 @@ exports.ClientException = void 0;
4
4
  const exception_1 = require("./exception");
5
5
  class ClientException extends exception_1.Exception {
6
6
  constructor(serviceName, originalStatusCode, details, originalMessage, statusCodeMapOverride) {
7
- super(`Dependent service "${serviceName}" returned error: ${originalMessage !== null && originalMessage !== void 0 ? originalMessage : 'Unknown error'}`, ClientException.convertStatusCode(originalStatusCode, statusCodeMapOverride), details);
7
+ super(`Dependent service "${serviceName}" returned error: ${originalMessage ?? 'Unknown error'}`, ClientException.convertStatusCode(originalStatusCode, statusCodeMapOverride), details);
8
8
  this.serviceName = serviceName;
9
9
  this.originalStatusCode = originalStatusCode;
10
10
  }
@@ -27,4 +27,5 @@ ClientException.statusCodeMap = {
27
27
  403: 403,
28
28
  404: 422,
29
29
  422: 422,
30
+ 429: 429,
30
31
  };
@@ -1,6 +1,6 @@
1
- import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
2
- import { IAxiosCacheAdapterOptions } from 'axios-cache-adapter';
1
+ import { AxiosHeaderValue, AxiosInstance, AxiosRequestConfig, AxiosResponse, RawAxiosRequestHeaders } from 'axios';
3
2
  import { RetryConfig } from 'retry-axios';
3
+ import { CacheOptions } from 'axios-cache-interceptor';
4
4
  /**
5
5
  * Allows to specify which http data should be logged.
6
6
  */
@@ -27,7 +27,11 @@ export default class HttpClient {
27
27
  /**
28
28
  * Resolves the token with the token provider and adds it to the headers
29
29
  */
30
- createHeadersWithResolvedToken(headers?: Record<string, string>): Promise<Record<string, string>>;
30
+ createHeadersWithResolvedToken(headers?: RawAxiosRequestHeaders | {
31
+ [key: string]: AxiosHeaderValue;
32
+ } | Record<string, string>): Promise<{
33
+ [p: string]: AxiosHeaderValue;
34
+ }>;
31
35
  /**
32
36
  * Get from the given url. Bearer token is automatically injected if tokenResolverFunction was provided to the constructor.
33
37
  */
@@ -83,10 +87,10 @@ export interface HttpClientOptions {
83
87
  */
84
88
  enableCache?: boolean;
85
89
  /**
86
- * Cache options
87
- * @link https://github.com/RasCarlito/axios-cache-adapter/blob/master/axios-cache-adapter.d.ts#L26
90
+ * Cache options (global defaults for axios-cache-interceptor)
91
+ * @link https://axios-cache-interceptor.js.org/config
88
92
  */
89
- cacheOptions?: IAxiosCacheAdapterOptions;
93
+ cacheOptions?: CacheOptions;
90
94
  /**
91
95
  * Enable automatic retries
92
96
  */
@@ -15,29 +15,37 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
15
15
  }) : function(o, v) {
16
16
  o["default"] = v;
17
17
  });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
25
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
26
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
27
37
  };
28
38
  Object.defineProperty(exports, "__esModule", { value: true });
29
39
  exports.HttpLogType = void 0;
30
40
  const uuid = __importStar(require("uuid"));
31
- const url_1 = require("url");
32
41
  const axios_1 = __importDefault(require("axios"));
33
- const axios_cache_adapter_1 = require("axios-cache-adapter");
34
42
  const rax = __importStar(require("retry-axios"));
35
43
  const md5_1 = __importDefault(require("md5"));
36
44
  const util_1 = require("../util");
37
45
  const internalException_1 = require("../exceptions/internalException");
38
46
  const clientException_1 = require("../exceptions/clientException");
39
47
  const shared_1 = require("../shared");
40
- const deduplicateRequestAdapter_1 = require("./deduplicateRequestAdapter");
48
+ const axios_cache_interceptor_1 = require("axios-cache-interceptor");
41
49
  const invalidToken = 'Invalid token';
42
50
  /**
43
51
  * Allows to specify which http data should be logged.
@@ -46,55 +54,47 @@ var HttpLogType;
46
54
  (function (HttpLogType) {
47
55
  HttpLogType["requests"] = "requests";
48
56
  HttpLogType["responses"] = "responses";
49
- })(HttpLogType = exports.HttpLogType || (exports.HttpLogType = {}));
57
+ })(HttpLogType || (exports.HttpLogType = HttpLogType = {}));
50
58
  class HttpClient {
51
59
  /**
52
60
  * Create a new Instance of the HttpClient
53
61
  */
54
62
  // eslint-disable-next-line complexity
55
63
  constructor(options) {
56
- var _a, _b, _c, _d, _e;
57
64
  // eslint-disable-next-line no-console
58
- this.logFunction = (_a = options === null || options === void 0 ? void 0 : options.logFunction) !== null && _a !== void 0 ? _a : console.log;
59
- this.logOptions = (_b = options === null || options === void 0 ? void 0 : options.logOptions) !== null && _b !== void 0 ? _b : { enabledLogs: [HttpLogType.requests] };
60
- this.tokenResolverFunction = options === null || options === void 0 ? void 0 : options.tokenResolver;
61
- this.correlationIdResolverFunction = options === null || options === void 0 ? void 0 : options.correlationIdResolver;
62
- this.enableCache = (_c = options === null || options === void 0 ? void 0 : options.enableCache) !== null && _c !== void 0 ? _c : false;
63
- this.enableRetry = (_d = options === null || options === void 0 ? void 0 : options.enableRetry) !== null && _d !== void 0 ? _d : false;
64
- this.timeout = options === null || options === void 0 ? void 0 : options.timeout;
65
- this.clientExceptionStatusCodeMapOverride = options === null || options === void 0 ? void 0 : options.clientExceptionStatusCodeMapOverride;
66
- this.client =
67
- (_e = options === null || options === void 0 ? void 0 : options.client) !== null && _e !== void 0 ? _e : axios_1.default.create({
68
- adapter: (() => {
69
- let adapters = axios_1.default.defaults.adapter;
70
- if (this.enableCache) {
71
- const cache = (0, axios_cache_adapter_1.setupCache)({
72
- maxAge: 5 * 60 * 1000,
73
- readHeaders: false,
74
- readOnError: true,
75
- exclude: {
76
- query: false, // also cache requests with query parameters
77
- },
78
- ...options === null || options === void 0 ? void 0 : options.cacheOptions,
79
- key: (req) => HttpClient.generateCacheKey(req),
80
- });
81
- // debounce concurrent calls with the same cacheKey so that only one HTTP request is made
82
- adapters = (0, deduplicateRequestAdapter_1.createDebounceRequestAdapter)(cache.adapter, HttpClient.generateCacheKey);
83
- }
84
- return adapters;
85
- })(),
65
+ this.logFunction = options?.logFunction ?? console.log;
66
+ this.logOptions = options?.logOptions ?? { enabledLogs: [HttpLogType.requests] };
67
+ this.tokenResolverFunction = options?.tokenResolver;
68
+ this.correlationIdResolverFunction = options?.correlationIdResolver;
69
+ this.enableCache = options?.enableCache ?? false;
70
+ this.enableRetry = options?.enableRetry ?? false;
71
+ this.timeout = options?.timeout;
72
+ this.clientExceptionStatusCodeMapOverride = options?.clientExceptionStatusCodeMapOverride;
73
+ this.client = options?.client ?? axios_1.default.create();
74
+ if (this.enableCache) {
75
+ (0, axios_cache_interceptor_1.setupCache)(this.client, {
76
+ // 5 minutes TTL
77
+ ttl: 5 * 60 * 1000,
78
+ // Respect cache-control headers and have ttl as a fallback https://axios-cache-interceptor.js.org/config/request-specifics#cache-interpretheader
79
+ interpretHeader: true,
80
+ // Serve stale cache on error
81
+ staleIfError: true,
82
+ // Use custom cache key to include auth, url, params and body hash
83
+ generateKey: (req) => HttpClient.generateCacheKey(req),
84
+ // Allow overriding defaults via provided options
85
+ ...options?.cacheOptions,
86
86
  });
87
+ }
87
88
  if (this.enableRetry) {
88
89
  this.client.defaults.raxConfig = {
89
- ...options === null || options === void 0 ? void 0 : options.retryOptions,
90
- instance: this.client,
91
- httpMethodsToRetry: ['GET', 'HEAD', 'OPTIONS', 'DELETE', 'PUT', 'POST'],
90
+ ...options?.retryOptions,
91
+ instance: this.client, // always attach retry-axios to the private axios client
92
+ httpMethodsToRetry: ['GET', 'HEAD', 'OPTIONS', 'DELETE', 'PUT', 'POST'], // extending the defaults to retry POST calls
92
93
  onRetryAttempt: (err) => {
93
- var _a, _b;
94
94
  this.logFunction({
95
95
  title: 'HTTP Response Error Retry',
96
96
  level: 'INFO',
97
- retryAttempt: (_b = (_a = err.config) === null || _a === void 0 ? void 0 : _a.raxConfig) === null || _b === void 0 ? void 0 : _b.currentRetryAttempt,
97
+ retryAttempt: err.config?.raxConfig?.currentRetryAttempt,
98
98
  ...HttpClient.extractRequestLogData(err.config),
99
99
  error: (0, util_1.serializeAxiosError)(err),
100
100
  });
@@ -119,7 +119,6 @@ class HttpClient {
119
119
  }
120
120
  return config;
121
121
  }, (error) => {
122
- var _a;
123
122
  const serializedAxiosError = (0, util_1.serializeAxiosError)(error);
124
123
  this.logFunction({
125
124
  title: 'HTTP Request Error',
@@ -127,10 +126,10 @@ class HttpClient {
127
126
  ...HttpClient.extractRequestLogData(error.config),
128
127
  error: serializedAxiosError,
129
128
  });
130
- const hostname = ((_a = error.config) === null || _a === void 0 ? void 0 : _a.url)
131
- ? new url_1.URL(error.config.url, error.config.baseURL).hostname
129
+ const hostname = error.config?.url
130
+ ? new URL(error.config.url, error.config.baseURL).hostname
132
131
  : 'N/A';
133
- throw new clientException_1.ClientException(hostname, serializedAxiosError === null || serializedAxiosError === void 0 ? void 0 : serializedAxiosError.status, serializedAxiosError === null || serializedAxiosError === void 0 ? void 0 : serializedAxiosError.details, serializedAxiosError === null || serializedAxiosError === void 0 ? void 0 : serializedAxiosError.message, this.clientExceptionStatusCodeMapOverride);
132
+ throw new clientException_1.ClientException(hostname, serializedAxiosError?.status, serializedAxiosError?.details, serializedAxiosError?.message, this.clientExceptionStatusCodeMapOverride);
134
133
  });
135
134
  this.client.interceptors.response.use((response) => {
136
135
  if (this.logOptions.enabledLogs.includes(HttpLogType.responses)) {
@@ -143,7 +142,6 @@ class HttpClient {
143
142
  }
144
143
  return response;
145
144
  }, (error) => {
146
- var _a;
147
145
  // when retries are configured, this middleware gets triggered for each retry
148
146
  // it changes the error object to ClientException and therefore the transformation can be run only once
149
147
  if (error instanceof clientException_1.ClientException) {
@@ -166,14 +164,13 @@ class HttpClient {
166
164
  error: serializedAxiosError,
167
165
  });
168
166
  }
169
- const hostname = ((_a = error.config) === null || _a === void 0 ? void 0 : _a.url)
170
- ? new url_1.URL(error.config.url, error.config.baseURL).hostname
167
+ const hostname = error.config?.url
168
+ ? new URL(error.config.url, error.config.baseURL).hostname
171
169
  : 'N/A';
172
- throw new clientException_1.ClientException(hostname, serializedAxiosError === null || serializedAxiosError === void 0 ? void 0 : serializedAxiosError.status, serializedAxiosError === null || serializedAxiosError === void 0 ? void 0 : serializedAxiosError.details, serializedAxiosError === null || serializedAxiosError === void 0 ? void 0 : serializedAxiosError.message, this.clientExceptionStatusCodeMapOverride);
170
+ throw new clientException_1.ClientException(hostname, serializedAxiosError?.status, serializedAxiosError?.details, serializedAxiosError?.message, this.clientExceptionStatusCodeMapOverride);
173
171
  });
174
172
  }
175
173
  static extractRequestLogData(requestConfig) {
176
- var _a;
177
174
  if (!requestConfig) {
178
175
  return {};
179
176
  }
@@ -182,14 +179,13 @@ class HttpClient {
182
179
  url: requestConfig.url,
183
180
  query: requestConfig.params,
184
181
  request: (0, util_1.safeJsonParse)(requestConfig.data, requestConfig.data),
185
- correlationId: (_a = requestConfig.headers) === null || _a === void 0 ? void 0 : _a[shared_1.orionCorrelationIdRoot],
182
+ correlationId: requestConfig.headers?.[shared_1.orionCorrelationIdRoot],
186
183
  };
187
184
  }
188
185
  // implemented based on https://github.com/RasCarlito/axios-cache-adapter/blob/master/src/cache.js#L77
189
186
  static generateCacheKey(req) {
190
- var _a, _b;
191
- const prefix = ((_a = req.headers) === null || _a === void 0 ? void 0 : _a.Authorization)
192
- ? (_b = (0, util_1.safeJwtCanonicalIdParse)(req.headers.Authorization.replace('Bearer ', ''))) !== null && _b !== void 0 ? _b : uuid.v4()
187
+ const prefix = req.headers?.Authorization
188
+ ? (0, util_1.safeJwtCanonicalIdParse)(req.headers.Authorization.replace('Bearer ', '')) ?? uuid.v4()
193
189
  : 'shared';
194
190
  const url = `${req.baseURL ? req.baseURL : ''}${req.url}`;
195
191
  const query = req.params ? JSON.stringify(req.params) : ''; // possible improvement: optimize cache-hit ratio by sorting the query params
@@ -199,13 +195,13 @@ class HttpClient {
199
195
  /**
200
196
  * Resolves the token with the token provider and adds it to the headers
201
197
  */
202
- async createHeadersWithResolvedToken(headers = {}) {
198
+ async createHeadersWithResolvedToken(headers) {
203
199
  const newHeaders = {};
204
200
  if (this.correlationIdResolverFunction) {
205
201
  newHeaders[shared_1.orionCorrelationIdRoot] = this.correlationIdResolverFunction();
206
202
  }
207
203
  if (this.tokenResolverFunction) {
208
- if (headers.Authorization) {
204
+ if (headers && headers.Authorization) {
209
205
  throw new internalException_1.InternalException('Authorization header already specified, please create a new HttpClient with a different (or without a) tokenResolver');
210
206
  }
211
207
  else {
@@ -0,0 +1,2 @@
1
+ import { createClient } from 'redis';
2
+ export default function createRedisStorage(client: ReturnType<typeof createClient>): import("axios-cache-interceptor").AxiosStorage;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = createRedisStorage;
4
+ const axios_cache_interceptor_1 = require("axios-cache-interceptor");
5
+ const KEY_PREFIX = 'axios-cache-';
6
+ const MIN_TTL = 60000;
7
+ function createRedisStorage(client) {
8
+ // source https://axios-cache-interceptor.js.org/guide/storages#node-redis-storage
9
+ return (0, axios_cache_interceptor_1.buildStorage)({
10
+ async find(key) {
11
+ const result = await client.get(`${KEY_PREFIX}${key}`);
12
+ return result ? JSON.parse(result) : undefined;
13
+ },
14
+ // eslint-disable-next-line complexity
15
+ async set(key, value, req) {
16
+ await client.set(`${KEY_PREFIX}${key}`, JSON.stringify(value), {
17
+ PXAT:
18
+ // We don't want to keep indefinitely values in the storage if
19
+ // their request don't finish somehow. Either set its value as
20
+ // the TTL or 1 minute (MIN_TTL).
21
+ value.state === 'loading'
22
+ ? Date.now() +
23
+ (req?.cache && typeof req.cache.ttl === 'number' ? req.cache.ttl : MIN_TTL)
24
+ : (value.state === 'stale' && value.ttl) ||
25
+ (value.state === 'cached' && !(0, axios_cache_interceptor_1.canStale)(value))
26
+ ? value.createdAt + value.ttl
27
+ : // otherwise, we can't determine when it should expire, so we keep
28
+ // it indefinitely.
29
+ undefined,
30
+ });
31
+ },
32
+ async remove(key) {
33
+ await client.del(`${KEY_PREFIX}${key}`);
34
+ },
35
+ });
36
+ }
package/lib/index.d.ts CHANGED
@@ -15,5 +15,6 @@ import { InvalidDataException } from './exceptions/invalidDataException';
15
15
  import { NotFoundException } from './exceptions/notFoundException';
16
16
  import { ValidationException } from './exceptions/validationException';
17
17
  import { serializeObject, serializeAxiosError } from './util';
18
- export { Logger, OpenApiWrapper, ApiResponse, HttpClient, HttpLogType, KmsTokenProvider, SecretsManagerTokenProvider, ValidationException, NotFoundException, InvalidDataException, InternalException, ForbiddenException, Exception, ConflictException, ClientException, serializeObject, serializeAxiosError, };
18
+ import RedisStorage from './httpClient/redisStorage';
19
+ export { Logger, OpenApiWrapper, ApiResponse, HttpClient, HttpLogType, KmsTokenProvider, SecretsManagerTokenProvider, ValidationException, NotFoundException, InvalidDataException, InternalException, ForbiddenException, Exception, ConflictException, ClientException, serializeObject, serializeAxiosError, RedisStorage, };
19
20
  export type { LoggerConfiguration, SuggestedLogObject, HttpClientOptions, HttpLogOptions, ApiRequest, GetRequest, PostRequest, PutRequest, DeleteRequest, TokenProviderHttpClient, KmsTokenProviderOptions, KmsTokenConfiguration, SecretsManagerTokenProviderOptions, SecretsManagerTokenConfiguration, };
package/lib/index.js CHANGED
@@ -15,18 +15,28 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
15
15
  }) : function(o, v) {
16
16
  o["default"] = v;
17
17
  });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
25
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
26
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
27
37
  };
28
38
  Object.defineProperty(exports, "__esModule", { value: true });
29
- exports.serializeAxiosError = exports.serializeObject = exports.ClientException = exports.ConflictException = exports.Exception = exports.ForbiddenException = exports.InternalException = exports.InvalidDataException = exports.NotFoundException = exports.ValidationException = exports.SecretsManagerTokenProvider = exports.KmsTokenProvider = exports.HttpLogType = exports.HttpClient = exports.ApiResponse = exports.OpenApiWrapper = exports.Logger = void 0;
39
+ exports.RedisStorage = exports.serializeAxiosError = exports.serializeObject = exports.ClientException = exports.ConflictException = exports.Exception = exports.ForbiddenException = exports.InternalException = exports.InvalidDataException = exports.NotFoundException = exports.ValidationException = exports.SecretsManagerTokenProvider = exports.KmsTokenProvider = exports.HttpLogType = exports.HttpClient = exports.ApiResponse = exports.OpenApiWrapper = exports.Logger = void 0;
30
40
  const httpClient_1 = __importStar(require("./httpClient/httpClient"));
31
41
  exports.HttpClient = httpClient_1.default;
32
42
  Object.defineProperty(exports, "HttpLogType", { enumerable: true, get: function () { return httpClient_1.HttpLogType; } });
@@ -59,3 +69,5 @@ Object.defineProperty(exports, "ValidationException", { enumerable: true, get: f
59
69
  const util_1 = require("./util");
60
70
  Object.defineProperty(exports, "serializeObject", { enumerable: true, get: function () { return util_1.serializeObject; } });
61
71
  Object.defineProperty(exports, "serializeAxiosError", { enumerable: true, get: function () { return util_1.serializeAxiosError; } });
72
+ const redisStorage_1 = __importDefault(require("./httpClient/redisStorage"));
73
+ exports.RedisStorage = redisStorage_1.default;
@@ -2,11 +2,13 @@ export default class Logger {
2
2
  invocationId: string;
3
3
  private readonly logFunction;
4
4
  private readonly jsonSpace;
5
+ private readonly payloadLimit;
5
6
  private staticData;
6
7
  constructor(configuration?: LoggerConfiguration);
7
8
  /**
8
9
  * Create a new invocation which will end up setting the additional invocation metadata for the request, which will be used when logging.
9
10
  * @param staticData Any static data that are assigned to every log message. Typical might be an environment parameter or version number.
11
+ * @param invocationId
10
12
  */
11
13
  startInvocation(staticData?: any, invocationId?: string): void;
12
14
  log(message: string | SuggestedLogObject): void;
@@ -21,6 +23,13 @@ export interface LoggerConfiguration {
21
23
  * the number of spaces that are used then stringifying the message.
22
24
  */
23
25
  jsonSpace?: number;
26
+ /**
27
+ * the limit of a stringified payload in characters above which the payload will be truncated.
28
+ * 3000 characters are reserved
29
+ * @min 10000
30
+ * @default 32768
31
+ */
32
+ payloadLimit?: number;
24
33
  }
25
34
  export interface SuggestedLogObject {
26
35
  /**
@@ -16,13 +16,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
16
16
  }) : function(o, v) {
17
17
  o["default"] = v;
18
18
  });
19
- var __importStar = (this && this.__importStar) || function (mod) {
20
- if (mod && mod.__esModule) return mod;
21
- var result = {};
22
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
23
- __setModuleDefault(result, mod);
24
- return result;
25
- };
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
26
36
  var __importDefault = (this && this.__importDefault) || function (mod) {
27
37
  return (mod && mod.__esModule) ? mod : { "default": mod };
28
38
  };
@@ -31,20 +41,27 @@ const uuid = __importStar(require("uuid"));
31
41
  const fast_safe_stringify_1 = __importDefault(require("fast-safe-stringify"));
32
42
  const is_error_1 = __importDefault(require("is-error"));
33
43
  const util_1 = require("../util");
44
+ const DEFAULT_PAYLOAD_LIMIT = 32768;
45
+ const MIN_PAYLOAD_LIMIT = 10000;
34
46
  class Logger {
35
47
  constructor(configuration) {
36
- var _a, _b;
37
- this.logFunction = (_a = configuration === null || configuration === void 0 ? void 0 : configuration.logFunction) !== null && _a !== void 0 ? _a : console.log;
38
- this.jsonSpace = (_b = configuration === null || configuration === void 0 ? void 0 : configuration.jsonSpace) !== null && _b !== void 0 ? _b : 2;
48
+ this.logFunction = configuration?.logFunction ?? console.log;
49
+ this.jsonSpace = configuration?.jsonSpace ?? 2;
50
+ this.payloadLimit = !configuration?.payloadLimit
51
+ ? DEFAULT_PAYLOAD_LIMIT
52
+ : configuration?.payloadLimit < MIN_PAYLOAD_LIMIT
53
+ ? MIN_PAYLOAD_LIMIT
54
+ : configuration.payloadLimit;
39
55
  this.invocationId = 'none';
40
56
  }
41
57
  /**
42
58
  * Create a new invocation which will end up setting the additional invocation metadata for the request, which will be used when logging.
43
59
  * @param staticData Any static data that are assigned to every log message. Typical might be an environment parameter or version number.
60
+ * @param invocationId
44
61
  */
45
62
  startInvocation(staticData, invocationId) {
46
63
  this.staticData = staticData;
47
- this.invocationId = invocationId !== null && invocationId !== void 0 ? invocationId : uuid.v4();
64
+ this.invocationId = invocationId ?? uuid.v4();
48
65
  }
49
66
  // eslint-disable-next-line complexity
50
67
  log(message) {
@@ -71,7 +88,7 @@ class Logger {
71
88
  }
72
89
  const payload = {
73
90
  invocationId: this.invocationId,
74
- message: messageAsObject,
91
+ ...messageAsObject,
75
92
  };
76
93
  const truncateToken = (innerPayload) => {
77
94
  return innerPayload.replace(/(eyJ[a-zA-Z0-9_-]{5,}\.eyJ[a-zA-Z0-9_-]{5,})\.[a-zA-Z0-9_-]*/gi, (m, p1) => `${p1}.<sig>`);
@@ -79,15 +96,12 @@ class Logger {
79
96
  const replacer = (key, value) => ((0, is_error_1.default)(value) ? Logger.errorToObject(value) : value);
80
97
  let stringifiedPayload = truncateToken((0, fast_safe_stringify_1.default)(payload, replacer, this.jsonSpace));
81
98
  stringifiedPayload = (0, util_1.redactSecret)(stringifiedPayload);
82
- // https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/cloudwatch_limits_cwl.html 256KB => 32768 characters
83
- if (stringifiedPayload.length >= 32768) {
99
+ if (stringifiedPayload.length >= this.payloadLimit) {
84
100
  const replacementPayload = {
85
101
  invocationId: this.invocationId,
86
- message: {
87
- title: 'Payload too large',
88
- fields: Object.keys(payload),
89
- truncatedPayload: stringifiedPayload.substring(0, 10000),
90
- },
102
+ title: 'Payload too large',
103
+ fields: Object.keys(payload),
104
+ truncatedPayload: stringifiedPayload.substring(0, this.payloadLimit - 3000),
91
105
  };
92
106
  stringifiedPayload = (0, fast_safe_stringify_1.default)(replacementPayload, replacer, this.jsonSpace);
93
107
  }
@@ -23,7 +23,7 @@ export interface OpenApiModel {
23
23
  errorMiddleware: ErrorMiddleware;
24
24
  }, overrideLogger?: () => void): any;
25
25
  }
26
- export declare type ApiControllerRoute<T = ApiRequest> = (path: string, handler: (request: T) => Promise<ApiResponse>) => void;
27
- export declare type RequestMiddleware = (request: ApiRequest) => ApiRequest;
28
- export declare type ResponseMiddleware = (request: ApiRequest, response: ApiResponse) => ApiResponse;
29
- export declare type ErrorMiddleware = (request: ApiRequest, error: Exception | Error) => ApiResponse;
26
+ export type ApiControllerRoute<T = ApiRequest> = (path: string, handler: (request: T) => Promise<ApiResponse>) => void;
27
+ export type RequestMiddleware = (request: ApiRequest) => ApiRequest;
28
+ export type ResponseMiddleware = (request: ApiRequest, response: ApiResponse) => ApiResponse;
29
+ export type ErrorMiddleware = (request: ApiRequest, error: Exception | Error) => ApiResponse;
@@ -15,13 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
15
15
  }) : function(o, v) {
16
16
  o["default"] = v;
17
17
  });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
25
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
26
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
27
37
  };
@@ -40,19 +50,18 @@ class OpenApiWrapper {
40
50
  this.userPrincipal = this.notSet;
41
51
  this.requestId = this.notSet;
42
52
  this.correlationId = this.notSet;
43
- if (config === null || config === void 0 ? void 0 : config.enableNewRelicTracking) {
53
+ if (config?.enableNewRelicTracking) {
44
54
  // eslint-disable-next-line global-require
45
55
  this.newrelic = require('newrelic');
46
56
  }
47
57
  // @ts-ignore Later Use the options Type from OpenApiFactory
48
58
  this.api = new openapi_factory_1.default({
49
59
  requestMiddleware: async (request) => {
50
- var _a, _b;
51
60
  const correlationId = this.generateCorrelationId(request.headers);
52
61
  requestLogger.startInvocation(null, correlationId);
53
62
  const userData = this.determineUserData(request.headers, request.requestContext.authorizer);
54
- this.userToken = (_a = userData.userToken) !== null && _a !== void 0 ? _a : this.notSet;
55
- this.userPrincipal = (_b = userData.userPrincipal) !== null && _b !== void 0 ? _b : this.notSet;
63
+ this.userToken = userData.userToken ?? this.notSet;
64
+ this.userPrincipal = userData.userPrincipal ?? this.notSet;
56
65
  this.requestId = request.requestContext.requestId;
57
66
  requestLogger.log({
58
67
  title: 'RequestLogger',
@@ -69,7 +78,7 @@ class OpenApiWrapper {
69
78
  user: this.userPrincipal,
70
79
  query: request.multiValueQueryStringParameters,
71
80
  });
72
- if (config === null || config === void 0 ? void 0 : config.enableNewRelicTracking) {
81
+ if (config?.enableNewRelicTracking) {
73
82
  this.newrelic.addCustomAttributes({
74
83
  canonicalId: this.userPrincipal,
75
84
  correlationId,
@@ -141,19 +150,18 @@ class OpenApiWrapper {
141
150
  }
142
151
  generateCorrelationId(headers) {
143
152
  const existingCorrelationId = headers[shared_1.orionCorrelationIdRoot];
144
- this.correlationId = existingCorrelationId !== null && existingCorrelationId !== void 0 ? existingCorrelationId : uuid.v4();
153
+ this.correlationId = existingCorrelationId ?? uuid.v4();
145
154
  return this.correlationId;
146
155
  }
147
156
  determineUserData(headers, authorizerContext) {
148
- var _a, _b, _c;
149
157
  if (authorizerContext) {
150
158
  return {
151
- userPrincipal: (_a = authorizerContext.canonicalId) !== null && _a !== void 0 ? _a : authorizerContext.principalId,
152
- userToken: (_b = authorizerContext.jwt) !== null && _b !== void 0 ? _b : authorizerContext.accessToken,
159
+ userPrincipal: authorizerContext.canonicalId ?? authorizerContext.principalId,
160
+ userToken: authorizerContext.jwt ?? authorizerContext.accessToken,
153
161
  };
154
162
  }
155
163
  if (headers.Authorization) {
156
- const userToken = (_c = headers.Authorization.split(' ')) === null || _c === void 0 ? void 0 : _c[1];
164
+ const userToken = headers.Authorization.split(' ')?.[1];
157
165
  const userPrincipal = (0, util_1.safeJwtCanonicalIdParse)(userToken);
158
166
  return { userToken, userPrincipal };
159
167
  }
@@ -12,11 +12,12 @@ class KmsTokenProvider extends tokenProvider_1.default {
12
12
  this.kmsConfiguration = options.tokenConfiguration;
13
13
  }
14
14
  async getClientSecret() {
15
- var _a;
16
15
  const data = await this.kmsClient.send(new client_kms_1.DecryptCommand({
17
- CiphertextBlob: Buffer.from(this.kmsConfiguration.encryptedClientSecret, 'base64'),
16
+ CiphertextBlob: new Uint8Array(Buffer.from(this.kmsConfiguration.encryptedClientSecret, 'base64')),
18
17
  }));
19
- const secret = (_a = data.Plaintext) === null || _a === void 0 ? void 0 : _a.toString();
18
+ const secret = data.Plaintext
19
+ ? Buffer.from(data.Plaintext).toString()
20
+ : undefined;
20
21
  if (!secret) {
21
22
  throw new Error('Request error: failed to decrypt secret using KMS');
22
23
  }
@@ -15,7 +15,7 @@ class SecretsManagerTokenProvider extends tokenProvider_1.default {
15
15
  const secret = await this.secretsManagerClient.send(new client_secrets_manager_1.GetSecretValueCommand({
16
16
  SecretId: this.secretsManagerConfiguration.clientSecretId,
17
17
  }));
18
- if (!(secret === null || secret === void 0 ? void 0 : secret.SecretString)) {
18
+ if (!secret?.SecretString) {
19
19
  throw new Error('Request error: failed to retrieve secret from Secrets Manager');
20
20
  }
21
21
  return JSON.parse(secret.SecretString);
@@ -23,7 +23,8 @@ export interface Auth0Secret {
23
23
  }
24
24
  export interface TokenProviderOptions {
25
25
  /**
26
- * Either an Axios instance or an @atsquad/httpclient instance, by default it creates an axios instance
26
+ * Either an Axios instance or an @atsquad/httpclient instance, by default it creates an @atsquad/httpclient instance without cache
27
+ * with retries
27
28
  */
28
29
  httpClient?: TokenProviderHttpClient;
29
30
  /**
@@ -4,21 +4,27 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
7
- const axios_1 = __importDefault(require("axios"));
7
+ const httpClient_1 = __importDefault(require("../httpClient/httpClient"));
8
8
  class TokenProvider {
9
9
  /**
10
10
  * Create a new Instance of the TokenProvider
11
11
  */
12
12
  constructor(options) {
13
- var _a;
14
- this.httpClient = (_a = options.httpClient) !== null && _a !== void 0 ? _a : axios_1.default.create();
13
+ this.httpClient =
14
+ options.httpClient ??
15
+ new httpClient_1.default({
16
+ enableCache: false,
17
+ enableRetry: true,
18
+ retryOptions: {
19
+ retry: 3,
20
+ },
21
+ });
15
22
  this.configuration = options.tokenConfiguration;
16
23
  }
17
24
  /**
18
25
  * Get access token. Subsequent calls are cached and the token is renewed only if it is expired.
19
26
  */
20
27
  async getToken() {
21
- var _a;
22
28
  if (!this.currentTokenPromise) {
23
29
  this.currentTokenPromise = this.getTokenWithoutCache();
24
30
  return this.currentTokenPromise;
@@ -26,7 +32,7 @@ class TokenProvider {
26
32
  try {
27
33
  const jwtToken = await this.currentTokenPromise;
28
34
  // lower the token expiry time by 10s so that the returned token will be not immediately expired
29
- if (!jwtToken || ((_a = jsonwebtoken_1.default.decode(jwtToken)) === null || _a === void 0 ? void 0 : _a.exp) < Date.now() / 1000 - 10) {
35
+ if (!jwtToken || jsonwebtoken_1.default.decode(jwtToken)?.exp < Date.now() / 1000 - 10) {
30
36
  this.currentTokenPromise = this.getTokenWithoutCache();
31
37
  }
32
38
  return await this.currentTokenPromise;
@@ -41,7 +47,7 @@ class TokenProvider {
41
47
  */
42
48
  async getTokenWithoutCache() {
43
49
  const secret = await this.getClientSecret();
44
- if (!(secret === null || secret === void 0 ? void 0 : secret.Auth0ClientID) || !secret.Auth0ClientSecret) {
50
+ if (!secret?.Auth0ClientID || !secret.Auth0ClientSecret) {
45
51
  throw new Error('Request error: failed to retrieve Auth0 Client ID/Secret');
46
52
  }
47
53
  const headers = { 'Content-Type': 'application/json' };
package/lib/util.d.ts CHANGED
@@ -3,7 +3,7 @@ export declare function safeJwtCanonicalIdParse(jwtToken: string): string | unde
3
3
  export declare function safeJsonParse(input: any, defaultValue: unknown): unknown;
4
4
  export declare const redactSecret: (data: string) => string;
5
5
  export declare function serializeObject(obj: unknown, redact?: boolean): object;
6
- export declare function serializeAxiosError(error: AxiosError): SerializedAxiosError | undefined;
6
+ export declare function serializeAxiosError(error: AxiosError<any>): SerializedAxiosError | undefined;
7
7
  export interface SerializedAxiosError {
8
8
  status: number;
9
9
  details: any;
package/lib/util.js CHANGED
@@ -3,18 +3,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.serializeAxiosError = exports.serializeObject = exports.redactSecret = exports.safeJsonParse = exports.safeJwtCanonicalIdParse = void 0;
6
+ exports.redactSecret = void 0;
7
+ exports.safeJwtCanonicalIdParse = safeJwtCanonicalIdParse;
8
+ exports.safeJsonParse = safeJsonParse;
9
+ exports.serializeObject = serializeObject;
10
+ exports.serializeAxiosError = serializeAxiosError;
7
11
  const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
8
12
  function safeJwtCanonicalIdParse(jwtToken) {
9
- var _a;
10
13
  try {
11
- return (_a = jsonwebtoken_1.default.decode(jwtToken)) === null || _a === void 0 ? void 0 : _a['https://claims.cimpress.io/canonical_id'];
14
+ return jsonwebtoken_1.default.decode(jwtToken)?.['https://claims.cimpress.io/canonical_id'];
12
15
  }
13
- catch (_b) {
16
+ catch {
14
17
  return undefined;
15
18
  }
16
19
  }
17
- exports.safeJwtCanonicalIdParse = safeJwtCanonicalIdParse;
18
20
  function safeJsonParse(input, defaultValue) {
19
21
  try {
20
22
  return JSON.parse(input);
@@ -23,7 +25,6 @@ function safeJsonParse(input, defaultValue) {
23
25
  return defaultValue;
24
26
  }
25
27
  }
26
- exports.safeJsonParse = safeJsonParse;
27
28
  const redactSecret = (data) => {
28
29
  return data.replace(/(\\*"*'*client_secret\\*"*'*:\s*\\*"*'*)([^"'\\]+)(\\*"*'*)/gi, (m, p1, p2, p3) => `${p1}<REDACTED>${p3}`);
29
30
  };
@@ -32,7 +33,6 @@ function serializeObject(obj, redact) {
32
33
  let modObj = obj;
33
34
  if (obj && typeof obj === 'object') {
34
35
  modObj = Object.getOwnPropertyNames(obj).reduce((map, key) => {
35
- // eslint-disable-next-line no-param-reassign
36
36
  map[key] = obj[key];
37
37
  return map;
38
38
  }, {});
@@ -41,9 +41,7 @@ function serializeObject(obj, redact) {
41
41
  ? JSON.parse((0, exports.redactSecret)(JSON.stringify(modObj)))
42
42
  : JSON.parse(JSON.stringify(modObj));
43
43
  }
44
- exports.serializeObject = serializeObject;
45
44
  function serializeAxiosError(error) {
46
- var _a;
47
45
  if (!error.response) {
48
46
  return {
49
47
  status: 500,
@@ -52,9 +50,8 @@ function serializeAxiosError(error) {
52
50
  }
53
51
  const { status, data } = error.response;
54
52
  return {
55
- status: (_a = data.originalStatusCode) !== null && _a !== void 0 ? _a : status,
56
- details: data.details && Object.keys(data.details).length > 0 ? data.details : data,
53
+ status: data.originalStatusCode ?? status, // Propagate original status code of ClientException
54
+ details: data.details && Object.keys(data.details).length > 0 ? data.details : data, // Prevent wrapping of Exception
57
55
  message: data.message,
58
56
  };
59
57
  }
60
- exports.serializeAxiosError = serializeAxiosError;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lambda-essentials-ts",
3
- "version": "6.1.0",
3
+ "version": "7.0.0-beta",
4
4
  "description": "A selection of the finest modules supporting authorization, API routing, error handling, logging and sending HTTP requests.",
5
5
  "main": "lib/index.js",
6
6
  "private": false,
@@ -26,30 +26,33 @@
26
26
  },
27
27
  "homepage": "https://github.com/Cimpress-MCP/lambda-essentials-ts#readme",
28
28
  "dependencies": {
29
- "@aws-sdk/client-kms": "^3.569.0",
30
- "@aws-sdk/client-secrets-manager": "^3.569.0",
31
- "axios": "~0.21.3",
32
- "axios-cache-adapter": "~2.7.3",
33
- "fast-safe-stringify": "~2.0.7",
29
+ "@aws-sdk/client-kms": "^3.930.0",
30
+ "@aws-sdk/client-secrets-manager": "^3.930.0",
31
+ "@types/node": "^24.10.1",
32
+ "axios": "1.13.2",
33
+ "axios-cache-interceptor": "^1.8.3",
34
+ "fast-safe-stringify": "~2.1.1",
34
35
  "is-error": "~2.2.2",
35
- "jsonwebtoken": "9.0.0",
36
+ "jsonwebtoken": "9.0.2",
36
37
  "md5": "~2.3.0",
37
38
  "openapi-factory": "5.4.60",
38
- "retry-axios": "~2.6.0",
39
- "uuid": "~8.3.2"
39
+ "retry-axios": "~3.2.1",
40
+ "uuid": "~11.1.0"
40
41
  },
41
42
  "devDependencies": {
42
- "@types/jest": "^26.0.20",
43
- "@types/newrelic": "^9.14.0",
44
- "eslint": "^7.18.0",
45
- "eslint-config-cimpress-atsquad": "^2.1.2",
46
- "husky": "^4.2.5",
47
- "jest": "^26.2.2",
48
- "lint-staged": "^10.2.13",
49
- "prettier": "^2.7.1",
50
- "ts-jest": "^26.1.4",
51
- "ts-node": "^10.9.1",
52
- "typescript": "^4.8.2"
43
+ "@types/jest": "^29.5.14",
44
+ "@types/newrelic": "^9.14.8",
45
+ "redis": "^5.9.0",
46
+ "axios-mock-adapter": "^2.1.0",
47
+ "eslint": "^8.57.1",
48
+ "eslint-config-cimpress-atsquad": "^2.2.0-beta",
49
+ "husky": "^9.1.7",
50
+ "jest": "^29.7.0",
51
+ "lint-staged": "^15.5.2",
52
+ "prettier": "^2.8.8",
53
+ "ts-jest": "^29.4.5",
54
+ "ts-node": "^10.9.2",
55
+ "typescript": "^5.9.3"
53
56
  },
54
57
  "eslintConfig": {
55
58
  "extends": "cimpress-atsquad"
@@ -74,9 +77,9 @@
74
77
  "collectCoverage": false
75
78
  },
76
79
  "engines": {
77
- "node": ">=14.0.0"
80
+ "node": ">=20.0.0"
78
81
  },
79
82
  "peerDependencies": {
80
- "newrelic": "^10.2.0"
83
+ "newrelic": "^12.0.0"
81
84
  }
82
85
  }
@@ -1,2 +0,0 @@
1
- import { AxiosAdapter } from 'axios';
2
- export declare function createDebounceRequestAdapter(requestAdapter: AxiosAdapter, requestKeyProvider: (AxiosRequestConfig: any) => string): AxiosAdapter;
@@ -1,20 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createDebounceRequestAdapter = void 0;
4
- // adopted from https://github.com/RasCarlito/axios-cache-adapter/issues/231#issuecomment-880288436
5
- function createDebounceRequestAdapter(requestAdapter, requestKeyProvider) {
6
- const runningRequests = {};
7
- return (req) => {
8
- const cacheKey = requestKeyProvider(req);
9
- // Add the request to runningRequests. If it is already there, drop the duplicated request.
10
- if (!runningRequests[cacheKey]) {
11
- runningRequests[cacheKey] = requestAdapter(req);
12
- }
13
- // Return the response promise
14
- return runningRequests[cacheKey].finally(() => {
15
- // Finally, delete the request from the runningRequests whether there's error or not
16
- delete runningRequests[cacheKey];
17
- });
18
- };
19
- }
20
- exports.createDebounceRequestAdapter = createDebounceRequestAdapter;