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