badmfck-api-server 2.1.9 → 2.2.0

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.0";
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,7 @@ export interface MysqlServiceOptions {
13
19
  port: number;
14
20
  database: string;
15
21
  queueLimit?: number;
22
+ transactionFailReport?: (trx: ITransaction, message: string) => void;
16
23
  migrations?: {
17
24
  dir: string;
18
25
  callback: () => void;
@@ -46,6 +53,15 @@ export interface MysqlQueryField {
46
53
  useInReplace?: boolean;
47
54
  _parsedValue?: string | number | boolean | null;
48
55
  }
56
+ export interface ITransaction {
57
+ id: number;
58
+ timestamp: number;
59
+ conn: mysql.PoolConnection;
60
+ queries: {
61
+ sql: string;
62
+ status: string;
63
+ }[];
64
+ }
49
65
  export interface MysqlQueryFieldObject extends Record<string, string | number | boolean | null | undefined | {
50
66
  value: string | number | boolean | null | undefined;
51
67
  system?: boolean;
@@ -62,9 +78,13 @@ export declare class MysqlService extends BaseService {
62
78
  serviceStarted: boolean;
63
79
  timeoutID: any;
64
80
  queries: never[];
81
+ static nextTransactionID: number;
82
+ transactions: ITransaction[];
83
+ maxTransactionWaitTime: number;
65
84
  constructor(options: MysqlServiceOptions);
66
85
  static executeQuery(query: MySqlQuery | MySqlQuery[]): Promise<MysqlResult[]>;
67
86
  init(): Promise<void>;
87
+ storeTransactionAsProblem(trx: ITransaction, message: string): Promise<void>;
68
88
  recreatePool(): Promise<boolean>;
69
89
  onApplicationReady(): Promise<void>;
70
90
  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,12 +59,40 @@ 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);
56
79
  }
57
80
  static async executeQuery(query) { return await exports.REQ_MYSQL_QUERY.request(query); }
58
81
  async init() {
59
82
  super.init();
60
83
  process.on('SIGINT', async () => {
61
84
  console.log('1. Received SIGINT. Performing cleanup...');
85
+ for (let i of this.transactions) {
86
+ try {
87
+ this.storeTransactionAsProblem(i, "SIGINT");
88
+ i.conn.removeAllListeners();
89
+ await i.conn.rollback();
90
+ i.conn.release();
91
+ }
92
+ catch (e) {
93
+ (0, LogService_1.logError)("Can't release transaction connection", e);
94
+ }
95
+ }
62
96
  if (this.pool) {
63
97
  try {
64
98
  await this.pool.end();
@@ -93,6 +127,93 @@ class MysqlService extends BaseService_1.BaseService {
93
127
  }
94
128
  return await Promise.all(promises);
95
129
  };
130
+ exports.REQ_MYSQL_TBEGIN.listener = async () => {
131
+ const conn = await this.pool?.getConnection();
132
+ if (!conn)
133
+ return { code: "NO_POOL", errno: 100000, fatal: true, sql: "", name: "NO_POOL", message: "Mysql pool not created" };
134
+ const tid = MysqlService.nextTransactionID++;
135
+ try {
136
+ await conn.beginTransaction();
137
+ await conn.query("SET autocommit=0");
138
+ }
139
+ catch (e) {
140
+ try {
141
+ conn.removeAllListeners();
142
+ await conn.rollback();
143
+ conn.release();
144
+ }
145
+ catch (e) { }
146
+ return this.createMysqlQueryError(e);
147
+ }
148
+ this.transactions.push({
149
+ id: tid,
150
+ timestamp: Date.now(),
151
+ conn: conn,
152
+ queries: []
153
+ });
154
+ return tid;
155
+ };
156
+ exports.REQ_MYSQL_TQUERY.listener = async (data) => {
157
+ const trx = this.transactions.find(i => i.id === data.tid);
158
+ if (!trx)
159
+ return { err: {
160
+ code: "NO_TRX",
161
+ errno: 100004,
162
+ fatal: true,
163
+ sql: "",
164
+ name: "NO_TRX",
165
+ message: "Transaction not found"
166
+ }, data: null, rollbackError: null };
167
+ const query = MysqlService.prepareQuery(data.query.query, data.query.fields);
168
+ let err = null;
169
+ let rollbackError = null;
170
+ let sqlData = null;
171
+ try {
172
+ sqlData = await trx.conn.query(query);
173
+ }
174
+ catch (e) {
175
+ err = this.createMysqlQueryError(e);
176
+ }
177
+ if (err) {
178
+ try {
179
+ trx.conn.removeAllListeners();
180
+ await trx.conn.rollback();
181
+ trx.conn.release();
182
+ }
183
+ catch (e) {
184
+ rollbackError = this.createMysqlQueryError(e);
185
+ }
186
+ }
187
+ trx.queries.push({ sql: query, status: err ? err.message : "completed" });
188
+ return {
189
+ data: sqlData,
190
+ error: err,
191
+ rollbackError: rollbackError
192
+ };
193
+ };
194
+ exports.REQ_MYSQL_TEND.listener = async (tid) => {
195
+ const trx = this.transactions.find(i => i.id === tid);
196
+ if (!trx)
197
+ return {
198
+ code: "NO_TRX",
199
+ errno: 100004,
200
+ fatal: true,
201
+ sql: "",
202
+ name: "NO_TRX",
203
+ message: "Transaction not found"
204
+ };
205
+ this.transactions = this.transactions.filter(i => i.id !== tid);
206
+ try {
207
+ await trx.conn.commit();
208
+ trx.conn.removeAllListeners();
209
+ trx.conn.release();
210
+ }
211
+ catch (e) {
212
+ this.storeTransactionAsProblem(trx, "Can't commit transaction");
213
+ return this.createMysqlQueryError(e);
214
+ }
215
+ return null;
216
+ };
96
217
  exports.REQ_MYSQL_TRANSACTION.listener = async (data) => {
97
218
  if (!this.pool)
98
219
  return { error: { code: "NO_POOL", errno: 100000, fatal: true, sql: "", name: "NO_POOL", message: "Mysql pool not created" }, data: null };
@@ -124,12 +245,21 @@ class MysqlService extends BaseService_1.BaseService {
124
245
  try {
125
246
  conn.release();
126
247
  }
127
- catch (e) { }
248
+ catch (e) {
249
+ (0, LogService_1.logCrit)("${MysqlService.js}", "Can't release connection!");
250
+ }
128
251
  if (!result)
129
252
  result = { error: null, data: income };
130
253
  return result;
131
254
  };
132
255
  }
256
+ async storeTransactionAsProblem(trx, message) {
257
+ if (!this.options.transactionFailReport) {
258
+ (0, LogService_1.logCrit)("${MysqlService.js}", "Can't report failed transaction, no report function: transactionFailReport in options");
259
+ return;
260
+ }
261
+ this.options.transactionFailReport(trx, message);
262
+ }
133
263
  async recreatePool() {
134
264
  (0, LogService_1.logInfo)("${MysqlService.js}", "Connecting to mysql: \n HOST: " + this.options.host + '\n PORT:' + this.options.port);
135
265
  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.0",
4
4
  "description": "Simple API http server based on express",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",