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