s3db.js 11.3.2 → 12.0.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.
Files changed (83) hide show
  1. package/README.md +102 -8
  2. package/dist/s3db.cjs.js +36945 -15510
  3. package/dist/s3db.cjs.js.map +1 -1
  4. package/dist/s3db.d.ts +66 -1
  5. package/dist/s3db.es.js +36914 -15534
  6. package/dist/s3db.es.js.map +1 -1
  7. package/mcp/entrypoint.js +58 -0
  8. package/mcp/tools/documentation.js +434 -0
  9. package/mcp/tools/index.js +4 -0
  10. package/package.json +35 -15
  11. package/src/behaviors/user-managed.js +13 -6
  12. package/src/client.class.js +79 -49
  13. package/src/concerns/base62.js +85 -0
  14. package/src/concerns/dictionary-encoding.js +294 -0
  15. package/src/concerns/geo-encoding.js +256 -0
  16. package/src/concerns/high-performance-inserter.js +34 -30
  17. package/src/concerns/ip.js +325 -0
  18. package/src/concerns/metadata-encoding.js +345 -66
  19. package/src/concerns/money.js +193 -0
  20. package/src/concerns/partition-queue.js +7 -4
  21. package/src/concerns/plugin-storage.js +97 -47
  22. package/src/database.class.js +76 -74
  23. package/src/errors.js +0 -4
  24. package/src/plugins/api/auth/api-key-auth.js +88 -0
  25. package/src/plugins/api/auth/basic-auth.js +154 -0
  26. package/src/plugins/api/auth/index.js +112 -0
  27. package/src/plugins/api/auth/jwt-auth.js +169 -0
  28. package/src/plugins/api/index.js +544 -0
  29. package/src/plugins/api/middlewares/index.js +15 -0
  30. package/src/plugins/api/middlewares/validator.js +185 -0
  31. package/src/plugins/api/routes/auth-routes.js +241 -0
  32. package/src/plugins/api/routes/resource-routes.js +304 -0
  33. package/src/plugins/api/server.js +354 -0
  34. package/src/plugins/api/utils/error-handler.js +147 -0
  35. package/src/plugins/api/utils/openapi-generator.js +1240 -0
  36. package/src/plugins/api/utils/response-formatter.js +218 -0
  37. package/src/plugins/backup/streaming-exporter.js +132 -0
  38. package/src/plugins/backup.plugin.js +103 -50
  39. package/src/plugins/cache/s3-cache.class.js +95 -47
  40. package/src/plugins/cache.plugin.js +107 -9
  41. package/src/plugins/concerns/plugin-dependencies.js +313 -0
  42. package/src/plugins/concerns/prometheus-formatter.js +255 -0
  43. package/src/plugins/consumers/rabbitmq-consumer.js +4 -0
  44. package/src/plugins/consumers/sqs-consumer.js +4 -0
  45. package/src/plugins/costs.plugin.js +255 -39
  46. package/src/plugins/eventual-consistency/helpers.js +15 -1
  47. package/src/plugins/geo.plugin.js +873 -0
  48. package/src/plugins/importer/index.js +1020 -0
  49. package/src/plugins/index.js +11 -0
  50. package/src/plugins/metrics.plugin.js +163 -4
  51. package/src/plugins/queue-consumer.plugin.js +6 -27
  52. package/src/plugins/relation.errors.js +139 -0
  53. package/src/plugins/relation.plugin.js +1242 -0
  54. package/src/plugins/replicator.plugin.js +2 -1
  55. package/src/plugins/replicators/bigquery-replicator.class.js +180 -8
  56. package/src/plugins/replicators/dynamodb-replicator.class.js +383 -0
  57. package/src/plugins/replicators/index.js +28 -3
  58. package/src/plugins/replicators/mongodb-replicator.class.js +391 -0
  59. package/src/plugins/replicators/mysql-replicator.class.js +558 -0
  60. package/src/plugins/replicators/planetscale-replicator.class.js +409 -0
  61. package/src/plugins/replicators/postgres-replicator.class.js +182 -7
  62. package/src/plugins/replicators/s3db-replicator.class.js +1 -12
  63. package/src/plugins/replicators/schema-sync.helper.js +601 -0
  64. package/src/plugins/replicators/sqs-replicator.class.js +11 -9
  65. package/src/plugins/replicators/turso-replicator.class.js +416 -0
  66. package/src/plugins/replicators/webhook-replicator.class.js +612 -0
  67. package/src/plugins/state-machine.plugin.js +122 -68
  68. package/src/plugins/tfstate/README.md +745 -0
  69. package/src/plugins/tfstate/base-driver.js +80 -0
  70. package/src/plugins/tfstate/errors.js +112 -0
  71. package/src/plugins/tfstate/filesystem-driver.js +129 -0
  72. package/src/plugins/tfstate/index.js +2660 -0
  73. package/src/plugins/tfstate/s3-driver.js +192 -0
  74. package/src/plugins/ttl.plugin.js +536 -0
  75. package/src/resource.class.js +315 -36
  76. package/src/s3db.d.ts +66 -1
  77. package/src/schema.class.js +366 -32
  78. package/SECURITY.md +0 -76
  79. package/src/partition-drivers/base-partition-driver.js +0 -106
  80. package/src/partition-drivers/index.js +0 -66
  81. package/src/partition-drivers/memory-partition-driver.js +0 -289
  82. package/src/partition-drivers/sqs-partition-driver.js +0 -337
  83. package/src/partition-drivers/sync-partition-driver.js +0 -38
@@ -0,0 +1,558 @@
1
+ import tryFn from "#src/concerns/try-fn.js";
2
+ import requirePluginDependency from "#src/plugins/concerns/plugin-dependencies.js";
3
+ import BaseReplicator from './base-replicator.class.js';
4
+ import { ReplicationError } from '../replicator.errors.js';
5
+ import {
6
+ generateMySQLCreateTable,
7
+ getMySQLTableSchema,
8
+ generateMySQLAlterTable
9
+ } from './schema-sync.helper.js';
10
+
11
+ /**
12
+ * MySQL/MariaDB Replicator - Replicate data to MySQL or MariaDB tables
13
+ *
14
+ * ⚠️ REQUIRED DEPENDENCY: You must install the MySQL client library:
15
+ * ```bash
16
+ * pnpm add mysql2
17
+ * ```
18
+ *
19
+ * Configuration:
20
+ * @param {string} connectionString - MySQL connection string (optional)
21
+ * @param {string} host - Database host (default: localhost)
22
+ * @param {number} port - Database port (default: 3306)
23
+ * @param {string} database - Database name (required)
24
+ * @param {string} user - Database user (required)
25
+ * @param {string} password - Database password (required)
26
+ * @param {Object} ssl - SSL configuration (optional)
27
+ * @param {number} connectionLimit - Max connections in pool (default: 10)
28
+ * @param {string} logTable - Table name for operation logging (optional)
29
+ * @param {Object} schemaSync - Schema synchronization configuration
30
+ * @param {boolean} schemaSync.enabled - Enable automatic schema management (default: false)
31
+ * @param {string} schemaSync.strategy - Sync strategy: 'alter' | 'drop-create' | 'validate-only' (default: 'alter')
32
+ * @param {string} schemaSync.onMismatch - Action on schema mismatch: 'error' | 'warn' | 'ignore' (default: 'error')
33
+ * @param {boolean} schemaSync.autoCreateTable - Auto-create table if not exists (default: true)
34
+ * @param {boolean} schemaSync.autoCreateColumns - Auto-add missing columns (default: true, only with strategy: 'alter')
35
+ * @param {boolean} schemaSync.dropMissingColumns - Remove extra columns (default: false, dangerous!)
36
+ *
37
+ * @example
38
+ * new MySQLReplicator({
39
+ * host: 'localhost',
40
+ * port: 3306,
41
+ * database: 'analytics',
42
+ * user: 'replicator',
43
+ * password: 'secret',
44
+ * logTable: 'replication_log',
45
+ * schemaSync: {
46
+ * enabled: true,
47
+ * strategy: 'alter',
48
+ * onMismatch: 'error'
49
+ * }
50
+ * }, {
51
+ * users: [{ actions: ['insert', 'update'], table: 'users_table' }],
52
+ * orders: 'orders_table'
53
+ * })
54
+ *
55
+ * See PLUGINS.md for comprehensive configuration documentation.
56
+ */
57
+ class MySQLReplicator extends BaseReplicator {
58
+ constructor(config = {}, resources = {}) {
59
+ super(config);
60
+ this.connectionString = config.connectionString;
61
+ this.host = config.host || 'localhost';
62
+ this.port = config.port || 3306;
63
+ this.database = config.database;
64
+ this.user = config.user;
65
+ this.password = config.password;
66
+ this.pool = null;
67
+ this.ssl = config.ssl;
68
+ this.connectionLimit = config.connectionLimit || 10;
69
+ this.logTable = config.logTable;
70
+
71
+ // Schema sync configuration
72
+ this.schemaSync = {
73
+ enabled: config.schemaSync?.enabled || false,
74
+ strategy: config.schemaSync?.strategy || 'alter',
75
+ onMismatch: config.schemaSync?.onMismatch || 'error',
76
+ autoCreateTable: config.schemaSync?.autoCreateTable !== false,
77
+ autoCreateColumns: config.schemaSync?.autoCreateColumns !== false,
78
+ dropMissingColumns: config.schemaSync?.dropMissingColumns || false
79
+ };
80
+
81
+ // Parse resources configuration
82
+ this.resources = this.parseResourcesConfig(resources);
83
+ }
84
+
85
+ parseResourcesConfig(resources) {
86
+ const parsed = {};
87
+
88
+ for (const [resourceName, config] of Object.entries(resources)) {
89
+ if (typeof config === 'string') {
90
+ // Short form: just table name
91
+ parsed[resourceName] = [{
92
+ table: config,
93
+ actions: ['insert']
94
+ }];
95
+ } else if (Array.isArray(config)) {
96
+ // Array form: multiple table mappings
97
+ parsed[resourceName] = config.map(item => {
98
+ if (typeof item === 'string') {
99
+ return { table: item, actions: ['insert'] };
100
+ }
101
+ return {
102
+ table: item.table,
103
+ actions: item.actions || ['insert']
104
+ };
105
+ });
106
+ } else if (typeof config === 'object') {
107
+ // Single object form
108
+ parsed[resourceName] = [{
109
+ table: config.table,
110
+ actions: config.actions || ['insert']
111
+ }];
112
+ }
113
+ }
114
+
115
+ return parsed;
116
+ }
117
+
118
+ validateConfig() {
119
+ const errors = [];
120
+ if (!this.database) {
121
+ errors.push('Database name is required');
122
+ }
123
+ if (!this.user) {
124
+ errors.push('Database user is required');
125
+ }
126
+ if (!this.password) {
127
+ errors.push('Database password is required');
128
+ }
129
+ if (Object.keys(this.resources).length === 0) {
130
+ errors.push('At least one resource must be configured');
131
+ }
132
+
133
+ // Validate resource configurations
134
+ for (const [resourceName, tables] of Object.entries(this.resources)) {
135
+ for (const tableConfig of tables) {
136
+ if (!tableConfig.table) {
137
+ errors.push(`Table name is required for resource '${resourceName}'`);
138
+ }
139
+ if (!Array.isArray(tableConfig.actions) || tableConfig.actions.length === 0) {
140
+ errors.push(`Actions array is required for resource '${resourceName}'`);
141
+ }
142
+ }
143
+ }
144
+
145
+ return { isValid: errors.length === 0, errors };
146
+ }
147
+
148
+ async initialize(database) {
149
+ await super.initialize(database);
150
+
151
+ // Load mysql2 dependency
152
+ const mysql = requirePluginDependency('mysql2', 'MySQLReplicator');
153
+
154
+ // Create connection pool
155
+ const [ok, err] = await tryFn(async () => {
156
+ const poolConfig = {
157
+ host: this.host,
158
+ port: this.port,
159
+ user: this.user,
160
+ password: this.password,
161
+ database: this.database,
162
+ connectionLimit: this.connectionLimit,
163
+ waitForConnections: true,
164
+ queueLimit: 0
165
+ };
166
+
167
+ if (this.ssl) {
168
+ poolConfig.ssl = this.ssl;
169
+ }
170
+
171
+ this.pool = mysql.createPool(poolConfig);
172
+
173
+ // Test connection
174
+ const connection = await this.pool.promise().getConnection();
175
+ await connection.ping();
176
+ connection.release();
177
+ });
178
+
179
+ if (!ok) {
180
+ throw new ReplicationError('Failed to connect to MySQL database', {
181
+ operation: 'initialize',
182
+ replicatorClass: 'MySQLReplicator',
183
+ host: this.host,
184
+ port: this.port,
185
+ database: this.database,
186
+ original: err,
187
+ suggestion: 'Check MySQL connection credentials and ensure database is accessible'
188
+ });
189
+ }
190
+
191
+ // Create log table if configured
192
+ if (this.logTable) {
193
+ await this._createLogTable();
194
+ }
195
+
196
+ // Sync schemas if enabled
197
+ if (this.schemaSync.enabled) {
198
+ await this.syncSchemas(database);
199
+ }
200
+
201
+ this.emit('connected', {
202
+ replicator: 'MySQLReplicator',
203
+ host: this.host,
204
+ database: this.database
205
+ });
206
+ }
207
+
208
+ /**
209
+ * Sync table schemas based on S3DB resource definitions
210
+ */
211
+ async syncSchemas(database) {
212
+ for (const [resourceName, tableConfigs] of Object.entries(this.resources)) {
213
+ const [okRes, errRes, resource] = await tryFn(async () => {
214
+ return await database.getResource(resourceName);
215
+ });
216
+
217
+ if (!okRes) {
218
+ if (this.config.verbose) {
219
+ console.warn(`[MySQLReplicator] Could not get resource ${resourceName} for schema sync: ${errRes.message}`);
220
+ }
221
+ continue;
222
+ }
223
+
224
+ const attributes = resource.config.versions[resource.config.currentVersion]?.attributes || {};
225
+
226
+ for (const tableConfig of tableConfigs) {
227
+ const tableName = tableConfig.table;
228
+
229
+ const [okSync, errSync] = await tryFn(async () => {
230
+ await this.syncTableSchema(tableName, attributes);
231
+ });
232
+
233
+ if (!okSync) {
234
+ const message = `Schema sync failed for table ${tableName}: ${errSync.message}`;
235
+
236
+ if (this.schemaSync.onMismatch === 'error') {
237
+ throw new Error(message);
238
+ } else if (this.schemaSync.onMismatch === 'warn') {
239
+ console.warn(`[MySQLReplicator] ${message}`);
240
+ }
241
+ }
242
+ }
243
+ }
244
+
245
+ this.emit('schema_sync_completed', {
246
+ replicator: this.name,
247
+ resources: Object.keys(this.resources)
248
+ });
249
+ }
250
+
251
+ /**
252
+ * Sync a single table schema
253
+ */
254
+ async syncTableSchema(tableName, attributes) {
255
+ const connection = await this.pool.promise().getConnection();
256
+
257
+ try {
258
+ // Check if table exists
259
+ const existingSchema = await getMySQLTableSchema(connection, tableName);
260
+
261
+ if (!existingSchema) {
262
+ if (!this.schemaSync.autoCreateTable) {
263
+ throw new Error(`Table ${tableName} does not exist and autoCreateTable is disabled`);
264
+ }
265
+
266
+ if (this.schemaSync.strategy === 'validate-only') {
267
+ throw new Error(`Table ${tableName} does not exist (validate-only mode)`);
268
+ }
269
+
270
+ // Create table
271
+ const createSQL = generateMySQLCreateTable(tableName, attributes);
272
+
273
+ if (this.config.verbose) {
274
+ console.log(`[MySQLReplicator] Creating table ${tableName}:\n${createSQL}`);
275
+ }
276
+
277
+ await connection.query(createSQL);
278
+
279
+ this.emit('table_created', {
280
+ replicator: this.name,
281
+ tableName,
282
+ attributes: Object.keys(attributes)
283
+ });
284
+
285
+ return;
286
+ }
287
+
288
+ // Table exists - check for schema changes
289
+ if (this.schemaSync.strategy === 'drop-create') {
290
+ if (this.config.verbose) {
291
+ console.warn(`[MySQLReplicator] Dropping and recreating table ${tableName}`);
292
+ }
293
+
294
+ await connection.query(`DROP TABLE IF EXISTS ${tableName}`);
295
+ const createSQL = generateMySQLCreateTable(tableName, attributes);
296
+ await connection.query(createSQL);
297
+
298
+ this.emit('table_recreated', {
299
+ replicator: this.name,
300
+ tableName,
301
+ attributes: Object.keys(attributes)
302
+ });
303
+
304
+ return;
305
+ }
306
+
307
+ if (this.schemaSync.strategy === 'alter' && this.schemaSync.autoCreateColumns) {
308
+ const alterStatements = generateMySQLAlterTable(tableName, attributes, existingSchema);
309
+
310
+ if (alterStatements.length > 0) {
311
+ if (this.config.verbose) {
312
+ console.log(`[MySQLReplicator] Altering table ${tableName}:`, alterStatements);
313
+ }
314
+
315
+ for (const stmt of alterStatements) {
316
+ await connection.query(stmt);
317
+ }
318
+
319
+ this.emit('table_altered', {
320
+ replicator: this.name,
321
+ tableName,
322
+ addedColumns: alterStatements.length
323
+ });
324
+ }
325
+ }
326
+
327
+ if (this.schemaSync.strategy === 'validate-only') {
328
+ const alterStatements = generateMySQLAlterTable(tableName, attributes, existingSchema);
329
+
330
+ if (alterStatements.length > 0) {
331
+ throw new Error(`Table ${tableName} schema mismatch. Missing columns: ${alterStatements.length}`);
332
+ }
333
+ }
334
+ } finally {
335
+ connection.release();
336
+ }
337
+ }
338
+
339
+ shouldReplicateResource(resourceName) {
340
+ return this.resources.hasOwnProperty(resourceName);
341
+ }
342
+
343
+ async _createLogTable() {
344
+ const mysql = requirePluginDependency('mysql2', 'MySQLReplicator');
345
+
346
+ const [ok] = await tryFn(async () => {
347
+ await this.pool.promise().query(`
348
+ CREATE TABLE IF NOT EXISTS ${mysql.escapeId(this.logTable)} (
349
+ id INT AUTO_INCREMENT PRIMARY KEY,
350
+ resource_name VARCHAR(255) NOT NULL,
351
+ operation VARCHAR(50) NOT NULL,
352
+ record_id VARCHAR(255),
353
+ data JSON,
354
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
355
+ INDEX idx_resource (resource_name),
356
+ INDEX idx_timestamp (timestamp)
357
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
358
+ `);
359
+ });
360
+
361
+ if (!ok && this.config.verbose) {
362
+ console.warn('[MySQLReplicator] Failed to create log table');
363
+ }
364
+ }
365
+
366
+ async replicate(resourceName, operation, data, id) {
367
+ if (!this.resources[resourceName]) {
368
+ throw new ReplicationError('Resource not configured for replication', {
369
+ operation: 'replicate',
370
+ replicatorClass: 'MySQLReplicator',
371
+ resourceName,
372
+ configuredResources: Object.keys(this.resources),
373
+ suggestion: 'Add resource to replicator resources configuration'
374
+ });
375
+ }
376
+
377
+ const results = [];
378
+
379
+ for (const tableConfig of this.resources[resourceName]) {
380
+ if (!tableConfig.actions.includes(operation)) {
381
+ continue; // Skip if operation not allowed for this table
382
+ }
383
+
384
+ const [ok, error, result] = await tryFn(async () => {
385
+ switch (operation) {
386
+ case 'insert':
387
+ return await this._insertRecord(tableConfig.table, data);
388
+ case 'update':
389
+ return await this._updateRecord(tableConfig.table, id, data);
390
+ case 'delete':
391
+ return await this._deleteRecord(tableConfig.table, id);
392
+ default:
393
+ throw new ReplicationError(`Unsupported operation: ${operation}`, {
394
+ operation: 'replicate',
395
+ replicatorClass: 'MySQLReplicator',
396
+ invalidOperation: operation,
397
+ supportedOperations: ['insert', 'update', 'delete']
398
+ });
399
+ }
400
+ });
401
+
402
+ if (ok) {
403
+ results.push(result);
404
+
405
+ // Log to replication log table if configured
406
+ if (this.logTable) {
407
+ await this._logOperation(resourceName, operation, id, data);
408
+ }
409
+ } else {
410
+ this.emit('replication_error', {
411
+ resource: resourceName,
412
+ operation,
413
+ table: tableConfig.table,
414
+ error: error.message
415
+ });
416
+
417
+ if (this.config.verbose) {
418
+ console.error(`[MySQLReplicator] Failed to replicate ${operation} for ${resourceName}:`, error);
419
+ }
420
+ }
421
+ }
422
+
423
+ return results.length > 0 ? results[0] : null;
424
+ }
425
+
426
+ async _insertRecord(table, data) {
427
+ const mysql = requirePluginDependency('mysql2', 'MySQLReplicator');
428
+ const cleanData = this._cleanInternalFields(data);
429
+
430
+ const columns = Object.keys(cleanData);
431
+ const values = Object.values(cleanData);
432
+ const placeholders = values.map(() => '?').join(', ');
433
+
434
+ const query = `INSERT INTO ${mysql.escapeId(table)} (${columns.map(c => mysql.escapeId(c)).join(', ')}) VALUES (${placeholders})`;
435
+
436
+ const [result] = await this.pool.promise().query(query, values);
437
+ return result;
438
+ }
439
+
440
+ async _updateRecord(table, id, data) {
441
+ const mysql = requirePluginDependency('mysql2', 'MySQLReplicator');
442
+ const cleanData = this._cleanInternalFields(data);
443
+
444
+ const updates = Object.keys(cleanData)
445
+ .map(col => `${mysql.escapeId(col)} = ?`)
446
+ .join(', ');
447
+
448
+ const values = [...Object.values(cleanData), id];
449
+
450
+ const query = `UPDATE ${mysql.escapeId(table)} SET ${updates} WHERE id = ?`;
451
+
452
+ const [result] = await this.pool.promise().query(query, values);
453
+ return result;
454
+ }
455
+
456
+ async _deleteRecord(table, id) {
457
+ const mysql = requirePluginDependency('mysql2', 'MySQLReplicator');
458
+ const query = `DELETE FROM ${mysql.escapeId(table)} WHERE id = ?`;
459
+
460
+ const [result] = await this.pool.promise().query(query, [id]);
461
+ return result;
462
+ }
463
+
464
+ async _logOperation(resourceName, operation, id, data) {
465
+ const mysql = requirePluginDependency('mysql2', 'MySQLReplicator');
466
+
467
+ const [ok] = await tryFn(async () => {
468
+ const query = `INSERT INTO ${mysql.escapeId(this.logTable)} (resource_name, operation, record_id, data) VALUES (?, ?, ?, ?)`;
469
+ await this.pool.promise().query(query, [resourceName, operation, id, JSON.stringify(data)]);
470
+ });
471
+
472
+ if (!ok && this.config.verbose) {
473
+ console.warn('[MySQLReplicator] Failed to log operation');
474
+ }
475
+ }
476
+
477
+ _cleanInternalFields(data) {
478
+ if (!data || typeof data !== 'object') return data;
479
+
480
+ const cleanData = { ...data };
481
+
482
+ // Remove internal s3db fields
483
+ Object.keys(cleanData).forEach(key => {
484
+ if (key.startsWith('$') || key.startsWith('_')) {
485
+ delete cleanData[key];
486
+ }
487
+ });
488
+
489
+ return cleanData;
490
+ }
491
+
492
+ async replicateBatch(resourceName, records) {
493
+ const results = [];
494
+ const errors = [];
495
+
496
+ for (const record of records) {
497
+ const [ok, err, result] = await tryFn(() =>
498
+ this.replicate(resourceName, record.operation, record.data, record.id)
499
+ );
500
+
501
+ if (ok) {
502
+ results.push(result);
503
+ } else {
504
+ errors.push({ id: record.id, error: err.message });
505
+ }
506
+ }
507
+
508
+ return {
509
+ success: errors.length === 0,
510
+ results,
511
+ errors,
512
+ total: records.length
513
+ };
514
+ }
515
+
516
+ async testConnection() {
517
+ const [ok, err] = await tryFn(async () => {
518
+ if (!this.pool) {
519
+ throw new Error('Pool not initialized');
520
+ }
521
+
522
+ const connection = await this.pool.promise().getConnection();
523
+ await connection.ping();
524
+ connection.release();
525
+ return true;
526
+ });
527
+
528
+ if (!ok) {
529
+ this.emit('connection_error', { replicator: 'MySQLReplicator', error: err.message });
530
+ return false;
531
+ }
532
+
533
+ return true;
534
+ }
535
+
536
+ async getStatus() {
537
+ const baseStatus = await super.getStatus();
538
+ return {
539
+ ...baseStatus,
540
+ connected: !!this.pool,
541
+ host: this.host,
542
+ database: this.database,
543
+ resources: Object.keys(this.resources),
544
+ poolConnections: this.pool ? this.pool.pool.allConnections.length : 0,
545
+ schemaSync: this.schemaSync
546
+ };
547
+ }
548
+
549
+ async cleanup() {
550
+ if (this.pool) {
551
+ await this.pool.end();
552
+ this.pool = null;
553
+ }
554
+ await super.cleanup();
555
+ }
556
+ }
557
+
558
+ export default MySQLReplicator;