lambda-essentials-ts 3.0.1 → 4.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,35 @@ 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
+ ## [4.1.2] - 2021-12-02
8
+
9
+ ### Changed
10
+
11
+ Expose the `Location`, `Access-Control-Allow-Origin` and `orion-correlation-id-root` headers
12
+
13
+ ## [4.1.1] - 2021-11-22
14
+
15
+ ### Fixed
16
+
17
+ - `ApiResponse` default content-type header was renamed to `Content-Type` to overwrite the default header
18
+ of [openapi-factory.js](https://github.com/Rhosys/openapi-factory.js/blob/release/5.2/src/response.js#L15)
19
+ - Also upgraded `openapi-factory.js` to get support of over-writing response headers
20
+
21
+ ## [4.1.0] - 2021-11-22
22
+
23
+ ### Changed
24
+
25
+ - `ApiResponse` default content-type header was changed from `application/links+json` to `application/hal+json`
26
+
27
+ ## [4.0.0] - 2021-11-12
28
+
29
+ ### Changed
30
+
31
+ - `HttpClient` the [retryAdapterEnhancer axios adapter](https://github.com/kuitos/axios-extensions#cacheadapterenhancer)
32
+ was replaced by the more flexible [axios-cache-adapter](https://github.com/RasCarlito/axios-cache-adapter).
33
+ - **[Breaking change]** `HttpClientOptions.cacheOptions` now accepts [extensive cache configuration](https://github.com/RasCarlito/axios-cache-adapter/blob/master/axios-cache-adapter.d.ts#L26).
34
+ - The cache is now partitioned by `canonical_id` JWT claim.
35
+
7
36
  ## [3.0.1] - 2021-09-13
8
37
 
9
38
  ### Fixed
package/README.md CHANGED
@@ -61,6 +61,14 @@ let httpClient = new HttpClient({
61
61
  [500, 599],
62
62
  ],
63
63
  },
64
+ enableCache: true,
65
+ cacheOptions: {
66
+ maxAge: 5 * 60 * 1000,
67
+ readOnError: true,
68
+ exclude: {
69
+ query: false, // also cache requests with query parameters
70
+ },
71
+ },
64
72
  });
65
73
 
66
74
  let headers = {};
@@ -0,0 +1,2 @@
1
+ import { AxiosAdapter } from 'axios';
2
+ export declare function createDebounceRequestAdapter(requestAdapter: AxiosAdapter, requestKeyProvider: (AxiosRequestConfig: any) => string): AxiosAdapter;
@@ -0,0 +1,20 @@
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;
@@ -1,5 +1,5 @@
1
1
  import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
2
- import { Options as CacheOptions } from 'axios-extensions/lib/cacheAdapterEnhancer';
2
+ import { IAxiosCacheAdapterOptions } from 'axios-cache-adapter';
3
3
  import { RetryConfig } from 'retry-axios';
4
4
  /**
5
5
  * Allows to specify which http data should be logged.
@@ -21,6 +21,7 @@ export default class HttpClient {
21
21
  */
22
22
  constructor(options?: HttpClientOptions);
23
23
  private static extractRequestLogData;
24
+ static generateCacheKey(req: AxiosRequestConfig): string;
24
25
  /**
25
26
  * Resolves the token with the token provider and adds it to the headers
26
27
  */
@@ -81,9 +82,9 @@ export interface HttpClientOptions {
81
82
  enableCache?: boolean;
82
83
  /**
83
84
  * Cache options
84
- * @link https://github.com/kuitos/axios-extensions#cacheadapterenhancer
85
+ * @link https://github.com/RasCarlito/axios-cache-adapter/blob/master/axios-cache-adapter.d.ts#L26
85
86
  */
86
- cacheOptions?: CacheOptions;
87
+ cacheOptions?: IAxiosCacheAdapterOptions;
87
88
  /**
88
89
  * Enable automatic retries
89
90
  */
@@ -23,14 +23,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
23
23
  };
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
25
  exports.HttpLogType = void 0;
26
+ const uuid = __importStar(require("uuid"));
26
27
  const url_1 = require("url");
27
28
  const axios_1 = __importDefault(require("axios"));
28
- const cacheAdapterEnhancer_1 = __importDefault(require("axios-extensions/lib/cacheAdapterEnhancer"));
29
+ const axios_cache_adapter_1 = require("axios-cache-adapter");
29
30
  const rax = __importStar(require("retry-axios"));
31
+ const md5_1 = __importDefault(require("md5"));
30
32
  const util_1 = require("../util");
31
33
  const internalException_1 = require("../exceptions/internalException");
32
34
  const clientException_1 = require("../exceptions/clientException");
33
35
  const shared_1 = require("../shared");
36
+ const deduplicateRequestAdapter_1 = require("./deduplicateRequestAdapter");
34
37
  const invalidToken = 'Invalid token';
35
38
  /**
36
39
  * Allows to specify which http data should be logged.
@@ -57,7 +60,18 @@ class HttpClient {
57
60
  adapter: (() => {
58
61
  let adapters = axios_1.default.defaults.adapter;
59
62
  if (this.enableCache) {
60
- adapters = cacheAdapterEnhancer_1.default(adapters, options === null || options === void 0 ? void 0 : options.cacheOptions);
63
+ const cache = axios_cache_adapter_1.setupCache({
64
+ maxAge: 5 * 60 * 1000,
65
+ readHeaders: false,
66
+ readOnError: true,
67
+ exclude: {
68
+ query: false,
69
+ },
70
+ ...options === null || options === void 0 ? void 0 : options.cacheOptions,
71
+ key: (req) => HttpClient.generateCacheKey(req),
72
+ });
73
+ // debounce concurrent calls with the same cacheKey so that only one HTTP request is made
74
+ adapters = deduplicateRequestAdapter_1.createDebounceRequestAdapter(cache.adapter, HttpClient.generateCacheKey);
61
75
  }
62
76
  return adapters;
63
77
  })(),
@@ -156,6 +170,15 @@ class HttpClient {
156
170
  correlationId: (_a = requestConfig.headers) === null || _a === void 0 ? void 0 : _a[shared_1.orionCorrelationIdRoot],
157
171
  };
158
172
  }
173
+ // implemented based on https://github.com/RasCarlito/axios-cache-adapter/blob/master/src/cache.js#L77
174
+ static generateCacheKey(req) {
175
+ var _a, _b;
176
+ const prefix = ((_a = req.headers) === null || _a === void 0 ? void 0 : _a.Authorization) ? (_b = util_1.safeJwtCanonicalIdParse(req.headers.Authorization.replace('Bearer ', ''))) !== null && _b !== void 0 ? _b : uuid.v4() : 'shared';
177
+ const url = `${req.baseURL ? req.baseURL : ''}${req.url}`;
178
+ const query = req.params ? JSON.stringify(req.params) : ''; // possible improvement: optimize cache-hit ratio by sorting the query params
179
+ const key = `${prefix}/${url}${query}`;
180
+ return `${key}${req.data ? md5_1.default(req.data) : ''}`;
181
+ }
159
182
  /**
160
183
  * Resolves the token with the token provider and adds it to the headers
161
184
  */
@@ -6,7 +6,11 @@ class ApiResponse {
6
6
  constructor(statusCode, body, headers) {
7
7
  this.body = body;
8
8
  this.statusCode = statusCode;
9
- this.headers = headers !== null && headers !== void 0 ? headers : {};
9
+ this.headers = {
10
+ 'Access-Control-Expose-Headers': 'Location, Access-Control-Allow-Origin, orion-correlation-id-root',
11
+ 'Content-Type': 'application/hal+json',
12
+ ...headers,
13
+ };
10
14
  }
11
15
  withCorrelationId(correlationId) {
12
16
  this.headers[shared_1.orionCorrelationIdRoot] = correlationId;
@@ -23,7 +23,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
23
23
  };
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
25
  const openapi_factory_1 = __importDefault(require("openapi-factory"));
26
- const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
27
26
  const uuid = __importStar(require("uuid"));
28
27
  const apiResponseModel_1 = require("./apiResponseModel");
29
28
  const exception_1 = require("../exceptions/exception");
@@ -45,7 +44,7 @@ class OpenApiWrapper {
45
44
  requestLogger.startInvocation(null, correlationId);
46
45
  // TODO: restrict the alternative way of resolving token and principal only for localhost
47
46
  this.userToken = (_b = (_a = request.requestContext.authorizer) === null || _a === void 0 ? void 0 : _a.jwt) !== null && _b !== void 0 ? _b : (_c = request.headers.Authorization) === null || _c === void 0 ? void 0 : _c.split(' ')[1];
48
- this.userPrincipal = (_e = (_d = request.requestContext.authorizer) === null || _d === void 0 ? void 0 : _d.canonicalId) !== null && _e !== void 0 ? _e : (_f = jsonwebtoken_1.default.decode(this.userToken)) === null || _f === void 0 ? void 0 : _f[this.canonicalIdKey];
47
+ this.userPrincipal = (_f = (_e = (_d = request.requestContext.authorizer) === null || _d === void 0 ? void 0 : _d.canonicalId) !== null && _e !== void 0 ? _e : util_1.safeJwtCanonicalIdParse(this.userToken)) !== null && _f !== void 0 ? _f : 'unknown';
49
48
  this.requestId = request.requestContext.requestId;
50
49
  requestLogger.log({
51
50
  title: 'RequestLogger',
package/lib/util.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { AxiosError } from 'axios';
2
+ export declare function safeJwtCanonicalIdParse(jwtToken: string): string | undefined;
2
3
  export declare function safeJsonParse(input: any, defaultValue: unknown): unknown;
3
4
  export declare function serializeObject(obj: unknown): object;
4
5
  export declare function serializeAxiosError(error: AxiosError): SerializedAxiosError | undefined;
package/lib/util.js CHANGED
@@ -1,6 +1,20 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.serializeAxiosError = exports.serializeObject = exports.safeJsonParse = void 0;
6
+ exports.serializeAxiosError = exports.serializeObject = exports.safeJsonParse = exports.safeJwtCanonicalIdParse = void 0;
7
+ const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
8
+ function safeJwtCanonicalIdParse(jwtToken) {
9
+ var _a;
10
+ try {
11
+ return (_a = jsonwebtoken_1.default.decode(jwtToken)) === null || _a === void 0 ? void 0 : _a['https://claims.cimpress.io/canonical_id'];
12
+ }
13
+ catch (_b) {
14
+ return undefined;
15
+ }
16
+ }
17
+ exports.safeJwtCanonicalIdParse = safeJwtCanonicalIdParse;
4
18
  function safeJsonParse(input, defaultValue) {
5
19
  try {
6
20
  return JSON.parse(input);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lambda-essentials-ts",
3
- "version": "3.0.1",
3
+ "version": "4.1.2",
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,
@@ -28,11 +28,12 @@
28
28
  "dependencies": {
29
29
  "aws-sdk": "2.939.0",
30
30
  "axios": "0.21.1",
31
- "axios-extensions": "3.1.3",
31
+ "axios-cache-adapter": "2.7.3",
32
32
  "fast-safe-stringify": "2.0.7",
33
33
  "is-error": "2.2.2",
34
34
  "jsonwebtoken": "8.5.1",
35
- "openapi-factory": "4.4.247",
35
+ "md5": "2.3.0",
36
+ "openapi-factory": "5.2.17",
36
37
  "retry-axios": "2.6.0",
37
38
  "uuid": "8.3.2"
38
39
  },
@@ -71,6 +72,6 @@
71
72
  "collectCoverage": false
72
73
  },
73
74
  "engines": {
74
- "node": ">=10.0.0"
75
+ "node": ">=12.0.0"
75
76
  }
76
77
  }