s3db.js 7.2.1 → 7.3.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.
@@ -126,35 +126,31 @@ function normalizeResourceName(name) {
126
126
  export class ReplicatorPlugin extends Plugin {
127
127
  constructor(options = {}) {
128
128
  super();
129
- if (options.verbose) {
130
- console.log('[PLUGIN][CONSTRUCTOR] ReplicatorPlugin constructor called');
131
- }
132
- if (options.verbose) {
133
- console.log('[PLUGIN][constructor] New ReplicatorPlugin instance created with config:', options);
134
- }
135
129
  // Validation for config tests
136
130
  if (!options.replicators || !Array.isArray(options.replicators)) {
137
131
  throw new Error('ReplicatorPlugin: replicators array is required');
138
132
  }
139
133
  for (const rep of options.replicators) {
140
134
  if (!rep.driver) throw new Error('ReplicatorPlugin: each replicator must have a driver');
135
+ if (!rep.resources || typeof rep.resources !== 'object') throw new Error('ReplicatorPlugin: each replicator must have resources config');
136
+ if (Object.keys(rep.resources).length === 0) throw new Error('ReplicatorPlugin: each replicator must have at least one resource configured');
141
137
  }
142
- // Aceita apenas os parâmetros válidos
138
+
143
139
  this.config = {
144
- verbose: options.verbose ?? false,
145
- persistReplicatorLog: options.persistReplicatorLog ?? false,
146
- replicatorLogResource: options.replicatorLogResource ?? 'replicator_logs',
147
140
  replicators: options.replicators || [],
141
+ logErrors: options.logErrors !== false,
142
+ replicatorLogResource: options.replicatorLogResource || 'replicator_log',
143
+ enabled: options.enabled !== false,
144
+ batchSize: options.batchSize || 100,
145
+ maxRetries: options.maxRetries || 3,
146
+ timeout: options.timeout || 30000,
147
+ verbose: options.verbose || false,
148
+ ...options
148
149
  };
150
+
149
151
  this.replicators = [];
150
- this.queue = [];
151
- this.isProcessing = false;
152
- this.stats = {
153
- totalOperations: 0,
154
- totalErrors: 0,
155
- lastError: null,
156
- };
157
- this._installedListeners = [];
152
+ this.database = null;
153
+ this.eventListenersInstalled = new Set();
158
154
  }
159
155
 
160
156
  /**
@@ -176,87 +172,39 @@ export class ReplicatorPlugin extends Plugin {
176
172
  return filtered;
177
173
  }
178
174
 
179
- installEventListeners(resource) {
180
- const plugin = this;
181
- if (plugin.config.verbose) {
182
- console.log('[PLUGIN] installEventListeners called for:', resource && resource.name, {
183
- hasDatabase: !!resource.database,
184
- sameDatabase: resource.database === plugin.database,
185
- alreadyInstalled: resource._replicatorListenersInstalled,
186
- resourceObj: resource,
187
- resourceObjId: resource && resource.id,
188
- resourceObjType: typeof resource,
189
- resourceObjIs: resource && Object.is(resource, plugin.database.resources && plugin.database.resources[resource.name]),
190
- resourceObjEq: resource === (plugin.database.resources && plugin.database.resources[resource.name])
191
- });
192
- }
193
- // Only install listeners on resources belonging to the source database
194
- if (!resource || resource.name === plugin.config.replicatorLogResource || !resource.database || resource.database !== plugin.database) return;
195
- if (resource._replicatorListenersInstalled) return;
196
- resource._replicatorListenersInstalled = true;
197
- // Track listeners for cleanup
198
- this._installedListeners.push(resource);
199
- if (plugin.config.verbose) {
200
- console.log(`[PLUGIN] installEventListeners INSTALLED for resource: ${resource && resource.name}`);
175
+ installEventListeners(resource, database, plugin) {
176
+ if (!resource || this.eventListenersInstalled.has(resource.name) ||
177
+ resource.name === this.config.replicatorLogResource) {
178
+ return;
201
179
  }
202
- // Insert event
180
+
203
181
  resource.on('insert', async (data) => {
204
- if (plugin.config.verbose) {
205
- console.log('[PLUGIN] Listener INSERT on', resource.name, 'plugin.replicators.length:', plugin.replicators.length, plugin.replicators.map(r => ({id: r.id, driver: r.driver})));
206
- }
207
182
  try {
208
- const completeData = await plugin.getCompleteData(resource, data);
209
- if (plugin.config.verbose) {
210
- console.log(`[PLUGIN] Listener INSERT completeData for ${resource.name} id=${data && data.id}:`, completeData);
211
- }
212
- await plugin.processReplicatorEvent(resource.name, 'insert', data.id, completeData, null);
213
- } catch (err) {
214
- if (plugin.config.verbose) {
215
- console.error(`[PLUGIN] Listener INSERT error on ${resource.name} id=${data && data.id}:`, err);
216
- }
183
+ const completeData = { ...data, createdAt: new Date().toISOString() };
184
+ await plugin.processReplicatorEvent('insert', resource.name, completeData.id, completeData);
185
+ } catch (error) {
186
+ this.emit('error', { operation: 'insert', error: error.message, resource: resource.name });
217
187
  }
218
188
  });
219
189
 
220
- // Update event
221
- resource.on('update', async (data) => {
222
- console.log('[PLUGIN][Listener][UPDATE][START] triggered for resource:', resource.name, 'data:', data);
223
- const beforeData = data && data.$before;
224
- if (plugin.config.verbose) {
225
- console.log('[PLUGIN] Listener UPDATE on', resource.name, 'plugin.replicators.length:', plugin.replicators.length, plugin.replicators.map(r => ({id: r.id, driver: r.driver})), 'data:', data, 'beforeData:', beforeData);
226
- }
190
+ resource.on('update', async (data, beforeData) => {
227
191
  try {
228
- // Always fetch the full, current object for update replication
229
- let completeData;
230
- const [ok, err, record] = await tryFn(() => resource.get(data.id));
231
- if (ok && record) {
232
- completeData = record;
233
- } else {
234
- completeData = data;
235
- }
236
- await plugin.processReplicatorEvent(resource.name, 'update', data.id, completeData, beforeData);
237
- } catch (err) {
238
- if (plugin.config.verbose) {
239
- console.error(`[PLUGIN] Listener UPDATE erro em ${resource.name} id=${data && data.id}:`, err);
240
- }
192
+ const completeData = { ...data, updatedAt: new Date().toISOString() };
193
+ await plugin.processReplicatorEvent('update', resource.name, completeData.id, completeData, beforeData);
194
+ } catch (error) {
195
+ this.emit('error', { operation: 'update', error: error.message, resource: resource.name });
241
196
  }
242
197
  });
243
198
 
244
- // Delete event
245
- resource.on('delete', async (data, beforeData) => {
246
- if (plugin.config.verbose) {
247
- console.log('[PLUGIN] Listener DELETE on', resource.name, 'plugin.replicators.length:', plugin.replicators.length, plugin.replicators.map(r => ({id: r.id, driver: r.driver})));
248
- }
199
+ resource.on('delete', async (data) => {
249
200
  try {
250
- await plugin.processReplicatorEvent(resource.name, 'delete', data.id, null, beforeData);
251
- } catch (err) {
252
- if (plugin.config.verbose) {
253
- console.error(`[PLUGIN] Listener DELETE erro em ${resource.name} id=${data && data.id}:`, err);
254
- }
201
+ await plugin.processReplicatorEvent('delete', resource.name, data.id, data);
202
+ } catch (error) {
203
+ this.emit('error', { operation: 'delete', error: error.message, resource: resource.name });
255
204
  }
256
205
  });
257
- if (plugin.config.verbose) {
258
- console.log(`[PLUGIN] Listeners instalados para resource: ${resource && resource.name} (insert: ${resource.listenerCount('insert')}, update: ${resource.listenerCount('update')}, delete: ${resource.listenerCount('delete')})`);
259
- }
206
+
207
+ this.eventListenersInstalled.add(resource.name);
260
208
  }
261
209
 
262
210
  /**
@@ -271,120 +219,72 @@ export class ReplicatorPlugin extends Plugin {
271
219
  }
272
220
 
273
221
  async setup(database) {
274
- console.log('[PLUGIN][SETUP] setup called');
275
- if (this.config.verbose) {
276
- console.log('[PLUGIN][setup] called with database:', database && database.name);
277
- }
278
222
  this.database = database;
279
- // 1. Sempre crie a resource de log antes de qualquer outra coisa
280
- if (this.config.persistReplicatorLog) {
281
- let logRes = database.resources[normalizeResourceName(this.config.replicatorLogResource)];
282
- if (!logRes) {
283
- logRes = await database.createResource({
223
+
224
+ try {
225
+ await this.initializeReplicators(database);
226
+ } catch (error) {
227
+ this.emit('error', { operation: 'setup', error: error.message });
228
+ throw error;
229
+ }
230
+
231
+ try {
232
+ if (this.config.replicatorLogResource) {
233
+ const logRes = await database.createResource({
284
234
  name: this.config.replicatorLogResource,
285
- behavior: 'truncate-data',
235
+ behavior: 'body-overflow',
286
236
  attributes: {
287
- id: 'string|required',
288
- resource: 'string|required',
289
- action: 'string|required',
290
- data: 'object',
291
- timestamp: 'number|required',
292
- createdAt: 'string|required',
293
- },
294
- partitions: {
295
- byDate: { fields: { 'createdAt': 'string|maxlength:10' } }
237
+ operation: 'string',
238
+ resourceName: 'string',
239
+ recordId: 'string',
240
+ data: 'string',
241
+ error: 'string|optional',
242
+ replicator: 'string',
243
+ timestamp: 'string',
244
+ status: 'string'
296
245
  }
297
246
  });
298
- if (this.config.verbose) {
299
- console.log('[PLUGIN] Log resource created:', this.config.replicatorLogResource, !!logRes);
300
- }
301
- }
302
- database.resources[normalizeResourceName(this.config.replicatorLogResource)] = logRes;
303
- this.replicatorLog = logRes; // Salva referência para uso futuro
304
- if (this.config.verbose) {
305
- console.log('[PLUGIN] Log resource created and registered:', this.config.replicatorLogResource, !!database.resources[normalizeResourceName(this.config.replicatorLogResource)]);
306
- }
307
- // Persist the log resource to metadata
308
- if (typeof database.uploadMetadataFile === 'function') {
309
- await database.uploadMetadataFile();
310
- if (this.config.verbose) {
311
- console.log('[PLUGIN] uploadMetadataFile called. database.resources keys:', Object.keys(database.resources));
312
- }
313
- }
314
- }
315
- // 2. Só depois inicialize replicators e listeners
316
- if (this.config.replicators && this.config.replicators.length > 0 && this.replicators.length === 0) {
317
- await this.initializeReplicators();
318
- console.log('[PLUGIN][SETUP] after initializeReplicators, replicators.length:', this.replicators.length);
319
- if (this.config.verbose) {
320
- console.log('[PLUGIN][setup] After initializeReplicators, replicators.length:', this.replicators.length, this.replicators.map(r => ({id: r.id, driver: r.driver})));
321
247
  }
248
+ } catch (error) {
249
+ // Log resource creation failed, continue without it
322
250
  }
323
- // Only install event listeners after replicators are initialized
324
- for (const resourceName in database.resources) {
325
- if (normalizeResourceName(resourceName) !== normalizeResourceName(this.config.replicatorLogResource)) {
326
- this.installEventListeners(database.resources[resourceName]);
327
- }
328
- }
329
- database.on('connected', () => {
330
- for (const resourceName in database.resources) {
331
- if (normalizeResourceName(resourceName) !== normalizeResourceName(this.config.replicatorLogResource)) {
332
- this.installEventListeners(database.resources[resourceName]);
333
- }
334
- }
335
- });
251
+
252
+ await this.uploadMetadataFile(database);
253
+
336
254
  const originalCreateResource = database.createResource.bind(database);
337
255
  database.createResource = async (config) => {
338
- if (this.config.verbose) {
339
- console.log('[PLUGIN] createResource proxy called for:', config && config.name);
340
- }
341
256
  const resource = await originalCreateResource(config);
342
- if (resource && resource.name !== this.config.replicatorLogResource) {
343
- this.installEventListeners(resource);
257
+ if (resource) {
258
+ this.installEventListeners(resource, database, this);
344
259
  }
345
260
  return resource;
346
261
  };
347
- database.on('s3db.resourceCreated', (resourceName) => {
348
- const resource = database.resources[resourceName];
349
- if (resource && resource.name !== this.config.replicatorLogResource) {
350
- this.installEventListeners(resource);
351
- }
352
- });
353
-
354
- database.on('s3db.resourceUpdated', (resourceName) => {
262
+
263
+ for (const resourceName in database.resources) {
355
264
  const resource = database.resources[resourceName];
356
- if (resource && resource.name !== this.config.replicatorLogResource) {
357
- this.installEventListeners(resource);
358
- }
359
- });
265
+ this.installEventListeners(resource, database, this);
266
+ }
267
+ }
268
+
269
+ createReplicator(driver, config, resources, client) {
270
+ return createReplicator(driver, config, resources, client);
360
271
  }
361
272
 
362
- async initializeReplicators() {
363
- console.log('[PLUGIN][INIT] initializeReplicators called');
273
+ async initializeReplicators(database) {
364
274
  for (const replicatorConfig of this.config.replicators) {
365
- try {
366
- console.log('[PLUGIN][INIT] processing replicatorConfig:', replicatorConfig);
367
- const driver = replicatorConfig.driver;
368
- const resources = replicatorConfig.resources;
369
- const client = replicatorConfig.client;
370
- const replicator = createReplicator(driver, replicatorConfig, resources, client);
371
- if (replicator) {
372
- // Initialize the replicator with the database
373
- await replicator.initialize(this.database);
374
-
375
- this.replicators.push({
376
- id: Math.random().toString(36).slice(2),
377
- driver,
378
- config: replicatorConfig,
379
- resources,
380
- instance: replicator
381
- });
382
- console.log('[PLUGIN][INIT] pushed replicator:', driver, resources);
383
- } else {
384
- console.log('[PLUGIN][INIT] createReplicator returned null/undefined for driver:', driver);
385
- }
386
- } catch (err) {
387
- console.error('[PLUGIN][INIT] Error creating replicator:', err);
275
+ const { driver, config = {}, resources, client, ...otherConfig } = replicatorConfig;
276
+
277
+ // Extract resources from replicatorConfig or config
278
+ const replicatorResources = resources || config.resources || {};
279
+
280
+ // Merge config with other top-level config options (like queueUrlDefault)
281
+ const mergedConfig = { ...config, ...otherConfig };
282
+
283
+ // Pass config, resources, and client in correct order
284
+ const replicator = this.createReplicator(driver, mergedConfig, replicatorResources, client);
285
+ if (replicator) {
286
+ await replicator.initialize(database);
287
+ this.replicators.push(replicator);
388
288
  }
389
289
  }
390
290
  }
@@ -400,180 +300,198 @@ export class ReplicatorPlugin extends Plugin {
400
300
  // await this.processQueue(); // Removed as per edit hint
401
301
  }
402
302
 
403
- async processReplicatorEvent(resourceName, operation, recordId, data, beforeData = null) {
404
- if (this.config.verbose) {
405
- console.log('[PLUGIN][processReplicatorEvent] replicators.length:', this.replicators.length, this.replicators.map(r => ({id: r.id, driver: r.driver})));
406
- console.log(`[PLUGIN][processReplicatorEvent] operation: ${operation}, resource: ${resourceName}, recordId: ${recordId}, data:`, data, 'beforeData:', beforeData);
303
+ filterInternalFields(data) {
304
+ if (!data || typeof data !== 'object') return data;
305
+ const filtered = {};
306
+ for (const [key, value] of Object.entries(data)) {
307
+ // Filter out internal fields that start with _ or $
308
+ if (!key.startsWith('_') && !key.startsWith('$')) {
309
+ filtered[key] = value;
310
+ }
407
311
  }
408
- if (this.config.verbose) {
409
- console.log(`[PLUGIN] processReplicatorEvent: resource=${resourceName} op=${operation} id=${recordId} data=`, data);
312
+ return filtered;
313
+ }
314
+
315
+ async uploadMetadataFile(database) {
316
+ if (typeof database.uploadMetadataFile === 'function') {
317
+ await database.uploadMetadataFile();
410
318
  }
411
- if (this.config.verbose) {
412
- console.log(`[PLUGIN] processReplicatorEvent: resource=${resourceName} op=${operation} replicators=${this.replicators.length}`);
319
+ }
320
+
321
+ async getCompleteData(resource, data) {
322
+ try {
323
+ const [ok, err, record] = await tryFn(() => resource.get(data.id));
324
+ if (ok && record) {
325
+ return record;
326
+ }
327
+ } catch (error) {
328
+ // Fallback to provided data
413
329
  }
414
- if (this.replicators.length === 0) {
415
- if (this.config.verbose) {
416
- console.log('[PLUGIN] No replicators registered');
330
+ return data;
331
+ }
332
+
333
+ async retryWithBackoff(operation, maxRetries = 3) {
334
+ let lastError;
335
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
336
+ try {
337
+ return await operation();
338
+ } catch (error) {
339
+ lastError = error;
340
+ if (attempt === maxRetries) {
341
+ throw error;
342
+ }
343
+ // Simple backoff: wait 1s, 2s, 4s...
344
+ const delay = Math.pow(2, attempt - 1) * 1000;
345
+ await new Promise(resolve => setTimeout(resolve, delay));
417
346
  }
418
- return;
419
347
  }
420
- const applicableReplicators = this.replicators.filter(replicator => {
421
- const should = replicator.instance.shouldReplicateResource(resourceName, operation);
422
- if (this.config.verbose) {
423
- console.log(`[PLUGIN] Replicator ${replicator.driver} shouldReplicateResource(${resourceName}, ${operation}):`, should);
348
+ throw lastError;
349
+ }
350
+
351
+ async logError(replicator, resourceName, operation, recordId, data, error) {
352
+ try {
353
+ const logResourceName = this.config.replicatorLogResource;
354
+ if (this.database && this.database.resources && this.database.resources[logResourceName]) {
355
+ const logResource = this.database.resources[logResourceName];
356
+ await logResource.insert({
357
+ replicator: replicator.name || replicator.id,
358
+ resourceName,
359
+ operation,
360
+ recordId,
361
+ data: JSON.stringify(data),
362
+ error: error.message,
363
+ timestamp: new Date().toISOString(),
364
+ status: 'error'
365
+ });
424
366
  }
367
+ } catch (logError) {
368
+ // Silent log errors
369
+ }
370
+ }
371
+
372
+ async processReplicatorEvent(operation, resourceName, recordId, data, beforeData = null) {
373
+ if (!this.config.enabled) return;
374
+
375
+ const applicableReplicators = this.replicators.filter(replicator => {
376
+ const should = replicator.shouldReplicateResource && replicator.shouldReplicateResource(resourceName, operation);
425
377
  return should;
426
378
  });
427
- if (this.config.verbose) {
428
- console.log(`[PLUGIN] processReplicatorEvent: applicableReplicators for resource=${resourceName}:`, applicableReplicators.map(r => r.driver));
429
- }
379
+
430
380
  if (applicableReplicators.length === 0) {
431
- if (this.config.verbose) {
432
- console.log('[PLUGIN] No applicable replicators for resource', resourceName);
433
- }
434
381
  return;
435
382
  }
436
383
 
437
- // Filtrar campos internos antes de replicar
438
- const filteredData = this.filterInternalFields(isPlainObject(data) ? data : { raw: data });
439
- const filteredBeforeData = beforeData ? this.filterInternalFields(isPlainObject(beforeData) ? beforeData : { raw: beforeData }) : null;
440
-
441
- const item = {
442
- id: `repl-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
443
- resourceName,
444
- operation,
445
- recordId,
446
- data: filteredData,
447
- beforeData: filteredBeforeData,
448
- timestamp: new Date().toISOString(),
449
- attempts: 0
450
- };
451
-
452
- // Log replicator attempt
453
- const logId = await this.logreplicator(item);
454
-
455
- // Sempre processa imediatamente (sincrono)
456
- const [ok, err, result] = await tryFn(async () => this.processreplicatorItem(item));
457
- if (ok) {
458
- if (logId) {
459
- await this.updatereplicatorLog(logId, {
460
- status: result.success ? 'success' : 'failed',
461
- attempts: 1,
462
- error: result.success ? '' : JSON.stringify(result.results)
384
+ const promises = applicableReplicators.map(async (replicator) => {
385
+ try {
386
+ const result = await this.retryWithBackoff(
387
+ () => replicator.replicate(resourceName, operation, data, recordId, beforeData),
388
+ this.config.maxRetries
389
+ );
390
+
391
+ this.emit('replicated', {
392
+ replicator: replicator.name || replicator.id,
393
+ resourceName,
394
+ operation,
395
+ recordId,
396
+ result,
397
+ success: true
463
398
  });
464
- }
465
- this.stats.totalOperations++;
466
- if (result.success) {
467
- this.stats.successfulOperations++;
468
- } else {
469
- this.stats.failedOperations++;
470
- }
471
- } else {
472
- if (logId) {
473
- await this.updatereplicatorLog(logId, {
474
- status: 'failed',
475
- attempts: 1,
476
- error: err.message
399
+
400
+ return result;
401
+ } catch (error) {
402
+ this.emit('replicator_error', {
403
+ replicator: replicator.name || replicator.id,
404
+ resourceName,
405
+ operation,
406
+ recordId,
407
+ error: error.message
477
408
  });
409
+
410
+ if (this.config.logErrors && this.database) {
411
+ await this.logError(replicator, resourceName, operation, recordId, data, error);
412
+ }
413
+
414
+ throw error;
478
415
  }
479
- this.stats.failedOperations++;
480
- }
416
+ });
417
+
418
+ return Promise.allSettled(promises);
481
419
  }
482
420
 
483
421
  async processreplicatorItem(item) {
484
- if (this.config.verbose) {
485
- console.log('[PLUGIN][processreplicatorItem] called with item:', item);
486
- }
487
422
  const applicableReplicators = this.replicators.filter(replicator => {
488
- const should = replicator.instance.shouldReplicateResource(item.resourceName, item.operation);
489
- if (this.config.verbose) {
490
- console.log(`[PLUGIN] processreplicatorItem: Replicator ${replicator.driver} shouldReplicateResource(${item.resourceName}, ${item.operation}):`, should);
491
- }
423
+ const should = replicator.shouldReplicateResource && replicator.shouldReplicateResource(item.resourceName, item.operation);
492
424
  return should;
493
425
  });
494
- if (this.config.verbose) {
495
- console.log(`[PLUGIN] processreplicatorItem: applicableReplicators for resource=${item.resourceName}:`, applicableReplicators.map(r => r.driver));
496
- }
426
+
497
427
  if (applicableReplicators.length === 0) {
498
- if (this.config.verbose) {
499
- console.log('[PLUGIN] processreplicatorItem: No applicable replicators for resource', item.resourceName);
500
- }
501
- return { success: true, skipped: true, reason: 'no_applicable_replicators' };
428
+ return;
502
429
  }
503
430
 
504
- const results = [];
505
-
506
- for (const replicator of applicableReplicators) {
507
- let result;
508
- let ok, err;
509
- if (this.config.verbose) {
510
- console.log('[PLUGIN] processReplicatorItem', {
511
- resource: item.resourceName,
431
+ const promises = applicableReplicators.map(async (replicator) => {
432
+ try {
433
+ const [ok, err, result] = await tryFn(() =>
434
+ replicator.replicate(item.resourceName, item.operation, item.data, item.recordId, item.beforeData)
435
+ );
436
+
437
+ if (!ok) {
438
+ this.emit('replicator_error', {
439
+ replicator: replicator.name || replicator.id,
440
+ resourceName: item.resourceName,
441
+ operation: item.operation,
442
+ recordId: item.recordId,
443
+ error: err.message
444
+ });
445
+
446
+ if (this.config.logErrors && this.database) {
447
+ await this.logError(replicator, item.resourceName, item.operation, item.recordId, item.data, err);
448
+ }
449
+
450
+ return { success: false, error: err.message };
451
+ }
452
+
453
+ this.emit('replicated', {
454
+ replicator: replicator.name || replicator.id,
455
+ resourceName: item.resourceName,
512
456
  operation: item.operation,
513
- data: item.data,
514
- beforeData: item.beforeData,
515
- replicator: replicator.instance?.constructor?.name
457
+ recordId: item.recordId,
458
+ result,
459
+ success: true
516
460
  });
517
- }
518
- if (replicator.instance && replicator.instance.constructor && replicator.instance.constructor.name === 'S3dbReplicator') {
519
- [ok, err, result] = await tryFn(() =>
520
- replicator.instance.replicate({
521
- resource: item.resourceName,
522
- operation: item.operation,
523
- data: item.data,
524
- id: item.recordId,
525
- beforeData: item.beforeData
526
- })
527
- );
528
- } else {
529
- [ok, err, result] = await tryFn(() =>
530
- replicator.instance.replicate(
531
- item.resourceName,
532
- item.operation,
533
- item.data,
534
- item.recordId,
535
- item.beforeData
536
- )
537
- );
538
- }
539
- // Remove or comment out this line:
540
- // console.log('[PLUGIN] replicate result', { ok, err, result });
541
- results.push({
542
- replicatorId: replicator.id,
543
- driver: replicator.driver,
544
- success: result && result.success,
545
- error: result && result.error,
546
- skipped: result && result.skipped
461
+
462
+ return { success: true, result };
463
+ } catch (error) {
464
+ this.emit('replicator_error', {
465
+ replicator: replicator.name || replicator.id,
466
+ resourceName: item.resourceName,
467
+ operation: item.operation,
468
+ recordId: item.recordId,
469
+ error: error.message
547
470
  });
548
- }
549
471
 
550
- return {
551
- success: results.every(r => r.success || r.skipped),
552
- results
553
- };
472
+ if (this.config.logErrors && this.database) {
473
+ await this.logError(replicator, item.resourceName, item.operation, item.recordId, item.data, error);
474
+ }
475
+
476
+ return { success: false, error: error.message };
477
+ }
478
+ });
479
+
480
+ return Promise.allSettled(promises);
554
481
  }
555
482
 
556
483
  async logreplicator(item) {
557
- // Use sempre a referência salva
484
+ // Always use the saved reference
558
485
  const logRes = this.replicatorLog || this.database.resources[normalizeResourceName(this.config.replicatorLogResource)];
559
486
  if (!logRes) {
560
- if (this.config.verbose) {
561
- console.error('[PLUGIN] replicator log resource not found!');
562
- }
563
487
  if (this.database) {
564
- if (this.config.verbose) {
565
- console.warn('[PLUGIN] database.resources keys:', Object.keys(this.database.resources));
566
- }
567
488
  if (this.database.options && this.database.options.connectionString) {
568
- if (this.config.verbose) {
569
- console.warn('[PLUGIN] database connectionString:', this.database.options.connectionString);
570
- }
571
489
  }
572
490
  }
573
491
  this.emit('replicator.log.failed', { error: 'replicator log resource not found', item });
574
492
  return;
575
493
  }
576
- // Corrigir campos obrigatórios do log resource
494
+ // Fix required fields of log resource
577
495
  const logItem = {
578
496
  id: item.id || `repl-${Date.now()}-${Math.random().toString(36).slice(2)}`,
579
497
  resource: item.resource || item.resourceName || '',
@@ -585,9 +503,6 @@ export class ReplicatorPlugin extends Plugin {
585
503
  try {
586
504
  await logRes.insert(logItem);
587
505
  } catch (err) {
588
- if (this.config.verbose) {
589
- console.error('[PLUGIN] Error writing to replicator log:', err);
590
- }
591
506
  this.emit('replicator.log.failed', { error: err, item });
592
507
  }
593
508
  }
@@ -610,7 +525,7 @@ export class ReplicatorPlugin extends Plugin {
610
525
  async getreplicatorStats() {
611
526
  const replicatorStats = await Promise.all(
612
527
  this.replicators.map(async (replicator) => {
613
- const status = await replicator.instance.getStatus();
528
+ const status = await replicator.getStatus();
614
529
  return {
615
530
  id: replicator.id,
616
531
  driver: replicator.driver,
@@ -688,9 +603,7 @@ export class ReplicatorPlugin extends Plugin {
688
603
  if (ok) {
689
604
  retried++;
690
605
  } else {
691
- if (this.config.verbose) {
692
- console.error('Failed to retry replicator:', err);
693
- }
606
+ // Retry failed, continue
694
607
  }
695
608
  }
696
609
 
@@ -708,14 +621,14 @@ export class ReplicatorPlugin extends Plugin {
708
621
  for (const resourceName in this.database.resources) {
709
622
  if (normalizeResourceName(resourceName) === normalizeResourceName('replicator_logs')) continue;
710
623
 
711
- if (replicator.instance.shouldReplicateResource(resourceName)) {
624
+ if (replicator.shouldReplicateResource(resourceName)) {
712
625
  this.emit('replicator.sync.resource', { resourceName, replicatorId });
713
626
 
714
627
  const resource = this.database.resources[resourceName];
715
628
  const allRecords = await resource.getAll();
716
629
 
717
630
  for (const record of allRecords) {
718
- await replicator.instance.replicate(resourceName, 'insert', record, record.id);
631
+ await replicator.replicate(resourceName, 'insert', record, record.id);
719
632
  }
720
633
  }
721
634
  }
@@ -724,44 +637,28 @@ export class ReplicatorPlugin extends Plugin {
724
637
  }
725
638
 
726
639
  async cleanup() {
727
- if (this.config.verbose) {
728
- console.log('[PLUGIN][CLEANUP] Cleaning up ReplicatorPlugin');
729
- }
730
- // Remove all event listeners installed on resources
731
- if (this._installedListeners && Array.isArray(this._installedListeners)) {
732
- for (const resource of this._installedListeners) {
733
- if (resource && typeof resource.removeAllListeners === 'function') {
734
- resource.removeAllListeners('insert');
735
- resource.removeAllListeners('update');
736
- resource.removeAllListeners('delete');
737
- }
738
- resource._replicatorListenersInstalled = false;
739
- }
740
- this._installedListeners = [];
741
- }
742
- // Remove all event listeners from the database
743
- if (this.database && typeof this.database.removeAllListeners === 'function') {
744
- this.database.removeAllListeners();
745
- }
746
- // Cleanup all replicator instances
747
- if (this.replicators && Array.isArray(this.replicators)) {
748
- for (const rep of this.replicators) {
749
- if (rep.instance && typeof rep.instance.cleanup === 'function') {
750
- await rep.instance.cleanup();
751
- }
640
+ try {
641
+ if (this.replicators && this.replicators.length > 0) {
642
+ const cleanupPromises = this.replicators.map(async (replicator) => {
643
+ try {
644
+ if (replicator && typeof replicator.cleanup === 'function') {
645
+ await replicator.cleanup();
646
+ }
647
+ } catch (error) {
648
+ // Silent cleanup errors
649
+ }
650
+ });
651
+
652
+ await Promise.allSettled(cleanupPromises);
752
653
  }
654
+
753
655
  this.replicators = [];
754
- }
755
- // Clear other internal state
756
- this.queue = [];
757
- this.isProcessing = false;
758
- this.stats = {
759
- totalOperations: 0,
760
- totalErrors: 0,
761
- lastError: null,
762
- };
763
- if (this.config.verbose) {
764
- console.log('[PLUGIN][CLEANUP] ReplicatorPlugin cleanup complete');
656
+ this.database = null;
657
+ this.eventListenersInstalled.clear();
658
+
659
+ this.removeAllListeners();
660
+ } catch (error) {
661
+ // Silent cleanup errors
765
662
  }
766
663
  }
767
664
  }