@tachybase/module-multi-app 0.23.8

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.
Files changed (117) hide show
  1. package/.turbo/turbo-build.log +14 -0
  2. package/README.md +34 -0
  3. package/README.zh-CN.md +34 -0
  4. package/client.d.ts +1 -0
  5. package/client.js +1 -0
  6. package/dist/client/AppManager.d.ts +2 -0
  7. package/dist/client/AppNameInput.d.ts +2 -0
  8. package/dist/client/MultiAppBlockInitializer.d.ts +2 -0
  9. package/dist/client/MultiAppManagerProvider.d.ts +2 -0
  10. package/dist/client/MultiAppManagerProvider.style.d.ts +5 -0
  11. package/dist/client/Settings.d.ts +2 -0
  12. package/dist/client/index.d.ts +6 -0
  13. package/dist/client/index.js +1 -0
  14. package/dist/client/settings/schemas/applications.d.ts +13 -0
  15. package/dist/client/utils.d.ts +4 -0
  16. package/dist/constants.d.ts +1 -0
  17. package/dist/constants.js +27 -0
  18. package/dist/externalVersion.js +16 -0
  19. package/dist/index.d.ts +2 -0
  20. package/dist/index.js +39 -0
  21. package/dist/locale/en-US.json +27 -0
  22. package/dist/locale/es-ES.json +9 -0
  23. package/dist/locale/ko_KR.json +11 -0
  24. package/dist/locale/pt-BR.json +9 -0
  25. package/dist/locale/zh-CN.json +27 -0
  26. package/dist/node_modules/mariadb/LICENSE +502 -0
  27. package/dist/node_modules/mariadb/callback.js +41 -0
  28. package/dist/node_modules/mariadb/lib/cmd/batch-bulk.js +278 -0
  29. package/dist/node_modules/mariadb/lib/cmd/batch-rewrite.js +372 -0
  30. package/dist/node_modules/mariadb/lib/cmd/change-user.js +149 -0
  31. package/dist/node_modules/mariadb/lib/cmd/class/ok-packet.js +17 -0
  32. package/dist/node_modules/mariadb/lib/cmd/column-definition.js +102 -0
  33. package/dist/node_modules/mariadb/lib/cmd/command.js +168 -0
  34. package/dist/node_modules/mariadb/lib/cmd/common-binary-cmd.js +327 -0
  35. package/dist/node_modules/mariadb/lib/cmd/common-text-cmd.js +427 -0
  36. package/dist/node_modules/mariadb/lib/cmd/handshake/auth/caching-sha2-password-auth.js +168 -0
  37. package/dist/node_modules/mariadb/lib/cmd/handshake/auth/clear-password-auth.js +23 -0
  38. package/dist/node_modules/mariadb/lib/cmd/handshake/auth/ed25519-password-auth.js +761 -0
  39. package/dist/node_modules/mariadb/lib/cmd/handshake/auth/native-password-auth.js +55 -0
  40. package/dist/node_modules/mariadb/lib/cmd/handshake/auth/pam-password-auth.js +58 -0
  41. package/dist/node_modules/mariadb/lib/cmd/handshake/auth/plugin-auth.js +19 -0
  42. package/dist/node_modules/mariadb/lib/cmd/handshake/auth/sha256-password-auth.js +142 -0
  43. package/dist/node_modules/mariadb/lib/cmd/handshake/client-capabilities.js +74 -0
  44. package/dist/node_modules/mariadb/lib/cmd/handshake/client-handshake-response.js +126 -0
  45. package/dist/node_modules/mariadb/lib/cmd/handshake/handshake.js +292 -0
  46. package/dist/node_modules/mariadb/lib/cmd/handshake/initial-handshake.js +74 -0
  47. package/dist/node_modules/mariadb/lib/cmd/handshake/ssl-request.js +29 -0
  48. package/dist/node_modules/mariadb/lib/cmd/ping.js +52 -0
  49. package/dist/node_modules/mariadb/lib/cmd/query.js +255 -0
  50. package/dist/node_modules/mariadb/lib/cmd/quit.js +28 -0
  51. package/dist/node_modules/mariadb/lib/cmd/reset.js +54 -0
  52. package/dist/node_modules/mariadb/lib/cmd/resultset.js +607 -0
  53. package/dist/node_modules/mariadb/lib/cmd/stream.js +45 -0
  54. package/dist/node_modules/mariadb/lib/config/connection-options.js +258 -0
  55. package/dist/node_modules/mariadb/lib/config/pool-cluster-options.js +19 -0
  56. package/dist/node_modules/mariadb/lib/config/pool-options.js +47 -0
  57. package/dist/node_modules/mariadb/lib/connection-callback.js +160 -0
  58. package/dist/node_modules/mariadb/lib/connection.js +1460 -0
  59. package/dist/node_modules/mariadb/lib/const/capabilities.js +64 -0
  60. package/dist/node_modules/mariadb/lib/const/collations.js +473 -0
  61. package/dist/node_modules/mariadb/lib/const/connection_status.js +13 -0
  62. package/dist/node_modules/mariadb/lib/const/error-code.js +1282 -0
  63. package/dist/node_modules/mariadb/lib/const/field-detail.js +35 -0
  64. package/dist/node_modules/mariadb/lib/const/field-type.js +71 -0
  65. package/dist/node_modules/mariadb/lib/const/server-status.js +30 -0
  66. package/dist/node_modules/mariadb/lib/const/state-change.js +12 -0
  67. package/dist/node_modules/mariadb/lib/filtered-pool-cluster.js +81 -0
  68. package/dist/node_modules/mariadb/lib/io/bulk-packet.js +590 -0
  69. package/dist/node_modules/mariadb/lib/io/compression-input-stream.js +141 -0
  70. package/dist/node_modules/mariadb/lib/io/compression-output-stream.js +171 -0
  71. package/dist/node_modules/mariadb/lib/io/packet-input-stream.js +193 -0
  72. package/dist/node_modules/mariadb/lib/io/packet-node-encoded.js +36 -0
  73. package/dist/node_modules/mariadb/lib/io/packet-node-iconv.js +37 -0
  74. package/dist/node_modules/mariadb/lib/io/packet-output-stream.js +502 -0
  75. package/dist/node_modules/mariadb/lib/io/packet.js +515 -0
  76. package/dist/node_modules/mariadb/lib/io/rewrite-packet.js +481 -0
  77. package/dist/node_modules/mariadb/lib/misc/connection-information.js +96 -0
  78. package/dist/node_modules/mariadb/lib/misc/errors.js +123 -0
  79. package/dist/node_modules/mariadb/lib/misc/parse.js +1033 -0
  80. package/dist/node_modules/mariadb/lib/misc/utils.js +298 -0
  81. package/dist/node_modules/mariadb/lib/pool-base.js +611 -0
  82. package/dist/node_modules/mariadb/lib/pool-callback.js +202 -0
  83. package/dist/node_modules/mariadb/lib/pool-cluster-callback.js +66 -0
  84. package/dist/node_modules/mariadb/lib/pool-cluster.js +407 -0
  85. package/dist/node_modules/mariadb/lib/pool-promise.js +108 -0
  86. package/dist/node_modules/mariadb/package.json +1 -0
  87. package/dist/node_modules/mariadb/promise.js +34 -0
  88. package/dist/node_modules/mariadb/types/index.d.ts +870 -0
  89. package/dist/server/actions/apps.d.ts +5 -0
  90. package/dist/server/actions/apps.js +117 -0
  91. package/dist/server/app-lifecycle.d.ts +8 -0
  92. package/dist/server/app-lifecycle.js +99 -0
  93. package/dist/server/app-start-env.d.ts +2 -0
  94. package/dist/server/app-start-env.js +105 -0
  95. package/dist/server/collections/applications.d.ts +2 -0
  96. package/dist/server/collections/applications.js +82 -0
  97. package/dist/server/index.d.ts +4 -0
  98. package/dist/server/index.js +29 -0
  99. package/dist/server/middlewares/app-selector.d.ts +1 -0
  100. package/dist/server/middlewares/app-selector.js +47 -0
  101. package/dist/server/middlewares/index.d.ts +2 -0
  102. package/dist/server/middlewares/index.js +23 -0
  103. package/dist/server/middlewares/inject-app-list.d.ts +1 -0
  104. package/dist/server/middlewares/inject-app-list.js +48 -0
  105. package/dist/server/migrations/20240820153000-add-apps-tmpl.d.ts +6 -0
  106. package/dist/server/migrations/20240820153000-add-apps-tmpl.js +47 -0
  107. package/dist/server/migrations/20241126124904-add-createdBy.d.ts +6 -0
  108. package/dist/server/migrations/20241126124904-add-createdBy.js +41 -0
  109. package/dist/server/models/application.d.ts +10 -0
  110. package/dist/server/models/application.js +57 -0
  111. package/dist/server/server.d.ts +19 -0
  112. package/dist/server/server.js +246 -0
  113. package/dist/swagger/index.d.ts +197 -0
  114. package/dist/swagger/index.js +227 -0
  115. package/package.json +38 -0
  116. package/server.d.ts +2 -0
  117. package/server.js +1 -0
@@ -0,0 +1,1460 @@
1
+ 'use strict';
2
+
3
+ const EventEmitter = require('events');
4
+ const util = require('util');
5
+ const Queue = require('denque');
6
+ const Net = require('net');
7
+ const PacketInputStream = require('./io/packet-input-stream');
8
+ const PacketOutputStream = require('./io/packet-output-stream');
9
+ const CompressionInputStream = require('./io/compression-input-stream');
10
+ const CompressionOutputStream = require('./io/compression-output-stream');
11
+ const ServerStatus = require('./const/server-status');
12
+ const ConnectionInformation = require('./misc/connection-information');
13
+ const tls = require('tls');
14
+ const Errors = require('./misc/errors');
15
+ const Utils = require('./misc/utils');
16
+ const Capabilities = require('./const/capabilities');
17
+ const moment = require('moment-timezone');
18
+
19
+ /*commands*/
20
+ const Handshake = require('./cmd/handshake/handshake');
21
+ const Quit = require('./cmd/quit');
22
+ const Ping = require('./cmd/ping');
23
+ const Reset = require('./cmd/reset');
24
+ const Query = require('./cmd/query');
25
+ const BatchRewrite = require('./cmd/batch-rewrite');
26
+ const BatchBulk = require('./cmd/batch-bulk');
27
+ const Stream = require('./cmd/stream');
28
+ const ChangeUser = require('./cmd/change-user');
29
+ const { Status } = require('./const/connection_status');
30
+
31
+ /**
32
+ * New Connection instance.
33
+ *
34
+ * @param options connection options
35
+ * @returns Connection instance
36
+ * @constructor
37
+ * @fires Connection#connect
38
+ * @fires Connection#end
39
+ * @fires Connection#error
40
+ *
41
+ */
42
+ function Connection(options) {
43
+ //*****************************************************************
44
+ // public API functions
45
+ //*****************************************************************
46
+
47
+ /**
48
+ * Connect event
49
+ *
50
+ * @returns {Promise} promise
51
+ */
52
+ this.connect = () => {
53
+ switch (_status) {
54
+ case Status.NOT_CONNECTED:
55
+ _status = Status.CONNECTING;
56
+ return new Promise(function (resolve, reject) {
57
+ _registerHandshakeCmd(resolve, reject);
58
+ });
59
+
60
+ case Status.CLOSING:
61
+ case Status.CLOSED:
62
+ return Promise.reject(
63
+ Errors.createError(
64
+ 'Connection closed',
65
+ null,
66
+ true,
67
+ info,
68
+ '08S01',
69
+ Errors.ER_CONNECTION_ALREADY_CLOSED
70
+ )
71
+ );
72
+
73
+ case Status.CONNECTING:
74
+ case Status.AUTHENTICATING:
75
+ return Promise.reject(
76
+ Errors.createError(
77
+ 'Connection is already connecting',
78
+ null,
79
+ true,
80
+ info,
81
+ '08S01',
82
+ Errors.ER_ALREADY_CONNECTING
83
+ )
84
+ );
85
+ }
86
+ //status Connected
87
+ return Promise.resolve(this);
88
+ };
89
+
90
+ /**
91
+ * Permit to change user during connection.
92
+ * All user variables will be reset, Prepare commands will be released.
93
+ * !!! mysql has a bug when CONNECT_ATTRS capability is set, that is default !!!!
94
+ *
95
+ * @param options connection options
96
+ * @returns {Promise} promise
97
+ */
98
+ this.changeUser = (options) => {
99
+ if (!info.isMariaDB()) {
100
+ return Promise.reject(
101
+ Errors.createError(
102
+ 'method changeUser not available for MySQL server due to Bug #83472',
103
+ null,
104
+ false,
105
+ info,
106
+ '0A000',
107
+ Errors.ER_MYSQL_CHANGE_USER_BUG
108
+ )
109
+ );
110
+ }
111
+
112
+ return new Promise(function (resolve, reject) {
113
+ _addCommand(
114
+ new ChangeUser(
115
+ options,
116
+ (res) => {
117
+ if (options && options.collation) opts.collation = options.collation;
118
+ resolve(res);
119
+ },
120
+ _authFailHandler.bind(this, reject),
121
+ _addCommand.bind(this)
122
+ )
123
+ );
124
+ });
125
+ };
126
+
127
+ /**
128
+ * Start transaction
129
+ *
130
+ * @returns {Promise} promise
131
+ */
132
+ this.beginTransaction = () => {
133
+ return this.query('START TRANSACTION');
134
+ };
135
+
136
+ /**
137
+ * Commit a transaction.
138
+ *
139
+ * @returns {Promise} command if commit was needed only
140
+ */
141
+ this.commit = () => {
142
+ return _changeTransaction('COMMIT');
143
+ };
144
+
145
+ /**
146
+ * Roll back a transaction.
147
+ *
148
+ * @returns {Promise} promise
149
+ */
150
+ this.rollback = () => {
151
+ return _changeTransaction('ROLLBACK');
152
+ };
153
+
154
+ /**
155
+ * Execute query using text protocol.
156
+ *
157
+ * @param sql sql parameter Object can be used to supersede default option.
158
+ * Object must then have sql property.
159
+ * @param values object / array of placeholder values (not mandatory)
160
+ * @returns {Promise} promise
161
+ */
162
+ this._queryPromise = (sql, values) => {
163
+ let _cmdOpt,
164
+ _sql,
165
+ _values = values;
166
+ if (typeof sql === 'object') {
167
+ _cmdOpt = sql;
168
+ _sql = _cmdOpt.sql;
169
+ if (_cmdOpt.values) _values = _cmdOpt.values;
170
+ } else {
171
+ _sql = sql;
172
+ }
173
+
174
+ return new Promise(function (resolve, reject) {
175
+ const cmd = new Query(resolve, reject, _cmdOpt, opts, _sql, _values);
176
+ if (opts.trace) Error.captureStackTrace(cmd);
177
+ _addCommand(cmd);
178
+ });
179
+ };
180
+
181
+ /**
182
+ * Execute batch using text protocol.
183
+ *
184
+ * @param sql sql parameter Object can be used to supersede default option.
185
+ * Object must then have sql property.
186
+ * @param initialValues object / array of placeholder values (not mandatory)
187
+ * @returns {Promise} promise
188
+ */
189
+ this.batch = (sql, initialValues) => {
190
+ let _options,
191
+ _sql,
192
+ _values = initialValues;
193
+ if (typeof sql === 'object') {
194
+ _options = sql;
195
+ _sql = _options.sql;
196
+ if (_options.values) _values = _options.values;
197
+ } else {
198
+ _sql = sql;
199
+ }
200
+
201
+ if (!_values) {
202
+ return Promise.reject(
203
+ Errors.createError(
204
+ 'Batch must have values set',
205
+ _sql,
206
+ false,
207
+ info,
208
+ 'HY000',
209
+ Errors.ER_BATCH_WITH_NO_VALUES
210
+ )
211
+ );
212
+ }
213
+
214
+ const vals = Array.isArray(_values) ? _values : [_values];
215
+
216
+ return new Promise(function (resolve, reject) {
217
+ let useBulk = canUseBulk(vals);
218
+
219
+ const cmd = useBulk
220
+ ? new BatchBulk(resolve, reject, _options, opts, _sql, vals)
221
+ : new BatchRewrite(resolve, reject, _options, opts, _sql, vals);
222
+ if (opts.trace) Error.captureStackTrace(cmd);
223
+ _addCommand(cmd);
224
+ });
225
+ };
226
+
227
+ /**
228
+ * Execute query returning a Readable Object that will emit columns/data/end/error events
229
+ * to permit streaming big result-set
230
+ *
231
+ * @param sql sql parameter Object can be used to supersede default option.
232
+ * Object must then have sql property.
233
+ * @param values object / array of placeholder values (not mandatory)
234
+ * @returns {Readable}
235
+ */
236
+ this.queryStream = (sql, values) => {
237
+ let _cmdOpt,
238
+ _sql,
239
+ _values = values;
240
+ if (typeof sql === 'object') {
241
+ _cmdOpt = sql;
242
+ _sql = _cmdOpt.sql;
243
+ if (sql.values) _values = sql.values;
244
+ } else {
245
+ _sql = sql;
246
+ }
247
+
248
+ const cmd = new Stream(_cmdOpt, opts, _sql, _values, _socket);
249
+ if (opts.trace) Error.captureStackTrace(cmd);
250
+ _addCommand(cmd);
251
+ return cmd.inStream;
252
+ };
253
+
254
+ /**
255
+ * Send an empty MySQL packet to ensure connection is active, and reset @@wait_timeout
256
+ * @param timeout (optional) timeout value in ms. If reached, throw error and close connection
257
+ * @returns {Promise} promise
258
+ */
259
+ this.ping = (timeout) => {
260
+ return new Promise(function (resolve, reject) {
261
+ if (timeout) {
262
+ if (timeout < 0) {
263
+ reject(
264
+ Errors.createError(
265
+ 'Ping cannot have negative timeout value',
266
+ null,
267
+ false,
268
+ info,
269
+ '0A000',
270
+ Errors.ER_BAD_PARAMETER_VALUE
271
+ )
272
+ );
273
+ return;
274
+ }
275
+ const tOut = setTimeout(() => {
276
+ reject(
277
+ Errors.createError('Ping timeout', null, true, info, '0A000', Errors.ER_PING_TIMEOUT)
278
+ );
279
+ // close connection
280
+ _addCommand = _addCommandDisabled;
281
+ clearTimeout(_timeout);
282
+ if (_status !== Status.CLOSING && _status !== Status.CLOSED) {
283
+ _sendQueue.clear();
284
+ _status = Status.CLOSED;
285
+ _socket.destroy();
286
+ }
287
+ _clear();
288
+ }, timeout);
289
+ return _addCommand(
290
+ new Ping(
291
+ () => {
292
+ clearTimeout(tOut);
293
+ resolve();
294
+ },
295
+ (err) => {
296
+ clearTimeout(tOut);
297
+ reject(err);
298
+ }
299
+ )
300
+ );
301
+ }
302
+ return _addCommand(new Ping(resolve, reject));
303
+ });
304
+ };
305
+
306
+ /**
307
+ * Send a reset command that will
308
+ * - rollback any open transaction
309
+ * - reset transaction isolation level
310
+ * - reset session variables
311
+ * - delete user variables
312
+ * - remove temporary tables
313
+ * - remove all PREPARE statement
314
+ *
315
+ * @returns {Promise} promise
316
+ */
317
+ this.reset = () => {
318
+ if (
319
+ (info.isMariaDB() && info.hasMinVersion(10, 2, 4)) ||
320
+ (!info.isMariaDB() && info.hasMinVersion(5, 7, 3))
321
+ ) {
322
+ return new Promise(function (resolve, reject) {
323
+ return _addCommand(new Reset(resolve, reject));
324
+ });
325
+ }
326
+ return Promise.reject(
327
+ new Error(
328
+ 'Reset command not permitted for server ' +
329
+ this.info.serverVersion +
330
+ ' (requires server MariaDB version 10.2.4+ or MySQL 5.7.3+)'
331
+ )
332
+ );
333
+ };
334
+
335
+ /**
336
+ * Indicates the state of the connection as the driver knows it
337
+ * @returns {boolean}
338
+ */
339
+ this.isValid = () => {
340
+ return _status === Status.CONNECTED;
341
+ };
342
+
343
+ /**
344
+ * Terminate connection gracefully.
345
+ *
346
+ * @returns {Promise} promise
347
+ */
348
+ this.end = () => {
349
+ _addCommand = _addCommandDisabled;
350
+ clearTimeout(_timeout);
351
+
352
+ if (
353
+ _status !== Status.CLOSING &&
354
+ _status !== Status.CLOSED &&
355
+ _status !== Status.NOT_CONNECTED
356
+ ) {
357
+ _status = Status.CLOSING;
358
+ return new Promise(function (resolve, reject) {
359
+ const ended = () => {
360
+ _status = Status.CLOSED;
361
+ _socket.destroy();
362
+ _socket.unref();
363
+ _clear();
364
+ _receiveQueue.clear();
365
+ resolve();
366
+ };
367
+ const quitCmd = new Quit(ended, ended);
368
+ _sendQueue.push(quitCmd);
369
+ _receiveQueue.push(quitCmd);
370
+ if (_sendQueue.length === 1) {
371
+ process.nextTick(_nextSendCmd.bind(this));
372
+ }
373
+ });
374
+ }
375
+ return Promise.resolve();
376
+ };
377
+
378
+ /**
379
+ * Alias for destroy.
380
+ */
381
+ this.close = function () {
382
+ this.destroy();
383
+ };
384
+
385
+ /**
386
+ * Force connection termination by closing the underlying socket and killing server process if any.
387
+ */
388
+ this.destroy = () => {
389
+ _addCommand = _addCommandDisabled;
390
+ clearTimeout(_timeout);
391
+ if (_status !== Status.CLOSING && _status !== Status.CLOSED) {
392
+ _status = Status.CLOSING;
393
+ _sendQueue.clear();
394
+ if (_receiveQueue.length > 0) {
395
+ //socket is closed, but server may still be processing a huge select
396
+ //only possibility is to kill process by another thread
397
+ //TODO reuse a pool connection to avoid connection creation
398
+ const self = this;
399
+ const killCon = new Connection(opts);
400
+ killCon
401
+ .connect()
402
+ .then(() => {
403
+ //*************************************************
404
+ //kill connection
405
+ //*************************************************
406
+ const killResHandler = () => {
407
+ const destroyError = Errors.createError(
408
+ 'Connection destroyed, command was killed',
409
+ null,
410
+ true,
411
+ info,
412
+ '08S01',
413
+ Errors.ER_CMD_NOT_EXECUTED_DESTROYED
414
+ );
415
+ socketErrorDispatchToQueries(destroyError);
416
+ process.nextTick(() => {
417
+ if (_socket) _socket.destroy();
418
+ });
419
+ _status = Status.CLOSED;
420
+ killCon.end().catch(() => {});
421
+ };
422
+
423
+ killCon
424
+ .query('KILL ' + info.threadId)
425
+ .then(killResHandler)
426
+ .catch(killResHandler);
427
+ })
428
+ .catch((err) => {
429
+ //*************************************************
430
+ //failing to create a kill connection, end normally
431
+ //*************************************************
432
+ const ended = () => {
433
+ let sock = _socket;
434
+ _clear();
435
+ _status = Status.CLOSED;
436
+ setImmediate(resolve);
437
+ sock.destroy();
438
+ _receiveQueue.clear();
439
+ };
440
+ const quitCmd = new Quit(ended, ended);
441
+ _sendQueue.push(quitCmd);
442
+ _receiveQueue.push(quitCmd);
443
+ if (_sendQueue.length === 1) {
444
+ process.nextTick(_nextSendCmd.bind(self));
445
+ }
446
+ });
447
+ } else {
448
+ _status = Status.CLOSED;
449
+ _socket.destroy();
450
+ }
451
+ }
452
+ _clear();
453
+ };
454
+
455
+ this.pause = () => {
456
+ _socket.pause();
457
+ };
458
+
459
+ this.resume = () => {
460
+ _socket.resume();
461
+ };
462
+
463
+ this.format = (sql, values) => {
464
+ throw Errors.createError(
465
+ '"Connection.format intentionally not implemented. please use Connection.query(sql, values), it will be more secure and faster',
466
+ null,
467
+ false,
468
+ info,
469
+ '0A000',
470
+ Errors.ER_NOT_IMPLEMENTED_FORMAT
471
+ );
472
+ };
473
+
474
+ //*****************************************************************
475
+ // additional public methods
476
+ //*****************************************************************
477
+
478
+ /**
479
+ * return current connected server version information.
480
+ *
481
+ * @returns {*}
482
+ */
483
+ this.serverVersion = () => {
484
+ if (!info.serverVersion)
485
+ throw new Error('cannot know if server information until connection is established');
486
+ return info.serverVersion.raw;
487
+ };
488
+
489
+ /**
490
+ * Change option "debug" during connection.
491
+ * @param val debug value
492
+ */
493
+ this.debug = (val) => {
494
+ opts.debug = val;
495
+ opts.emit('debug', opts.logPackets, opts.debug);
496
+ };
497
+
498
+ this.debugCompress = (val) => {
499
+ opts.debugCompress = val;
500
+ };
501
+
502
+ //*****************************************************************
503
+ // internal public testing methods
504
+ //*****************************************************************
505
+
506
+ function TestMethods() {}
507
+
508
+ TestMethods.prototype.getCollation = () => {
509
+ return opts.collation;
510
+ };
511
+
512
+ TestMethods.prototype.getSocket = () => {
513
+ return _socket;
514
+ };
515
+
516
+ this.__tests = new TestMethods();
517
+
518
+ //*****************************************************************
519
+ // internal methods
520
+ //*****************************************************************
521
+
522
+ this._status = () => {
523
+ return _status;
524
+ };
525
+
526
+ /**
527
+ * Execute query using text protocol with callback emit columns/data/end/error
528
+ * events to permit streaming big result-set
529
+ *
530
+ * @param sql sql parameter Object can be used to supersede default option.
531
+ * Object must then have sql property.
532
+ * @param values object / array of placeholder values (not mandatory)
533
+ * @param cb callback
534
+ * @returns {Query} query
535
+ */
536
+ this._queryCallback = (sql, values, cb) => {
537
+ let _cmdOpts,
538
+ _sql,
539
+ _values = values,
540
+ _cb = cb;
541
+
542
+ if (typeof values === 'function') {
543
+ _cb = values;
544
+ _values = undefined;
545
+ }
546
+
547
+ if (typeof sql === 'object') {
548
+ _cmdOpts = sql;
549
+ _sql = _cmdOpts.sql;
550
+ if (sql.values) _values = sql.values;
551
+ } else {
552
+ _sql = sql;
553
+ }
554
+
555
+ let cmd;
556
+ if (_cb) {
557
+ const resolve = (rows) => {
558
+ const meta = rows.meta;
559
+ delete rows.meta;
560
+ _cb(null, rows, meta);
561
+ };
562
+ cmd = new Query(resolve, _cb, _cmdOpts, opts, _sql, _values);
563
+ } else {
564
+ cmd = new Query(
565
+ () => {},
566
+ () => {},
567
+ _cmdOpts,
568
+ opts,
569
+ _sql,
570
+ _values
571
+ );
572
+ }
573
+ cmd.handleNewRows = (row) => {
574
+ cmd._rows[cmd._responseIndex].push(row);
575
+ cmd.emit('data', row);
576
+ };
577
+
578
+ if (opts.trace) Error.captureStackTrace(cmd);
579
+ _addCommand(cmd);
580
+ return cmd;
581
+ };
582
+
583
+ /**
584
+ * Execute a batch using text protocol with callback emit columns/data/end/error
585
+ * events to permit streaming big result-set
586
+ *
587
+ * @param sql sql parameter Object can be used to supersede default option.
588
+ * Object must then have sql property.
589
+ * @param values object / array of placeholder values (not mandatory)
590
+ * @param cb callback
591
+ * @returns {Query} query
592
+ */
593
+ this._batchCallback = (sql, values, cb) => {
594
+ let _cmdOpts,
595
+ _sql,
596
+ _values = values,
597
+ _cb = cb;
598
+
599
+ if (typeof values === 'function') {
600
+ _cb = values;
601
+ _values = undefined;
602
+ }
603
+
604
+ if (typeof sql === 'object') {
605
+ _cmdOpts = sql;
606
+ _sql = _cmdOpts.sql;
607
+ if (sql.values) _values = sql.values;
608
+ } else {
609
+ _sql = sql;
610
+ }
611
+
612
+ if (_values !== undefined) {
613
+ _values = Array.isArray(_values) ? _values : [_values];
614
+ }
615
+
616
+ let cmd;
617
+
618
+ if (!_values) {
619
+ if (_cb) {
620
+ _cb(
621
+ Errors.createError(
622
+ 'Batch must have values set',
623
+ _sql,
624
+ false,
625
+ info,
626
+ 'HY000',
627
+ Errors.ER_BATCH_WITH_NO_VALUES
628
+ )
629
+ );
630
+ }
631
+ return null;
632
+ }
633
+
634
+ let useBulk = canUseBulk(_values);
635
+
636
+ const fct = useBulk ? BatchBulk : BatchRewrite;
637
+
638
+ if (_cb) {
639
+ const resolve = (rows) => {
640
+ const meta = rows.meta;
641
+ delete rows.meta;
642
+ _cb(null, rows, meta);
643
+ };
644
+ cmd = new fct(resolve, _cb, _cmdOpts, opts, _sql, _values);
645
+ } else {
646
+ cmd = new fct(
647
+ () => {},
648
+ () => {},
649
+ _cmdOpts,
650
+ opts,
651
+ _sql,
652
+ _values
653
+ );
654
+ }
655
+ cmd.handleNewRows = (row) => {
656
+ cmd._rows[cmd._responseIndex].push(row);
657
+ cmd.emit('data', row);
658
+ };
659
+
660
+ if (opts.trace) Error.captureStackTrace(cmd);
661
+ _addCommand(cmd);
662
+ return cmd;
663
+ };
664
+
665
+ /**
666
+ * Use Batch rewrite or MariaDB bulk protocol.
667
+ *
668
+ * @param values current batch values
669
+ * @return {boolean} indicating if must use rewrite or bulk
670
+ */
671
+ const canUseBulk = (values) => {
672
+ // not using info.isMariaDB() directly in case of callback use,
673
+ // without connection beeing completly finished.
674
+ let useBulk =
675
+ info.serverVersion &&
676
+ info.serverVersion.mariaDb &&
677
+ info.hasMinVersion(10, 2, 7) &&
678
+ opts.bulk &&
679
+ (info.serverCapabilities & Capabilities.MARIADB_CLIENT_STMT_BULK_OPERATIONS) > BigInt(0);
680
+
681
+ if (useBulk) {
682
+ //ensure that there is no stream object
683
+ if (values !== undefined) {
684
+ if (!opts.namedPlaceholders) {
685
+ //ensure that all parameters have same length
686
+ //single array is considered as an array of single element.
687
+ const paramLen = Array.isArray(values[0]) ? values[0].length : values[0] ? 1 : 0;
688
+ if (paramLen == 0) return false;
689
+ for (let r = 0; r < values.length; r++) {
690
+ let row = values[r];
691
+ if (!Array.isArray(row)) row = [row];
692
+ if (paramLen !== row.length) {
693
+ return false;
694
+ }
695
+ for (let j = 0; j < paramLen; j++) {
696
+ const val = row[j];
697
+ if (
698
+ val !== null &&
699
+ typeof val === 'object' &&
700
+ typeof val.pipe === 'function' &&
701
+ typeof val.read === 'function'
702
+ ) {
703
+ return false;
704
+ }
705
+ }
706
+ }
707
+ } else {
708
+ for (let r = 0; r < values.length; r++) {
709
+ let row = values[r];
710
+ const keys = Object.keys(row);
711
+ for (let j = 0; j < keys.length; j++) {
712
+ const val = row[keys[j]];
713
+ if (
714
+ val !== null &&
715
+ typeof val === 'object' &&
716
+ typeof val.pipe === 'function' &&
717
+ typeof val.read === 'function'
718
+ ) {
719
+ return false;
720
+ }
721
+ }
722
+ }
723
+ }
724
+ }
725
+ }
726
+ return useBulk;
727
+ };
728
+
729
+ /**
730
+ * Add handshake command to queue.
731
+ *
732
+ * @private
733
+ */
734
+ const _registerHandshakeCmd = (resolve, rejected) => {
735
+ const _authFail = _authFailHandler.bind(this, rejected);
736
+ const _authSucceed = _authSucceedHandler.bind(this, resolve, _authFail);
737
+
738
+ const handshake = new Handshake(
739
+ _authSucceed,
740
+ _authFail,
741
+ _createSecureContext.bind(this, _authFail),
742
+ _addCommandEnable.bind(this),
743
+ _getSocket
744
+ );
745
+ Error.captureStackTrace(handshake);
746
+
747
+ handshake.once('end', () => {
748
+ process.nextTick(_nextSendCmd);
749
+ });
750
+
751
+ _receiveQueue.push(handshake);
752
+ _initSocket(_authFail);
753
+ };
754
+
755
+ const _executeSessionVariableQuery = () => {
756
+ if (opts.sessionVariables) {
757
+ const values = [];
758
+ let sessionQuery = 'set ';
759
+ let keys = Object.keys(opts.sessionVariables);
760
+ if (keys.length > 0) {
761
+ return new Promise(function (resolve, reject) {
762
+ for (let k = 0; k < keys.length; ++k) {
763
+ sessionQuery +=
764
+ (k !== 0 ? ',' : '') + '@@' + keys[k].replace(/[^a-z0-9_]/gi, '') + '=?';
765
+ values.push(opts.sessionVariables[keys[k]]);
766
+ }
767
+ const errorHandling = (initialErr) => {
768
+ reject(
769
+ Errors.createError(
770
+ 'Error setting session variable (value ' +
771
+ JSON.stringify(opts.sessionVariables) +
772
+ '). Error: ' +
773
+ initialErr.message,
774
+ sessionQuery,
775
+ true,
776
+ info,
777
+ '08S01',
778
+ Errors.ER_SETTING_SESSION_ERROR,
779
+ null
780
+ )
781
+ );
782
+ };
783
+ const cmd = new Query(resolve, errorHandling, null, opts, sessionQuery, values);
784
+ if (opts.trace) Error.captureStackTrace(cmd);
785
+ _addCommand(cmd);
786
+ });
787
+ }
788
+ }
789
+ return Promise.resolve();
790
+ };
791
+
792
+ /**
793
+ * Asking server timezone if not set in case of 'auto'
794
+ * @returns {Promise<void>}
795
+ * @private
796
+ */
797
+ const _checkServerTimezone = () => {
798
+ if (opts.timezone === 'auto') {
799
+ return this._queryPromise('SELECT @@system_time_zone stz, @@time_zone tz').then((res) => {
800
+ const serverTimezone = res[0].tz === 'SYSTEM' ? res[0].stz : res[0].tz;
801
+ const serverZone = moment.tz.zone(serverTimezone);
802
+ if (serverZone) {
803
+ const localTz = moment.tz.guess();
804
+ if (serverTimezone === localTz) {
805
+ //db server and client use same timezone, avoid any conversion
806
+ opts.tz = null;
807
+ } else {
808
+ opts._localTz = localTz;
809
+ opts.tz = serverTimezone;
810
+ }
811
+ } else {
812
+ return Promise.reject(
813
+ Errors.createError(
814
+ "Automatic timezone setting fails. Server timezone '" +
815
+ serverTimezone +
816
+ "' does't have a corresponding IANA timezone. Option timezone must be set according to server timezone",
817
+ null,
818
+ true,
819
+ info,
820
+ '08S01',
821
+ Errors.ER_WRONG_AUTO_TIMEZONE
822
+ )
823
+ );
824
+ }
825
+ return Promise.resolve();
826
+ });
827
+ }
828
+ if (opts.tz && !opts.skipSetTimezone) {
829
+ let tz = opts.tz;
830
+ if (opts.tz === 'Etc/UTC') {
831
+ tz = '+00:00';
832
+ } else if (opts.tz.startsWith('Etc/GMT')) {
833
+ let zone = moment.tz.zone(opts.tz);
834
+ tz = zone.abbrs[0] + ':00';
835
+ }
836
+
837
+ return this._queryPromise('SET time_zone=?', tz)
838
+ .then((res) => {
839
+ return Promise.resolve();
840
+ })
841
+ .catch((err) => {
842
+ console.log(
843
+ `warning: setting timezone '${opts.tz}' fails on server.\n look at https://mariadb.com/kb/en/mysql_tzinfo_to_sql/ to load IANA timezone.\nSetting timezone can be disabled with option \`skipSetTimezone\``
844
+ );
845
+ return Promise.resolve();
846
+ });
847
+ }
848
+ return Promise.resolve();
849
+ };
850
+
851
+ const _checkServerVersion = () => {
852
+ if (!opts.forceVersionCheck) {
853
+ return Promise.resolve();
854
+ }
855
+ return this._queryPromise('SELECT @@VERSION AS v').then((res) => {
856
+ info.serverVersion.raw = res[0].v;
857
+ info.serverVersion.mariaDb = info.serverVersion.raw.includes('MariaDB');
858
+ ConnectionInformation.parseVersionString(info);
859
+ return Promise.resolve();
860
+ });
861
+ };
862
+
863
+ const _executeInitQuery = () => {
864
+ if (opts.initSql) {
865
+ const initialArr = Array.isArray(opts.initSql) ? opts.initSql : [opts.initSql];
866
+ const initialPromises = [];
867
+ initialArr.forEach((sql) => {
868
+ initialPromises.push(this._queryPromise(sql));
869
+ });
870
+
871
+ return Promise.all(initialPromises).catch((initialErr) => {
872
+ return Promise.reject(
873
+ Errors.createError(
874
+ 'Error executing initial sql command: ' + initialErr.message,
875
+ null,
876
+ true,
877
+ info,
878
+ '08S01',
879
+ Errors.ER_INITIAL_SQL_ERROR,
880
+ null
881
+ )
882
+ );
883
+ });
884
+ }
885
+ return Promise.resolve();
886
+ };
887
+
888
+ const _executeSessionTimeout = () => {
889
+ if (opts.queryTimeout) {
890
+ if (info.isMariaDB() && info.hasMinVersion(10, 1, 2)) {
891
+ const query = 'SET max_statement_time=' + opts.queryTimeout / 1000;
892
+ this._queryPromise(query).catch((initialErr) => {
893
+ return Promise.reject(
894
+ Errors.createError(
895
+ 'Error setting session queryTimeout: ' + initialErr.message,
896
+ query,
897
+ true,
898
+ info,
899
+ '08S01',
900
+ Errors.ER_INITIAL_TIMEOUT_ERROR,
901
+ null
902
+ )
903
+ );
904
+ });
905
+ } else {
906
+ return Promise.reject(
907
+ Errors.createError(
908
+ 'Can only use queryTimeout for MariaDB server after 10.1.1. queryTimeout value: ' +
909
+ null,
910
+ opts.queryTimeout,
911
+ false,
912
+ info,
913
+ 'HY000',
914
+ Errors.ER_TIMEOUT_NOT_SUPPORTED
915
+ )
916
+ );
917
+ }
918
+ }
919
+ return Promise.resolve();
920
+ };
921
+
922
+ const _getSocket = () => {
923
+ return _socket;
924
+ };
925
+
926
+ /**
927
+ * Initialize socket and associate events.
928
+ * @private
929
+ */
930
+ const _initSocket = (authFailHandler) => {
931
+ if (opts.socketPath) {
932
+ _socket = Net.connect(opts.socketPath);
933
+ } else {
934
+ _socket = Net.connect(opts.port, opts.host);
935
+ }
936
+
937
+ if (opts.connectTimeout) {
938
+ _timeout = setTimeout(
939
+ _connectTimeoutReached,
940
+ opts.connectTimeout,
941
+ authFailHandler,
942
+ Date.now()
943
+ );
944
+ }
945
+
946
+ const _socketError = _socketErrorHandler.bind(this, authFailHandler);
947
+
948
+ _socket.on('data', _in.onData.bind(_in));
949
+ _socket.on('error', _socketError);
950
+ _socket.on('end', _socketError);
951
+ _socket.on(
952
+ 'connect',
953
+ function () {
954
+ clearTimeout(_timeout);
955
+ if (_status === Status.CONNECTING) {
956
+ _status = Status.AUTHENTICATING;
957
+ _socketConnected = true;
958
+ _socket.setTimeout(opts.socketTimeout, _socketTimeoutReached.bind(this, authFailHandler));
959
+ _socket.setNoDelay(true);
960
+
961
+ // keep alive for socket. This won't reset server wait_timeout use pool option idleTimeout for that
962
+ if (opts.keepAliveDelay) {
963
+ _socket.setKeepAlive(true, opts.keepAliveDelay);
964
+ }
965
+ }
966
+ }.bind(this)
967
+ );
968
+
969
+ _socket.writeBuf = (buf) => _socket.write(buf);
970
+ _socket.flush = () => {};
971
+ _out.setStream(_socket);
972
+ };
973
+
974
+ /**
975
+ * Authentication success result handler.
976
+ *
977
+ * @private
978
+ */
979
+ const _authSucceedHandler = (resolve, rejected) => {
980
+ //enable packet compression according to option
981
+ if (opts.logPackets) info.enableLogPacket();
982
+ if (opts.compress) {
983
+ if (info.serverCapabilities & Capabilities.COMPRESS) {
984
+ _out.setStream(new CompressionOutputStream(_socket, opts, info));
985
+ _in = new CompressionInputStream(_in, _receiveQueue, opts, info);
986
+ _socket.removeAllListeners('data');
987
+ _socket.on('data', _in.onData.bind(_in));
988
+ } else {
989
+ console.error(
990
+ "connection is configured to use packet compression, but the server doesn't have this capability"
991
+ );
992
+ }
993
+ }
994
+
995
+ _addCommand = opts.pipelining ? _addCommandEnablePipeline : _addCommandEnable;
996
+
997
+ const commands = _waitingAuthenticationQueue.toArray();
998
+ commands.forEach((cmd) => {
999
+ _addCommand(cmd);
1000
+ });
1001
+
1002
+ const errorInitialQueries = (err) => {
1003
+ if (!err.fatal) this.end().catch((err) => {});
1004
+ process.nextTick(rejected, err);
1005
+ };
1006
+ _status = Status.INIT_CMD;
1007
+ _executeSessionVariableQuery()
1008
+ .then(() => {
1009
+ return _checkServerTimezone();
1010
+ })
1011
+ .then(() => {
1012
+ return _checkServerVersion();
1013
+ })
1014
+ .then(() => {
1015
+ return _executeInitQuery();
1016
+ })
1017
+ .then(() => {
1018
+ return _executeSessionTimeout();
1019
+ })
1020
+ .then(() => {
1021
+ _status = Status.CONNECTED;
1022
+ process.nextTick(resolve, this);
1023
+ })
1024
+ .catch(errorInitialQueries);
1025
+ };
1026
+
1027
+ /**
1028
+ * Authentication failed result handler.
1029
+ *
1030
+ * @private
1031
+ */
1032
+ const _authFailHandler = (reject, err) => {
1033
+ process.nextTick(reject, err);
1034
+ //remove handshake command
1035
+ _receiveQueue.shift();
1036
+
1037
+ _fatalError(err, true);
1038
+ };
1039
+
1040
+ /**
1041
+ * Create TLS socket and associate events.
1042
+ *
1043
+ * @param rejected rejected function when error
1044
+ * @param callback callback function when done
1045
+ * @private
1046
+ */
1047
+ const _createSecureContext = (rejected, callback) => {
1048
+ const _socketError = _socketErrorHandler.bind(this, rejected);
1049
+ const sslOption = Object.assign({}, opts.ssl, {
1050
+ servername: opts.host,
1051
+ socket: _socket
1052
+ });
1053
+
1054
+ try {
1055
+ const secureSocket = tls.connect(sslOption, callback);
1056
+
1057
+ secureSocket.on('data', _in.onData.bind(_in));
1058
+ secureSocket.on('error', _socketError);
1059
+ secureSocket.on('end', _socketError);
1060
+ secureSocket.writeBuf = (buf) => secureSocket.write(buf);
1061
+ secureSocket.flush = () => {};
1062
+
1063
+ _socket.removeAllListeners('data');
1064
+ _socket = secureSocket;
1065
+
1066
+ _out.setStream(secureSocket);
1067
+ } catch (err) {
1068
+ _socketError(err);
1069
+ }
1070
+ };
1071
+
1072
+ /**
1073
+ * Handle packet when no packet is expected.
1074
+ * (there can be an ERROR packet send by server/proxy to inform that connection is ending).
1075
+ *
1076
+ * @param packet packet
1077
+ * @private
1078
+ */
1079
+ const _unexpectedPacket = function (packet) {
1080
+ if (packet && packet.peek() === 0xff) {
1081
+ //can receive unexpected error packet from server/proxy
1082
+ //to inform that connection is closed (usually by timeout)
1083
+ let err = packet.readError(info);
1084
+ if (err.fatal && _status !== Status.CLOSING && _status !== Status.CLOSED) {
1085
+ this.emit('error', err);
1086
+ this.end();
1087
+ }
1088
+ } else if (_status !== Status.CLOSING && _status !== Status.CLOSED) {
1089
+ this.emit(
1090
+ 'error',
1091
+ Errors.createError(
1092
+ 'receiving packet from server without active commands\n' +
1093
+ 'conn:' +
1094
+ (info.threadId ? info.threadId : -1) +
1095
+ '(' +
1096
+ packet.pos +
1097
+ ',' +
1098
+ packet.end +
1099
+ ')\n' +
1100
+ Utils.log(opts, packet.buf, packet.pos, packet.end),
1101
+ null,
1102
+ true,
1103
+ info,
1104
+ '08S01',
1105
+ Errors.ER_UNEXPECTED_PACKET
1106
+ )
1107
+ );
1108
+ this.destroy();
1109
+ }
1110
+ };
1111
+
1112
+ /**
1113
+ * Change transaction state.
1114
+ *
1115
+ * @param sql sql
1116
+ * @returns {Promise} promise
1117
+ * @private
1118
+ */
1119
+ const _changeTransaction = (sql) => {
1120
+ //if command in progress, driver cannot rely on status and must execute query
1121
+ if (_status === Status.CLOSING || _status === Status.CLOSED) {
1122
+ return Promise.reject(
1123
+ Errors.createError(
1124
+ 'Cannot execute new commands: connection closed',
1125
+ sql,
1126
+ true,
1127
+ info,
1128
+ '08S01',
1129
+ Errors.ER_CMD_CONNECTION_CLOSED
1130
+ )
1131
+ );
1132
+ }
1133
+
1134
+ //Command in progress => must execute query
1135
+ //or if no command in progress, can rely on status to know if query is needed
1136
+ if (_receiveQueue.peekFront() || info.status & ServerStatus.STATUS_IN_TRANS) {
1137
+ return new Promise(function (resolve, reject) {
1138
+ const cmd = new Query(resolve, reject, null, opts, sql, null);
1139
+ if (opts.trace) Error.captureStackTrace(cmd);
1140
+ _addCommand(cmd);
1141
+ });
1142
+ }
1143
+ return Promise.resolve();
1144
+ };
1145
+
1146
+ /**
1147
+ * Handle connection timeout.
1148
+ *
1149
+ * @private
1150
+ */
1151
+ const _connectTimeoutReached = function (authFailHandler, initialConnectionTime) {
1152
+ _timeout = null;
1153
+ const handshake = _receiveQueue.peekFront();
1154
+ authFailHandler(
1155
+ Errors.createError(
1156
+ 'Connection timeout: failed to create socket after ' +
1157
+ (Date.now() - initialConnectionTime) +
1158
+ 'ms',
1159
+ null,
1160
+ true,
1161
+ info,
1162
+ '08S01',
1163
+ Errors.ER_CONNECTION_TIMEOUT,
1164
+ handshake ? handshake.stack : null
1165
+ )
1166
+ );
1167
+ };
1168
+
1169
+ /**
1170
+ * Handle socket timeout.
1171
+ *
1172
+ * @private
1173
+ */
1174
+ const _socketTimeoutReached = function () {
1175
+ const err = Errors.createError(
1176
+ 'socket timeout',
1177
+ null,
1178
+ true,
1179
+ info,
1180
+ '08S01',
1181
+ Errors.ER_SOCKET_TIMEOUT
1182
+ );
1183
+ const packetMsgs = info.getLastPackets();
1184
+ if (packetMsgs !== '') {
1185
+ err.message = err.message + '\nlast received packets:\n' + packetMsgs;
1186
+ }
1187
+ _fatalError(err, true);
1188
+ };
1189
+
1190
+ /**
1191
+ * Add command to waiting queue until authentication.
1192
+ *
1193
+ * @param cmd command
1194
+ * @returns {*} current command
1195
+ * @private
1196
+ */
1197
+ const _addCommandQueue = (cmd) => {
1198
+ _waitingAuthenticationQueue.push(cmd);
1199
+ return cmd;
1200
+ };
1201
+
1202
+ /**
1203
+ * Add command to command sending and receiving queue.
1204
+ *
1205
+ * @param cmd command
1206
+ * @returns {*} current command
1207
+ * @private
1208
+ */
1209
+ const _addCommandEnable = (cmd) => {
1210
+ cmd.once('end', () => {
1211
+ setImmediate(_nextSendCmd);
1212
+ });
1213
+
1214
+ //send immediately only if no current active receiver
1215
+ if (_sendQueue.isEmpty() && (_status === Status.INIT_CMD || _status === Status.CONNECTED)) {
1216
+ if (_receiveQueue.peekFront()) {
1217
+ _receiveQueue.push(cmd);
1218
+ _sendQueue.push(cmd);
1219
+ return cmd;
1220
+ }
1221
+
1222
+ _receiveQueue.push(cmd);
1223
+ cmd.start(_out, opts, info);
1224
+ } else {
1225
+ _receiveQueue.push(cmd);
1226
+ _sendQueue.push(cmd);
1227
+ }
1228
+ return cmd;
1229
+ };
1230
+
1231
+ /**
1232
+ * Add command to command sending and receiving queue using pipelining
1233
+ *
1234
+ * @param cmd command
1235
+ * @returns {*} current command
1236
+ * @private
1237
+ */
1238
+ const _addCommandEnablePipeline = (cmd) => {
1239
+ cmd.once('send_end', () => {
1240
+ setImmediate(_nextSendCmd);
1241
+ });
1242
+
1243
+ _receiveQueue.push(cmd);
1244
+ if (_sendQueue.isEmpty()) {
1245
+ cmd.start(_out, opts, info);
1246
+ if (cmd.sending) {
1247
+ _sendQueue.push(cmd);
1248
+ cmd.prependOnceListener('send_end', () => {
1249
+ _sendQueue.shift();
1250
+ });
1251
+ }
1252
+ } else {
1253
+ _sendQueue.push(cmd);
1254
+ }
1255
+ return cmd;
1256
+ };
1257
+
1258
+ /**
1259
+ * Replacing command when connection is closing or closed to send a proper error message.
1260
+ *
1261
+ * @param cmd command
1262
+ * @private
1263
+ */
1264
+ const _addCommandDisabled = (cmd) => {
1265
+ cmd.throwNewError(
1266
+ 'Cannot execute new commands: connection closed',
1267
+ true,
1268
+ info,
1269
+ '08S01',
1270
+ Errors.ER_CMD_CONNECTION_CLOSED
1271
+ );
1272
+ };
1273
+
1274
+ /**
1275
+ * Handle socket error.
1276
+ *
1277
+ * @param authFailHandler authentication handler
1278
+ * @param err socket error
1279
+ * @private
1280
+ */
1281
+ const _socketErrorHandler = function (authFailHandler, err) {
1282
+ if (_status === Status.CLOSING || _status === Status.CLOSED) return;
1283
+ if (_socket) {
1284
+ _socket.writeBuf = () => {};
1285
+ _socket.flush = () => {};
1286
+ }
1287
+
1288
+ //socket has been ended without error
1289
+ if (!err) {
1290
+ err = Errors.createError(
1291
+ 'socket has unexpectedly been closed',
1292
+ null,
1293
+ true,
1294
+ info,
1295
+ '08S01',
1296
+ Errors.ER_SOCKET_UNEXPECTED_CLOSE
1297
+ );
1298
+ } else {
1299
+ err.fatal = true;
1300
+ this.sqlState = 'HY000';
1301
+ }
1302
+
1303
+ const packetMsgs = info.getLastPackets();
1304
+ if (packetMsgs !== '') {
1305
+ err.message += '\nlast received packets:\n' + packetMsgs;
1306
+ }
1307
+
1308
+ switch (_status) {
1309
+ case Status.CONNECTING:
1310
+ case Status.AUTHENTICATING:
1311
+ const currentCmd = _receiveQueue.peekFront();
1312
+ if (currentCmd && currentCmd.stack && err) {
1313
+ err.stack +=
1314
+ '\n From event:\n' + currentCmd.stack.substring(currentCmd.stack.indexOf('\n') + 1);
1315
+ }
1316
+ authFailHandler(err);
1317
+ break;
1318
+
1319
+ default:
1320
+ _fatalError(err, false);
1321
+ }
1322
+ };
1323
+
1324
+ /**
1325
+ * Fatal unexpected error : closing connection, and throw exception.
1326
+ *
1327
+ * @param self current connection
1328
+ * @private
1329
+ */
1330
+ const _fatalErrorHandler = function (self) {
1331
+ return function (err, avoidThrowError) {
1332
+ if (_status === Status.CLOSING || _status === Status.CLOSED) {
1333
+ socketErrorDispatchToQueries(err);
1334
+ return;
1335
+ }
1336
+ const mustThrowError = _status !== Status.CONNECTING;
1337
+ _status = Status.CLOSING;
1338
+
1339
+ //prevent executing new commands
1340
+ _addCommand = _addCommandDisabled;
1341
+
1342
+ if (_socket) {
1343
+ _socket.removeAllListeners('error');
1344
+ _socket.removeAllListeners('timeout');
1345
+ _socket.removeAllListeners('close');
1346
+ _socket.removeAllListeners('data');
1347
+ if (!_socket.destroyed) _socket.destroy();
1348
+ _socket = undefined;
1349
+ }
1350
+ _status = Status.CLOSED;
1351
+
1352
+ const errorThrownByCmd = socketErrorDispatchToQueries(err);
1353
+ if (mustThrowError) {
1354
+ if (self.listenerCount('error') > 0) {
1355
+ self.emit('error', err);
1356
+ self.emit('end');
1357
+ _clear();
1358
+ } else {
1359
+ self.emit('end');
1360
+ _clear();
1361
+ //error will be thrown if no error listener and no command did throw the exception
1362
+ if (!avoidThrowError && !errorThrownByCmd) throw err;
1363
+ }
1364
+ } else {
1365
+ _clear();
1366
+ }
1367
+ };
1368
+ };
1369
+
1370
+ /**
1371
+ * Dispatch fatal error to current running queries.
1372
+ *
1373
+ * @param err the fatal error
1374
+ * @return {boolean} return if error has been relayed to queries
1375
+ */
1376
+ const socketErrorDispatchToQueries = (err) => {
1377
+ let receiveCmd;
1378
+ let errorThrownByCmd = false;
1379
+ while ((receiveCmd = _receiveQueue.shift())) {
1380
+ if (receiveCmd && receiveCmd.onPacketReceive) {
1381
+ errorThrownByCmd = true;
1382
+ setImmediate(receiveCmd.throwError.bind(receiveCmd), err, info);
1383
+ }
1384
+ }
1385
+ return errorThrownByCmd;
1386
+ };
1387
+
1388
+ /**
1389
+ * Will send next command in queue if any.
1390
+ *
1391
+ * @private
1392
+ */
1393
+ const _nextSendCmd = () => {
1394
+ let sendCmd;
1395
+ if ((sendCmd = _sendQueue.shift())) {
1396
+ if (sendCmd.sending) {
1397
+ _sendQueue.unshift(sendCmd);
1398
+ } else {
1399
+ sendCmd.start(_out, opts, info);
1400
+ if (sendCmd.sending) {
1401
+ sendCmd.prependOnceListener('send_end', () => {
1402
+ _sendQueue.shift();
1403
+ });
1404
+ _sendQueue.unshift(sendCmd);
1405
+ }
1406
+ }
1407
+ }
1408
+ };
1409
+
1410
+ /**
1411
+ * Clearing connection variables when ending.
1412
+ *
1413
+ * @private
1414
+ */
1415
+ const _clear = () => {
1416
+ _sendQueue.clear();
1417
+ opts.removeAllListeners();
1418
+ _out = undefined;
1419
+ _socket = undefined;
1420
+ };
1421
+
1422
+ //*****************************************************************
1423
+ // internal variables
1424
+ //*****************************************************************
1425
+
1426
+ EventEmitter.call(this);
1427
+ const opts = Object.assign(new EventEmitter(), options);
1428
+ const info = new ConnectionInformation();
1429
+ const _sendQueue = new Queue();
1430
+ const _receiveQueue = new Queue();
1431
+ const _waitingAuthenticationQueue = new Queue();
1432
+ let _status = Status.NOT_CONNECTED;
1433
+ let _socketConnected = false;
1434
+ let _socket = null;
1435
+ let _timeout = null;
1436
+ let _addCommand = _addCommandQueue;
1437
+ const _fatalError = _fatalErrorHandler(this);
1438
+ let _out = new PacketOutputStream(opts, info);
1439
+ let _in = new PacketInputStream(_unexpectedPacket.bind(this), _receiveQueue, _out, opts, info);
1440
+
1441
+ this.query = this._queryPromise;
1442
+ this.escape = Utils.escape.bind(this, opts, info);
1443
+ this.escapeId = Utils.escapeId.bind(this, opts, info);
1444
+
1445
+ //add alias threadId for mysql/mysql2 compatibility
1446
+ Object.defineProperty(this, 'threadId', {
1447
+ get() {
1448
+ return info ? info.threadId : undefined;
1449
+ }
1450
+ });
1451
+ Object.defineProperty(this, 'info', {
1452
+ get() {
1453
+ return info;
1454
+ }
1455
+ });
1456
+ }
1457
+
1458
+ util.inherits(Connection, EventEmitter);
1459
+
1460
+ module.exports = Connection;