badmfck-api-server 2.1.9 → 2.2.0

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