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.
package/dist/s3db.cjs.js CHANGED
@@ -7917,614 +7917,6 @@ class MetricsPlugin extends plugin_class_default {
7917
7917
  }
7918
7918
  }
7919
7919
 
7920
- class BaseReplicator extends EventEmitter {
7921
- constructor(config = {}) {
7922
- super();
7923
- this.config = config;
7924
- this.name = this.constructor.name;
7925
- this.enabled = config.enabled !== false;
7926
- }
7927
- /**
7928
- * Initialize the replicator
7929
- * @param {Object} database - The s3db database instance
7930
- * @returns {Promise<void>}
7931
- */
7932
- async initialize(database) {
7933
- this.database = database;
7934
- this.emit("initialized", { replicator: this.name });
7935
- }
7936
- /**
7937
- * Replicate data to the target
7938
- * @param {string} resourceName - Name of the resource being replicated
7939
- * @param {string} operation - Operation type (insert, update, delete)
7940
- * @param {Object} data - The data to replicate
7941
- * @param {string} id - Record ID
7942
- * @returns {Promise<Object>} replicator result
7943
- */
7944
- async replicate(resourceName, operation, data, id) {
7945
- throw new Error(`replicate() method must be implemented by ${this.name}`);
7946
- }
7947
- /**
7948
- * Replicate multiple records in batch
7949
- * @param {string} resourceName - Name of the resource being replicated
7950
- * @param {Array} records - Array of records to replicate
7951
- * @returns {Promise<Object>} Batch replicator result
7952
- */
7953
- async replicateBatch(resourceName, records) {
7954
- throw new Error(`replicateBatch() method must be implemented by ${this.name}`);
7955
- }
7956
- /**
7957
- * Test the connection to the target
7958
- * @returns {Promise<boolean>} True if connection is successful
7959
- */
7960
- async testConnection() {
7961
- throw new Error(`testConnection() method must be implemented by ${this.name}`);
7962
- }
7963
- /**
7964
- * Get replicator status and statistics
7965
- * @returns {Promise<Object>} Status information
7966
- */
7967
- async getStatus() {
7968
- return {
7969
- name: this.name,
7970
- // Removed: enabled: this.enabled,
7971
- config: this.config,
7972
- connected: false
7973
- };
7974
- }
7975
- /**
7976
- * Cleanup resources
7977
- * @returns {Promise<void>}
7978
- */
7979
- async cleanup() {
7980
- this.emit("cleanup", { replicator: this.name });
7981
- }
7982
- /**
7983
- * Validate replicator configuration
7984
- * @returns {Object} Validation result
7985
- */
7986
- validateConfig() {
7987
- return { isValid: true, errors: [] };
7988
- }
7989
- }
7990
- var base_replicator_class_default = BaseReplicator;
7991
-
7992
- class BigqueryReplicator extends base_replicator_class_default {
7993
- constructor(config = {}, resources = {}) {
7994
- super(config);
7995
- this.projectId = config.projectId;
7996
- this.datasetId = config.datasetId;
7997
- this.bigqueryClient = null;
7998
- this.credentials = config.credentials;
7999
- this.location = config.location || "US";
8000
- this.logTable = config.logTable;
8001
- this.resources = this.parseResourcesConfig(resources);
8002
- }
8003
- parseResourcesConfig(resources) {
8004
- const parsed = {};
8005
- for (const [resourceName, config] of Object.entries(resources)) {
8006
- if (typeof config === "string") {
8007
- parsed[resourceName] = [{
8008
- table: config,
8009
- actions: ["insert"],
8010
- transform: null
8011
- }];
8012
- } else if (Array.isArray(config)) {
8013
- parsed[resourceName] = config.map((item) => {
8014
- if (typeof item === "string") {
8015
- return { table: item, actions: ["insert"], transform: null };
8016
- }
8017
- return {
8018
- table: item.table,
8019
- actions: item.actions || ["insert"],
8020
- transform: item.transform || null
8021
- };
8022
- });
8023
- } else if (typeof config === "object") {
8024
- parsed[resourceName] = [{
8025
- table: config.table,
8026
- actions: config.actions || ["insert"],
8027
- transform: config.transform || null
8028
- }];
8029
- }
8030
- }
8031
- return parsed;
8032
- }
8033
- validateConfig() {
8034
- const errors = [];
8035
- if (!this.projectId) errors.push("projectId is required");
8036
- if (!this.datasetId) errors.push("datasetId is required");
8037
- if (Object.keys(this.resources).length === 0) errors.push("At least one resource must be configured");
8038
- for (const [resourceName, tables] of Object.entries(this.resources)) {
8039
- for (const tableConfig of tables) {
8040
- if (!tableConfig.table) {
8041
- errors.push(`Table name is required for resource '${resourceName}'`);
8042
- }
8043
- if (!Array.isArray(tableConfig.actions) || tableConfig.actions.length === 0) {
8044
- errors.push(`Actions array is required for resource '${resourceName}'`);
8045
- }
8046
- const validActions = ["insert", "update", "delete"];
8047
- const invalidActions = tableConfig.actions.filter((action) => !validActions.includes(action));
8048
- if (invalidActions.length > 0) {
8049
- errors.push(`Invalid actions for resource '${resourceName}': ${invalidActions.join(", ")}. Valid actions: ${validActions.join(", ")}`);
8050
- }
8051
- if (tableConfig.transform && typeof tableConfig.transform !== "function") {
8052
- errors.push(`Transform must be a function for resource '${resourceName}'`);
8053
- }
8054
- }
8055
- }
8056
- return { isValid: errors.length === 0, errors };
8057
- }
8058
- async initialize(database) {
8059
- await super.initialize(database);
8060
- const [ok, err, sdk] = await try_fn_default(() => import('@google-cloud/bigquery'));
8061
- if (!ok) {
8062
- this.emit("initialization_error", { replicator: this.name, error: err.message });
8063
- throw err;
8064
- }
8065
- const { BigQuery } = sdk;
8066
- this.bigqueryClient = new BigQuery({
8067
- projectId: this.projectId,
8068
- credentials: this.credentials,
8069
- location: this.location
8070
- });
8071
- this.emit("initialized", {
8072
- replicator: this.name,
8073
- projectId: this.projectId,
8074
- datasetId: this.datasetId,
8075
- resources: Object.keys(this.resources)
8076
- });
8077
- }
8078
- shouldReplicateResource(resourceName) {
8079
- return this.resources.hasOwnProperty(resourceName);
8080
- }
8081
- shouldReplicateAction(resourceName, operation) {
8082
- if (!this.resources[resourceName]) return false;
8083
- return this.resources[resourceName].some(
8084
- (tableConfig) => tableConfig.actions.includes(operation)
8085
- );
8086
- }
8087
- getTablesForResource(resourceName, operation) {
8088
- if (!this.resources[resourceName]) return [];
8089
- return this.resources[resourceName].filter((tableConfig) => tableConfig.actions.includes(operation)).map((tableConfig) => ({
8090
- table: tableConfig.table,
8091
- transform: tableConfig.transform
8092
- }));
8093
- }
8094
- applyTransform(data, transformFn) {
8095
- if (!transformFn) return data;
8096
- let transformedData = JSON.parse(JSON.stringify(data));
8097
- if (transformedData._length) delete transformedData._length;
8098
- return transformFn(transformedData);
8099
- }
8100
- async replicate(resourceName, operation, data, id, beforeData = null) {
8101
- if (!this.enabled || !this.shouldReplicateResource(resourceName)) {
8102
- return { skipped: true, reason: "resource_not_included" };
8103
- }
8104
- if (!this.shouldReplicateAction(resourceName, operation)) {
8105
- return { skipped: true, reason: "action_not_included" };
8106
- }
8107
- const tableConfigs = this.getTablesForResource(resourceName, operation);
8108
- if (tableConfigs.length === 0) {
8109
- return { skipped: true, reason: "no_tables_for_action" };
8110
- }
8111
- const results = [];
8112
- const errors = [];
8113
- const [ok, err, result] = await try_fn_default(async () => {
8114
- const dataset = this.bigqueryClient.dataset(this.datasetId);
8115
- for (const tableConfig of tableConfigs) {
8116
- const [okTable, errTable] = await try_fn_default(async () => {
8117
- const table = dataset.table(tableConfig.table);
8118
- let job;
8119
- if (operation === "insert") {
8120
- const transformedData = this.applyTransform(data, tableConfig.transform);
8121
- job = await table.insert([transformedData]);
8122
- } else if (operation === "update") {
8123
- const transformedData = this.applyTransform(data, tableConfig.transform);
8124
- const keys = Object.keys(transformedData).filter((k) => k !== "id");
8125
- const setClause = keys.map((k) => `${k} = @${k}`).join(", ");
8126
- const params = { id, ...transformedData };
8127
- const query = `UPDATE \`${this.projectId}.${this.datasetId}.${tableConfig.table}\` SET ${setClause} WHERE id = @id`;
8128
- const maxRetries = 2;
8129
- let lastError = null;
8130
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
8131
- try {
8132
- const [updateJob] = await this.bigqueryClient.createQueryJob({
8133
- query,
8134
- params,
8135
- location: this.location
8136
- });
8137
- await updateJob.getQueryResults();
8138
- job = [updateJob];
8139
- break;
8140
- } catch (error) {
8141
- lastError = error;
8142
- if (error?.message?.includes("streaming buffer") && attempt < maxRetries) {
8143
- const delaySeconds = 30;
8144
- await new Promise((resolve) => setTimeout(resolve, delaySeconds * 1e3));
8145
- continue;
8146
- }
8147
- throw error;
8148
- }
8149
- }
8150
- if (!job) throw lastError;
8151
- } else if (operation === "delete") {
8152
- const query = `DELETE FROM \`${this.projectId}.${this.datasetId}.${tableConfig.table}\` WHERE id = @id`;
8153
- const [deleteJob] = await this.bigqueryClient.createQueryJob({
8154
- query,
8155
- params: { id },
8156
- location: this.location
8157
- });
8158
- await deleteJob.getQueryResults();
8159
- job = [deleteJob];
8160
- } else {
8161
- throw new Error(`Unsupported operation: ${operation}`);
8162
- }
8163
- results.push({
8164
- table: tableConfig.table,
8165
- success: true,
8166
- jobId: job[0]?.id
8167
- });
8168
- });
8169
- if (!okTable) {
8170
- errors.push({
8171
- table: tableConfig.table,
8172
- error: errTable.message
8173
- });
8174
- }
8175
- }
8176
- if (this.logTable) {
8177
- const [okLog, errLog] = await try_fn_default(async () => {
8178
- const logTable = dataset.table(this.logTable);
8179
- await logTable.insert([{
8180
- resource_name: resourceName,
8181
- operation,
8182
- record_id: id,
8183
- data: JSON.stringify(data),
8184
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
8185
- source: "s3db-replicator"
8186
- }]);
8187
- });
8188
- if (!okLog) {
8189
- }
8190
- }
8191
- const success = errors.length === 0;
8192
- this.emit("replicated", {
8193
- replicator: this.name,
8194
- resourceName,
8195
- operation,
8196
- id,
8197
- tables: tableConfigs.map((t) => t.table),
8198
- results,
8199
- errors,
8200
- success
8201
- });
8202
- return {
8203
- success,
8204
- results,
8205
- errors,
8206
- tables: tableConfigs.map((t) => t.table)
8207
- };
8208
- });
8209
- if (ok) return result;
8210
- this.emit("replicator_error", {
8211
- replicator: this.name,
8212
- resourceName,
8213
- operation,
8214
- id,
8215
- error: err.message
8216
- });
8217
- return { success: false, error: err.message };
8218
- }
8219
- async replicateBatch(resourceName, records) {
8220
- const results = [];
8221
- const errors = [];
8222
- for (const record of records) {
8223
- const [ok, err, res] = await try_fn_default(() => this.replicate(
8224
- resourceName,
8225
- record.operation,
8226
- record.data,
8227
- record.id,
8228
- record.beforeData
8229
- ));
8230
- if (ok) results.push(res);
8231
- else errors.push({ id: record.id, error: err.message });
8232
- }
8233
- return {
8234
- success: errors.length === 0,
8235
- results,
8236
- errors
8237
- };
8238
- }
8239
- async testConnection() {
8240
- const [ok, err] = await try_fn_default(async () => {
8241
- if (!this.bigqueryClient) await this.initialize();
8242
- const dataset = this.bigqueryClient.dataset(this.datasetId);
8243
- await dataset.getMetadata();
8244
- return true;
8245
- });
8246
- if (ok) return true;
8247
- this.emit("connection_error", { replicator: this.name, error: err.message });
8248
- return false;
8249
- }
8250
- async cleanup() {
8251
- }
8252
- getStatus() {
8253
- return {
8254
- ...super.getStatus(),
8255
- projectId: this.projectId,
8256
- datasetId: this.datasetId,
8257
- resources: this.resources,
8258
- logTable: this.logTable
8259
- };
8260
- }
8261
- }
8262
- var bigquery_replicator_class_default = BigqueryReplicator;
8263
-
8264
- class PostgresReplicator extends base_replicator_class_default {
8265
- constructor(config = {}, resources = {}) {
8266
- super(config);
8267
- this.connectionString = config.connectionString;
8268
- this.host = config.host;
8269
- this.port = config.port || 5432;
8270
- this.database = config.database;
8271
- this.user = config.user;
8272
- this.password = config.password;
8273
- this.client = null;
8274
- this.ssl = config.ssl;
8275
- this.logTable = config.logTable;
8276
- this.resources = this.parseResourcesConfig(resources);
8277
- }
8278
- parseResourcesConfig(resources) {
8279
- const parsed = {};
8280
- for (const [resourceName, config] of Object.entries(resources)) {
8281
- if (typeof config === "string") {
8282
- parsed[resourceName] = [{
8283
- table: config,
8284
- actions: ["insert"]
8285
- }];
8286
- } else if (Array.isArray(config)) {
8287
- parsed[resourceName] = config.map((item) => {
8288
- if (typeof item === "string") {
8289
- return { table: item, actions: ["insert"] };
8290
- }
8291
- return {
8292
- table: item.table,
8293
- actions: item.actions || ["insert"]
8294
- };
8295
- });
8296
- } else if (typeof config === "object") {
8297
- parsed[resourceName] = [{
8298
- table: config.table,
8299
- actions: config.actions || ["insert"]
8300
- }];
8301
- }
8302
- }
8303
- return parsed;
8304
- }
8305
- validateConfig() {
8306
- const errors = [];
8307
- if (!this.connectionString && (!this.host || !this.database)) {
8308
- errors.push("Either connectionString or host+database must be provided");
8309
- }
8310
- if (Object.keys(this.resources).length === 0) {
8311
- errors.push("At least one resource must be configured");
8312
- }
8313
- for (const [resourceName, tables] of Object.entries(this.resources)) {
8314
- for (const tableConfig of tables) {
8315
- if (!tableConfig.table) {
8316
- errors.push(`Table name is required for resource '${resourceName}'`);
8317
- }
8318
- if (!Array.isArray(tableConfig.actions) || tableConfig.actions.length === 0) {
8319
- errors.push(`Actions array is required for resource '${resourceName}'`);
8320
- }
8321
- const validActions = ["insert", "update", "delete"];
8322
- const invalidActions = tableConfig.actions.filter((action) => !validActions.includes(action));
8323
- if (invalidActions.length > 0) {
8324
- errors.push(`Invalid actions for resource '${resourceName}': ${invalidActions.join(", ")}. Valid actions: ${validActions.join(", ")}`);
8325
- }
8326
- }
8327
- }
8328
- return { isValid: errors.length === 0, errors };
8329
- }
8330
- async initialize(database) {
8331
- await super.initialize(database);
8332
- const [ok, err, sdk] = await try_fn_default(() => import('pg'));
8333
- if (!ok) {
8334
- this.emit("initialization_error", {
8335
- replicator: this.name,
8336
- error: err.message
8337
- });
8338
- throw err;
8339
- }
8340
- const { Client } = sdk;
8341
- const config = this.connectionString ? {
8342
- connectionString: this.connectionString,
8343
- ssl: this.ssl
8344
- } : {
8345
- host: this.host,
8346
- port: this.port,
8347
- database: this.database,
8348
- user: this.user,
8349
- password: this.password,
8350
- ssl: this.ssl
8351
- };
8352
- this.client = new Client(config);
8353
- await this.client.connect();
8354
- if (this.logTable) {
8355
- await this.createLogTableIfNotExists();
8356
- }
8357
- this.emit("initialized", {
8358
- replicator: this.name,
8359
- database: this.database || "postgres",
8360
- resources: Object.keys(this.resources)
8361
- });
8362
- }
8363
- async createLogTableIfNotExists() {
8364
- const createTableQuery = `
8365
- CREATE TABLE IF NOT EXISTS ${this.logTable} (
8366
- id SERIAL PRIMARY KEY,
8367
- resource_name VARCHAR(255) NOT NULL,
8368
- operation VARCHAR(50) NOT NULL,
8369
- record_id VARCHAR(255) NOT NULL,
8370
- data JSONB,
8371
- timestamp TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
8372
- source VARCHAR(100) DEFAULT 's3db-replicator',
8373
- created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
8374
- );
8375
- CREATE INDEX IF NOT EXISTS idx_${this.logTable}_resource_name ON ${this.logTable}(resource_name);
8376
- CREATE INDEX IF NOT EXISTS idx_${this.logTable}_operation ON ${this.logTable}(operation);
8377
- CREATE INDEX IF NOT EXISTS idx_${this.logTable}_record_id ON ${this.logTable}(record_id);
8378
- CREATE INDEX IF NOT EXISTS idx_${this.logTable}_timestamp ON ${this.logTable}(timestamp);
8379
- `;
8380
- await this.client.query(createTableQuery);
8381
- }
8382
- shouldReplicateResource(resourceName) {
8383
- return this.resources.hasOwnProperty(resourceName);
8384
- }
8385
- shouldReplicateAction(resourceName, operation) {
8386
- if (!this.resources[resourceName]) return false;
8387
- return this.resources[resourceName].some(
8388
- (tableConfig) => tableConfig.actions.includes(operation)
8389
- );
8390
- }
8391
- getTablesForResource(resourceName, operation) {
8392
- if (!this.resources[resourceName]) return [];
8393
- return this.resources[resourceName].filter((tableConfig) => tableConfig.actions.includes(operation)).map((tableConfig) => tableConfig.table);
8394
- }
8395
- async replicate(resourceName, operation, data, id, beforeData = null) {
8396
- if (!this.enabled || !this.shouldReplicateResource(resourceName)) {
8397
- return { skipped: true, reason: "resource_not_included" };
8398
- }
8399
- if (!this.shouldReplicateAction(resourceName, operation)) {
8400
- return { skipped: true, reason: "action_not_included" };
8401
- }
8402
- const tables = this.getTablesForResource(resourceName, operation);
8403
- if (tables.length === 0) {
8404
- return { skipped: true, reason: "no_tables_for_action" };
8405
- }
8406
- const results = [];
8407
- const errors = [];
8408
- const [ok, err, result] = await try_fn_default(async () => {
8409
- for (const table of tables) {
8410
- const [okTable, errTable] = await try_fn_default(async () => {
8411
- let result2;
8412
- if (operation === "insert") {
8413
- const keys = Object.keys(data);
8414
- const values = keys.map((k) => data[k]);
8415
- const columns = keys.map((k) => `"${k}"`).join(", ");
8416
- const params = keys.map((_, i) => `$${i + 1}`).join(", ");
8417
- const sql = `INSERT INTO ${table} (${columns}) VALUES (${params}) ON CONFLICT (id) DO NOTHING RETURNING *`;
8418
- result2 = await this.client.query(sql, values);
8419
- } else if (operation === "update") {
8420
- const keys = Object.keys(data).filter((k) => k !== "id");
8421
- const setClause = keys.map((k, i) => `"${k}"=$${i + 1}`).join(", ");
8422
- const values = keys.map((k) => data[k]);
8423
- values.push(id);
8424
- const sql = `UPDATE ${table} SET ${setClause} WHERE id=$${keys.length + 1} RETURNING *`;
8425
- result2 = await this.client.query(sql, values);
8426
- } else if (operation === "delete") {
8427
- const sql = `DELETE FROM ${table} WHERE id=$1 RETURNING *`;
8428
- result2 = await this.client.query(sql, [id]);
8429
- } else {
8430
- throw new Error(`Unsupported operation: ${operation}`);
8431
- }
8432
- results.push({
8433
- table,
8434
- success: true,
8435
- rows: result2.rows,
8436
- rowCount: result2.rowCount
8437
- });
8438
- });
8439
- if (!okTable) {
8440
- errors.push({
8441
- table,
8442
- error: errTable.message
8443
- });
8444
- }
8445
- }
8446
- if (this.logTable) {
8447
- const [okLog, errLog] = await try_fn_default(async () => {
8448
- await this.client.query(
8449
- `INSERT INTO ${this.logTable} (resource_name, operation, record_id, data, timestamp, source) VALUES ($1, $2, $3, $4, $5, $6)`,
8450
- [resourceName, operation, id, JSON.stringify(data), (/* @__PURE__ */ new Date()).toISOString(), "s3db-replicator"]
8451
- );
8452
- });
8453
- if (!okLog) {
8454
- }
8455
- }
8456
- const success = errors.length === 0;
8457
- this.emit("replicated", {
8458
- replicator: this.name,
8459
- resourceName,
8460
- operation,
8461
- id,
8462
- tables,
8463
- results,
8464
- errors,
8465
- success
8466
- });
8467
- return {
8468
- success,
8469
- results,
8470
- errors,
8471
- tables
8472
- };
8473
- });
8474
- if (ok) return result;
8475
- this.emit("replicator_error", {
8476
- replicator: this.name,
8477
- resourceName,
8478
- operation,
8479
- id,
8480
- error: err.message
8481
- });
8482
- return { success: false, error: err.message };
8483
- }
8484
- async replicateBatch(resourceName, records) {
8485
- const results = [];
8486
- const errors = [];
8487
- for (const record of records) {
8488
- const [ok, err, res] = await try_fn_default(() => this.replicate(
8489
- resourceName,
8490
- record.operation,
8491
- record.data,
8492
- record.id,
8493
- record.beforeData
8494
- ));
8495
- if (ok) results.push(res);
8496
- else errors.push({ id: record.id, error: err.message });
8497
- }
8498
- return {
8499
- success: errors.length === 0,
8500
- results,
8501
- errors
8502
- };
8503
- }
8504
- async testConnection() {
8505
- const [ok, err] = await try_fn_default(async () => {
8506
- if (!this.client) await this.initialize();
8507
- await this.client.query("SELECT 1");
8508
- return true;
8509
- });
8510
- if (ok) return true;
8511
- this.emit("connection_error", { replicator: this.name, error: err.message });
8512
- return false;
8513
- }
8514
- async cleanup() {
8515
- if (this.client) await this.client.end();
8516
- }
8517
- getStatus() {
8518
- return {
8519
- ...super.getStatus(),
8520
- database: this.database || "postgres",
8521
- resources: this.resources,
8522
- logTable: this.logTable
8523
- };
8524
- }
8525
- }
8526
- var postgres_replicator_class_default = PostgresReplicator;
8527
-
8528
7920
  const S3_DEFAULT_REGION = "us-east-1";
8529
7921
  const S3_DEFAULT_ENDPOINT = "https://s3.us-east-1.amazonaws.com";
8530
7922
  class ConnectionString {
@@ -12242,7 +11634,7 @@ class Database extends EventEmitter {
12242
11634
  super();
12243
11635
  this.version = "1";
12244
11636
  this.s3dbVersion = (() => {
12245
- const [ok, err, version] = try_fn_default(() => true ? "7.2.0" : "latest");
11637
+ const [ok, err, version] = try_fn_default(() => true ? "7.2.1" : "latest");
12246
11638
  return ok ? version : "latest";
12247
11639
  })();
12248
11640
  this.resources = {};
@@ -12717,580 +12109,34 @@ class Database extends EventEmitter {
12717
12109
  class S3db extends Database {
12718
12110
  }
12719
12111
 
12720
- function normalizeResourceName$1(name) {
12721
- return typeof name === "string" ? name.trim().toLowerCase() : name;
12722
- }
12723
- class S3dbReplicator extends base_replicator_class_default {
12724
- constructor(config = {}, resources = [], client = null) {
12725
- super(config);
12726
- this.instanceId = Math.random().toString(36).slice(2, 10);
12727
- this.client = client;
12728
- this.connectionString = config.connectionString;
12729
- let normalizedResources = resources;
12730
- if (!resources) normalizedResources = {};
12731
- else if (Array.isArray(resources)) {
12732
- normalizedResources = {};
12733
- for (const res of resources) {
12734
- if (typeof res === "string") normalizedResources[normalizeResourceName$1(res)] = res;
12735
- }
12736
- } else if (typeof resources === "string") {
12737
- normalizedResources[normalizeResourceName$1(resources)] = resources;
12738
- }
12739
- this.resourcesMap = this._normalizeResources(normalizedResources);
12740
- }
12741
- _normalizeResources(resources) {
12742
- if (!resources) return {};
12743
- if (Array.isArray(resources)) {
12744
- const map = {};
12745
- for (const res of resources) {
12746
- if (typeof res === "string") map[normalizeResourceName$1(res)] = res;
12747
- else if (Array.isArray(res) && typeof res[0] === "string") map[normalizeResourceName$1(res[0])] = res;
12748
- else if (typeof res === "object" && res.resource) {
12749
- map[normalizeResourceName$1(res.resource)] = { ...res };
12750
- }
12751
- }
12752
- return map;
12753
- }
12754
- if (typeof resources === "object") {
12755
- const map = {};
12756
- for (const [src, dest] of Object.entries(resources)) {
12757
- const normSrc = normalizeResourceName$1(src);
12758
- if (typeof dest === "string") map[normSrc] = dest;
12759
- else if (Array.isArray(dest)) {
12760
- map[normSrc] = dest.map((item) => {
12761
- if (typeof item === "string") return item;
12762
- if (typeof item === "function") return item;
12763
- if (typeof item === "object" && item.resource) {
12764
- return { ...item };
12765
- }
12766
- return item;
12767
- });
12768
- } else if (typeof dest === "function") map[normSrc] = dest;
12769
- else if (typeof dest === "object" && dest.resource) {
12770
- map[normSrc] = { ...dest };
12771
- }
12772
- }
12773
- return map;
12774
- }
12775
- if (typeof resources === "function") {
12776
- return resources;
12777
- }
12778
- if (typeof resources === "string") {
12779
- const map = { [normalizeResourceName$1(resources)]: resources };
12780
- return map;
12781
- }
12782
- return {};
12783
- }
12784
- validateConfig() {
12785
- const errors = [];
12786
- if (!this.client && !this.connectionString) {
12787
- errors.push("You must provide a client or a connectionString");
12788
- }
12789
- if (!this.resourcesMap || typeof this.resourcesMap === "object" && Object.keys(this.resourcesMap).length === 0) {
12790
- errors.push("You must provide a resources map or array");
12791
- }
12792
- return { isValid: errors.length === 0, errors };
12793
- }
12794
- async initialize(database) {
12795
- try {
12796
- await super.initialize(database);
12797
- if (this.client) {
12798
- this.targetDatabase = this.client;
12799
- } else if (this.connectionString) {
12800
- const targetConfig = {
12801
- connectionString: this.connectionString,
12802
- region: this.region,
12803
- keyPrefix: this.keyPrefix,
12804
- verbose: this.config.verbose || false
12805
- };
12806
- this.targetDatabase = new S3db(targetConfig);
12807
- await this.targetDatabase.connect();
12808
- } else {
12809
- throw new Error("S3dbReplicator: No client or connectionString provided");
12810
- }
12811
- this.emit("connected", {
12812
- replicator: this.name,
12813
- target: this.connectionString || "client-provided"
12814
- });
12815
- } catch (err) {
12816
- throw err;
12817
- }
12818
- }
12819
- // Change signature to accept id
12820
- async replicate({ resource, operation, data, id: explicitId }) {
12821
- const normResource = normalizeResourceName$1(resource);
12822
- const destResource = this._resolveDestResource(normResource, data);
12823
- const destResourceObj = this._getDestResourceObj(destResource);
12824
- const transformedData = this._applyTransformer(normResource, data);
12825
- let result;
12826
- if (operation === "insert") {
12827
- result = await destResourceObj.insert(transformedData);
12828
- } else if (operation === "update") {
12829
- result = await destResourceObj.update(explicitId, transformedData);
12830
- } else if (operation === "delete") {
12831
- result = await destResourceObj.delete(explicitId);
12832
- } else {
12833
- throw new Error(`Invalid operation: ${operation}. Supported operations are: insert, update, delete`);
12834
- }
12835
- return result;
12836
- }
12837
- _applyTransformer(resource, data) {
12838
- const normResource = normalizeResourceName$1(resource);
12839
- const entry = this.resourcesMap[normResource];
12840
- let result;
12841
- if (!entry) return data;
12842
- if (Array.isArray(entry) && typeof entry[1] === "function") {
12843
- result = entry[1](data);
12844
- } else if (typeof entry === "function") {
12845
- result = entry(data);
12846
- } else if (typeof entry === "object") {
12847
- if (typeof entry.transform === "function") result = entry.transform(data);
12848
- else if (typeof entry.transformer === "function") result = entry.transformer(data);
12849
- } else {
12850
- result = data;
12851
- }
12852
- if (result && data && data.id && !result.id) result.id = data.id;
12853
- if (!result && data) result = data;
12854
- return result;
12855
- }
12856
- _resolveDestResource(resource, data) {
12857
- const normResource = normalizeResourceName$1(resource);
12858
- const entry = this.resourcesMap[normResource];
12859
- if (!entry) return resource;
12860
- if (Array.isArray(entry)) {
12861
- if (typeof entry[0] === "string") return entry[0];
12862
- if (typeof entry[0] === "object" && entry[0].resource) return entry[0].resource;
12863
- if (typeof entry[0] === "function") return resource;
12864
- }
12865
- if (typeof entry === "string") return entry;
12866
- if (typeof entry === "function") return resource;
12867
- if (typeof entry === "object" && entry.resource) return entry.resource;
12868
- return resource;
12869
- }
12870
- _getDestResourceObj(resource) {
12871
- if (!this.client || !this.client.resources) return null;
12872
- const available = Object.keys(this.client.resources);
12873
- const norm = normalizeResourceName$1(resource);
12874
- const found = available.find((r) => normalizeResourceName$1(r) === norm);
12875
- if (!found) {
12876
- throw new Error(`[S3dbReplicator] Destination resource not found: ${resource}. Available: ${available.join(", ")}`);
12877
- }
12878
- return this.client.resources[found];
12879
- }
12880
- async replicateBatch(resourceName, records) {
12881
- if (!this.enabled || !this.shouldReplicateResource(resourceName)) {
12882
- return { skipped: true, reason: "resource_not_included" };
12883
- }
12884
- const results = [];
12885
- const errors = [];
12886
- for (const record of records) {
12887
- const [ok, err, result] = await try_fn_default(() => this.replicate({
12888
- resource: resourceName,
12889
- operation: record.operation,
12890
- id: record.id,
12891
- data: record.data,
12892
- beforeData: record.beforeData
12893
- }));
12894
- if (ok) results.push(result);
12895
- else errors.push({ id: record.id, error: err.message });
12896
- }
12897
- this.emit("batch_replicated", {
12898
- replicator: this.name,
12899
- resourceName,
12900
- total: records.length,
12901
- successful: results.length,
12902
- errors: errors.length
12903
- });
12904
- return {
12905
- success: errors.length === 0,
12906
- results,
12907
- errors,
12908
- total: records.length
12909
- };
12910
- }
12911
- async testConnection() {
12912
- const [ok, err] = await try_fn_default(async () => {
12913
- if (!this.targetDatabase) {
12914
- await this.initialize(this.database);
12915
- }
12916
- await this.targetDatabase.listResources();
12917
- return true;
12918
- });
12919
- if (ok) return true;
12920
- this.emit("connection_error", {
12921
- replicator: this.name,
12922
- error: err.message
12923
- });
12924
- return false;
12925
- }
12926
- async getStatus() {
12927
- const baseStatus = await super.getStatus();
12928
- return {
12929
- ...baseStatus,
12930
- connected: !!this.targetDatabase,
12931
- targetDatabase: this.connectionString || "client-provided",
12932
- resources: Object.keys(this.resourcesMap || {}),
12933
- totalreplicators: this.listenerCount("replicated"),
12934
- totalErrors: this.listenerCount("replicator_error")
12935
- };
12936
- }
12937
- async cleanup() {
12938
- if (this.targetDatabase) {
12939
- this.targetDatabase.removeAllListeners();
12940
- }
12941
- await super.cleanup();
12942
- }
12943
- shouldReplicateResource(resource, action) {
12944
- const normResource = normalizeResourceName$1(resource);
12945
- const entry = this.resourcesMap[normResource];
12946
- if (!entry) return false;
12947
- if (!action) return true;
12948
- if (Array.isArray(entry)) {
12949
- for (const item of entry) {
12950
- if (typeof item === "object" && item.resource) {
12951
- if (item.actions && Array.isArray(item.actions)) {
12952
- if (item.actions.includes(action)) return true;
12953
- } else {
12954
- return true;
12955
- }
12956
- } else if (typeof item === "string" || typeof item === "function") {
12957
- return true;
12958
- }
12959
- }
12960
- return false;
12961
- }
12962
- if (typeof entry === "object" && entry.resource) {
12963
- if (entry.actions && Array.isArray(entry.actions)) {
12964
- return entry.actions.includes(action);
12965
- }
12966
- return true;
12967
- }
12968
- if (typeof entry === "string" || typeof entry === "function") {
12969
- return true;
12970
- }
12971
- return false;
12972
- }
12973
- }
12974
- var s3db_replicator_class_default = S3dbReplicator;
12975
-
12976
- class SqsReplicator extends base_replicator_class_default {
12977
- constructor(config = {}, resources = [], client = null) {
12978
- super(config);
12979
- this.resources = resources;
12980
- this.client = client;
12981
- this.queueUrl = config.queueUrl;
12982
- this.queues = config.queues || {};
12983
- this.defaultQueue = config.defaultQueue || config.defaultQueueUrl || config.queueUrlDefault;
12984
- this.region = config.region || "us-east-1";
12985
- this.sqsClient = client || null;
12986
- this.messageGroupId = config.messageGroupId;
12987
- this.deduplicationId = config.deduplicationId;
12988
- if (resources && typeof resources === "object") {
12989
- for (const [resourceName, resourceConfig] of Object.entries(resources)) {
12990
- if (resourceConfig.queueUrl) {
12991
- this.queues[resourceName] = resourceConfig.queueUrl;
12992
- }
12993
- }
12994
- }
12995
- }
12996
- validateConfig() {
12997
- const errors = [];
12998
- if (!this.queueUrl && Object.keys(this.queues).length === 0 && !this.defaultQueue && !this.resourceQueueMap) {
12999
- errors.push("Either queueUrl, queues object, defaultQueue, or resourceQueueMap must be provided");
13000
- }
13001
- return {
13002
- isValid: errors.length === 0,
13003
- errors
13004
- };
13005
- }
13006
- getQueueUrlsForResource(resource) {
13007
- if (this.resourceQueueMap && this.resourceQueueMap[resource]) {
13008
- return this.resourceQueueMap[resource];
13009
- }
13010
- if (this.queues[resource]) {
13011
- return [this.queues[resource]];
13012
- }
13013
- if (this.queueUrl) {
13014
- return [this.queueUrl];
13015
- }
13016
- if (this.defaultQueue) {
13017
- return [this.defaultQueue];
13018
- }
13019
- throw new Error(`No queue URL found for resource '${resource}'`);
13020
- }
13021
- _applyTransformer(resource, data) {
13022
- const entry = this.resources[resource];
13023
- let result = data;
13024
- if (!entry) return data;
13025
- if (typeof entry.transform === "function") {
13026
- result = entry.transform(data);
13027
- } else if (typeof entry.transformer === "function") {
13028
- result = entry.transformer(data);
13029
- }
13030
- return result || data;
13031
- }
13032
- /**
13033
- * Create standardized message structure
13034
- */
13035
- createMessage(resource, operation, data, id, beforeData = null) {
13036
- const baseMessage = {
13037
- resource,
13038
- // padronizado para 'resource'
13039
- action: operation,
13040
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
13041
- source: "s3db-replicator"
13042
- };
13043
- switch (operation) {
13044
- case "insert":
13045
- return {
13046
- ...baseMessage,
13047
- data
13048
- };
13049
- case "update":
13050
- return {
13051
- ...baseMessage,
13052
- before: beforeData,
13053
- data
13054
- };
13055
- case "delete":
13056
- return {
13057
- ...baseMessage,
13058
- data
13059
- };
13060
- default:
13061
- return {
13062
- ...baseMessage,
13063
- data
13064
- };
13065
- }
13066
- }
13067
- async initialize(database, client) {
13068
- await super.initialize(database);
13069
- if (!this.sqsClient) {
13070
- const [ok, err, sdk] = await try_fn_default(() => import('@aws-sdk/client-sqs'));
13071
- if (!ok) {
13072
- this.emit("initialization_error", {
13073
- replicator: this.name,
13074
- error: err.message
13075
- });
13076
- throw err;
13077
- }
13078
- const { SQSClient } = sdk;
13079
- this.sqsClient = client || new SQSClient({
13080
- region: this.region,
13081
- credentials: this.config.credentials
13082
- });
13083
- this.emit("initialized", {
13084
- replicator: this.name,
13085
- queueUrl: this.queueUrl,
13086
- queues: this.queues,
13087
- defaultQueue: this.defaultQueue
13088
- });
13089
- }
13090
- }
13091
- async replicate(resource, operation, data, id, beforeData = null) {
13092
- if (!this.enabled || !this.shouldReplicateResource(resource)) {
13093
- return { skipped: true, reason: "resource_not_included" };
13094
- }
13095
- const [ok, err, result] = await try_fn_default(async () => {
13096
- const { SendMessageCommand } = await import('@aws-sdk/client-sqs');
13097
- const queueUrls = this.getQueueUrlsForResource(resource);
13098
- const transformedData = this._applyTransformer(resource, data);
13099
- const message = this.createMessage(resource, operation, transformedData, id, beforeData);
13100
- const results = [];
13101
- for (const queueUrl of queueUrls) {
13102
- const command = new SendMessageCommand({
13103
- QueueUrl: queueUrl,
13104
- MessageBody: JSON.stringify(message),
13105
- MessageGroupId: this.messageGroupId,
13106
- MessageDeduplicationId: this.deduplicationId ? `${resource}:${operation}:${id}` : void 0
13107
- });
13108
- const result2 = await this.sqsClient.send(command);
13109
- results.push({ queueUrl, messageId: result2.MessageId });
13110
- this.emit("replicated", {
13111
- replicator: this.name,
13112
- resource,
13113
- operation,
13114
- id,
13115
- queueUrl,
13116
- messageId: result2.MessageId,
13117
- success: true
13118
- });
13119
- }
13120
- return { success: true, results };
13121
- });
13122
- if (ok) return result;
13123
- this.emit("replicator_error", {
13124
- replicator: this.name,
13125
- resource,
13126
- operation,
13127
- id,
13128
- error: err.message
13129
- });
13130
- return { success: false, error: err.message };
13131
- }
13132
- async replicateBatch(resource, records) {
13133
- if (!this.enabled || !this.shouldReplicateResource(resource)) {
13134
- return { skipped: true, reason: "resource_not_included" };
13135
- }
13136
- const [ok, err, result] = await try_fn_default(async () => {
13137
- const { SendMessageBatchCommand } = await import('@aws-sdk/client-sqs');
13138
- const queueUrls = this.getQueueUrlsForResource(resource);
13139
- const batchSize = 10;
13140
- const batches = [];
13141
- for (let i = 0; i < records.length; i += batchSize) {
13142
- batches.push(records.slice(i, i + batchSize));
13143
- }
13144
- const results = [];
13145
- const errors = [];
13146
- for (const batch of batches) {
13147
- const [okBatch, errBatch] = await try_fn_default(async () => {
13148
- const entries = batch.map((record, index) => ({
13149
- Id: `${record.id}-${index}`,
13150
- MessageBody: JSON.stringify(this.createMessage(
13151
- resource,
13152
- record.operation,
13153
- record.data,
13154
- record.id,
13155
- record.beforeData
13156
- )),
13157
- MessageGroupId: this.messageGroupId,
13158
- MessageDeduplicationId: this.deduplicationId ? `${resource}:${record.operation}:${record.id}` : void 0
13159
- }));
13160
- const command = new SendMessageBatchCommand({
13161
- QueueUrl: queueUrls[0],
13162
- // Assuming all queueUrls in a batch are the same for batching
13163
- Entries: entries
13164
- });
13165
- const result2 = await this.sqsClient.send(command);
13166
- results.push(result2);
13167
- });
13168
- if (!okBatch) {
13169
- errors.push({ batch: batch.length, error: errBatch.message });
13170
- if (errBatch.message && (errBatch.message.includes("Batch error") || errBatch.message.includes("Connection") || errBatch.message.includes("Network"))) {
13171
- throw errBatch;
13172
- }
13173
- }
13174
- }
13175
- this.emit("batch_replicated", {
13176
- replicator: this.name,
13177
- resource,
13178
- queueUrl: queueUrls[0],
13179
- // Assuming all queueUrls in a batch are the same for batching
13180
- total: records.length,
13181
- successful: results.length,
13182
- errors: errors.length
13183
- });
13184
- return {
13185
- success: errors.length === 0,
13186
- results,
13187
- errors,
13188
- total: records.length,
13189
- queueUrl: queueUrls[0]
13190
- // Assuming all queueUrls in a batch are the same for batching
13191
- };
13192
- });
13193
- if (ok) return result;
13194
- const errorMessage = err?.message || err || "Unknown error";
13195
- this.emit("batch_replicator_error", {
13196
- replicator: this.name,
13197
- resource,
13198
- error: errorMessage
13199
- });
13200
- return { success: false, error: errorMessage };
13201
- }
13202
- async testConnection() {
13203
- const [ok, err] = await try_fn_default(async () => {
13204
- if (!this.sqsClient) {
13205
- await this.initialize(this.database);
13206
- }
13207
- const { GetQueueAttributesCommand } = await import('@aws-sdk/client-sqs');
13208
- const command = new GetQueueAttributesCommand({
13209
- QueueUrl: this.queueUrl,
13210
- AttributeNames: ["QueueArn"]
13211
- });
13212
- await this.sqsClient.send(command);
13213
- return true;
13214
- });
13215
- if (ok) return true;
13216
- this.emit("connection_error", {
13217
- replicator: this.name,
13218
- error: err.message
13219
- });
13220
- return false;
13221
- }
13222
- async getStatus() {
13223
- const baseStatus = await super.getStatus();
13224
- return {
13225
- ...baseStatus,
13226
- connected: !!this.sqsClient,
13227
- queueUrl: this.queueUrl,
13228
- region: this.region,
13229
- resources: this.resources,
13230
- totalreplicators: this.listenerCount("replicated"),
13231
- totalErrors: this.listenerCount("replicator_error")
13232
- };
13233
- }
13234
- async cleanup() {
13235
- if (this.sqsClient) {
13236
- this.sqsClient.destroy();
13237
- }
13238
- await super.cleanup();
13239
- }
13240
- shouldReplicateResource(resource) {
13241
- const result = this.resourceQueueMap && Object.keys(this.resourceQueueMap).includes(resource) || this.queues && Object.keys(this.queues).includes(resource) || !!(this.defaultQueue || this.queueUrl) || this.resources && Object.keys(this.resources).includes(resource) || false;
13242
- return result;
13243
- }
13244
- }
13245
- var sqs_replicator_class_default = SqsReplicator;
13246
-
13247
- const REPLICATOR_DRIVERS = {
13248
- s3db: s3db_replicator_class_default,
13249
- sqs: sqs_replicator_class_default,
13250
- bigquery: bigquery_replicator_class_default,
13251
- postgres: postgres_replicator_class_default
13252
- };
13253
- function createReplicator(driver, config = {}, resources = [], client = null) {
13254
- const ReplicatorClass = REPLICATOR_DRIVERS[driver];
13255
- if (!ReplicatorClass) {
13256
- throw new Error(`Unknown replicator driver: ${driver}. Available drivers: ${Object.keys(REPLICATOR_DRIVERS).join(", ")}`);
13257
- }
13258
- return new ReplicatorClass(config, resources, client);
13259
- }
13260
-
13261
12112
  function normalizeResourceName(name) {
13262
12113
  return typeof name === "string" ? name.trim().toLowerCase() : name;
13263
12114
  }
13264
12115
  class ReplicatorPlugin extends plugin_class_default {
13265
12116
  constructor(options = {}) {
13266
12117
  super();
13267
- if (options.verbose) {
13268
- console.log("[PLUGIN][CONSTRUCTOR] ReplicatorPlugin constructor called");
13269
- }
13270
- if (options.verbose) {
13271
- console.log("[PLUGIN][constructor] New ReplicatorPlugin instance created with config:", options);
13272
- }
13273
12118
  if (!options.replicators || !Array.isArray(options.replicators)) {
13274
12119
  throw new Error("ReplicatorPlugin: replicators array is required");
13275
12120
  }
13276
12121
  for (const rep of options.replicators) {
13277
12122
  if (!rep.driver) throw new Error("ReplicatorPlugin: each replicator must have a driver");
12123
+ if (!rep.resources || typeof rep.resources !== "object") throw new Error("ReplicatorPlugin: each replicator must have resources config");
12124
+ if (Object.keys(rep.resources).length === 0) throw new Error("ReplicatorPlugin: each replicator must have at least one resource configured");
13278
12125
  }
13279
12126
  this.config = {
13280
- verbose: options.verbose ?? false,
13281
- persistReplicatorLog: options.persistReplicatorLog ?? false,
13282
- replicatorLogResource: options.replicatorLogResource ?? "replicator_logs",
13283
- replicators: options.replicators || []
12127
+ replicators: options.replicators || [],
12128
+ logErrors: options.logErrors !== false,
12129
+ replicatorLogResource: options.replicatorLogResource || "replicator_log",
12130
+ enabled: options.enabled !== false,
12131
+ batchSize: options.batchSize || 100,
12132
+ maxRetries: options.maxRetries || 3,
12133
+ timeout: options.timeout || 3e4,
12134
+ verbose: options.verbose || false,
12135
+ ...options
13284
12136
  };
13285
12137
  this.replicators = [];
13286
- this.queue = [];
13287
- this.isProcessing = false;
13288
- this.stats = {
13289
- totalOperations: 0,
13290
- totalErrors: 0,
13291
- lastError: null
13292
- };
13293
- this._installedListeners = [];
12138
+ this.database = null;
12139
+ this.eventListenersInstalled = /* @__PURE__ */ new Set();
13294
12140
  }
13295
12141
  /**
13296
12142
  * Decompress data if it was compressed
@@ -13309,79 +12155,34 @@ class ReplicatorPlugin extends plugin_class_default {
13309
12155
  }
13310
12156
  return filtered;
13311
12157
  }
13312
- installEventListeners(resource) {
13313
- const plugin = this;
13314
- if (plugin.config.verbose) {
13315
- console.log("[PLUGIN] installEventListeners called for:", resource && resource.name, {
13316
- hasDatabase: !!resource.database,
13317
- sameDatabase: resource.database === plugin.database,
13318
- alreadyInstalled: resource._replicatorListenersInstalled,
13319
- resourceObj: resource,
13320
- resourceObjId: resource && resource.id,
13321
- resourceObjType: typeof resource,
13322
- resourceObjIs: resource && Object.is(resource, plugin.database.resources && plugin.database.resources[resource.name]),
13323
- resourceObjEq: resource === (plugin.database.resources && plugin.database.resources[resource.name])
13324
- });
13325
- }
13326
- if (!resource || resource.name === plugin.config.replicatorLogResource || !resource.database || resource.database !== plugin.database) return;
13327
- if (resource._replicatorListenersInstalled) return;
13328
- resource._replicatorListenersInstalled = true;
13329
- this._installedListeners.push(resource);
13330
- if (plugin.config.verbose) {
13331
- console.log(`[PLUGIN] installEventListeners INSTALLED for resource: ${resource && resource.name}`);
12158
+ installEventListeners(resource, database, plugin) {
12159
+ if (!resource || this.eventListenersInstalled.has(resource.name)) {
12160
+ return;
13332
12161
  }
13333
12162
  resource.on("insert", async (data) => {
13334
- if (plugin.config.verbose) {
13335
- console.log("[PLUGIN] Listener INSERT on", resource.name, "plugin.replicators.length:", plugin.replicators.length, plugin.replicators.map((r) => ({ id: r.id, driver: r.driver })));
13336
- }
13337
12163
  try {
13338
- const completeData = await plugin.getCompleteData(resource, data);
13339
- if (plugin.config.verbose) {
13340
- console.log(`[PLUGIN] Listener INSERT completeData for ${resource.name} id=${data && data.id}:`, completeData);
13341
- }
13342
- await plugin.processReplicatorEvent(resource.name, "insert", data.id, completeData, null);
13343
- } catch (err) {
13344
- if (plugin.config.verbose) {
13345
- console.error(`[PLUGIN] Listener INSERT error on ${resource.name} id=${data && data.id}:`, err);
13346
- }
12164
+ const completeData = { ...data, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
12165
+ await plugin.processReplicatorEvent("insert", resource.name, completeData.id, completeData);
12166
+ } catch (error) {
12167
+ this.emit("error", { operation: "insert", error: error.message, resource: resource.name });
13347
12168
  }
13348
12169
  });
13349
- resource.on("update", async (data) => {
13350
- console.log("[PLUGIN][Listener][UPDATE][START] triggered for resource:", resource.name, "data:", data);
13351
- const beforeData = data && data.$before;
13352
- if (plugin.config.verbose) {
13353
- 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);
13354
- }
12170
+ resource.on("update", async (data, beforeData) => {
13355
12171
  try {
13356
- let completeData;
13357
- const [ok, err, record] = await try_fn_default(() => resource.get(data.id));
13358
- if (ok && record) {
13359
- completeData = record;
13360
- } else {
13361
- completeData = data;
13362
- }
13363
- await plugin.processReplicatorEvent(resource.name, "update", data.id, completeData, beforeData);
13364
- } catch (err) {
13365
- if (plugin.config.verbose) {
13366
- console.error(`[PLUGIN] Listener UPDATE erro em ${resource.name} id=${data && data.id}:`, err);
13367
- }
12172
+ const completeData = { ...data, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
12173
+ await plugin.processReplicatorEvent("update", resource.name, completeData.id, completeData, beforeData);
12174
+ } catch (error) {
12175
+ this.emit("error", { operation: "update", error: error.message, resource: resource.name });
13368
12176
  }
13369
12177
  });
13370
- resource.on("delete", async (data, beforeData) => {
13371
- if (plugin.config.verbose) {
13372
- console.log("[PLUGIN] Listener DELETE on", resource.name, "plugin.replicators.length:", plugin.replicators.length, plugin.replicators.map((r) => ({ id: r.id, driver: r.driver })));
13373
- }
12178
+ resource.on("delete", async (data) => {
13374
12179
  try {
13375
- await plugin.processReplicatorEvent(resource.name, "delete", data.id, null, beforeData);
13376
- } catch (err) {
13377
- if (plugin.config.verbose) {
13378
- console.error(`[PLUGIN] Listener DELETE erro em ${resource.name} id=${data && data.id}:`, err);
13379
- }
12180
+ await plugin.processReplicatorEvent("delete", resource.name, data.id, data);
12181
+ } catch (error) {
12182
+ this.emit("error", { operation: "delete", error: error.message, resource: resource.name });
13380
12183
  }
13381
12184
  });
13382
- if (plugin.config.verbose) {
13383
- console.log(`[PLUGIN] Listeners instalados para resource: ${resource && resource.name} (insert: ${resource.listenerCount("insert")}, update: ${resource.listenerCount("update")}, delete: ${resource.listenerCount("delete")})`);
13384
- }
12185
+ this.eventListenersInstalled.add(resource.name);
13385
12186
  }
13386
12187
  /**
13387
12188
  * Get complete data by always fetching the full record from the resource
@@ -13392,112 +12193,53 @@ class ReplicatorPlugin extends plugin_class_default {
13392
12193
  return ok ? completeRecord : data;
13393
12194
  }
13394
12195
  async setup(database) {
13395
- console.log("[PLUGIN][SETUP] setup called");
13396
- if (this.config.verbose) {
13397
- console.log("[PLUGIN][setup] called with database:", database && database.name);
13398
- }
13399
12196
  this.database = database;
13400
- if (this.config.persistReplicatorLog) {
13401
- let logRes = database.resources[normalizeResourceName(this.config.replicatorLogResource)];
13402
- if (!logRes) {
13403
- logRes = await database.createResource({
12197
+ try {
12198
+ await this.initializeReplicators(database);
12199
+ } catch (error) {
12200
+ this.emit("error", { operation: "setup", error: error.message });
12201
+ throw error;
12202
+ }
12203
+ try {
12204
+ if (this.config.replicatorLogResource) {
12205
+ const logRes = await database.createResource({
13404
12206
  name: this.config.replicatorLogResource,
13405
- behavior: "truncate-data",
12207
+ behavior: "body-overflow",
13406
12208
  attributes: {
13407
- id: "string|required",
13408
- resource: "string|required",
13409
- action: "string|required",
13410
- data: "object",
13411
- timestamp: "number|required",
13412
- createdAt: "string|required"
13413
- },
13414
- partitions: {
13415
- byDate: { fields: { "createdAt": "string|maxlength:10" } }
12209
+ operation: "string",
12210
+ resourceName: "string",
12211
+ recordId: "string",
12212
+ data: "string",
12213
+ error: "string|optional",
12214
+ replicator: "string",
12215
+ timestamp: "string",
12216
+ status: "string"
13416
12217
  }
13417
12218
  });
13418
- if (this.config.verbose) {
13419
- console.log("[PLUGIN] Log resource created:", this.config.replicatorLogResource, !!logRes);
13420
- }
13421
- }
13422
- database.resources[normalizeResourceName(this.config.replicatorLogResource)] = logRes;
13423
- this.replicatorLog = logRes;
13424
- if (this.config.verbose) {
13425
- console.log("[PLUGIN] Log resource created and registered:", this.config.replicatorLogResource, !!database.resources[normalizeResourceName(this.config.replicatorLogResource)]);
13426
- }
13427
- if (typeof database.uploadMetadataFile === "function") {
13428
- await database.uploadMetadataFile();
13429
- if (this.config.verbose) {
13430
- console.log("[PLUGIN] uploadMetadataFile called. database.resources keys:", Object.keys(database.resources));
13431
- }
13432
- }
13433
- }
13434
- if (this.config.replicators && this.config.replicators.length > 0 && this.replicators.length === 0) {
13435
- await this.initializeReplicators();
13436
- console.log("[PLUGIN][SETUP] after initializeReplicators, replicators.length:", this.replicators.length);
13437
- if (this.config.verbose) {
13438
- console.log("[PLUGIN][setup] After initializeReplicators, replicators.length:", this.replicators.length, this.replicators.map((r) => ({ id: r.id, driver: r.driver })));
13439
- }
13440
- }
13441
- for (const resourceName in database.resources) {
13442
- if (normalizeResourceName(resourceName) !== normalizeResourceName(this.config.replicatorLogResource)) {
13443
- this.installEventListeners(database.resources[resourceName]);
13444
12219
  }
12220
+ } catch (error) {
13445
12221
  }
13446
- database.on("connected", () => {
13447
- for (const resourceName in database.resources) {
13448
- if (normalizeResourceName(resourceName) !== normalizeResourceName(this.config.replicatorLogResource)) {
13449
- this.installEventListeners(database.resources[resourceName]);
13450
- }
13451
- }
13452
- });
12222
+ await this.uploadMetadataFile(database);
13453
12223
  const originalCreateResource = database.createResource.bind(database);
13454
12224
  database.createResource = async (config) => {
13455
- if (this.config.verbose) {
13456
- console.log("[PLUGIN] createResource proxy called for:", config && config.name);
13457
- }
13458
12225
  const resource = await originalCreateResource(config);
13459
- if (resource && resource.name !== this.config.replicatorLogResource) {
13460
- this.installEventListeners(resource);
12226
+ if (resource) {
12227
+ this.installEventListeners(resource, database, this);
13461
12228
  }
13462
12229
  return resource;
13463
12230
  };
13464
- database.on("s3db.resourceCreated", (resourceName) => {
13465
- const resource = database.resources[resourceName];
13466
- if (resource && resource.name !== this.config.replicatorLogResource) {
13467
- this.installEventListeners(resource);
13468
- }
13469
- });
13470
- database.on("s3db.resourceUpdated", (resourceName) => {
12231
+ for (const resourceName in database.resources) {
13471
12232
  const resource = database.resources[resourceName];
13472
- if (resource && resource.name !== this.config.replicatorLogResource) {
13473
- this.installEventListeners(resource);
13474
- }
13475
- });
12233
+ this.installEventListeners(resource, database, this);
12234
+ }
13476
12235
  }
13477
- async initializeReplicators() {
13478
- console.log("[PLUGIN][INIT] initializeReplicators called");
12236
+ async initializeReplicators(database) {
13479
12237
  for (const replicatorConfig of this.config.replicators) {
13480
- try {
13481
- console.log("[PLUGIN][INIT] processing replicatorConfig:", replicatorConfig);
13482
- const driver = replicatorConfig.driver;
13483
- const resources = replicatorConfig.resources;
13484
- const client = replicatorConfig.client;
13485
- const replicator = createReplicator(driver, replicatorConfig, resources, client);
13486
- if (replicator) {
13487
- await replicator.initialize(this.database);
13488
- this.replicators.push({
13489
- id: Math.random().toString(36).slice(2),
13490
- driver,
13491
- config: replicatorConfig,
13492
- resources,
13493
- instance: replicator
13494
- });
13495
- console.log("[PLUGIN][INIT] pushed replicator:", driver, resources);
13496
- } else {
13497
- console.log("[PLUGIN][INIT] createReplicator returned null/undefined for driver:", driver);
13498
- }
13499
- } catch (err) {
13500
- console.error("[PLUGIN][INIT] Error creating replicator:", err);
12238
+ const { driver, config, resources } = replicatorConfig;
12239
+ const replicator = this.createReplicator(driver, config, resources);
12240
+ if (replicator) {
12241
+ await replicator.initialize(database);
12242
+ this.replicators.push(replicator);
13501
12243
  }
13502
12244
  }
13503
12245
  }
@@ -13505,160 +12247,102 @@ class ReplicatorPlugin extends plugin_class_default {
13505
12247
  }
13506
12248
  async stop() {
13507
12249
  }
13508
- async processReplicatorEvent(resourceName, operation, recordId, data, beforeData = null) {
13509
- if (this.config.verbose) {
13510
- console.log("[PLUGIN][processReplicatorEvent] replicators.length:", this.replicators.length, this.replicators.map((r) => ({ id: r.id, driver: r.driver })));
13511
- console.log(`[PLUGIN][processReplicatorEvent] operation: ${operation}, resource: ${resourceName}, recordId: ${recordId}, data:`, data, "beforeData:", beforeData);
13512
- }
13513
- if (this.config.verbose) {
13514
- console.log(`[PLUGIN] processReplicatorEvent: resource=${resourceName} op=${operation} id=${recordId} data=`, data);
13515
- }
13516
- if (this.config.verbose) {
13517
- console.log(`[PLUGIN] processReplicatorEvent: resource=${resourceName} op=${operation} replicators=${this.replicators.length}`);
13518
- }
13519
- if (this.replicators.length === 0) {
13520
- if (this.config.verbose) {
13521
- console.log("[PLUGIN] No replicators registered");
13522
- }
13523
- return;
13524
- }
12250
+ async processReplicatorEvent(operation, resourceName, recordId, data, beforeData = null) {
12251
+ if (!this.config.enabled) return;
13525
12252
  const applicableReplicators = this.replicators.filter((replicator) => {
13526
- const should = replicator.instance.shouldReplicateResource(resourceName, operation);
13527
- if (this.config.verbose) {
13528
- console.log(`[PLUGIN] Replicator ${replicator.driver} shouldReplicateResource(${resourceName}, ${operation}):`, should);
13529
- }
12253
+ const should = replicator.shouldReplicateResource && replicator.shouldReplicateResource(resourceName, operation);
13530
12254
  return should;
13531
12255
  });
13532
- if (this.config.verbose) {
13533
- console.log(`[PLUGIN] processReplicatorEvent: applicableReplicators for resource=${resourceName}:`, applicableReplicators.map((r) => r.driver));
13534
- }
13535
12256
  if (applicableReplicators.length === 0) {
13536
- if (this.config.verbose) {
13537
- console.log("[PLUGIN] No applicable replicators for resource", resourceName);
13538
- }
13539
12257
  return;
13540
12258
  }
13541
- const filteredData = this.filterInternalFields(lodashEs.isPlainObject(data) ? data : { raw: data });
13542
- const filteredBeforeData = beforeData ? this.filterInternalFields(lodashEs.isPlainObject(beforeData) ? beforeData : { raw: beforeData }) : null;
13543
- const item = {
13544
- id: `repl-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
13545
- resourceName,
13546
- operation,
13547
- recordId,
13548
- data: filteredData,
13549
- beforeData: filteredBeforeData,
13550
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
13551
- attempts: 0
13552
- };
13553
- const logId = await this.logreplicator(item);
13554
- const [ok, err, result] = await try_fn_default(async () => this.processreplicatorItem(item));
13555
- if (ok) {
13556
- if (logId) {
13557
- await this.updatereplicatorLog(logId, {
13558
- status: result.success ? "success" : "failed",
13559
- attempts: 1,
13560
- error: result.success ? "" : JSON.stringify(result.results)
12259
+ const promises = applicableReplicators.map(async (replicator) => {
12260
+ try {
12261
+ const result = await this.retryWithBackoff(
12262
+ () => replicator.replicate(resourceName, operation, data, recordId, beforeData),
12263
+ this.config.maxRetries
12264
+ );
12265
+ this.emit("replicated", {
12266
+ replicator: replicator.name || replicator.id,
12267
+ resourceName,
12268
+ operation,
12269
+ recordId,
12270
+ result,
12271
+ success: true
13561
12272
  });
13562
- }
13563
- this.stats.totalOperations++;
13564
- if (result.success) {
13565
- this.stats.successfulOperations++;
13566
- } else {
13567
- this.stats.failedOperations++;
13568
- }
13569
- } else {
13570
- if (logId) {
13571
- await this.updatereplicatorLog(logId, {
13572
- status: "failed",
13573
- attempts: 1,
13574
- error: err.message
12273
+ return result;
12274
+ } catch (error) {
12275
+ this.emit("replicator_error", {
12276
+ replicator: replicator.name || replicator.id,
12277
+ resourceName,
12278
+ operation,
12279
+ recordId,
12280
+ error: error.message
13575
12281
  });
12282
+ if (this.config.logErrors && this.database) {
12283
+ await this.logError(replicator, resourceName, operation, recordId, data, error);
12284
+ }
12285
+ throw error;
13576
12286
  }
13577
- this.stats.failedOperations++;
13578
- }
12287
+ });
12288
+ return Promise.allSettled(promises);
13579
12289
  }
13580
12290
  async processreplicatorItem(item) {
13581
- if (this.config.verbose) {
13582
- console.log("[PLUGIN][processreplicatorItem] called with item:", item);
13583
- }
13584
12291
  const applicableReplicators = this.replicators.filter((replicator) => {
13585
- const should = replicator.instance.shouldReplicateResource(item.resourceName, item.operation);
13586
- if (this.config.verbose) {
13587
- console.log(`[PLUGIN] processreplicatorItem: Replicator ${replicator.driver} shouldReplicateResource(${item.resourceName}, ${item.operation}):`, should);
13588
- }
12292
+ const should = replicator.shouldReplicateResource && replicator.shouldReplicateResource(item.resourceName, item.operation);
13589
12293
  return should;
13590
12294
  });
13591
- if (this.config.verbose) {
13592
- console.log(`[PLUGIN] processreplicatorItem: applicableReplicators for resource=${item.resourceName}:`, applicableReplicators.map((r) => r.driver));
13593
- }
13594
12295
  if (applicableReplicators.length === 0) {
13595
- if (this.config.verbose) {
13596
- console.log("[PLUGIN] processreplicatorItem: No applicable replicators for resource", item.resourceName);
13597
- }
13598
- return { success: true, skipped: true, reason: "no_applicable_replicators" };
12296
+ return;
13599
12297
  }
13600
- const results = [];
13601
- for (const replicator of applicableReplicators) {
13602
- let result;
13603
- let ok, err;
13604
- if (this.config.verbose) {
13605
- console.log("[PLUGIN] processReplicatorItem", {
13606
- resource: item.resourceName,
12298
+ const promises = applicableReplicators.map(async (replicator) => {
12299
+ try {
12300
+ const [ok, err, result] = await try_fn_default(
12301
+ () => replicator.replicate(item.resourceName, item.operation, item.data, item.recordId, item.beforeData)
12302
+ );
12303
+ if (!ok) {
12304
+ this.emit("replicator_error", {
12305
+ replicator: replicator.name || replicator.id,
12306
+ resourceName: item.resourceName,
12307
+ operation: item.operation,
12308
+ recordId: item.recordId,
12309
+ error: err.message
12310
+ });
12311
+ if (this.config.logErrors && this.database) {
12312
+ await this.logError(replicator, item.resourceName, item.operation, item.recordId, item.data, err);
12313
+ }
12314
+ return { success: false, error: err.message };
12315
+ }
12316
+ this.emit("replicated", {
12317
+ replicator: replicator.name || replicator.id,
12318
+ resourceName: item.resourceName,
13607
12319
  operation: item.operation,
13608
- data: item.data,
13609
- beforeData: item.beforeData,
13610
- replicator: replicator.instance?.constructor?.name
12320
+ recordId: item.recordId,
12321
+ result,
12322
+ success: true
13611
12323
  });
12324
+ return { success: true, result };
12325
+ } catch (error) {
12326
+ this.emit("replicator_error", {
12327
+ replicator: replicator.name || replicator.id,
12328
+ resourceName: item.resourceName,
12329
+ operation: item.operation,
12330
+ recordId: item.recordId,
12331
+ error: error.message
12332
+ });
12333
+ if (this.config.logErrors && this.database) {
12334
+ await this.logError(replicator, item.resourceName, item.operation, item.recordId, item.data, error);
12335
+ }
12336
+ return { success: false, error: error.message };
13612
12337
  }
13613
- if (replicator.instance && replicator.instance.constructor && replicator.instance.constructor.name === "S3dbReplicator") {
13614
- [ok, err, result] = await try_fn_default(
13615
- () => replicator.instance.replicate({
13616
- resource: item.resourceName,
13617
- operation: item.operation,
13618
- data: item.data,
13619
- id: item.recordId,
13620
- beforeData: item.beforeData
13621
- })
13622
- );
13623
- } else {
13624
- [ok, err, result] = await try_fn_default(
13625
- () => replicator.instance.replicate(
13626
- item.resourceName,
13627
- item.operation,
13628
- item.data,
13629
- item.recordId,
13630
- item.beforeData
13631
- )
13632
- );
13633
- }
13634
- results.push({
13635
- replicatorId: replicator.id,
13636
- driver: replicator.driver,
13637
- success: result && result.success,
13638
- error: result && result.error,
13639
- skipped: result && result.skipped
13640
- });
13641
- }
13642
- return {
13643
- success: results.every((r) => r.success || r.skipped),
13644
- results
13645
- };
12338
+ });
12339
+ return Promise.allSettled(promises);
13646
12340
  }
13647
12341
  async logreplicator(item) {
13648
12342
  const logRes = this.replicatorLog || this.database.resources[normalizeResourceName(this.config.replicatorLogResource)];
13649
12343
  if (!logRes) {
13650
- if (this.config.verbose) {
13651
- console.error("[PLUGIN] replicator log resource not found!");
13652
- }
13653
12344
  if (this.database) {
13654
- if (this.config.verbose) {
13655
- console.warn("[PLUGIN] database.resources keys:", Object.keys(this.database.resources));
13656
- }
13657
- if (this.database.options && this.database.options.connectionString) {
13658
- if (this.config.verbose) {
13659
- console.warn("[PLUGIN] database connectionString:", this.database.options.connectionString);
13660
- }
13661
- }
12345
+ if (this.database.options && this.database.options.connectionString) ;
13662
12346
  }
13663
12347
  this.emit("replicator.log.failed", { error: "replicator log resource not found", item });
13664
12348
  return;
@@ -13674,9 +12358,6 @@ class ReplicatorPlugin extends plugin_class_default {
13674
12358
  try {
13675
12359
  await logRes.insert(logItem);
13676
12360
  } catch (err) {
13677
- if (this.config.verbose) {
13678
- console.error("[PLUGIN] Error writing to replicator log:", err);
13679
- }
13680
12361
  this.emit("replicator.log.failed", { error: err, item });
13681
12362
  }
13682
12363
  }
@@ -13758,10 +12439,6 @@ class ReplicatorPlugin extends plugin_class_default {
13758
12439
  });
13759
12440
  if (ok) {
13760
12441
  retried++;
13761
- } else {
13762
- if (this.config.verbose) {
13763
- console.error("Failed to retry replicator:", err);
13764
- }
13765
12442
  }
13766
12443
  }
13767
12444
  return { retried };
@@ -13786,40 +12463,23 @@ class ReplicatorPlugin extends plugin_class_default {
13786
12463
  this.emit("replicator.sync.completed", { replicatorId, stats: this.stats });
13787
12464
  }
13788
12465
  async cleanup() {
13789
- if (this.config.verbose) {
13790
- console.log("[PLUGIN][CLEANUP] Cleaning up ReplicatorPlugin");
13791
- }
13792
- if (this._installedListeners && Array.isArray(this._installedListeners)) {
13793
- for (const resource of this._installedListeners) {
13794
- if (resource && typeof resource.removeAllListeners === "function") {
13795
- resource.removeAllListeners("insert");
13796
- resource.removeAllListeners("update");
13797
- resource.removeAllListeners("delete");
13798
- }
13799
- resource._replicatorListenersInstalled = false;
13800
- }
13801
- this._installedListeners = [];
13802
- }
13803
- if (this.database && typeof this.database.removeAllListeners === "function") {
13804
- this.database.removeAllListeners();
13805
- }
13806
- if (this.replicators && Array.isArray(this.replicators)) {
13807
- for (const rep of this.replicators) {
13808
- if (rep.instance && typeof rep.instance.cleanup === "function") {
13809
- await rep.instance.cleanup();
13810
- }
12466
+ try {
12467
+ if (this.replicators && this.replicators.length > 0) {
12468
+ const cleanupPromises = this.replicators.map(async (replicator) => {
12469
+ try {
12470
+ if (replicator && typeof replicator.cleanup === "function") {
12471
+ await replicator.cleanup();
12472
+ }
12473
+ } catch (error) {
12474
+ }
12475
+ });
12476
+ await Promise.allSettled(cleanupPromises);
13811
12477
  }
13812
12478
  this.replicators = [];
13813
- }
13814
- this.queue = [];
13815
- this.isProcessing = false;
13816
- this.stats = {
13817
- totalOperations: 0,
13818
- totalErrors: 0,
13819
- lastError: null
13820
- };
13821
- if (this.config.verbose) {
13822
- console.log("[PLUGIN][CLEANUP] ReplicatorPlugin cleanup complete");
12479
+ this.database = null;
12480
+ this.eventListenersInstalled.clear();
12481
+ this.removeAllListeners();
12482
+ } catch (error) {
13823
12483
  }
13824
12484
  }
13825
12485
  }