anote-server-libs 0.0.4 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/models/ApiCall.js +37 -0
- package/models/ApiCall.ts +16 -4
- package/models/Migration.js +35 -0
- package/models/Migration.ts +15 -4
- package/models/repository/BaseModelRepository.js +156 -0
- package/models/repository/BaseModelRepository.ts +37 -20
- package/models/repository/MemoryCache.js +92 -0
- package/models/repository/ModelDao.js +238 -0
- package/models/repository/ModelDao.ts +192 -36
- package/package.json +14 -9
- package/services/WithBody.js +60 -0
- package/services/WithTransaction.js +137 -0
- package/services/WithTransaction.ts +17 -29
- package/services/utils.js +188 -0
- package/services/utils.ts +18 -1
- package/tsconfig.json +30 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withTransaction = void 0;
|
|
4
|
+
const utils_1 = require("./utils");
|
|
5
|
+
function withTransaction(repo, logger, previousMethod, lock) {
|
|
6
|
+
return function (req, res, next) {
|
|
7
|
+
const endTerminator = res.end.bind(res);
|
|
8
|
+
const jsonTerminator = (obj) => {
|
|
9
|
+
res.write((0, utils_1.jsonStringify)(obj) || '{}');
|
|
10
|
+
endTerminator();
|
|
11
|
+
};
|
|
12
|
+
const connectTimeoutHandler = setTimeout(() => {
|
|
13
|
+
logger.error('Error timed out getting a client, exiting...');
|
|
14
|
+
process.exit(22);
|
|
15
|
+
}, 3000);
|
|
16
|
+
Promise.all([
|
|
17
|
+
repo.db ? repo.db.connect() : Promise.resolve(undefined),
|
|
18
|
+
repo.dbMssql ? Promise.resolve(repo.dbMssql.transaction()) : Promise.resolve(undefined)
|
|
19
|
+
]).then(([c1, c2]) => {
|
|
20
|
+
const dbClient = c1 || c2;
|
|
21
|
+
clearTimeout(connectTimeoutHandler);
|
|
22
|
+
utils_1.utils.logger = logger;
|
|
23
|
+
dbClient.removeListener('error', utils_1.utils.clientErrorHandler);
|
|
24
|
+
dbClient.on('error', utils_1.utils.clientErrorHandler);
|
|
25
|
+
res.locals.dbClient = dbClient;
|
|
26
|
+
res.locals.dbClientCommited = false;
|
|
27
|
+
res.locals.dbClientCommit = (cb) => {
|
|
28
|
+
if (!res.locals.dbClientCommited) {
|
|
29
|
+
res.locals.dbClientCommited = true;
|
|
30
|
+
(repo.db ? dbClient.query('COMMIT') : dbClient.commit()).catch((err) => err).then((err) => {
|
|
31
|
+
if (repo.db)
|
|
32
|
+
dbClient.release();
|
|
33
|
+
if (!(err instanceof Error)) {
|
|
34
|
+
for (let i = 0; i < res.locals.dbClientOnCommit.length; i++) {
|
|
35
|
+
res.locals.dbClientOnCommit[i]();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
cb(err instanceof Error ? err : undefined);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
cb(undefined);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
res.locals.dbClientOnCommit = [];
|
|
46
|
+
return (repo.db ? dbClient.query('BEGIN') : dbClient.begin()).then(() => {
|
|
47
|
+
const finish = () => {
|
|
48
|
+
res.json = (obj) => {
|
|
49
|
+
if (res.statusCode > 303 && res.statusCode !== 412) {
|
|
50
|
+
if (logger && res.statusCode > 499) {
|
|
51
|
+
logger.error('Uncaught 500: %j', obj.error.additionalInfo);
|
|
52
|
+
}
|
|
53
|
+
(repo.db ? dbClient.query('ROLLBACK') : dbClient.rollback()).catch((err) => obj.error.additionalInfo2 = { message: err.message }).then(() => {
|
|
54
|
+
if (repo.db)
|
|
55
|
+
dbClient.release();
|
|
56
|
+
jsonTerminator(obj);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
res.locals.dbClientCommit((err) => {
|
|
61
|
+
if (err) {
|
|
62
|
+
res.status(500);
|
|
63
|
+
jsonTerminator({
|
|
64
|
+
error: {
|
|
65
|
+
errorKey: 'internal.db',
|
|
66
|
+
additionalInfo: { message: err.message }
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
else
|
|
71
|
+
jsonTerminator(obj);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return res;
|
|
75
|
+
};
|
|
76
|
+
res.end = () => {
|
|
77
|
+
if (res.statusCode > 303 && res.statusCode !== 412) {
|
|
78
|
+
if (logger && res.statusCode > 499) {
|
|
79
|
+
logger.error('Uncaught 500 with no details...');
|
|
80
|
+
}
|
|
81
|
+
(repo.db ? dbClient.query('ROLLBACK') : dbClient.rollback()).catch(() => undefined).then(() => {
|
|
82
|
+
if (repo.db)
|
|
83
|
+
dbClient.release();
|
|
84
|
+
endTerminator();
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
res.locals.dbClientCommit((err) => {
|
|
89
|
+
if (err) {
|
|
90
|
+
res.status(500);
|
|
91
|
+
jsonTerminator({
|
|
92
|
+
error: {
|
|
93
|
+
errorKey: 'internal.db',
|
|
94
|
+
additionalInfo: { message: err.message }
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
else
|
|
99
|
+
endTerminator();
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return res;
|
|
103
|
+
};
|
|
104
|
+
return previousMethod.call(this, req, res, next);
|
|
105
|
+
};
|
|
106
|
+
if (lock) {
|
|
107
|
+
dbClient.query('SELECT pg_advisory_xact_lock(' + lock + ')').then(() => finish()).catch((err) => {
|
|
108
|
+
res.status(500).json({
|
|
109
|
+
error: {
|
|
110
|
+
errorKey: 'internal.db',
|
|
111
|
+
additionalInfo: { message: err.message }
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
dbClient.release();
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
finish();
|
|
119
|
+
}
|
|
120
|
+
}).catch((err) => {
|
|
121
|
+
if (repo.db)
|
|
122
|
+
dbClient.release();
|
|
123
|
+
else
|
|
124
|
+
dbClient.rollback();
|
|
125
|
+
throw err;
|
|
126
|
+
});
|
|
127
|
+
}).catch(err => {
|
|
128
|
+
res.status(500).json({
|
|
129
|
+
error: {
|
|
130
|
+
errorKey: 'internal.db',
|
|
131
|
+
additionalInfo: { message: err.message }
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
exports.withTransaction = withTransaction;
|
|
@@ -1,30 +1,13 @@
|
|
|
1
1
|
import {NextFunction, Request, Response} from 'express';
|
|
2
2
|
import {Logger} from 'winston';
|
|
3
3
|
import {BaseModelRepository} from '../models/repository/BaseModelRepository';
|
|
4
|
-
import {utils} from './utils';
|
|
4
|
+
import {jsonStringify, utils} from './utils';
|
|
5
5
|
|
|
6
6
|
export const enum SystemLock {
|
|
7
7
|
CHECK_CROSSING = 1,
|
|
8
8
|
FLUSH_CALLS = 2
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
function jsonStringify(obj: any): string {
|
|
12
|
-
const cache: any = {};
|
|
13
|
-
return JSON.stringify(obj, function(_, value) {
|
|
14
|
-
if(typeof value === 'object' && value !== null) {
|
|
15
|
-
if(cache[value] !== -1) {
|
|
16
|
-
try {
|
|
17
|
-
return JSON.parse(JSON.stringify(value));
|
|
18
|
-
} catch(error) {
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
cache[value] = true;
|
|
23
|
-
}
|
|
24
|
-
return value;
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
11
|
export function withTransaction(repo: BaseModelRepository, logger: Logger, previousMethod: (req: Request, res: Response, next: NextFunction) => void, lock?: SystemLock) {
|
|
29
12
|
return function(req: Request, res: Response, next: NextFunction) {
|
|
30
13
|
const endTerminator = res.end.bind(res);
|
|
@@ -37,7 +20,11 @@ export function withTransaction(repo: BaseModelRepository, logger: Logger, previ
|
|
|
37
20
|
logger.error('Error timed out getting a client, exiting...');
|
|
38
21
|
process.exit(22);
|
|
39
22
|
}, 3000);
|
|
40
|
-
|
|
23
|
+
Promise.all([
|
|
24
|
+
repo.db ? repo.db.connect() : Promise.resolve(undefined),
|
|
25
|
+
repo.dbMssql ? Promise.resolve(repo.dbMssql.transaction()) : Promise.resolve(undefined)
|
|
26
|
+
]).then(([c1, c2]) => {
|
|
27
|
+
const dbClient = c1 || c2;
|
|
41
28
|
clearTimeout(connectTimeoutHandler);
|
|
42
29
|
// On error, will rollback...
|
|
43
30
|
utils.logger = logger;
|
|
@@ -49,8 +36,8 @@ export function withTransaction(repo: BaseModelRepository, logger: Logger, previ
|
|
|
49
36
|
res.locals.dbClientCommit = (cb: (err: any) => any) => {
|
|
50
37
|
if(!res.locals.dbClientCommited) {
|
|
51
38
|
res.locals.dbClientCommited = true;
|
|
52
|
-
dbClient.query('COMMIT').catch(err => err).then((err: any) => {
|
|
53
|
-
dbClient.release();
|
|
39
|
+
(repo.db ? dbClient.query('COMMIT') : dbClient.commit()).catch((err: any) => err).then((err: any) => {
|
|
40
|
+
if(repo.db) dbClient.release();
|
|
54
41
|
if(!(err instanceof Error)) {
|
|
55
42
|
for(let i = 0; i < res.locals.dbClientOnCommit.length; i++) {
|
|
56
43
|
res.locals.dbClientOnCommit[i]();
|
|
@@ -63,15 +50,15 @@ export function withTransaction(repo: BaseModelRepository, logger: Logger, previ
|
|
|
63
50
|
}
|
|
64
51
|
};
|
|
65
52
|
res.locals.dbClientOnCommit = [];
|
|
66
|
-
return dbClient.query('BEGIN').then(() => {
|
|
53
|
+
return (repo.db ? dbClient.query('BEGIN') : dbClient.begin()).then(() => {
|
|
67
54
|
const finish = () => {
|
|
68
55
|
res.json = (obj: any) => {
|
|
69
56
|
if(res.statusCode > 303 && res.statusCode !== 412) {
|
|
70
57
|
if(logger && res.statusCode > 499) {
|
|
71
58
|
logger.error('Uncaught 500: %j', obj.error.additionalInfo);
|
|
72
59
|
}
|
|
73
|
-
dbClient.query('ROLLBACK').catch(err => obj.error.additionalInfo2 = {message: err.message}).then(() => {
|
|
74
|
-
dbClient.release();
|
|
60
|
+
(repo.db ? dbClient.query('ROLLBACK') : dbClient.rollback()).catch((err: any) => obj.error.additionalInfo2 = {message: err.message}).then(() => {
|
|
61
|
+
if(repo.db) dbClient.release();
|
|
75
62
|
jsonTerminator(obj);
|
|
76
63
|
});
|
|
77
64
|
} else {
|
|
@@ -94,8 +81,8 @@ export function withTransaction(repo: BaseModelRepository, logger: Logger, previ
|
|
|
94
81
|
if(logger && res.statusCode > 499) {
|
|
95
82
|
logger.error('Uncaught 500 with no details...');
|
|
96
83
|
}
|
|
97
|
-
dbClient.query('ROLLBACK').catch((): any => undefined).then(() => {
|
|
98
|
-
dbClient.release();
|
|
84
|
+
(repo.db ? dbClient.query('ROLLBACK') : dbClient.rollback()).catch((): any => undefined).then(() => {
|
|
85
|
+
if(repo.db) dbClient.release();
|
|
99
86
|
endTerminator();
|
|
100
87
|
});
|
|
101
88
|
} else {
|
|
@@ -117,7 +104,7 @@ export function withTransaction(repo: BaseModelRepository, logger: Logger, previ
|
|
|
117
104
|
};
|
|
118
105
|
|
|
119
106
|
if(lock) {
|
|
120
|
-
dbClient.query('SELECT pg_advisory_xact_lock(' + lock + ')').then(() => finish()).catch(err => {
|
|
107
|
+
dbClient.query('SELECT pg_advisory_xact_lock(' + lock + ')').then(() => finish()).catch((err: any) => {
|
|
121
108
|
res.status(500).json({
|
|
122
109
|
error: {
|
|
123
110
|
errorKey: 'internal.db',
|
|
@@ -129,8 +116,9 @@ export function withTransaction(repo: BaseModelRepository, logger: Logger, previ
|
|
|
129
116
|
} else {
|
|
130
117
|
finish();
|
|
131
118
|
}
|
|
132
|
-
}).catch(err => {
|
|
133
|
-
dbClient.release();
|
|
119
|
+
}).catch((err: any) => {
|
|
120
|
+
if(repo.db) dbClient.release();
|
|
121
|
+
else dbClient.rollback();
|
|
134
122
|
throw err;
|
|
135
123
|
});
|
|
136
124
|
}).catch(err => {
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.digitize = exports.fpEuros = exports.sendSelfPostableMessage = exports.idempotent = exports.jsonStringify = exports.lcm = exports.gcd = exports.utils = exports.clientErrorHandle = exports.btoa = exports.atob = void 0;
|
|
4
|
+
function atob(str) {
|
|
5
|
+
return Buffer.from(str, 'base64').toString('binary');
|
|
6
|
+
}
|
|
7
|
+
exports.atob = atob;
|
|
8
|
+
function btoa(str) {
|
|
9
|
+
return Buffer.from(str).toString('base64');
|
|
10
|
+
}
|
|
11
|
+
exports.btoa = btoa;
|
|
12
|
+
function clientErrorHandle(err) {
|
|
13
|
+
this.error('Error on DB client: %j', err);
|
|
14
|
+
}
|
|
15
|
+
exports.clientErrorHandle = clientErrorHandle;
|
|
16
|
+
exports.utils = {
|
|
17
|
+
clientErrorHandler: undefined
|
|
18
|
+
};
|
|
19
|
+
function gcdTwo(a, b) {
|
|
20
|
+
if (a === 0)
|
|
21
|
+
return b;
|
|
22
|
+
return gcdTwo(b % a, a);
|
|
23
|
+
}
|
|
24
|
+
function gcd(values) {
|
|
25
|
+
let result = values[0];
|
|
26
|
+
for (let i = 1; i < values.length; i++)
|
|
27
|
+
result = gcdTwo(values[i], result);
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
exports.gcd = gcd;
|
|
31
|
+
function lcm(values) {
|
|
32
|
+
let l = 1, divisor = 2;
|
|
33
|
+
while (true) {
|
|
34
|
+
let counter = 0;
|
|
35
|
+
let divisible = false;
|
|
36
|
+
for (let i = 0; i < values.length; i++) {
|
|
37
|
+
if (values[i] === 0) {
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
else if (values[i] < 0) {
|
|
41
|
+
values[i] = values[i] * (-1);
|
|
42
|
+
}
|
|
43
|
+
if (values[i] === 1) {
|
|
44
|
+
counter++;
|
|
45
|
+
}
|
|
46
|
+
if (values[i] % divisor === 0) {
|
|
47
|
+
divisible = true;
|
|
48
|
+
values[i] = values[i] / divisor;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (divisible) {
|
|
52
|
+
l = l * divisor;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
divisor++;
|
|
56
|
+
}
|
|
57
|
+
if (counter === values.length) {
|
|
58
|
+
return l;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
exports.lcm = lcm;
|
|
63
|
+
function jsonStringify(obj) {
|
|
64
|
+
const cache = {};
|
|
65
|
+
return JSON.stringify(obj, function (_, value) {
|
|
66
|
+
if (typeof value === 'object' && value !== null) {
|
|
67
|
+
if (cache[value] !== -1) {
|
|
68
|
+
try {
|
|
69
|
+
return JSON.parse(JSON.stringify(value));
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
cache[value] = true;
|
|
76
|
+
}
|
|
77
|
+
return value;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
exports.jsonStringify = jsonStringify;
|
|
81
|
+
function idempotent(repo, debug, logger) {
|
|
82
|
+
return function (req, res, next) {
|
|
83
|
+
let idempotenceKey = req.header('x-idempotent-key');
|
|
84
|
+
if (idempotenceKey) {
|
|
85
|
+
idempotenceKey = idempotenceKey.substring(0, 40);
|
|
86
|
+
repo.ApiCall.get(idempotenceKey).then(call => {
|
|
87
|
+
if (!call) {
|
|
88
|
+
const jsonTerminator = res.json;
|
|
89
|
+
const endTerminator = res.end;
|
|
90
|
+
res.json = (function (obj) {
|
|
91
|
+
repo.ApiCall.create({
|
|
92
|
+
id: idempotenceKey,
|
|
93
|
+
responseCode: res.statusCode,
|
|
94
|
+
responseJson: jsonStringify(obj),
|
|
95
|
+
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000)
|
|
96
|
+
}).then(() => jsonTerminator(obj), err => {
|
|
97
|
+
if (err)
|
|
98
|
+
logger.warn('Cannot save idempotent key: %j', err);
|
|
99
|
+
jsonTerminator(obj);
|
|
100
|
+
});
|
|
101
|
+
}).bind(res);
|
|
102
|
+
res.end = (function () {
|
|
103
|
+
repo.ApiCall.create({
|
|
104
|
+
id: idempotenceKey,
|
|
105
|
+
responseCode: res.statusCode,
|
|
106
|
+
responseJson: undefined,
|
|
107
|
+
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000)
|
|
108
|
+
}).then(() => endTerminator(), err => {
|
|
109
|
+
if (err)
|
|
110
|
+
logger.warn('Cannot save idempotent key: %j', err);
|
|
111
|
+
endTerminator();
|
|
112
|
+
});
|
|
113
|
+
}).bind(res);
|
|
114
|
+
next();
|
|
115
|
+
}
|
|
116
|
+
else
|
|
117
|
+
res.status(417).json({
|
|
118
|
+
responseCode: call.responseCode,
|
|
119
|
+
responseBody: JSON.parse(call.responseJson)
|
|
120
|
+
});
|
|
121
|
+
}, err => res.status(500).json({
|
|
122
|
+
error: {
|
|
123
|
+
errorKey: 'internal.db',
|
|
124
|
+
additionalInfo: { message: err.message, stack: debug && err.stack }
|
|
125
|
+
}
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
else
|
|
129
|
+
next();
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
exports.idempotent = idempotent;
|
|
133
|
+
function sendSelfPostableMessage(res, code, messageType, err) {
|
|
134
|
+
res.type('text/html').status(code).write(`
|
|
135
|
+
<!DOCTYPE HTML>
|
|
136
|
+
<html>
|
|
137
|
+
<head>
|
|
138
|
+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
|
139
|
+
</head>
|
|
140
|
+
<body>
|
|
141
|
+
<script type="text/javascript">
|
|
142
|
+
window.parent.postMessage({
|
|
143
|
+
type: '${messageType}',
|
|
144
|
+
confirm: ${!err},
|
|
145
|
+
error: JSON.parse('${JSON.stringify(err) || 'null'}')
|
|
146
|
+
}, '*');
|
|
147
|
+
</script>
|
|
148
|
+
</body>
|
|
149
|
+
</html>
|
|
150
|
+
`);
|
|
151
|
+
res.end();
|
|
152
|
+
}
|
|
153
|
+
exports.sendSelfPostableMessage = sendSelfPostableMessage;
|
|
154
|
+
function fpEuros(n) {
|
|
155
|
+
return Math.round(n * 100) / 100;
|
|
156
|
+
}
|
|
157
|
+
exports.fpEuros = fpEuros;
|
|
158
|
+
function digitize(value, opts) {
|
|
159
|
+
if (value === undefined || value === null)
|
|
160
|
+
return 'undefined';
|
|
161
|
+
if (typeof value === 'number') {
|
|
162
|
+
if (isNaN(value))
|
|
163
|
+
return '-';
|
|
164
|
+
if (!isFinite(value))
|
|
165
|
+
return 'Infinite';
|
|
166
|
+
value = String(value);
|
|
167
|
+
}
|
|
168
|
+
else if (typeof value === 'string')
|
|
169
|
+
return value;
|
|
170
|
+
const parts = value.split('.');
|
|
171
|
+
const initialLength = parts[0].length;
|
|
172
|
+
for (let i = initialLength - 3; i > 0; i -= 3) {
|
|
173
|
+
parts[0] = parts[0].slice(0, i) + ',' + parts[0].slice(i);
|
|
174
|
+
}
|
|
175
|
+
if (parts[0].startsWith('-,'))
|
|
176
|
+
parts[0] = '-' + parts[0].slice(2);
|
|
177
|
+
if (parts[1]) {
|
|
178
|
+
const expDecimals = parts[1].split(/[eE]-/);
|
|
179
|
+
if (expDecimals.length > 1 && parseInt(expDecimals[1], 10) > 2)
|
|
180
|
+
return '0.00';
|
|
181
|
+
let decimals = fpEuros(parseFloat('0.' + parts[1])).toString().substr(2, 2);
|
|
182
|
+
if (decimals.length === 1)
|
|
183
|
+
decimals += '0';
|
|
184
|
+
return parts[0] + '.' + decimals;
|
|
185
|
+
}
|
|
186
|
+
return (opts && opts.currency) ? (parts[0] + '.00') : parts[0];
|
|
187
|
+
}
|
|
188
|
+
exports.digitize = digitize;
|
package/services/utils.ts
CHANGED
|
@@ -60,6 +60,23 @@ export function lcm(values: number[]): number {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
export function jsonStringify(obj: any): string {
|
|
64
|
+
const cache: any = {};
|
|
65
|
+
return JSON.stringify(obj, function(_, value) {
|
|
66
|
+
if(typeof value === 'object' && value !== null) {
|
|
67
|
+
if(cache[value] !== -1) {
|
|
68
|
+
try {
|
|
69
|
+
return JSON.parse(JSON.stringify(value));
|
|
70
|
+
} catch(error) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
cache[value] = true;
|
|
75
|
+
}
|
|
76
|
+
return value;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
63
80
|
export function idempotent(repo: BaseModelRepository, debug: boolean, logger: Logger) {
|
|
64
81
|
return function(req: Request, res: Response, next: NextFunction) {
|
|
65
82
|
let idempotenceKey = req.header('x-idempotent-key');
|
|
@@ -73,7 +90,7 @@ export function idempotent(repo: BaseModelRepository, debug: boolean, logger: Lo
|
|
|
73
90
|
repo.ApiCall.create({
|
|
74
91
|
id: idempotenceKey,
|
|
75
92
|
responseCode: res.statusCode,
|
|
76
|
-
responseJson:
|
|
93
|
+
responseJson: jsonStringify(obj),
|
|
77
94
|
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000)
|
|
78
95
|
}).then(() => jsonTerminator(obj), err => {
|
|
79
96
|
if(err) logger.warn('Cannot save idempotent key: %j', err);
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"allowUnusedLabels": false,
|
|
4
|
+
"emitDecoratorMetadata": true,
|
|
5
|
+
"experimentalDecorators": true,
|
|
6
|
+
"lib": ["es2015", "dom"],
|
|
7
|
+
"module": "commonjs",
|
|
8
|
+
"moduleResolution": "node",
|
|
9
|
+
"noImplicitAny": true,
|
|
10
|
+
"noImplicitReturns": true,
|
|
11
|
+
"noUnusedLocals": true,
|
|
12
|
+
"noUnusedParameters": true,
|
|
13
|
+
"removeComments": true,
|
|
14
|
+
"sourceMap": false,
|
|
15
|
+
"skipDefaultLibCheck": true,
|
|
16
|
+
"skipLibCheck": true,
|
|
17
|
+
"suppressImplicitAnyIndexErrors": true,
|
|
18
|
+
"target": "es2021",
|
|
19
|
+
"typeRoots": [
|
|
20
|
+
"node_modules/@types"
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
"exclude": [
|
|
24
|
+
"node_modules",
|
|
25
|
+
"dist",
|
|
26
|
+
"**/*.spec.ts",
|
|
27
|
+
"example",
|
|
28
|
+
"tests"
|
|
29
|
+
]
|
|
30
|
+
}
|