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