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.
@@ -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",