badmfck-api-server 2.1.9 → 2.2.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.
@@ -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) {
|