@autofleet/sequelize-utils 5.2.3 → 5.2.4
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/dist/common.d.ts +2 -0
- package/dist/common.js +8 -0
- package/dist/http-based-transaction.d.ts +3 -0
- package/dist/http-based-transaction.js +55 -0
- package/dist/index.d.ts +9 -6
- package/dist/index.js +10 -74
- package/dist/model-event-hooks.d.ts +1 -1
- package/dist/model-event-hooks.js +5 -5
- package/dist/test-utils/db.d.ts +2 -0
- package/dist/test-utils/db.js +19 -0
- package/dist/transaction-with-retry.d.ts +2 -0
- package/dist/transaction-with-retry.js +34 -0
- package/package.json +4 -4
- package/src/common.ts +3 -0
- package/src/http-based-transaction.ts +56 -0
- package/src/index.ts +16 -80
- package/src/model-event-hooks.ts +6 -7
- package/src/test-utils/db.ts +22 -0
- package/src/transaction-with-retry.ts +32 -0
- package/tsconfig.build.json +1 -0
package/dist/common.d.ts
ADDED
package/dist/common.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
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.debugLog = void 0;
|
|
7
|
+
const debug_1 = __importDefault(require("debug"));
|
|
8
|
+
exports.debugLog = (0, debug_1.default)('sequelize-utils');
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import type { Sequelize, Transaction } from 'sequelize';
|
|
3
|
+
export declare const httpBasedTransaction: <T>(sequelize: Sequelize, req: IncomingMessage, res: ServerResponse, cb: (transaction: Transaction) => Promise<T>) => Promise<T>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.httpBasedTransaction = void 0;
|
|
4
|
+
const common_1 = require("./common");
|
|
5
|
+
const rollbackErrorText = 'rollback has been called on this transaction';
|
|
6
|
+
const abortErrorText = 'Transaction cancelled due to request cancellation';
|
|
7
|
+
const httpBasedTransaction = (sequelize, req, res, cb) => {
|
|
8
|
+
let aborted = false;
|
|
9
|
+
if (req.socket.destroyed) {
|
|
10
|
+
(0, common_1.debugLog)(abortErrorText);
|
|
11
|
+
throw new Error(abortErrorText);
|
|
12
|
+
}
|
|
13
|
+
return sequelize.transaction(async (transaction) => {
|
|
14
|
+
// https://github.com/nodejs/node/blob/e46c680bf2b211bbd52cf959ca17ee98c7f657f5/test/parallel/test-http-aborted.js#L9
|
|
15
|
+
// 2023-04-13: Node.js 16.0.0
|
|
16
|
+
// Added also the `res.writableFinished` check, because it seems that the socket is not destroyed when the request is aborted, but the response is not finished.
|
|
17
|
+
// https://github.com/Jimbly/http-proxy-node16/commit/ba0c414cd03799e357c5d867c11dea06a9c34ec8#diff-b2d1e3b7c5f3b424a0af7971c582c822fd25a5ea279040ef6dc93fc4b2cf619dR151
|
|
18
|
+
const rollback = async () => {
|
|
19
|
+
(0, common_1.debugLog)(abortErrorText);
|
|
20
|
+
if (aborted)
|
|
21
|
+
return;
|
|
22
|
+
aborted = true;
|
|
23
|
+
await transaction.rollback();
|
|
24
|
+
};
|
|
25
|
+
const resRollback = async () => {
|
|
26
|
+
const didNotFinishWrite = !res.writableFinished;
|
|
27
|
+
if (didNotFinishWrite) {
|
|
28
|
+
(0, common_1.debugLog)(abortErrorText);
|
|
29
|
+
if (aborted)
|
|
30
|
+
return;
|
|
31
|
+
aborted = true;
|
|
32
|
+
await transaction.rollback();
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
req.socket.once('close', rollback);
|
|
36
|
+
res.once('close', resRollback);
|
|
37
|
+
const removeListeners = () => {
|
|
38
|
+
req.socket.removeListener('close', rollback);
|
|
39
|
+
res.removeListener('close', resRollback);
|
|
40
|
+
};
|
|
41
|
+
try {
|
|
42
|
+
const response = await cb(transaction);
|
|
43
|
+
removeListeners();
|
|
44
|
+
return response;
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
removeListeners();
|
|
48
|
+
if (e.message.includes(rollbackErrorText)) {
|
|
49
|
+
throw new Error(abortErrorText);
|
|
50
|
+
}
|
|
51
|
+
throw e;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
exports.httpBasedTransaction = httpBasedTransaction;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { Sequelize, Transaction
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
1
|
+
import type { Sequelize, Transaction } from 'sequelize';
|
|
2
|
+
import addModelEventHooks from './model-event-hooks';
|
|
3
|
+
import { transactionWithRetry as _transactionWithRetry } from './transaction-with-retry';
|
|
4
|
+
import { httpBasedTransaction as _httpBasedTransaction } from './http-based-transaction';
|
|
4
5
|
import runAfterTransactionCommits from './runAfterTransactionCommits';
|
|
6
|
+
type ArgsWithoutSequelize<T> = T extends (arg1: Sequelize, ...args: infer U) => any ? U : never;
|
|
5
7
|
declare const _default: (sequelize: Sequelize) => {
|
|
6
|
-
transactionWithRetry: <T>(
|
|
7
|
-
httpBasedTransaction: <T>(
|
|
8
|
-
registerModelEventHooks: (
|
|
8
|
+
transactionWithRetry: <T>(...args: ArgsWithoutSequelize<typeof _transactionWithRetry<T>>) => Promise<T>;
|
|
9
|
+
httpBasedTransaction: <T>(...args: ArgsWithoutSequelize<typeof _httpBasedTransaction<T>>) => Promise<T>;
|
|
10
|
+
registerModelEventHooks: (...args: ArgsWithoutSequelize<typeof addModelEventHooks>) => void;
|
|
9
11
|
runAfterTransactionCommits: typeof runAfterTransactionCommits;
|
|
12
|
+
useOrCreateTransaction: <T>(transaction: Transaction, cb: (t: Transaction) => Promise<T>) => Promise<T>;
|
|
10
13
|
};
|
|
11
14
|
export default _default;
|
package/dist/index.js
CHANGED
|
@@ -3,89 +3,25 @@ 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
|
-
/* eslint-disable import/prefer-default-export */
|
|
7
|
-
const sequelize_1 = require("sequelize");
|
|
8
|
-
const debug_1 = __importDefault(require("debug"));
|
|
9
6
|
const model_event_hooks_1 = __importDefault(require("./model-event-hooks"));
|
|
7
|
+
const transaction_with_retry_1 = require("./transaction-with-retry");
|
|
8
|
+
const http_based_transaction_1 = require("./http-based-transaction");
|
|
10
9
|
const runAfterTransactionCommits_1 = __importDefault(require("./runAfterTransactionCommits"));
|
|
11
|
-
const log = (0, debug_1.default)('sequelize-utils');
|
|
12
|
-
const rollbackErrorText = 'rollback has been called on this transaction';
|
|
13
|
-
const abortErrorText = 'Transaction cancelled due to request cancellation';
|
|
14
10
|
exports.default = (sequelize) => {
|
|
15
|
-
const transactionWithRetry =
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return transValue;
|
|
22
|
-
}
|
|
23
|
-
catch (e) {
|
|
24
|
-
if (e instanceof sequelize_1.DatabaseError || e?.constructor?.name === 'DatabaseError') {
|
|
25
|
-
if (retriesCount === 0) {
|
|
26
|
-
log('error inside transactionWithRetry - will stop retry', e);
|
|
27
|
-
throw e;
|
|
28
|
-
}
|
|
29
|
-
log(`error inside transactionWithRetry - will retry times ${retriesCount - 1}`, e);
|
|
30
|
-
return transactionWithRetry(funcToRun, retriesCount - 1);
|
|
31
|
-
}
|
|
32
|
-
throw e;
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
const httpBasedTransaction = (req, res, cb) => {
|
|
36
|
-
let aborted = false;
|
|
37
|
-
if (req.socket.destroyed) {
|
|
38
|
-
log(abortErrorText);
|
|
39
|
-
throw new Error(abortErrorText);
|
|
11
|
+
const transactionWithRetry = (funcToRun, retriesCount, options) => (0, transaction_with_retry_1.transactionWithRetry)(sequelize, funcToRun, retriesCount, options);
|
|
12
|
+
const httpBasedTransaction = (req, res, cb) => (0, http_based_transaction_1.httpBasedTransaction)(sequelize, req, res, cb);
|
|
13
|
+
const registerModelEventHooks = (modelTableMapping) => (0, model_event_hooks_1.default)(sequelize, modelTableMapping);
|
|
14
|
+
const useOrCreateTransaction = (transaction, cb) => {
|
|
15
|
+
if (transaction) {
|
|
16
|
+
return cb(transaction);
|
|
40
17
|
}
|
|
41
|
-
return
|
|
42
|
-
// https://github.com/nodejs/node/blob/e46c680bf2b211bbd52cf959ca17ee98c7f657f5/test/parallel/test-http-aborted.js#L9
|
|
43
|
-
// 2023-04-13: Node.js 16.0.0
|
|
44
|
-
// Added also the res.writableFinished check, becurse it seems that the socket is not destroyed
|
|
45
|
-
// when the request is aborted, but the response is not finished.
|
|
46
|
-
// https://github.com/Jimbly/http-proxy-node16/commit/ba0c414cd03799e357c5d867c11dea06a9c34ec8#diff-b2d1e3b7c5f3b424a0af7971c582c822fd25a5ea279040ef6dc93fc4b2cf619dR151
|
|
47
|
-
const rollback = async () => {
|
|
48
|
-
log(abortErrorText);
|
|
49
|
-
if (aborted)
|
|
50
|
-
return;
|
|
51
|
-
aborted = true;
|
|
52
|
-
await transaction.rollback();
|
|
53
|
-
};
|
|
54
|
-
const resRollback = async () => {
|
|
55
|
-
const didNotFinishWrite = !res.writableFinished;
|
|
56
|
-
if (didNotFinishWrite) {
|
|
57
|
-
log(abortErrorText);
|
|
58
|
-
if (aborted)
|
|
59
|
-
return;
|
|
60
|
-
aborted = true;
|
|
61
|
-
await transaction.rollback();
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
req.socket.once('close', rollback);
|
|
65
|
-
res.once('close', resRollback);
|
|
66
|
-
const removeListeners = () => {
|
|
67
|
-
req.socket.removeListener('close', rollback);
|
|
68
|
-
res.removeListener('close', resRollback);
|
|
69
|
-
};
|
|
70
|
-
try {
|
|
71
|
-
const response = await cb(transaction);
|
|
72
|
-
removeListeners();
|
|
73
|
-
return response;
|
|
74
|
-
}
|
|
75
|
-
catch (e) {
|
|
76
|
-
removeListeners();
|
|
77
|
-
if (e.message.includes(rollbackErrorText)) {
|
|
78
|
-
throw new Error(abortErrorText);
|
|
79
|
-
}
|
|
80
|
-
throw e;
|
|
81
|
-
}
|
|
82
|
-
});
|
|
18
|
+
return transactionWithRetry(cb, 0);
|
|
83
19
|
};
|
|
84
|
-
const registerModelEventHooks = (modelTableMapping) => (0, model_event_hooks_1.default)(sequelize, modelTableMapping);
|
|
85
20
|
return {
|
|
86
21
|
httpBasedTransaction,
|
|
87
22
|
transactionWithRetry,
|
|
88
23
|
registerModelEventHooks,
|
|
89
24
|
runAfterTransactionCommits: runAfterTransactionCommits_1.default,
|
|
25
|
+
useOrCreateTransaction,
|
|
90
26
|
};
|
|
91
27
|
};
|
|
@@ -4,5 +4,5 @@ export interface ModelMapping {
|
|
|
4
4
|
tableName: string;
|
|
5
5
|
};
|
|
6
6
|
}
|
|
7
|
-
declare const addModelEventHooks: (sequelize: Sequelize, modelTableMapping: ModelMapping) =>
|
|
7
|
+
declare const addModelEventHooks: (sequelize: Sequelize, modelTableMapping: ModelMapping) => void;
|
|
8
8
|
export default addModelEventHooks;
|
|
@@ -6,16 +6,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const events_1 = __importDefault(require("./events"));
|
|
7
7
|
const logger_1 = __importDefault(require("./logger"));
|
|
8
8
|
const runAfterTransactionCommits_1 = __importDefault(require("./runAfterTransactionCommits"));
|
|
9
|
+
const isDate = (input) => input instanceof Date || Object.prototype.toString.call(input) === '[object Date]';
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
9
11
|
const formatDatesInObject = (obj) => {
|
|
10
12
|
const newObj = { ...obj };
|
|
11
|
-
Object.
|
|
12
|
-
|
|
13
|
-
newObj[key] = newObj[key].getTime() / 1000;
|
|
14
|
-
}
|
|
13
|
+
Object.entries(newObj).filter(([, value]) => isDate(value)).forEach(([key, value]) => {
|
|
14
|
+
newObj[key] = value.getTime() / 1000;
|
|
15
15
|
});
|
|
16
16
|
return newObj;
|
|
17
17
|
};
|
|
18
|
-
const addModelEventHooks =
|
|
18
|
+
const addModelEventHooks = (sequelize, modelTableMapping) => {
|
|
19
19
|
const updateEventToDimTable = async (object, isDelete = false) => {
|
|
20
20
|
try {
|
|
21
21
|
const objectToSend = object.get();
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createSequelizeInstance = createSequelizeInstance;
|
|
4
|
+
const node_process_1 = require("node:process");
|
|
5
|
+
const sequelize_1 = require("sequelize");
|
|
6
|
+
function createSequelizeInstance() {
|
|
7
|
+
return new sequelize_1.Sequelize(node_process_1.env.DB_NAME || 'test_service_development', node_process_1.env.DB_USERNAME || '', node_process_1.env.DB_PASSWORD, {
|
|
8
|
+
host: node_process_1.env.DB_HOST || '127.0.0.1',
|
|
9
|
+
dialect: 'postgres',
|
|
10
|
+
operatorsAliases: sequelize_1.Op,
|
|
11
|
+
define: {
|
|
12
|
+
underscored: true,
|
|
13
|
+
},
|
|
14
|
+
pool: {
|
|
15
|
+
max: 10,
|
|
16
|
+
min: 1,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { type Sequelize, type Transaction, type TransactionOptions } from 'sequelize';
|
|
2
|
+
export declare const transactionWithRetry: <T>(sequelize: Sequelize, funcToRun: (transaction: Transaction) => Promise<T>, retriesCount?: number, options?: TransactionOptions) => Promise<T>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.transactionWithRetry = void 0;
|
|
4
|
+
const sequelize_1 = require("sequelize");
|
|
5
|
+
const common_1 = require("./common");
|
|
6
|
+
const transactionWithRetry = async (sequelize, funcToRun, retriesCount = 2, options = {}) => {
|
|
7
|
+
if (typeof funcToRun !== 'function') {
|
|
8
|
+
throw new Error('funcToRun must be a function');
|
|
9
|
+
}
|
|
10
|
+
if (typeof retriesCount !== 'number') {
|
|
11
|
+
throw new Error('if defined, retriesCount must be a number');
|
|
12
|
+
}
|
|
13
|
+
if (retriesCount < 0) {
|
|
14
|
+
throw new Error('retriesCount must be a positive number');
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
const transValue = await sequelize.transaction(options, async (transaction) => {
|
|
18
|
+
const funcValue = await funcToRun(transaction);
|
|
19
|
+
return funcValue;
|
|
20
|
+
});
|
|
21
|
+
return transValue;
|
|
22
|
+
}
|
|
23
|
+
catch (e) {
|
|
24
|
+
if ((e instanceof sequelize_1.DatabaseError || e?.constructor?.name === 'DatabaseError') && --retriesCount >= 0) {
|
|
25
|
+
(0, common_1.debugLog)(`error inside transactionWithRetry - will retry times ${retriesCount}`, e);
|
|
26
|
+
return (0, exports.transactionWithRetry)(sequelize, funcToRun, retriesCount, options);
|
|
27
|
+
}
|
|
28
|
+
if (retriesCount === -1) {
|
|
29
|
+
(0, common_1.debugLog)('error inside transactionWithRetry - will stop retry', e);
|
|
30
|
+
}
|
|
31
|
+
throw e;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
exports.transactionWithRetry = transactionWithRetry;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@autofleet/sequelize-utils",
|
|
3
|
-
"version": "5.2.
|
|
3
|
+
"version": "5.2.4",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
"prepublish": "tsc -p tsconfig.build.json",
|
|
9
9
|
"build": "tsc -p tsconfig.build.json",
|
|
10
10
|
"lint": "eslint .",
|
|
11
|
-
"test": "jest
|
|
12
|
-
"test-local": "jest
|
|
13
|
-
"coverage": "jest --coverage --
|
|
11
|
+
"test": "jest",
|
|
12
|
+
"test-local": "jest",
|
|
13
|
+
"coverage": "jest --coverage --runInBand"
|
|
14
14
|
},
|
|
15
15
|
"author": "",
|
|
16
16
|
"license": "ISC",
|
package/src/common.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import type { Sequelize, Transaction } from 'sequelize';
|
|
3
|
+
import { debugLog } from './common';
|
|
4
|
+
|
|
5
|
+
const rollbackErrorText = 'rollback has been called on this transaction';
|
|
6
|
+
const abortErrorText = 'Transaction cancelled due to request cancellation';
|
|
7
|
+
|
|
8
|
+
export const httpBasedTransaction = <T>(sequelize: Sequelize, req: IncomingMessage, res: ServerResponse, cb: (transaction: Transaction) => Promise<T>): Promise<T> => {
|
|
9
|
+
let aborted = false;
|
|
10
|
+
if (req.socket.destroyed) {
|
|
11
|
+
debugLog(abortErrorText);
|
|
12
|
+
throw new Error(abortErrorText);
|
|
13
|
+
}
|
|
14
|
+
return sequelize.transaction(async (transaction: Transaction) => {
|
|
15
|
+
// https://github.com/nodejs/node/blob/e46c680bf2b211bbd52cf959ca17ee98c7f657f5/test/parallel/test-http-aborted.js#L9
|
|
16
|
+
|
|
17
|
+
// 2023-04-13: Node.js 16.0.0
|
|
18
|
+
// Added also the `res.writableFinished` check, because it seems that the socket is not destroyed when the request is aborted, but the response is not finished.
|
|
19
|
+
// https://github.com/Jimbly/http-proxy-node16/commit/ba0c414cd03799e357c5d867c11dea06a9c34ec8#diff-b2d1e3b7c5f3b424a0af7971c582c822fd25a5ea279040ef6dc93fc4b2cf619dR151
|
|
20
|
+
|
|
21
|
+
const rollback = async () => {
|
|
22
|
+
debugLog(abortErrorText);
|
|
23
|
+
if (aborted) return;
|
|
24
|
+
aborted = true;
|
|
25
|
+
await transaction.rollback();
|
|
26
|
+
};
|
|
27
|
+
const resRollback = async () => {
|
|
28
|
+
const didNotFinishWrite = !res.writableFinished;
|
|
29
|
+
if (didNotFinishWrite) {
|
|
30
|
+
debugLog(abortErrorText);
|
|
31
|
+
if (aborted) return;
|
|
32
|
+
aborted = true;
|
|
33
|
+
await transaction.rollback();
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
req.socket.once('close', rollback);
|
|
37
|
+
res.once('close', resRollback);
|
|
38
|
+
|
|
39
|
+
const removeListeners = () => {
|
|
40
|
+
req.socket.removeListener('close', rollback);
|
|
41
|
+
res.removeListener('close', resRollback);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const response = await cb(transaction);
|
|
46
|
+
removeListeners();
|
|
47
|
+
return response;
|
|
48
|
+
} catch (e) {
|
|
49
|
+
removeListeners();
|
|
50
|
+
if (e.message.includes(rollbackErrorText)) {
|
|
51
|
+
throw new Error(abortErrorText);
|
|
52
|
+
}
|
|
53
|
+
throw e;
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -1,103 +1,39 @@
|
|
|
1
1
|
/* eslint-disable import/prefer-default-export */
|
|
2
|
-
import {
|
|
3
|
-
DatabaseError,
|
|
2
|
+
import type {
|
|
4
3
|
Sequelize,
|
|
5
4
|
Transaction,
|
|
6
5
|
TransactionOptions,
|
|
7
6
|
} from 'sequelize';
|
|
8
|
-
import debug from 'debug';
|
|
9
7
|
import type { IncomingMessage, ServerResponse } from 'http';
|
|
10
8
|
import addModelEventHooks, { ModelMapping } from './model-event-hooks';
|
|
9
|
+
import { transactionWithRetry as _transactionWithRetry } from './transaction-with-retry';
|
|
10
|
+
import { httpBasedTransaction as _httpBasedTransaction } from './http-based-transaction';
|
|
11
11
|
import runAfterTransactionCommits from './runAfterTransactionCommits';
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const rollbackErrorText = 'rollback has been called on this transaction';
|
|
16
|
-
const abortErrorText = 'Transaction cancelled due to request cancellation';
|
|
13
|
+
type ArgsWithoutSequelize<T> = T extends (arg1: Sequelize, ...args: infer U) => any ? U : never;
|
|
17
14
|
|
|
18
15
|
export default (sequelize: Sequelize): {
|
|
19
|
-
transactionWithRetry: <T>(
|
|
20
|
-
httpBasedTransaction: <T>(
|
|
21
|
-
registerModelEventHooks: (
|
|
16
|
+
transactionWithRetry: <T>(...args: ArgsWithoutSequelize<typeof _transactionWithRetry<T>>) => Promise<T>;
|
|
17
|
+
httpBasedTransaction: <T>(...args: ArgsWithoutSequelize<typeof _httpBasedTransaction<T>>) => Promise<T>;
|
|
18
|
+
registerModelEventHooks: (...args: ArgsWithoutSequelize<typeof addModelEventHooks>) => void;
|
|
22
19
|
runAfterTransactionCommits: typeof runAfterTransactionCommits;
|
|
20
|
+
useOrCreateTransaction: <T>(transaction: Transaction, cb: (t: Transaction) => Promise<T>) => Promise<T>;
|
|
23
21
|
} => {
|
|
24
|
-
const transactionWithRetry =
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return transValue;
|
|
31
|
-
} catch (e) {
|
|
32
|
-
if (e instanceof DatabaseError || e?.constructor?.name === 'DatabaseError') {
|
|
33
|
-
if (retriesCount === 0) {
|
|
34
|
-
log('error inside transactionWithRetry - will stop retry', e);
|
|
35
|
-
throw e;
|
|
36
|
-
}
|
|
37
|
-
log(`error inside transactionWithRetry - will retry times ${retriesCount - 1}`, e);
|
|
38
|
-
return transactionWithRetry(funcToRun, retriesCount - 1);
|
|
39
|
-
}
|
|
40
|
-
throw e;
|
|
22
|
+
const transactionWithRetry = <T>(funcToRun: (transaction: Transaction) => Promise<T>, retriesCount?: number, options?: TransactionOptions): Promise<T> => _transactionWithRetry(sequelize, funcToRun, retriesCount, options);
|
|
23
|
+
const httpBasedTransaction = <T>(req: IncomingMessage, res: ServerResponse, cb: (transaction: Transaction) => Promise<T>): Promise<T> => _httpBasedTransaction(sequelize, req, res, cb);
|
|
24
|
+
const registerModelEventHooks = (modelTableMapping: ModelMapping) => addModelEventHooks(sequelize, modelTableMapping);
|
|
25
|
+
const useOrCreateTransaction = <T>(transaction: Transaction, cb: (t: Transaction) => Promise<T>) => {
|
|
26
|
+
if (transaction) {
|
|
27
|
+
return cb(transaction);
|
|
41
28
|
}
|
|
29
|
+
return transactionWithRetry(cb, 0);
|
|
42
30
|
};
|
|
43
31
|
|
|
44
|
-
const httpBasedTransaction = <T>(req: IncomingMessage, res: ServerResponse, cb: (transaction: Transaction) => Promise<T>): Promise<T> => {
|
|
45
|
-
let aborted = false;
|
|
46
|
-
if (req.socket.destroyed) {
|
|
47
|
-
log(abortErrorText);
|
|
48
|
-
throw new Error(abortErrorText);
|
|
49
|
-
}
|
|
50
|
-
return sequelize.transaction(async (transaction: Transaction) => {
|
|
51
|
-
// https://github.com/nodejs/node/blob/e46c680bf2b211bbd52cf959ca17ee98c7f657f5/test/parallel/test-http-aborted.js#L9
|
|
52
|
-
|
|
53
|
-
// 2023-04-13: Node.js 16.0.0
|
|
54
|
-
// Added also the res.writableFinished check, becurse it seems that the socket is not destroyed
|
|
55
|
-
// when the request is aborted, but the response is not finished.
|
|
56
|
-
// https://github.com/Jimbly/http-proxy-node16/commit/ba0c414cd03799e357c5d867c11dea06a9c34ec8#diff-b2d1e3b7c5f3b424a0af7971c582c822fd25a5ea279040ef6dc93fc4b2cf619dR151
|
|
57
|
-
|
|
58
|
-
const rollback = async () => {
|
|
59
|
-
log(abortErrorText);
|
|
60
|
-
if (aborted) return;
|
|
61
|
-
aborted = true;
|
|
62
|
-
await transaction.rollback();
|
|
63
|
-
};
|
|
64
|
-
const resRollback = async () => {
|
|
65
|
-
const didNotFinishWrite = !res.writableFinished;
|
|
66
|
-
if (didNotFinishWrite) {
|
|
67
|
-
log(abortErrorText);
|
|
68
|
-
if (aborted) return;
|
|
69
|
-
aborted = true;
|
|
70
|
-
await transaction.rollback();
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
req.socket.once('close', rollback);
|
|
74
|
-
res.once('close', resRollback);
|
|
75
|
-
|
|
76
|
-
const removeListeners = () => {
|
|
77
|
-
req.socket.removeListener('close', rollback);
|
|
78
|
-
res.removeListener('close', resRollback);
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
try {
|
|
82
|
-
const response = await cb(transaction);
|
|
83
|
-
removeListeners();
|
|
84
|
-
return response;
|
|
85
|
-
} catch (e) {
|
|
86
|
-
removeListeners();
|
|
87
|
-
if (e.message.includes(rollbackErrorText)) {
|
|
88
|
-
throw new Error(abortErrorText);
|
|
89
|
-
}
|
|
90
|
-
throw e;
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
const registerModelEventHooks = (modelTableMapping: ModelMapping) => addModelEventHooks(sequelize, modelTableMapping);
|
|
96
|
-
|
|
97
32
|
return {
|
|
98
33
|
httpBasedTransaction,
|
|
99
34
|
transactionWithRetry,
|
|
100
35
|
registerModelEventHooks,
|
|
101
36
|
runAfterTransactionCommits,
|
|
37
|
+
useOrCreateTransaction,
|
|
102
38
|
};
|
|
103
39
|
};
|
package/src/model-event-hooks.ts
CHANGED
|
@@ -8,18 +8,17 @@ export interface ModelMapping {
|
|
|
8
8
|
tableName: string
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
const isDate = (input: unknown): input is Date => input instanceof Date || Object.prototype.toString.call(input) === '[object Date]';
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
13
|
+
const formatDatesInObject = (obj: object) => {
|
|
13
14
|
const newObj = { ...obj };
|
|
14
|
-
Object.
|
|
15
|
-
|
|
16
|
-
newObj[key] = newObj[key].getTime() / 1000;
|
|
17
|
-
}
|
|
15
|
+
Object.entries(newObj).filter(([, value]) => isDate(value)).forEach(([key, value]) => {
|
|
16
|
+
newObj[key] = (value as Date).getTime() / 1000;
|
|
18
17
|
});
|
|
19
18
|
return newObj;
|
|
20
19
|
};
|
|
21
20
|
|
|
22
|
-
const addModelEventHooks =
|
|
21
|
+
const addModelEventHooks = (sequelize: Sequelize, modelTableMapping: ModelMapping): void => {
|
|
23
22
|
const updateEventToDimTable = async (object: Model, isDelete = false) => {
|
|
24
23
|
try {
|
|
25
24
|
const objectToSend = object.get();
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { env } from 'node:process';
|
|
2
|
+
import { Sequelize, Op } from 'sequelize';
|
|
3
|
+
|
|
4
|
+
export function createSequelizeInstance(): Sequelize {
|
|
5
|
+
return new Sequelize(
|
|
6
|
+
env.DB_NAME || 'test_service_development',
|
|
7
|
+
env.DB_USERNAME || '',
|
|
8
|
+
env.DB_PASSWORD,
|
|
9
|
+
{
|
|
10
|
+
host: env.DB_HOST || '127.0.0.1',
|
|
11
|
+
dialect: 'postgres',
|
|
12
|
+
operatorsAliases: Op,
|
|
13
|
+
define: {
|
|
14
|
+
underscored: true,
|
|
15
|
+
},
|
|
16
|
+
pool: {
|
|
17
|
+
max: 10,
|
|
18
|
+
min: 1,
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DatabaseError, type Sequelize, type Transaction, type TransactionOptions,
|
|
3
|
+
} from 'sequelize';
|
|
4
|
+
import { debugLog } from './common';
|
|
5
|
+
|
|
6
|
+
export const transactionWithRetry = async <T>(sequelize: Sequelize, funcToRun: (transaction: Transaction) => Promise<T>, retriesCount = 2, options: TransactionOptions = {}): Promise<T> => {
|
|
7
|
+
if (typeof funcToRun !== 'function') {
|
|
8
|
+
throw new Error('funcToRun must be a function');
|
|
9
|
+
}
|
|
10
|
+
if (typeof retriesCount !== 'number') {
|
|
11
|
+
throw new Error('if defined, retriesCount must be a number');
|
|
12
|
+
}
|
|
13
|
+
if (retriesCount < 0) {
|
|
14
|
+
throw new Error('retriesCount must be a positive number');
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
const transValue = await sequelize.transaction(options, async (transaction) => {
|
|
18
|
+
const funcValue = await funcToRun(transaction);
|
|
19
|
+
return funcValue;
|
|
20
|
+
});
|
|
21
|
+
return transValue;
|
|
22
|
+
} catch (e) {
|
|
23
|
+
if ((e instanceof DatabaseError || e?.constructor?.name === 'DatabaseError') && --retriesCount >= 0) {
|
|
24
|
+
debugLog(`error inside transactionWithRetry - will retry times ${retriesCount}`, e);
|
|
25
|
+
return transactionWithRetry(sequelize, funcToRun, retriesCount, options);
|
|
26
|
+
}
|
|
27
|
+
if (retriesCount === -1) {
|
|
28
|
+
debugLog('error inside transactionWithRetry - will stop retry', e);
|
|
29
|
+
}
|
|
30
|
+
throw e;
|
|
31
|
+
}
|
|
32
|
+
};
|