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.
@@ -83,7 +83,7 @@ async function Initializer(services) {
83
83
  exports.Initializer = Initializer;
84
84
  class APIService extends BaseService_1.BaseService {
85
85
  static nextLogID = 0;
86
- version = "2.1.9";
86
+ version = "2.2.1";
87
87
  options;
88
88
  monitor;
89
89
  monitorIndexFile;
@@ -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
- if (this.pool) {
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
- let result = null;
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 d = await conn.query(MysqlService.prepareQuery(i.query, i.fields));
109
- income.push(d[0]);
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.rollback();
246
+ await conn.commit();
117
247
  }
118
- catch (e2) {
119
- (0, LogService_1.logCrit)("${MysqlService.js}", "Can't rollback transaction!");
120
- rollbackError = this.createMysqlQueryError(e2);
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
- try {
125
- conn.release();
260
+ catch (e) {
261
+ return { error: this.createMysqlQueryError(e), data: null };
126
262
  }
127
- catch (e) { }
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "badmfck-api-server",
3
- "version": "2.1.9",
3
+ "version": "2.2.1",
4
4
  "description": "Simple API http server based on express",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",