badmfck-api-server 2.1.9 → 2.2.1
Sign up to get free protection for your applications and to get access to all the features.
@@ -4,6 +4,12 @@ import * as mysql from "mysql2/promise";
|
|
4
4
|
export declare const S_MYSQL_STARTED: Signal<void>;
|
5
5
|
export declare const REQ_MYSQL_QUERY: Req<MySqlQuery | MySqlQuery[], MysqlResult[]>;
|
6
6
|
export declare const REQ_MYSQL_TRANSACTION: Req<MySqlQuery[], MysqlResult>;
|
7
|
+
export declare const REQ_MYSQL_TBEGIN: Req<void, number | MysqlError>;
|
8
|
+
export declare const REQ_MYSQL_TQUERY: Req<{
|
9
|
+
query: MySqlQuery;
|
10
|
+
tid: number;
|
11
|
+
}, MysqlResult>;
|
12
|
+
export declare const REQ_MYSQL_TEND: Req<number, MysqlError | null>;
|
7
13
|
export declare const executeQuery: (query: MySqlQuery | MySqlQuery[]) => Promise<MysqlResult[]>;
|
8
14
|
export interface MysqlServiceOptions {
|
9
15
|
connectionLimit: number;
|
@@ -13,6 +19,8 @@ export interface MysqlServiceOptions {
|
|
13
19
|
port: number;
|
14
20
|
database: string;
|
15
21
|
queueLimit?: number;
|
22
|
+
transactionFailReport?: (trx: ITransaction, message: string) => void;
|
23
|
+
transactionFailReportDir?: string;
|
16
24
|
migrations?: {
|
17
25
|
dir: string;
|
18
26
|
callback: () => void;
|
@@ -46,6 +54,15 @@ export interface MysqlQueryField {
|
|
46
54
|
useInReplace?: boolean;
|
47
55
|
_parsedValue?: string | number | boolean | null;
|
48
56
|
}
|
57
|
+
export interface ITransaction {
|
58
|
+
id: number;
|
59
|
+
timestamp: number;
|
60
|
+
conn: mysql.PoolConnection;
|
61
|
+
queries: {
|
62
|
+
sql: string;
|
63
|
+
status: string;
|
64
|
+
}[];
|
65
|
+
}
|
49
66
|
export interface MysqlQueryFieldObject extends Record<string, string | number | boolean | null | undefined | {
|
50
67
|
value: string | number | boolean | null | undefined;
|
51
68
|
system?: boolean;
|
@@ -62,9 +79,14 @@ export declare class MysqlService extends BaseService {
|
|
62
79
|
serviceStarted: boolean;
|
63
80
|
timeoutID: any;
|
64
81
|
queries: never[];
|
82
|
+
static nextTransactionID: number;
|
83
|
+
transactions: ITransaction[];
|
84
|
+
maxTransactionWaitTime: number;
|
65
85
|
constructor(options: MysqlServiceOptions);
|
66
86
|
static executeQuery(query: MySqlQuery | MySqlQuery[]): Promise<MysqlResult[]>;
|
67
87
|
init(): Promise<void>;
|
88
|
+
finishApp(): Promise<void>;
|
89
|
+
storeTransactionAsProblem(trx: ITransaction, message: string): Promise<void>;
|
68
90
|
recreatePool(): Promise<boolean>;
|
69
91
|
onApplicationReady(): Promise<void>;
|
70
92
|
static fieldsToObject(fields: MysqlQueryField[] | MysqlQueryFieldObject, ignoreSystemParameters?: boolean): MysqlQueryFieldObject;
|
@@ -23,7 +23,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
23
23
|
return result;
|
24
24
|
};
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
26
|
-
exports.MysqlService = exports.executeQuery = exports.REQ_MYSQL_TRANSACTION = exports.REQ_MYSQL_QUERY = exports.S_MYSQL_STARTED = void 0;
|
26
|
+
exports.MysqlService = exports.executeQuery = exports.REQ_MYSQL_TEND = exports.REQ_MYSQL_TQUERY = exports.REQ_MYSQL_TBEGIN = exports.REQ_MYSQL_TRANSACTION = exports.REQ_MYSQL_QUERY = exports.S_MYSQL_STARTED = void 0;
|
27
27
|
const BaseService_1 = require("./BaseService");
|
28
28
|
const badmfck_signal_1 = __importStar(require("badmfck-signal"));
|
29
29
|
const crypto_1 = require("crypto");
|
@@ -32,6 +32,9 @@ const mysql = __importStar(require("mysql2/promise"));
|
|
32
32
|
exports.S_MYSQL_STARTED = new badmfck_signal_1.default();
|
33
33
|
exports.REQ_MYSQL_QUERY = new badmfck_signal_1.Req(undefined, "REQ_MYSQL_QUERY");
|
34
34
|
exports.REQ_MYSQL_TRANSACTION = new badmfck_signal_1.Req(undefined, "REQ_MYSQL_TRANSACTION");
|
35
|
+
exports.REQ_MYSQL_TBEGIN = new badmfck_signal_1.Req(undefined, "REQ_MYSQL_TRANSACTION_BEGING");
|
36
|
+
exports.REQ_MYSQL_TQUERY = new badmfck_signal_1.Req(undefined, "REQ_MYSQL_TRANSACTION_ADD_OPERATION");
|
37
|
+
exports.REQ_MYSQL_TEND = new badmfck_signal_1.Req(undefined, "REQ_MYSQL_TRANSACTION_END");
|
35
38
|
const executeQuery = async (query) => { return await exports.REQ_MYSQL_QUERY.request(query); };
|
36
39
|
exports.executeQuery = executeQuery;
|
37
40
|
class MysqlService extends BaseService_1.BaseService {
|
@@ -42,6 +45,9 @@ class MysqlService extends BaseService_1.BaseService {
|
|
42
45
|
serviceStarted = false;
|
43
46
|
timeoutID;
|
44
47
|
queries = [];
|
48
|
+
static nextTransactionID = 0;
|
49
|
+
transactions = [];
|
50
|
+
maxTransactionWaitTime = 1000 * 60 * 2;
|
45
51
|
constructor(options) {
|
46
52
|
super("mysql");
|
47
53
|
this.options = options;
|
@@ -53,24 +59,47 @@ class MysqlService extends BaseService_1.BaseService {
|
|
53
59
|
this.options.database = decrypt(this.options.database.substring(1)) ?? this.options.database;
|
54
60
|
if (this.options.password.startsWith("_"))
|
55
61
|
this.options.password = decrypt(this.options.password.substring(1)) ?? this.options.password;
|
62
|
+
setInterval(() => {
|
63
|
+
const now = Date.now();
|
64
|
+
this.transactions = this.transactions.filter(async (i) => {
|
65
|
+
if (now - i.timestamp < this.maxTransactionWaitTime)
|
66
|
+
return true;
|
67
|
+
try {
|
68
|
+
(0, LogService_1.logError)("Release transaction connection due to timeout");
|
69
|
+
i.conn.removeAllListeners();
|
70
|
+
await i.conn.rollback();
|
71
|
+
i.conn.release();
|
72
|
+
}
|
73
|
+
catch (e) {
|
74
|
+
(0, LogService_1.logError)("Can't release transaction", e);
|
75
|
+
}
|
76
|
+
return false;
|
77
|
+
});
|
78
|
+
}, 1000 * 30);
|
79
|
+
if (options.transactionFailReportDir) {
|
80
|
+
const fs = require('fs');
|
81
|
+
try {
|
82
|
+
if (!fs.existsSync(options.transactionFailReportDir)) {
|
83
|
+
fs.mkdirSync(options.transactionFailReportDir);
|
84
|
+
}
|
85
|
+
}
|
86
|
+
catch (e) {
|
87
|
+
options.transactionFailReportDir = undefined;
|
88
|
+
(0, LogService_1.logCrit)("${MysqlService.js}", "Can't create transaction fail report dir");
|
89
|
+
}
|
90
|
+
}
|
56
91
|
}
|
57
92
|
static async executeQuery(query) { return await exports.REQ_MYSQL_QUERY.request(query); }
|
58
93
|
async init() {
|
59
94
|
super.init();
|
60
95
|
process.on('SIGINT', async () => {
|
61
96
|
console.log('1. Received SIGINT. Performing cleanup...');
|
62
|
-
|
63
|
-
try {
|
64
|
-
await this.pool.end();
|
65
|
-
}
|
66
|
-
catch (e) {
|
67
|
-
(0, LogService_1.logCrit)("${MysqlService.js}", "Can't close MYSQL pool!");
|
68
|
-
}
|
69
|
-
}
|
97
|
+
await this.finishApp();
|
70
98
|
process.exit(0);
|
71
99
|
});
|
72
|
-
process.on('SIGTERM', () => {
|
100
|
+
process.on('SIGTERM', async () => {
|
73
101
|
console.log('2. Received SIGTERM. Performing cleanup...');
|
102
|
+
await this.finishApp();
|
74
103
|
process.exit(0);
|
75
104
|
});
|
76
105
|
this.serviceStarted = false;
|
@@ -93,43 +122,175 @@ class MysqlService extends BaseService_1.BaseService {
|
|
93
122
|
}
|
94
123
|
return await Promise.all(promises);
|
95
124
|
};
|
125
|
+
exports.REQ_MYSQL_TBEGIN.listener = async () => {
|
126
|
+
const conn = await this.pool?.getConnection();
|
127
|
+
if (!conn)
|
128
|
+
return { code: "NO_POOL", errno: 100000, fatal: true, sql: "", name: "NO_POOL", message: "Mysql pool not created" };
|
129
|
+
const tid = MysqlService.nextTransactionID++;
|
130
|
+
try {
|
131
|
+
await conn.beginTransaction();
|
132
|
+
await conn.query("SET autocommit=0");
|
133
|
+
}
|
134
|
+
catch (e) {
|
135
|
+
try {
|
136
|
+
conn.removeAllListeners();
|
137
|
+
await conn.rollback();
|
138
|
+
conn.release();
|
139
|
+
}
|
140
|
+
catch (e) { }
|
141
|
+
return this.createMysqlQueryError(e);
|
142
|
+
}
|
143
|
+
this.transactions.push({
|
144
|
+
id: tid,
|
145
|
+
timestamp: Date.now(),
|
146
|
+
conn: conn,
|
147
|
+
queries: []
|
148
|
+
});
|
149
|
+
return tid;
|
150
|
+
};
|
151
|
+
exports.REQ_MYSQL_TQUERY.listener = async (data) => {
|
152
|
+
const trx = this.transactions.find(i => i.id === data.tid);
|
153
|
+
if (!trx)
|
154
|
+
return { err: {
|
155
|
+
code: "NO_TRX",
|
156
|
+
errno: 100004,
|
157
|
+
fatal: true,
|
158
|
+
sql: "",
|
159
|
+
name: "NO_TRX",
|
160
|
+
message: "Transaction not found"
|
161
|
+
}, data: null, rollbackError: null };
|
162
|
+
const query = MysqlService.prepareQuery(data.query.query, data.query.fields);
|
163
|
+
let err = null;
|
164
|
+
let rollbackError = null;
|
165
|
+
let sqlData = null;
|
166
|
+
try {
|
167
|
+
sqlData = await trx.conn.query(query);
|
168
|
+
}
|
169
|
+
catch (e) {
|
170
|
+
err = this.createMysqlQueryError(e);
|
171
|
+
}
|
172
|
+
if (err) {
|
173
|
+
try {
|
174
|
+
trx.conn.removeAllListeners();
|
175
|
+
await trx.conn.rollback();
|
176
|
+
trx.conn.release();
|
177
|
+
}
|
178
|
+
catch (e) {
|
179
|
+
rollbackError = this.createMysqlQueryError(e);
|
180
|
+
}
|
181
|
+
}
|
182
|
+
trx.queries.push({ sql: query, status: err ? err.message : "completed" });
|
183
|
+
return {
|
184
|
+
data: sqlData,
|
185
|
+
error: err,
|
186
|
+
rollbackError: rollbackError
|
187
|
+
};
|
188
|
+
};
|
189
|
+
exports.REQ_MYSQL_TEND.listener = async (tid) => {
|
190
|
+
const trx = this.transactions.find(i => i.id === tid);
|
191
|
+
if (!trx)
|
192
|
+
return {
|
193
|
+
code: "NO_TRX",
|
194
|
+
errno: 100004,
|
195
|
+
fatal: true,
|
196
|
+
sql: "",
|
197
|
+
name: "NO_TRX",
|
198
|
+
message: "Transaction not found"
|
199
|
+
};
|
200
|
+
this.transactions = this.transactions.filter(i => i.id !== tid);
|
201
|
+
try {
|
202
|
+
await trx.conn.commit();
|
203
|
+
trx.conn.removeAllListeners();
|
204
|
+
trx.conn.release();
|
205
|
+
}
|
206
|
+
catch (e) {
|
207
|
+
this.storeTransactionAsProblem(trx, "Can't commit transaction");
|
208
|
+
return this.createMysqlQueryError(e);
|
209
|
+
}
|
210
|
+
return null;
|
211
|
+
};
|
96
212
|
exports.REQ_MYSQL_TRANSACTION.listener = async (data) => {
|
97
213
|
if (!this.pool)
|
98
214
|
return { error: { code: "NO_POOL", errno: 100000, fatal: true, sql: "", name: "NO_POOL", message: "Mysql pool not created" }, data: null };
|
99
215
|
const conn = await this.pool.getConnection();
|
100
216
|
if (!conn)
|
101
217
|
return { error: { code: "NO_CONN", errno: 100001, fatal: true, sql: "", name: "NO_CONN", message: "Mysql pool cant get connection" }, data: null };
|
102
|
-
|
218
|
+
const trx = {
|
219
|
+
id: MysqlService.nextTransactionID++,
|
220
|
+
timestamp: Date.now(),
|
221
|
+
conn: conn,
|
222
|
+
queries: []
|
223
|
+
};
|
103
224
|
let income = [];
|
104
225
|
try {
|
105
226
|
await conn.beginTransaction();
|
106
227
|
await conn.query("SET autocommit=0");
|
107
228
|
for (let i of data) {
|
108
|
-
const
|
109
|
-
|
229
|
+
const query = MysqlService.prepareQuery(i.query, i.fields);
|
230
|
+
let err = null;
|
231
|
+
try {
|
232
|
+
const d = await conn.query(query);
|
233
|
+
income.push(d[0]);
|
234
|
+
}
|
235
|
+
catch (e) {
|
236
|
+
err = this.createMysqlQueryError(e);
|
237
|
+
}
|
238
|
+
trx.queries.push({
|
239
|
+
sql: query,
|
240
|
+
status: err ? err.message : "completed"
|
241
|
+
});
|
242
|
+
if (err)
|
243
|
+
return { error: err, data: null };
|
110
244
|
}
|
111
|
-
await conn.commit();
|
112
|
-
}
|
113
|
-
catch (e) {
|
114
|
-
let rollbackError = null;
|
115
245
|
try {
|
116
|
-
await conn.
|
246
|
+
await conn.commit();
|
117
247
|
}
|
118
|
-
catch (
|
119
|
-
|
120
|
-
|
248
|
+
catch (e) {
|
249
|
+
let rollbackError = null;
|
250
|
+
try {
|
251
|
+
await conn.rollback();
|
252
|
+
}
|
253
|
+
catch (e2) {
|
254
|
+
(0, LogService_1.logCrit)("${MysqlService.js}", "Can't rollback transaction!");
|
255
|
+
rollbackError = this.createMysqlQueryError(e2);
|
256
|
+
}
|
257
|
+
return { error: this.createMysqlQueryError(e), data: null, rollbackError: rollbackError };
|
121
258
|
}
|
122
|
-
result = { error: this.createMysqlQueryError(e), data: null, rollbackError: rollbackError };
|
123
259
|
}
|
124
|
-
|
125
|
-
|
260
|
+
catch (e) {
|
261
|
+
return { error: this.createMysqlQueryError(e), data: null };
|
126
262
|
}
|
127
|
-
|
128
|
-
if (!result)
|
129
|
-
result = { error: null, data: income };
|
130
|
-
return result;
|
263
|
+
return { error: null, data: income };
|
131
264
|
};
|
132
265
|
}
|
266
|
+
async finishApp() {
|
267
|
+
for (let i of this.transactions) {
|
268
|
+
try {
|
269
|
+
this.storeTransactionAsProblem(i, "SIGINT");
|
270
|
+
i.conn.removeAllListeners();
|
271
|
+
await i.conn.rollback();
|
272
|
+
i.conn.release();
|
273
|
+
}
|
274
|
+
catch (e) {
|
275
|
+
(0, LogService_1.logError)("Can't release transaction connection", e);
|
276
|
+
}
|
277
|
+
}
|
278
|
+
if (this.pool) {
|
279
|
+
try {
|
280
|
+
await this.pool.end();
|
281
|
+
}
|
282
|
+
catch (e) {
|
283
|
+
(0, LogService_1.logCrit)("${MysqlService.js}", "Can't close MYSQL pool!");
|
284
|
+
}
|
285
|
+
}
|
286
|
+
}
|
287
|
+
async storeTransactionAsProblem(trx, message) {
|
288
|
+
if (!this.options.transactionFailReport) {
|
289
|
+
(0, LogService_1.logCrit)("${MysqlService.js}", "Can't report failed transaction, no report function: transactionFailReport in options");
|
290
|
+
return;
|
291
|
+
}
|
292
|
+
this.options.transactionFailReport(trx, message);
|
293
|
+
}
|
133
294
|
async recreatePool() {
|
134
295
|
(0, LogService_1.logInfo)("${MysqlService.js}", "Connecting to mysql: \n HOST: " + this.options.host + '\n PORT:' + this.options.port);
|
135
296
|
if (this.pool) {
|