s3db.js 7.2.1 → 7.3.2
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.
- package/dist/s3db.cjs.js +1241 -378
- package/dist/s3db.cjs.min.js +1 -1
- package/dist/s3db.es.js +1242 -379
- package/dist/s3db.es.min.js +1 -1
- package/dist/s3db.iife.js +1240 -377
- package/dist/s3db.iife.min.js +1 -1
- package/package.json +30 -24
- package/src/behaviors/body-only.js +2 -2
- package/src/behaviors/truncate-data.js +2 -2
- package/src/client.class.js +1 -1
- package/src/database.class.js +1 -1
- package/src/errors.js +1 -1
- package/src/plugins/audit.plugin.js +5 -5
- package/src/plugins/cache/filesystem-cache.class.js +661 -0
- package/src/plugins/cache/index.js +4 -0
- package/src/plugins/cache/partition-aware-filesystem-cache.class.js +480 -0
- package/src/plugins/cache/s3-cache.class.js +1 -1
- package/src/plugins/cache.plugin.js +159 -9
- package/src/plugins/consumers/index.js +3 -3
- package/src/plugins/consumers/sqs-consumer.js +2 -2
- package/src/plugins/fulltext.plugin.js +5 -5
- package/src/plugins/metrics.plugin.js +2 -2
- package/src/plugins/queue-consumer.plugin.js +3 -3
- package/src/plugins/replicator.plugin.js +259 -362
- package/src/plugins/replicators/s3db-replicator.class.js +35 -19
- package/src/plugins/replicators/sqs-replicator.class.js +17 -5
- package/src/resource.class.js +14 -14
- package/src/schema.class.js +3 -3
|
@@ -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
|
-
|
|
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.
|
|
151
|
-
this.
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
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 =
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
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,
|
|
251
|
-
} catch (
|
|
252
|
-
|
|
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
|
-
|
|
258
|
-
|
|
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
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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: '
|
|
235
|
+
behavior: 'body-overflow',
|
|
286
236
|
attributes: {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
data: '
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
|
343
|
-
this.installEventListeners(resource);
|
|
257
|
+
if (resource) {
|
|
258
|
+
this.installEventListeners(resource, database, this);
|
|
344
259
|
}
|
|
345
260
|
return resource;
|
|
346
261
|
};
|
|
347
|
-
|
|
348
|
-
|
|
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
|
-
|
|
357
|
-
|
|
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
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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
|
-
|
|
404
|
-
if (
|
|
405
|
-
|
|
406
|
-
|
|
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
|
-
|
|
409
|
-
|
|
312
|
+
return filtered;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
async uploadMetadataFile(database) {
|
|
316
|
+
if (typeof database.uploadMetadataFile === 'function') {
|
|
317
|
+
await database.uploadMetadataFile();
|
|
410
318
|
}
|
|
411
|
-
|
|
412
|
-
|
|
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
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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
|
-
|
|
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
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
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
|
-
|
|
466
|
-
|
|
467
|
-
this.
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
495
|
-
console.log(`[PLUGIN] processreplicatorItem: applicableReplicators for resource=${item.resourceName}:`, applicableReplicators.map(r => r.driver));
|
|
496
|
-
}
|
|
426
|
+
|
|
497
427
|
if (applicableReplicators.length === 0) {
|
|
498
|
-
|
|
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
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
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
|
-
|
|
514
|
-
|
|
515
|
-
|
|
457
|
+
recordId: item.recordId,
|
|
458
|
+
result,
|
|
459
|
+
success: true
|
|
516
460
|
});
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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
|
-
|
|
551
|
-
|
|
552
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
}
|
|
738
|
-
|
|
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
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
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
|
}
|