xpref 1.0.0

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 (61) hide show
  1. package/api-docs/body-schema.d.ts +1 -0
  2. package/api-docs/body-schema.js +31 -0
  3. package/api-docs/index.d.ts +3 -0
  4. package/api-docs/index.js +49 -0
  5. package/api-docs/parameter-schema.d.ts +1 -0
  6. package/api-docs/parameter-schema.js +57 -0
  7. package/api-docs/path-schema.d.ts +1 -0
  8. package/api-docs/path-schema.js +31 -0
  9. package/api-docs/path.d.ts +2 -0
  10. package/api-docs/path.js +79 -0
  11. package/api-docs/schema.d.ts +1 -0
  12. package/api-docs/schema.js +42 -0
  13. package/api-docs/security.d.ts +1 -0
  14. package/api-docs/security.js +48 -0
  15. package/api-docs/types.d.ts +27 -0
  16. package/api-docs/types.js +2 -0
  17. package/debug.d.ts +6 -0
  18. package/debug.js +26 -0
  19. package/i18n/index.d.ts +9 -0
  20. package/i18n/index.js +51 -0
  21. package/idempotency/index.d.ts +7 -0
  22. package/idempotency/index.js +130 -0
  23. package/idempotency/storage.d.ts +36 -0
  24. package/idempotency/storage.js +54 -0
  25. package/index.d.ts +7 -0
  26. package/index.js +126 -0
  27. package/package.json +36 -0
  28. package/request-forwarder/common.d.ts +11 -0
  29. package/request-forwarder/common.js +36 -0
  30. package/request-forwarder/forwarder.d.ts +2 -0
  31. package/request-forwarder/forwarder.js +81 -0
  32. package/request-forwarder/index.d.ts +2 -0
  33. package/request-forwarder/index.js +10 -0
  34. package/request-forwarder/pass-to-next.d.ts +4 -0
  35. package/request-forwarder/pass-to-next.js +48 -0
  36. package/request-forwarder/proxy.d.ts +8 -0
  37. package/request-forwarder/proxy.js +34 -0
  38. package/request-id.d.ts +7 -0
  39. package/request-id.js +19 -0
  40. package/request-log.d.ts +3 -0
  41. package/request-log.js +88 -0
  42. package/router.d.ts +13 -0
  43. package/router.js +65 -0
  44. package/setting/index.d.ts +1 -0
  45. package/setting/index.js +6 -0
  46. package/static.d.ts +2 -0
  47. package/static.js +12 -0
  48. package/types.d.ts +52 -0
  49. package/types.js +2 -0
  50. package/utils.d.ts +5 -0
  51. package/utils.js +22 -0
  52. package/validator/method-validator.d.ts +6 -0
  53. package/validator/method-validator.js +45 -0
  54. package/validator/route-schema.d.ts +2 -0
  55. package/validator/route-schema.js +71 -0
  56. package/validator/schema.d.ts +15 -0
  57. package/validator/schema.js +63 -0
  58. package/validator/validate.d.ts +8 -0
  59. package/validator/validate.js +94 -0
  60. package/validator/validator.test.d.ts +0 -0
  61. package/validator/validator.test.js +6 -0
@@ -0,0 +1 @@
1
+ export default function getBodySchemas(props: any): any;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = getBodySchemas;
4
+ const utils_1 = require("../utils");
5
+ const schemaFromString = (name) => {
6
+ const [schemaName, required] = (0, utils_1.decodeSchemaName)(name);
7
+ return {
8
+ [schemaName]: {
9
+ schema: { $ref: `#components/schemas/${schemaName}` },
10
+ required,
11
+ },
12
+ };
13
+ };
14
+ const getSchemasOfArrayProperties = (properties) => properties
15
+ .reduce((accu, item) => {
16
+ const isString = typeof item === 'string';
17
+ if (isString)
18
+ return { ...accu, ...schemaFromString(item) };
19
+ return accu;
20
+ }, {});
21
+ const getSchemasOfProperties = (properties) => {
22
+ const isArray = Array.isArray(properties);
23
+ if (!isArray)
24
+ return properties;
25
+ return getSchemasOfArrayProperties(properties);
26
+ };
27
+ function getBodySchemas(props) {
28
+ const { properties } = props;
29
+ const nextProps = getSchemasOfProperties(properties);
30
+ return { ...props, properties: nextProps };
31
+ }
@@ -0,0 +1,3 @@
1
+ import type { APIDocs, APIDocsProps, APIDocsOptions } from './types';
2
+ export default function setupApiDocs(apiDocs: APIDocsProps, options: APIDocsOptions): APIDocs;
3
+ export * from './types';
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ var __importDefault = (this && this.__importDefault) || function (mod) {
17
+ return (mod && mod.__esModule) ? mod : { "default": mod };
18
+ };
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.default = setupApiDocs;
21
+ const swagger_ui_express_1 = require("swagger-ui-express");
22
+ const security_1 = __importDefault(require("./security"));
23
+ const path_1 = __importDefault(require("./path"));
24
+ const schema_1 = __importDefault(require("./schema"));
25
+ const getTags = (tags) => {
26
+ const tagNames = Object.keys(tags);
27
+ return tagNames.reduce((acc, name) => {
28
+ const tag = tags[name] || {};
29
+ return [...acc, { name, ...tag }];
30
+ }, []);
31
+ };
32
+ function setupApiDocs(apiDocs, options) {
33
+ const { routes, schemas } = options;
34
+ const { bearerAuth = false, tags = {}, apiKeys = [], } = apiDocs;
35
+ const [securitySchemas, security] = (0, security_1.default)({ bearerAuth, apiKeys });
36
+ const touchedSchemas = (0, schema_1.default)(schemas);
37
+ const components = { schemas: touchedSchemas, ...securitySchemas };
38
+ const paths = (0, path_1.default)(routes);
39
+ const docs = {
40
+ openapi: '3.0.0',
41
+ ...apiDocs,
42
+ paths,
43
+ tags: getTags(tags),
44
+ components,
45
+ security,
46
+ };
47
+ return [swagger_ui_express_1.serve, (0, swagger_ui_express_1.setup)(docs)];
48
+ }
49
+ __exportStar(require("./types"), exports);
@@ -0,0 +1 @@
1
+ export default function getParameterSchemas(routeParams: any): any[];
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = getParameterSchemas;
4
+ const utils_1 = require("../utils");
5
+ const schemaFromString = (name, whereToBe = '') => {
6
+ const [schemaName, required] = (0, utils_1.decodeSchemaName)(name);
7
+ return {
8
+ name: schemaName,
9
+ schema: { $ref: `#components/schemas/${schemaName}` },
10
+ in: whereToBe,
11
+ required,
12
+ };
13
+ };
14
+ const inlineSchema = (schema, whereToBe = '') => {
15
+ const [name, props] = schema;
16
+ const [schemaName, required] = (0, utils_1.decodeSchemaName)(name);
17
+ const type = props.type || 'string';
18
+ if (type !== 'object')
19
+ return {
20
+ name: schemaName,
21
+ required,
22
+ in: whereToBe,
23
+ ...props,
24
+ };
25
+ return {
26
+ name: schemaName,
27
+ parameters: getObjectSchema({ name: schemaName, ...props }, whereToBe),
28
+ };
29
+ };
30
+ const getSchemasOfObjectProperties = (properties, _whereToBe = '') => properties;
31
+ const getSchemasOfArrayProperties = (properties, whereToBe = '') => properties.map((name) => {
32
+ const isString = typeof name === 'string';
33
+ if (isString)
34
+ return schemaFromString(name, whereToBe);
35
+ return inlineSchema(name, whereToBe);
36
+ });
37
+ const getObjectSchema = (schema, whereToBe = '') => {
38
+ const type = schema.type || 'string';
39
+ if (type !== 'object')
40
+ return [{ type, ...schema, in: whereToBe }];
41
+ const { properties } = schema;
42
+ const isArray = Array.isArray(properties);
43
+ if (isArray)
44
+ return getSchemasOfArrayProperties(properties, whereToBe);
45
+ return getSchemasOfObjectProperties(properties, whereToBe);
46
+ };
47
+ function getParameterSchemas(routeParams) {
48
+ const { headers = [], params = [], query = [] } = routeParams;
49
+ const headersSchema = { type: 'object', properties: headers, name: 'headers' };
50
+ const paramsSchema = { type: 'object', properties: params, name: 'params' };
51
+ const querySchema = { type: 'object', properties: query, name: 'query' };
52
+ return [
53
+ ...getObjectSchema(headersSchema, 'headers'),
54
+ ...getObjectSchema(paramsSchema, 'path'),
55
+ ...getObjectSchema(querySchema, 'query'),
56
+ ];
57
+ }
@@ -0,0 +1 @@
1
+ export declare const getMethodParameters: (methodHandler: any) => {};
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getMethodParameters = void 0;
7
+ const parameter_schema_1 = __importDefault(require("./parameter-schema"));
8
+ const body_schema_1 = __importDefault(require("./body-schema"));
9
+ const getMethodParameters = (methodHandler) => {
10
+ if (Array.isArray(methodHandler))
11
+ return {};
12
+ if (typeof methodHandler === 'function')
13
+ return {};
14
+ const { params } = methodHandler;
15
+ const parameters = {
16
+ parameters: (0, parameter_schema_1.default)(params),
17
+ };
18
+ const { body = [] } = params;
19
+ if (body.length === 0)
20
+ return parameters;
21
+ const content = {
22
+ 'application/json': {
23
+ schema: (0, body_schema_1.default)({
24
+ type: 'object',
25
+ properties: body,
26
+ }),
27
+ },
28
+ };
29
+ return { ...parameters, requestBody: { content } };
30
+ };
31
+ exports.getMethodParameters = getMethodParameters;
@@ -0,0 +1,2 @@
1
+ import type { Route } from '../types';
2
+ export default function getPaths(routes: Route): any;
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = getPaths;
4
+ const path_schema_1 = require("./path-schema");
5
+ const defaultResponse = {
6
+ 200: {
7
+ description: 'Operation completed successfully.',
8
+ },
9
+ };
10
+ const decodePath = (path) => path.replace(/:(\w*)/g, '{$1}');
11
+ const getMethodDescription = (methodHandler = {}) => {
12
+ if (Array.isArray(methodHandler) || typeof methodHandler === 'function') {
13
+ return {};
14
+ }
15
+ const { description = [] } = methodHandler;
16
+ const [summary, desc = ''] = Array.isArray(description) ? description : [description, ''];
17
+ return { summary, description: desc };
18
+ };
19
+ const getResponses = (methodProps = {}) => {
20
+ const { responses = {} } = methodProps;
21
+ const allResponse = { ...defaultResponse, ...responses };
22
+ return Object.entries(allResponse).reduce((accu, response) => {
23
+ const [code, res] = response;
24
+ const touchedResponse = { contentType: 'application/json', ...res };
25
+ return { ...accu, [code]: touchedResponse };
26
+ }, {});
27
+ };
28
+ const getMethodPath = (tag, methodProps) => {
29
+ const desc = getMethodDescription(methodProps);
30
+ const params = (0, path_schema_1.getMethodParameters)(methodProps);
31
+ const responses = getResponses(methodProps);
32
+ return {
33
+ responses,
34
+ tags: [tag],
35
+ ...desc,
36
+ ...params,
37
+ };
38
+ };
39
+ const generateMethodsPath = (props, { tag }) => {
40
+ const methods = Object.keys(props);
41
+ return methods.reduce((carry, method) => {
42
+ const methodDetail = props[method];
43
+ const result = {
44
+ ...carry,
45
+ [method]: getMethodPath(tag, methodDetail),
46
+ };
47
+ return result;
48
+ }, {});
49
+ };
50
+ const generatePath = (pPath, pathProps, { parentTag = false } = {}) => {
51
+ const path = decodePath(pPath);
52
+ const [tag, middlewares, methodHandler, children = {}] = pathProps;
53
+ const curTag = parentTag || tag;
54
+ const result = {
55
+ [path]: generateMethodsPath(methodHandler, { tag: curTag, middlewares }),
56
+ };
57
+ const childPaths = Object.keys(children);
58
+ if (childPaths.length === 0)
59
+ return result;
60
+ const paths = childPaths.reduce((carry, childPath) => {
61
+ const childProps = children[childPath];
62
+ return {
63
+ ...carry,
64
+ ...generatePath(`${path}${childPath}`, childProps, { parentTag: curTag }),
65
+ };
66
+ }, result);
67
+ return paths;
68
+ };
69
+ function getPaths(routes) {
70
+ const paths = Object.keys(routes);
71
+ const pathResult = paths.reduce((carry, path) => {
72
+ const pathProps = routes[path];
73
+ return {
74
+ ...carry,
75
+ ...generatePath(path, pathProps),
76
+ };
77
+ }, {});
78
+ return pathResult;
79
+ }
@@ -0,0 +1 @@
1
+ export default function initSchemas(schemaObj: any): any;
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = initSchemas;
4
+ const defaultSchema = {
5
+ type: 'string',
6
+ example: 'String value here',
7
+ description: 'Pre-defined string schema',
8
+ };
9
+ const touchObjectProperties = (names) => names.reduce((accu, name) => {
10
+ if (typeof name === 'string')
11
+ return {
12
+ ...accu,
13
+ [name]: { '$ref': `#/components/schemas/${name}` },
14
+ };
15
+ const [schemaName, schemaProps] = name;
16
+ return {
17
+ ...accu,
18
+ [schemaName]: senitizeSchema(schemaProps),
19
+ };
20
+ }, {});
21
+ const senitizeObjectSchema = (schemaProps) => {
22
+ const { properties } = schemaProps;
23
+ const touchedProperties = touchObjectProperties(properties);
24
+ return { ...schemaProps, properties: touchedProperties };
25
+ };
26
+ const senitizeSchema = (schemaProps) => {
27
+ const type = schemaProps.type || 'string';
28
+ if (type !== 'object')
29
+ return { ...defaultSchema, ...schemaProps };
30
+ return senitizeObjectSchema(schemaProps);
31
+ };
32
+ function initSchemas(schemaObj) {
33
+ const schemas = Object.keys(schemaObj);
34
+ return schemas.reduce((acc, schemaName) => {
35
+ const schemaProps = schemaObj[schemaName];
36
+ const schema = senitizeSchema(schemaProps);
37
+ return ({
38
+ ...acc,
39
+ [schemaName]: schema,
40
+ });
41
+ }, {});
42
+ }
@@ -0,0 +1 @@
1
+ export default function setSecurity(props: any): any;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = setSecurity;
4
+ const bearer = (bearerAuth = false) => {
5
+ if (bearerAuth)
6
+ return {};
7
+ return {
8
+ bearerAuth: {
9
+ type: 'http',
10
+ scheme: 'bearer',
11
+ bearerFormat: 'JWT',
12
+ },
13
+ };
14
+ };
15
+ const getApiKeys = (apiKeys) => {
16
+ if (apiKeys.length === 0)
17
+ return {};
18
+ return apiKeys.reduce((carry, apiKey) => {
19
+ const [name, where] = Array.isArray(apiKey)
20
+ ? apiKey
21
+ : [apiKey, 'header'];
22
+ return {
23
+ ...carry,
24
+ [name]: {
25
+ name,
26
+ type: 'apiKey',
27
+ in: where,
28
+ },
29
+ };
30
+ }, {});
31
+ };
32
+ function setSecurity(props) {
33
+ const { bearerAuth = false, apiKeys = [] } = props;
34
+ const res = {
35
+ ...bearer(bearerAuth),
36
+ ...getApiKeys(apiKeys),
37
+ };
38
+ const keys = Object.keys(res);
39
+ if (keys.length === 0)
40
+ return [{}, {}];
41
+ const globalSecurities = keys.map((key) => ({
42
+ [key]: [],
43
+ }));
44
+ return [
45
+ { securitySchemes: res },
46
+ globalSecurities,
47
+ ];
48
+ }
@@ -0,0 +1,27 @@
1
+ type Info = {
2
+ version: string;
3
+ title: string;
4
+ description: string;
5
+ };
6
+ type ExternalDocs = {
7
+ description: string;
8
+ url: string;
9
+ };
10
+ export type Tag = {
11
+ description: string;
12
+ externalDocs?: ExternalDocs;
13
+ };
14
+ export type APIKey = Array<string | string[]>;
15
+ export type APIDocs = [any, CallableFunction];
16
+ export type APIDocsOptions = {
17
+ routes: Record<string, any>;
18
+ schemas: Record<string, any>;
19
+ };
20
+ export type APIDocsProps = {
21
+ info: Info;
22
+ tags?: Record<string, Tag>;
23
+ bearerAuth?: boolean;
24
+ apiKeys?: APIKey;
25
+ paths?: any;
26
+ };
27
+ export {};
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/debug.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Gets the error information for debugin
3
+ * @param {Error} error - The Error object
4
+ * @return Object
5
+ */
6
+ export default function logDebug(error: any): void;
package/debug.js ADDED
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = logDebug;
4
+ /* eslint-disable no-console */
5
+ function getRaiser(error) {
6
+ const regex = /\((.*):(\d+):(\d+)\)$/;
7
+ const found = regex.exec(error.stack.split('\n')[2]) || false;
8
+ if (!found)
9
+ return {};
10
+ const [path] = found;
11
+ const { message } = error;
12
+ return {
13
+ path,
14
+ error: message,
15
+ };
16
+ }
17
+ /**
18
+ * Gets the error information for debugin
19
+ * @param {Error} error - The Error object
20
+ * @return Object
21
+ */
22
+ function logDebug(error) {
23
+ const res = getRaiser(error);
24
+ const msg = { ...res, source: 'xpressify' };
25
+ console.log(msg);
26
+ }
@@ -0,0 +1,9 @@
1
+ type Locale = Record<string, string>;
2
+ type I18n = {
3
+ locale: Locale;
4
+ fallbackLang?: string;
5
+ fallbackLangOnly?: boolean;
6
+ };
7
+ declare const translate: (key: string, replace?: Record<string, any>, pLang?: string) => string;
8
+ export default function initI18n(props: I18n): typeof translate;
9
+ export {};
package/i18n/index.js ADDED
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = initI18n;
4
+ const fs_1 = require("fs");
5
+ const singletonLocale = {};
6
+ let privProps = {
7
+ fallbackLang: 'en',
8
+ fallbackLangOnly: false,
9
+ };
10
+ const toLowerCase = (data) => {
11
+ const entries = Object.entries(data);
12
+ return entries.reduce((accu, item) => {
13
+ const [key, value] = item;
14
+ return { ...accu, [key.toLowerCase()]: value };
15
+ }, {});
16
+ };
17
+ const loadLocalContent = (locale) => {
18
+ Object.entries(locale).forEach((localeItem) => {
19
+ const [localeName, localePath] = localeItem;
20
+ const data = (0, fs_1.readFileSync)(localePath, 'utf8');
21
+ const jsonData = JSON.parse(data === '' ? '{}' : data);
22
+ singletonLocale[localeName] = toLowerCase(jsonData);
23
+ });
24
+ };
25
+ const localeEscape = (str, obj = {}) => {
26
+ const keys = Object.keys(obj);
27
+ return keys.reduce((carry, placeholder) => {
28
+ const nextResult = carry.replace(`{${placeholder}}`, obj[placeholder]);
29
+ return nextResult;
30
+ }, str);
31
+ };
32
+ const getLang = (lang) => {
33
+ const { fallbackLangOnly, fallbackLang } = privProps;
34
+ if (fallbackLangOnly)
35
+ return fallbackLang;
36
+ return lang;
37
+ };
38
+ const translate = (key, replace = {}, pLang = 'en') => {
39
+ const lang = getLang(pLang);
40
+ const { fallbackLang } = privProps;
41
+ const i18n = singletonLocale[lang] || singletonLocale[fallbackLang] || {};
42
+ const translation = i18n[key.toLowerCase()] || key;
43
+ const translated = localeEscape(translation, replace);
44
+ return translated;
45
+ };
46
+ function initI18n(props) {
47
+ const { locale, fallbackLang = 'en', fallbackLangOnly = false } = props;
48
+ privProps = { fallbackLang, fallbackLangOnly };
49
+ loadLocalContent(locale);
50
+ return translate;
51
+ }
@@ -0,0 +1,7 @@
1
+ export type Idempotency = {
2
+ ttl?: number;
3
+ enforced?: boolean;
4
+ headerKey?: string;
5
+ validateResponse?: CallableFunction;
6
+ };
7
+ export default function idempotency(props?: Idempotency): (req: any, res: any, next: any) => any;
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = idempotency;
4
+ /* eslint-disable no-console */
5
+ const stream_1 = require("stream");
6
+ const storage_1 = require("./storage");
7
+ const getContentToStore = (response, chunks) => {
8
+ const { statusCode } = response;
9
+ const headers = {
10
+ 'content-type': 'application/json',
11
+ ...response.getHeaders(),
12
+ };
13
+ const content = {
14
+ statusCode,
15
+ headers,
16
+ chunks,
17
+ };
18
+ return content;
19
+ };
20
+ const parseBody = (chunks) => {
21
+ const decoded = Buffer.from(chunks.join('')).toString('utf8');
22
+ try {
23
+ return JSON.parse(decoded);
24
+ }
25
+ catch (error) {
26
+ const { message } = error;
27
+ console.error(`[ERROR] API - ${message}`);
28
+ return {};
29
+ }
30
+ };
31
+ /**
32
+ * Validate the rsponse content, by default it validate the HTTP Status Code (200, 201),
33
+ * but in some case, the response is not only validated by status code, so we can
34
+ * inject the callback function with maniplucated content as parameter. The callback function
35
+ * returns boolean value indicate if the response is success or not.
36
+ */
37
+ const isSuccessResponse = (content, validateResponse = false) => {
38
+ const { statusCode, chunks } = content;
39
+ const isSuccess = (statusCode === 200 || statusCode === 201);
40
+ if (!validateResponse)
41
+ return isSuccess;
42
+ const body = parseBody(chunks);
43
+ return isSuccess && validateResponse({ ...content, body });
44
+ };
45
+ /**
46
+ * Handles the streaming between middleware
47
+ * @param {Response} res - ExpressJS HTTP Response object
48
+ * @param {NextFuction} next - ExpressJS next function
49
+ * @param {Object} props - Injected properties
50
+ */
51
+ const handleResponse = (res, next, props = {}) => {
52
+ const { idempotencyKey, validateResponse } = props;
53
+ (0, storage_1.create)(idempotencyKey);
54
+ const [oldWrite, oldEnd] = [res.write, res.end];
55
+ const chunks = [];
56
+ res.write = function (chunk) {
57
+ chunks.push(Buffer.from(chunk));
58
+ oldWrite.apply(res, arguments);
59
+ };
60
+ res.end = function (chunk) {
61
+ if (chunk)
62
+ chunks.push(Buffer.from(chunk));
63
+ const content = getContentToStore(res, chunks);
64
+ const success = isSuccessResponse(content, validateResponse);
65
+ if (success)
66
+ (0, storage_1.store)(idempotencyKey, content);
67
+ else
68
+ (0, storage_1.deleteItem)(idempotencyKey);
69
+ oldEnd.apply(res, arguments);
70
+ };
71
+ return next();
72
+ };
73
+ /*
74
+ * Writes the saved response back to the client, this write only
75
+ * just the content, but also the previous response headers. This
76
+ * to make sure the content is written back to the client is exactly
77
+ * the same as the result.
78
+ */
79
+ function writeResponse(res, item) {
80
+ const { chunks, headers, statusCode } = item.response;
81
+ res.writeHeader(statusCode, {
82
+ 'content-type': 'application/json',
83
+ 'cached-source': 'cache/idempotency',
84
+ ...headers,
85
+ });
86
+ return stream_1.Readable.from(chunks).pipe(res);
87
+ }
88
+ ;
89
+ function idempotency(props = {}) {
90
+ const { headerKey = 'idempotency-key', enforced = false, ttl = (60 * 5), validateResponse = false, } = props;
91
+ return (req, res, next) => {
92
+ const { headers, method = 'GET' } = req;
93
+ if (method.toUpperCase() !== 'POST')
94
+ return next();
95
+ const idempotencyKey = headers[headerKey] || false;
96
+ if (!enforced && !idempotencyKey)
97
+ return next();
98
+ if (enforced && !idempotencyKey) {
99
+ return res.status(422).json({
100
+ message: 'Idempotency key required for this request',
101
+ error: true,
102
+ });
103
+ }
104
+ if (!idempotencyKey)
105
+ return next();
106
+ if (!(0, storage_1.exists)(idempotencyKey))
107
+ return handleResponse(res, next, {
108
+ idempotencyKey,
109
+ validateResponse,
110
+ });
111
+ const item = (0, storage_1.getItem)(idempotencyKey);
112
+ const { createdAt } = item;
113
+ const now = new Date();
114
+ const timelapse = Math.ceil((now.valueOf() - createdAt.valueOf()) / 1000);
115
+ if (timelapse > ttl) {
116
+ return res.status(410).json({
117
+ message: 'Request using this idempotency key has expired.',
118
+ });
119
+ }
120
+ if (item.status === storage_1.status.IN_PROGERSS) {
121
+ return res.status(202).json({
122
+ at: new Date(),
123
+ message: 'Processing',
124
+ });
125
+ }
126
+ if (item.status === storage_1.status.DONE)
127
+ return writeResponse(res, item);
128
+ return next();
129
+ };
130
+ }
@@ -0,0 +1,36 @@
1
+ type Status = 'in-progress' | 'done';
2
+ type Response = {
3
+ statusCode: number;
4
+ headers: Record<string, any>;
5
+ chunks: any;
6
+ };
7
+ type Item = {
8
+ createdAt: Date;
9
+ status: Status;
10
+ referenceNumber?: string | number;
11
+ response?: Response;
12
+ };
13
+ export declare const status: Record<string, string>;
14
+ export declare const getAll: () => Record<string, Item>;
15
+ export declare const exists: (key: string) => boolean;
16
+ /**
17
+ * Creates the storage with minimal information (the predetermined default value)
18
+ * @param {string} key - The key to be added into storage.
19
+ */
20
+ export declare const create: (key: string) => any;
21
+ /**
22
+ * Gets the stored item by key
23
+ * @param {string} key - Storage key
24
+ * @return object;
25
+ */
26
+ export declare const getItem: (key: string) => Item | any;
27
+ /**
28
+ * Stores the response object to be used when passing the stored
29
+ * result back to the client. This make sure the previouse response
30
+ * (stored response) is sent back.
31
+ * @param {string} key - The key of the storage
32
+ * @param {Response} response - The response information
33
+ */
34
+ export declare const store: (key: string, response: Response) => void;
35
+ export declare const deleteItem: (key: string) => void;
36
+ export {};