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 +29 -0
- package/README.md +8 -0
- package/lib/httpClient/deduplicateRequestAdapter.d.ts +2 -0
- package/lib/httpClient/deduplicateRequestAdapter.js +20 -0
- package/lib/httpClient/httpClient.d.ts +4 -3
- package/lib/httpClient/httpClient.js +25 -2
- package/lib/openApi/apiResponseModel.js +5 -1
- package/lib/openApi/openApiWrapper.js +1 -2
- package/lib/util.d.ts +1 -0
- package/lib/util.js +15 -1
- package/package.json +5 -4
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,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 {
|
|
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/
|
|
85
|
+
* @link https://github.com/RasCarlito/axios-cache-adapter/blob/master/axios-cache-adapter.d.ts#L26
|
|
85
86
|
*/
|
|
86
|
-
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
|
|
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
|
-
|
|
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 =
|
|
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 :
|
|
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
|
+
"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-
|
|
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
|
-
"
|
|
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": ">=
|
|
75
|
+
"node": ">=12.0.0"
|
|
75
76
|
}
|
|
76
77
|
}
|