s3db.js 7.3.4 → 7.3.6
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/PLUGINS.md +1285 -157
- package/dist/s3db.cjs.js +322 -119
- package/dist/s3db.cjs.min.js +1 -1
- package/dist/s3db.es.js +322 -119
- package/dist/s3db.es.min.js +1 -1
- package/dist/s3db.iife.js +323 -120
- package/dist/s3db.iife.min.js +1 -1
- package/mcp/server.js +1410 -0
- package/package.json +30 -24
- package/src/database.class.js +10 -8
- package/src/plugins/cache/filesystem-cache.class.js +9 -0
- package/src/plugins/metrics.plugin.js +18 -8
- package/src/plugins/replicator.plugin.js +130 -72
- package/src/plugins/replicators/bigquery-replicator.class.js +31 -5
- package/src/plugins/replicators/postgres-replicator.class.js +17 -2
- package/src/plugins/replicators/s3db-replicator.class.js +175 -71
- package/src/plugins/replicators/sqs-replicator.class.js +13 -1
package/dist/s3db.es.js
CHANGED
|
@@ -3,8 +3,8 @@ import zlib from 'node:zlib';
|
|
|
3
3
|
import { PromisePool } from '@supercharge/promise-pool';
|
|
4
4
|
import { ReadableStream } from 'node:stream/web';
|
|
5
5
|
import { mkdir, writeFile, readFile, stat, unlink, readdir, rm } from 'fs/promises';
|
|
6
|
-
import { chunk, merge, isString as isString$1, isEmpty, invert, uniq, cloneDeep, get, set, isObject as isObject$1, isFunction as isFunction$1 } from 'lodash-es';
|
|
7
6
|
import { createHash } from 'crypto';
|
|
7
|
+
import { chunk, merge, isString as isString$1, isEmpty, invert, uniq, cloneDeep, get, set, isObject as isObject$1, isFunction as isFunction$1 } from 'lodash-es';
|
|
8
8
|
import jsonStableStringify from 'json-stable-stringify';
|
|
9
9
|
import { S3Client, PutObjectCommand, GetObjectCommand, HeadObjectCommand, CopyObjectCommand, DeleteObjectCommand, DeleteObjectsCommand, ListObjectsV2Command } from '@aws-sdk/client-s3';
|
|
10
10
|
import { flatten, unflatten } from 'flat';
|
|
@@ -7079,6 +7079,12 @@ class FilesystemCache extends Cache {
|
|
|
7079
7079
|
}
|
|
7080
7080
|
async _clear(prefix) {
|
|
7081
7081
|
try {
|
|
7082
|
+
if (!await this._fileExists(this.directory)) {
|
|
7083
|
+
if (this.enableStats) {
|
|
7084
|
+
this.stats.clears++;
|
|
7085
|
+
}
|
|
7086
|
+
return true;
|
|
7087
|
+
}
|
|
7082
7088
|
const files = await readdir(this.directory);
|
|
7083
7089
|
const cacheFiles = files.filter((file) => {
|
|
7084
7090
|
if (!file.startsWith(this.prefix)) return false;
|
|
@@ -8349,7 +8355,7 @@ class MetricsPlugin extends plugin_class_default {
|
|
|
8349
8355
|
}
|
|
8350
8356
|
async setup(database) {
|
|
8351
8357
|
this.database = database;
|
|
8352
|
-
if (process.env.NODE_ENV === "test") return;
|
|
8358
|
+
if (typeof process !== "undefined" && process.env.NODE_ENV === "test") return;
|
|
8353
8359
|
const [ok, err] = await try_fn_default(async () => {
|
|
8354
8360
|
const [ok1, err1, metricsResource] = await try_fn_default(() => database.createResource({
|
|
8355
8361
|
name: "metrics",
|
|
@@ -8399,7 +8405,7 @@ class MetricsPlugin extends plugin_class_default {
|
|
|
8399
8405
|
this.performanceResource = database.resources.performance_logs;
|
|
8400
8406
|
}
|
|
8401
8407
|
this.installMetricsHooks();
|
|
8402
|
-
if (process.env.NODE_ENV !== "test") {
|
|
8408
|
+
if (typeof process !== "undefined" && process.env.NODE_ENV !== "test") {
|
|
8403
8409
|
this.startFlushTimer();
|
|
8404
8410
|
}
|
|
8405
8411
|
}
|
|
@@ -8410,7 +8416,7 @@ class MetricsPlugin extends plugin_class_default {
|
|
|
8410
8416
|
clearInterval(this.flushTimer);
|
|
8411
8417
|
this.flushTimer = null;
|
|
8412
8418
|
}
|
|
8413
|
-
if (process.env.NODE_ENV !== "test") {
|
|
8419
|
+
if (typeof process !== "undefined" && process.env.NODE_ENV !== "test") {
|
|
8414
8420
|
await this.flushMetrics();
|
|
8415
8421
|
}
|
|
8416
8422
|
}
|
|
@@ -8589,10 +8595,18 @@ class MetricsPlugin extends plugin_class_default {
|
|
|
8589
8595
|
async flushMetrics() {
|
|
8590
8596
|
if (!this.metricsResource) return;
|
|
8591
8597
|
const [ok, err] = await try_fn_default(async () => {
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
|
|
8595
|
-
|
|
8598
|
+
let metadata, perfMetadata, errorMetadata, resourceMetadata;
|
|
8599
|
+
if (typeof process !== "undefined" && process.env.NODE_ENV === "test") {
|
|
8600
|
+
metadata = {};
|
|
8601
|
+
perfMetadata = {};
|
|
8602
|
+
errorMetadata = {};
|
|
8603
|
+
resourceMetadata = {};
|
|
8604
|
+
} else {
|
|
8605
|
+
metadata = { global: "true" };
|
|
8606
|
+
perfMetadata = { perf: "true" };
|
|
8607
|
+
errorMetadata = { error: "true" };
|
|
8608
|
+
resourceMetadata = { resource: "true" };
|
|
8609
|
+
}
|
|
8596
8610
|
for (const [operation, data] of Object.entries(this.metrics.operations)) {
|
|
8597
8611
|
if (data.count > 0) {
|
|
8598
8612
|
await this.metricsResource.insert({
|
|
@@ -8942,6 +8956,9 @@ class BigqueryReplicator extends base_replicator_class_default {
|
|
|
8942
8956
|
await super.initialize(database);
|
|
8943
8957
|
const [ok, err, sdk] = await try_fn_default(() => import('@google-cloud/bigquery'));
|
|
8944
8958
|
if (!ok) {
|
|
8959
|
+
if (this.config.verbose) {
|
|
8960
|
+
console.warn(`[BigqueryReplicator] Failed to import BigQuery SDK: ${err.message}`);
|
|
8961
|
+
}
|
|
8945
8962
|
this.emit("initialization_error", { replicator: this.name, error: err.message });
|
|
8946
8963
|
throw err;
|
|
8947
8964
|
}
|
|
@@ -9011,19 +9028,28 @@ class BigqueryReplicator extends base_replicator_class_default {
|
|
|
9011
9028
|
const maxRetries = 2;
|
|
9012
9029
|
let lastError = null;
|
|
9013
9030
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
9014
|
-
|
|
9031
|
+
const [ok2, error] = await try_fn_default(async () => {
|
|
9015
9032
|
const [updateJob] = await this.bigqueryClient.createQueryJob({
|
|
9016
9033
|
query,
|
|
9017
9034
|
params,
|
|
9018
9035
|
location: this.location
|
|
9019
9036
|
});
|
|
9020
9037
|
await updateJob.getQueryResults();
|
|
9021
|
-
|
|
9038
|
+
return [updateJob];
|
|
9039
|
+
});
|
|
9040
|
+
if (ok2) {
|
|
9041
|
+
job = ok2;
|
|
9022
9042
|
break;
|
|
9023
|
-
}
|
|
9043
|
+
} else {
|
|
9024
9044
|
lastError = error;
|
|
9045
|
+
if (this.config.verbose) {
|
|
9046
|
+
console.warn(`[BigqueryReplicator] Update attempt ${attempt} failed: ${error.message}`);
|
|
9047
|
+
}
|
|
9025
9048
|
if (error?.message?.includes("streaming buffer") && attempt < maxRetries) {
|
|
9026
9049
|
const delaySeconds = 30;
|
|
9050
|
+
if (this.config.verbose) {
|
|
9051
|
+
console.warn(`[BigqueryReplicator] Retrying in ${delaySeconds} seconds due to streaming buffer issue`);
|
|
9052
|
+
}
|
|
9027
9053
|
await new Promise((resolve) => setTimeout(resolve, delaySeconds * 1e3));
|
|
9028
9054
|
continue;
|
|
9029
9055
|
}
|
|
@@ -9090,6 +9116,9 @@ class BigqueryReplicator extends base_replicator_class_default {
|
|
|
9090
9116
|
};
|
|
9091
9117
|
});
|
|
9092
9118
|
if (ok) return result;
|
|
9119
|
+
if (this.config.verbose) {
|
|
9120
|
+
console.warn(`[BigqueryReplicator] Replication failed for ${resourceName}: ${err.message}`);
|
|
9121
|
+
}
|
|
9093
9122
|
this.emit("replicator_error", {
|
|
9094
9123
|
replicator: this.name,
|
|
9095
9124
|
resourceName,
|
|
@@ -9110,8 +9139,14 @@ class BigqueryReplicator extends base_replicator_class_default {
|
|
|
9110
9139
|
record.id,
|
|
9111
9140
|
record.beforeData
|
|
9112
9141
|
));
|
|
9113
|
-
if (ok)
|
|
9114
|
-
|
|
9142
|
+
if (ok) {
|
|
9143
|
+
results.push(res);
|
|
9144
|
+
} else {
|
|
9145
|
+
if (this.config.verbose) {
|
|
9146
|
+
console.warn(`[BigqueryReplicator] Batch replication failed for record ${record.id}: ${err.message}`);
|
|
9147
|
+
}
|
|
9148
|
+
errors.push({ id: record.id, error: err.message });
|
|
9149
|
+
}
|
|
9115
9150
|
}
|
|
9116
9151
|
return {
|
|
9117
9152
|
success: errors.length === 0,
|
|
@@ -9127,6 +9162,9 @@ class BigqueryReplicator extends base_replicator_class_default {
|
|
|
9127
9162
|
return true;
|
|
9128
9163
|
});
|
|
9129
9164
|
if (ok) return true;
|
|
9165
|
+
if (this.config.verbose) {
|
|
9166
|
+
console.warn(`[BigqueryReplicator] Connection test failed: ${err.message}`);
|
|
9167
|
+
}
|
|
9130
9168
|
this.emit("connection_error", { replicator: this.name, error: err.message });
|
|
9131
9169
|
return false;
|
|
9132
9170
|
}
|
|
@@ -9214,6 +9252,9 @@ class PostgresReplicator extends base_replicator_class_default {
|
|
|
9214
9252
|
await super.initialize(database);
|
|
9215
9253
|
const [ok, err, sdk] = await try_fn_default(() => import('pg'));
|
|
9216
9254
|
if (!ok) {
|
|
9255
|
+
if (this.config.verbose) {
|
|
9256
|
+
console.warn(`[PostgresReplicator] Failed to import pg SDK: ${err.message}`);
|
|
9257
|
+
}
|
|
9217
9258
|
this.emit("initialization_error", {
|
|
9218
9259
|
replicator: this.name,
|
|
9219
9260
|
error: err.message
|
|
@@ -9355,6 +9396,9 @@ class PostgresReplicator extends base_replicator_class_default {
|
|
|
9355
9396
|
};
|
|
9356
9397
|
});
|
|
9357
9398
|
if (ok) return result;
|
|
9399
|
+
if (this.config.verbose) {
|
|
9400
|
+
console.warn(`[PostgresReplicator] Replication failed for ${resourceName}: ${err.message}`);
|
|
9401
|
+
}
|
|
9358
9402
|
this.emit("replicator_error", {
|
|
9359
9403
|
replicator: this.name,
|
|
9360
9404
|
resourceName,
|
|
@@ -9375,8 +9419,14 @@ class PostgresReplicator extends base_replicator_class_default {
|
|
|
9375
9419
|
record.id,
|
|
9376
9420
|
record.beforeData
|
|
9377
9421
|
));
|
|
9378
|
-
if (ok)
|
|
9379
|
-
|
|
9422
|
+
if (ok) {
|
|
9423
|
+
results.push(res);
|
|
9424
|
+
} else {
|
|
9425
|
+
if (this.config.verbose) {
|
|
9426
|
+
console.warn(`[PostgresReplicator] Batch replication failed for record ${record.id}: ${err.message}`);
|
|
9427
|
+
}
|
|
9428
|
+
errors.push({ id: record.id, error: err.message });
|
|
9429
|
+
}
|
|
9380
9430
|
}
|
|
9381
9431
|
return {
|
|
9382
9432
|
success: errors.length === 0,
|
|
@@ -9391,6 +9441,9 @@ class PostgresReplicator extends base_replicator_class_default {
|
|
|
9391
9441
|
return true;
|
|
9392
9442
|
});
|
|
9393
9443
|
if (ok) return true;
|
|
9444
|
+
if (this.config.verbose) {
|
|
9445
|
+
console.warn(`[PostgresReplicator] Connection test failed: ${err.message}`);
|
|
9446
|
+
}
|
|
9394
9447
|
this.emit("connection_error", { replicator: this.name, error: err.message });
|
|
9395
9448
|
return false;
|
|
9396
9449
|
}
|
|
@@ -13125,7 +13178,7 @@ class Database extends EventEmitter {
|
|
|
13125
13178
|
super();
|
|
13126
13179
|
this.version = "1";
|
|
13127
13180
|
this.s3dbVersion = (() => {
|
|
13128
|
-
const [ok, err, version] = try_fn_default(() => true ? "7.3.
|
|
13181
|
+
const [ok, err, version] = try_fn_default(() => true ? "7.3.6" : "latest");
|
|
13129
13182
|
return ok ? version : "latest";
|
|
13130
13183
|
})();
|
|
13131
13184
|
this.resources = {};
|
|
@@ -13168,14 +13221,16 @@ class Database extends EventEmitter {
|
|
|
13168
13221
|
this.keyPrefix = this.client.keyPrefix;
|
|
13169
13222
|
if (!this._exitListenerRegistered) {
|
|
13170
13223
|
this._exitListenerRegistered = true;
|
|
13171
|
-
process
|
|
13172
|
-
|
|
13173
|
-
|
|
13174
|
-
|
|
13175
|
-
|
|
13224
|
+
if (typeof process !== "undefined") {
|
|
13225
|
+
process.on("exit", async () => {
|
|
13226
|
+
if (this.isConnected()) {
|
|
13227
|
+
try {
|
|
13228
|
+
await this.disconnect();
|
|
13229
|
+
} catch (err) {
|
|
13230
|
+
}
|
|
13176
13231
|
}
|
|
13177
|
-
}
|
|
13178
|
-
}
|
|
13232
|
+
});
|
|
13233
|
+
}
|
|
13179
13234
|
}
|
|
13180
13235
|
}
|
|
13181
13236
|
async connect() {
|
|
@@ -13627,9 +13682,8 @@ class S3dbReplicator extends base_replicator_class_default {
|
|
|
13627
13682
|
const map = {};
|
|
13628
13683
|
for (const res of resources) {
|
|
13629
13684
|
if (typeof res === "string") map[normalizeResourceName$1(res)] = res;
|
|
13630
|
-
else if (Array.isArray(res) && typeof res[0] === "string") map[normalizeResourceName$1(res[0])] = res;
|
|
13631
13685
|
else if (typeof res === "object" && res.resource) {
|
|
13632
|
-
map[normalizeResourceName$1(res.resource)] =
|
|
13686
|
+
map[normalizeResourceName$1(res.resource)] = res;
|
|
13633
13687
|
}
|
|
13634
13688
|
}
|
|
13635
13689
|
return map;
|
|
@@ -13642,15 +13696,14 @@ class S3dbReplicator extends base_replicator_class_default {
|
|
|
13642
13696
|
else if (Array.isArray(dest)) {
|
|
13643
13697
|
map[normSrc] = dest.map((item) => {
|
|
13644
13698
|
if (typeof item === "string") return item;
|
|
13645
|
-
if (typeof item === "function") return item;
|
|
13646
13699
|
if (typeof item === "object" && item.resource) {
|
|
13647
|
-
return
|
|
13700
|
+
return item;
|
|
13648
13701
|
}
|
|
13649
13702
|
return item;
|
|
13650
13703
|
});
|
|
13651
13704
|
} else if (typeof dest === "function") map[normSrc] = dest;
|
|
13652
13705
|
else if (typeof dest === "object" && dest.resource) {
|
|
13653
|
-
map[normSrc] =
|
|
13706
|
+
map[normSrc] = dest;
|
|
13654
13707
|
}
|
|
13655
13708
|
}
|
|
13656
13709
|
return map;
|
|
@@ -13658,10 +13711,6 @@ class S3dbReplicator extends base_replicator_class_default {
|
|
|
13658
13711
|
if (typeof resources === "function") {
|
|
13659
13712
|
return resources;
|
|
13660
13713
|
}
|
|
13661
|
-
if (typeof resources === "string") {
|
|
13662
|
-
const map = { [normalizeResourceName$1(resources)]: resources };
|
|
13663
|
-
return map;
|
|
13664
|
-
}
|
|
13665
13714
|
return {};
|
|
13666
13715
|
}
|
|
13667
13716
|
validateConfig() {
|
|
@@ -13675,8 +13724,8 @@ class S3dbReplicator extends base_replicator_class_default {
|
|
|
13675
13724
|
return { isValid: errors.length === 0, errors };
|
|
13676
13725
|
}
|
|
13677
13726
|
async initialize(database) {
|
|
13678
|
-
|
|
13679
|
-
|
|
13727
|
+
await super.initialize(database);
|
|
13728
|
+
const [ok, err] = await try_fn_default(async () => {
|
|
13680
13729
|
if (this.client) {
|
|
13681
13730
|
this.targetDatabase = this.client;
|
|
13682
13731
|
} else if (this.connectionString) {
|
|
@@ -13695,7 +13744,11 @@ class S3dbReplicator extends base_replicator_class_default {
|
|
|
13695
13744
|
replicator: this.name,
|
|
13696
13745
|
target: this.connectionString || "client-provided"
|
|
13697
13746
|
});
|
|
13698
|
-
}
|
|
13747
|
+
});
|
|
13748
|
+
if (!ok) {
|
|
13749
|
+
if (this.config.verbose) {
|
|
13750
|
+
console.warn(`[S3dbReplicator] Initialization failed: ${err.message}`);
|
|
13751
|
+
}
|
|
13699
13752
|
throw err;
|
|
13700
13753
|
}
|
|
13701
13754
|
}
|
|
@@ -13714,18 +13767,77 @@ class S3dbReplicator extends base_replicator_class_default {
|
|
|
13714
13767
|
id = recordId;
|
|
13715
13768
|
}
|
|
13716
13769
|
const normResource = normalizeResourceName$1(resource);
|
|
13717
|
-
const
|
|
13718
|
-
|
|
13719
|
-
|
|
13770
|
+
const entry = this.resourcesMap[normResource];
|
|
13771
|
+
if (!entry) {
|
|
13772
|
+
throw new Error(`[S3dbReplicator] Resource not configured: ${resource}`);
|
|
13773
|
+
}
|
|
13774
|
+
if (Array.isArray(entry)) {
|
|
13775
|
+
const results = [];
|
|
13776
|
+
for (const destConfig of entry) {
|
|
13777
|
+
const [ok, error, result] = await try_fn_default(async () => {
|
|
13778
|
+
return await this._replicateToSingleDestination(destConfig, normResource, op, payload, id);
|
|
13779
|
+
});
|
|
13780
|
+
if (!ok) {
|
|
13781
|
+
if (this.config && this.config.verbose) {
|
|
13782
|
+
console.warn(`[S3dbReplicator] Failed to replicate to destination ${JSON.stringify(destConfig)}: ${error.message}`);
|
|
13783
|
+
}
|
|
13784
|
+
throw error;
|
|
13785
|
+
}
|
|
13786
|
+
results.push(result);
|
|
13787
|
+
}
|
|
13788
|
+
return results;
|
|
13789
|
+
} else {
|
|
13790
|
+
const [ok, error, result] = await try_fn_default(async () => {
|
|
13791
|
+
return await this._replicateToSingleDestination(entry, normResource, op, payload, id);
|
|
13792
|
+
});
|
|
13793
|
+
if (!ok) {
|
|
13794
|
+
if (this.config && this.config.verbose) {
|
|
13795
|
+
console.warn(`[S3dbReplicator] Failed to replicate to destination ${JSON.stringify(entry)}: ${error.message}`);
|
|
13796
|
+
}
|
|
13797
|
+
throw error;
|
|
13798
|
+
}
|
|
13799
|
+
return result;
|
|
13800
|
+
}
|
|
13801
|
+
}
|
|
13802
|
+
async _replicateToSingleDestination(destConfig, sourceResource, operation, data, recordId) {
|
|
13803
|
+
let destResourceName;
|
|
13804
|
+
if (typeof destConfig === "string") {
|
|
13805
|
+
destResourceName = destConfig;
|
|
13806
|
+
} else if (typeof destConfig === "object" && destConfig.resource) {
|
|
13807
|
+
destResourceName = destConfig.resource;
|
|
13808
|
+
} else {
|
|
13809
|
+
destResourceName = sourceResource;
|
|
13810
|
+
}
|
|
13811
|
+
if (typeof destConfig === "object" && destConfig.actions && Array.isArray(destConfig.actions)) {
|
|
13812
|
+
if (!destConfig.actions.includes(operation)) {
|
|
13813
|
+
return { skipped: true, reason: "action_not_supported", action: operation, destination: destResourceName };
|
|
13814
|
+
}
|
|
13815
|
+
}
|
|
13816
|
+
const destResourceObj = this._getDestResourceObj(destResourceName);
|
|
13817
|
+
let transformedData;
|
|
13818
|
+
if (typeof destConfig === "object" && destConfig.transform && typeof destConfig.transform === "function") {
|
|
13819
|
+
transformedData = destConfig.transform(data);
|
|
13820
|
+
if (transformedData && data && data.id && !transformedData.id) {
|
|
13821
|
+
transformedData.id = data.id;
|
|
13822
|
+
}
|
|
13823
|
+
} else if (typeof destConfig === "object" && destConfig.transformer && typeof destConfig.transformer === "function") {
|
|
13824
|
+
transformedData = destConfig.transformer(data);
|
|
13825
|
+
if (transformedData && data && data.id && !transformedData.id) {
|
|
13826
|
+
transformedData.id = data.id;
|
|
13827
|
+
}
|
|
13828
|
+
} else {
|
|
13829
|
+
transformedData = data;
|
|
13830
|
+
}
|
|
13831
|
+
if (!transformedData && data) transformedData = data;
|
|
13720
13832
|
let result;
|
|
13721
|
-
if (
|
|
13833
|
+
if (operation === "insert") {
|
|
13722
13834
|
result = await destResourceObj.insert(transformedData);
|
|
13723
|
-
} else if (
|
|
13724
|
-
result = await destResourceObj.update(
|
|
13725
|
-
} else if (
|
|
13726
|
-
result = await destResourceObj.delete(
|
|
13835
|
+
} else if (operation === "update") {
|
|
13836
|
+
result = await destResourceObj.update(recordId, transformedData);
|
|
13837
|
+
} else if (operation === "delete") {
|
|
13838
|
+
result = await destResourceObj.delete(recordId);
|
|
13727
13839
|
} else {
|
|
13728
|
-
throw new Error(`Invalid operation: ${
|
|
13840
|
+
throw new Error(`Invalid operation: ${operation}. Supported operations are: insert, update, delete`);
|
|
13729
13841
|
}
|
|
13730
13842
|
return result;
|
|
13731
13843
|
}
|
|
@@ -13734,13 +13846,25 @@ class S3dbReplicator extends base_replicator_class_default {
|
|
|
13734
13846
|
const entry = this.resourcesMap[normResource];
|
|
13735
13847
|
let result;
|
|
13736
13848
|
if (!entry) return data;
|
|
13737
|
-
if (Array.isArray(entry)
|
|
13738
|
-
|
|
13849
|
+
if (Array.isArray(entry)) {
|
|
13850
|
+
for (const item of entry) {
|
|
13851
|
+
if (typeof item === "object" && item.transform && typeof item.transform === "function") {
|
|
13852
|
+
result = item.transform(data);
|
|
13853
|
+
break;
|
|
13854
|
+
} else if (typeof item === "object" && item.transformer && typeof item.transformer === "function") {
|
|
13855
|
+
result = item.transformer(data);
|
|
13856
|
+
break;
|
|
13857
|
+
}
|
|
13858
|
+
}
|
|
13859
|
+
if (!result) result = data;
|
|
13860
|
+
} else if (typeof entry === "object") {
|
|
13861
|
+
if (typeof entry.transform === "function") {
|
|
13862
|
+
result = entry.transform(data);
|
|
13863
|
+
} else if (typeof entry.transformer === "function") {
|
|
13864
|
+
result = entry.transformer(data);
|
|
13865
|
+
}
|
|
13739
13866
|
} else if (typeof entry === "function") {
|
|
13740
13867
|
result = entry(data);
|
|
13741
|
-
} else if (typeof entry === "object") {
|
|
13742
|
-
if (typeof entry.transform === "function") result = entry.transform(data);
|
|
13743
|
-
else if (typeof entry.transformer === "function") result = entry.transformer(data);
|
|
13744
13868
|
} else {
|
|
13745
13869
|
result = data;
|
|
13746
13870
|
}
|
|
@@ -13753,18 +13877,19 @@ class S3dbReplicator extends base_replicator_class_default {
|
|
|
13753
13877
|
const entry = this.resourcesMap[normResource];
|
|
13754
13878
|
if (!entry) return resource;
|
|
13755
13879
|
if (Array.isArray(entry)) {
|
|
13756
|
-
|
|
13757
|
-
|
|
13758
|
-
|
|
13880
|
+
for (const item of entry) {
|
|
13881
|
+
if (typeof item === "string") return item;
|
|
13882
|
+
if (typeof item === "object" && item.resource) return item.resource;
|
|
13883
|
+
}
|
|
13884
|
+
return resource;
|
|
13759
13885
|
}
|
|
13760
13886
|
if (typeof entry === "string") return entry;
|
|
13761
|
-
if (
|
|
13887
|
+
if (typeof entry === "function") return resource;
|
|
13762
13888
|
if (typeof entry === "object" && entry.resource) return entry.resource;
|
|
13763
13889
|
return resource;
|
|
13764
13890
|
}
|
|
13765
13891
|
_getDestResourceObj(resource) {
|
|
13766
|
-
|
|
13767
|
-
const available = Object.keys(this.client.resources);
|
|
13892
|
+
const available = Object.keys(this.client.resources || {});
|
|
13768
13893
|
const norm = normalizeResourceName$1(resource);
|
|
13769
13894
|
const found = available.find((r) => normalizeResourceName$1(r) === norm);
|
|
13770
13895
|
if (!found) {
|
|
@@ -13786,8 +13911,14 @@ class S3dbReplicator extends base_replicator_class_default {
|
|
|
13786
13911
|
data: record.data,
|
|
13787
13912
|
beforeData: record.beforeData
|
|
13788
13913
|
}));
|
|
13789
|
-
if (ok)
|
|
13790
|
-
|
|
13914
|
+
if (ok) {
|
|
13915
|
+
results.push(result);
|
|
13916
|
+
} else {
|
|
13917
|
+
if (this.config.verbose) {
|
|
13918
|
+
console.warn(`[S3dbReplicator] Batch replication failed for record ${record.id}: ${err.message}`);
|
|
13919
|
+
}
|
|
13920
|
+
errors.push({ id: record.id, error: err.message });
|
|
13921
|
+
}
|
|
13791
13922
|
}
|
|
13792
13923
|
this.emit("batch_replicated", {
|
|
13793
13924
|
replicator: this.name,
|
|
@@ -13805,18 +13936,20 @@ class S3dbReplicator extends base_replicator_class_default {
|
|
|
13805
13936
|
}
|
|
13806
13937
|
async testConnection() {
|
|
13807
13938
|
const [ok, err] = await try_fn_default(async () => {
|
|
13808
|
-
if (!this.targetDatabase)
|
|
13809
|
-
|
|
13939
|
+
if (!this.targetDatabase) throw new Error("No target database configured");
|
|
13940
|
+
if (typeof this.targetDatabase.connect === "function") {
|
|
13941
|
+
await this.targetDatabase.connect();
|
|
13810
13942
|
}
|
|
13811
|
-
await this.targetDatabase.listResources();
|
|
13812
13943
|
return true;
|
|
13813
13944
|
});
|
|
13814
|
-
if (ok)
|
|
13815
|
-
|
|
13816
|
-
|
|
13817
|
-
|
|
13818
|
-
|
|
13819
|
-
|
|
13945
|
+
if (!ok) {
|
|
13946
|
+
if (this.config.verbose) {
|
|
13947
|
+
console.warn(`[S3dbReplicator] Connection test failed: ${err.message}`);
|
|
13948
|
+
}
|
|
13949
|
+
this.emit("connection_error", { replicator: this.name, error: err.message });
|
|
13950
|
+
return false;
|
|
13951
|
+
}
|
|
13952
|
+
return true;
|
|
13820
13953
|
}
|
|
13821
13954
|
async getStatus() {
|
|
13822
13955
|
const baseStatus = await super.getStatus();
|
|
@@ -13848,7 +13981,7 @@ class S3dbReplicator extends base_replicator_class_default {
|
|
|
13848
13981
|
} else {
|
|
13849
13982
|
return true;
|
|
13850
13983
|
}
|
|
13851
|
-
} else if (typeof item === "string"
|
|
13984
|
+
} else if (typeof item === "string") {
|
|
13852
13985
|
return true;
|
|
13853
13986
|
}
|
|
13854
13987
|
}
|
|
@@ -13975,6 +14108,9 @@ class SqsReplicator extends base_replicator_class_default {
|
|
|
13975
14108
|
if (!this.sqsClient) {
|
|
13976
14109
|
const [ok, err, sdk] = await try_fn_default(() => import('@aws-sdk/client-sqs'));
|
|
13977
14110
|
if (!ok) {
|
|
14111
|
+
if (this.config.verbose) {
|
|
14112
|
+
console.warn(`[SqsReplicator] Failed to import SQS SDK: ${err.message}`);
|
|
14113
|
+
}
|
|
13978
14114
|
this.emit("initialization_error", {
|
|
13979
14115
|
replicator: this.name,
|
|
13980
14116
|
error: err.message
|
|
@@ -14026,6 +14162,9 @@ class SqsReplicator extends base_replicator_class_default {
|
|
|
14026
14162
|
return { success: true, results };
|
|
14027
14163
|
});
|
|
14028
14164
|
if (ok) return result;
|
|
14165
|
+
if (this.config.verbose) {
|
|
14166
|
+
console.warn(`[SqsReplicator] Replication failed for ${resource}: ${err.message}`);
|
|
14167
|
+
}
|
|
14029
14168
|
this.emit("replicator_error", {
|
|
14030
14169
|
replicator: this.name,
|
|
14031
14170
|
resource,
|
|
@@ -14098,6 +14237,9 @@ class SqsReplicator extends base_replicator_class_default {
|
|
|
14098
14237
|
});
|
|
14099
14238
|
if (ok) return result;
|
|
14100
14239
|
const errorMessage = err?.message || err || "Unknown error";
|
|
14240
|
+
if (this.config.verbose) {
|
|
14241
|
+
console.warn(`[SqsReplicator] Batch replication failed for ${resource}: ${errorMessage}`);
|
|
14242
|
+
}
|
|
14101
14243
|
this.emit("batch_replicator_error", {
|
|
14102
14244
|
replicator: this.name,
|
|
14103
14245
|
resource,
|
|
@@ -14119,6 +14261,9 @@ class SqsReplicator extends base_replicator_class_default {
|
|
|
14119
14261
|
return true;
|
|
14120
14262
|
});
|
|
14121
14263
|
if (ok) return true;
|
|
14264
|
+
if (this.config.verbose) {
|
|
14265
|
+
console.warn(`[SqsReplicator] Connection test failed: ${err.message}`);
|
|
14266
|
+
}
|
|
14122
14267
|
this.emit("connection_error", {
|
|
14123
14268
|
replicator: this.name,
|
|
14124
14269
|
error: err.message
|
|
@@ -14215,25 +14360,37 @@ class ReplicatorPlugin extends plugin_class_default {
|
|
|
14215
14360
|
return;
|
|
14216
14361
|
}
|
|
14217
14362
|
resource.on("insert", async (data) => {
|
|
14218
|
-
|
|
14363
|
+
const [ok, error] = await try_fn_default(async () => {
|
|
14219
14364
|
const completeData = { ...data, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
14220
14365
|
await plugin.processReplicatorEvent("insert", resource.name, completeData.id, completeData);
|
|
14221
|
-
}
|
|
14366
|
+
});
|
|
14367
|
+
if (!ok) {
|
|
14368
|
+
if (this.config.verbose) {
|
|
14369
|
+
console.warn(`[ReplicatorPlugin] Insert event failed for resource ${resource.name}: ${error.message}`);
|
|
14370
|
+
}
|
|
14222
14371
|
this.emit("error", { operation: "insert", error: error.message, resource: resource.name });
|
|
14223
14372
|
}
|
|
14224
14373
|
});
|
|
14225
14374
|
resource.on("update", async (data, beforeData) => {
|
|
14226
|
-
|
|
14375
|
+
const [ok, error] = await try_fn_default(async () => {
|
|
14227
14376
|
const completeData = { ...data, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
14228
14377
|
await plugin.processReplicatorEvent("update", resource.name, completeData.id, completeData, beforeData);
|
|
14229
|
-
}
|
|
14378
|
+
});
|
|
14379
|
+
if (!ok) {
|
|
14380
|
+
if (this.config.verbose) {
|
|
14381
|
+
console.warn(`[ReplicatorPlugin] Update event failed for resource ${resource.name}: ${error.message}`);
|
|
14382
|
+
}
|
|
14230
14383
|
this.emit("error", { operation: "update", error: error.message, resource: resource.name });
|
|
14231
14384
|
}
|
|
14232
14385
|
});
|
|
14233
14386
|
resource.on("delete", async (data) => {
|
|
14234
|
-
|
|
14387
|
+
const [ok, error] = await try_fn_default(async () => {
|
|
14235
14388
|
await plugin.processReplicatorEvent("delete", resource.name, data.id, data);
|
|
14236
|
-
}
|
|
14389
|
+
});
|
|
14390
|
+
if (!ok) {
|
|
14391
|
+
if (this.config.verbose) {
|
|
14392
|
+
console.warn(`[ReplicatorPlugin] Delete event failed for resource ${resource.name}: ${error.message}`);
|
|
14393
|
+
}
|
|
14237
14394
|
this.emit("error", { operation: "delete", error: error.message, resource: resource.name });
|
|
14238
14395
|
}
|
|
14239
14396
|
});
|
|
@@ -14249,13 +14406,17 @@ class ReplicatorPlugin extends plugin_class_default {
|
|
|
14249
14406
|
}
|
|
14250
14407
|
async setup(database) {
|
|
14251
14408
|
this.database = database;
|
|
14252
|
-
|
|
14409
|
+
const [initOk, initError] = await try_fn_default(async () => {
|
|
14253
14410
|
await this.initializeReplicators(database);
|
|
14254
|
-
}
|
|
14255
|
-
|
|
14256
|
-
|
|
14411
|
+
});
|
|
14412
|
+
if (!initOk) {
|
|
14413
|
+
if (this.config.verbose) {
|
|
14414
|
+
console.warn(`[ReplicatorPlugin] Replicator initialization failed: ${initError.message}`);
|
|
14415
|
+
}
|
|
14416
|
+
this.emit("error", { operation: "setup", error: initError.message });
|
|
14417
|
+
throw initError;
|
|
14257
14418
|
}
|
|
14258
|
-
|
|
14419
|
+
const [logOk, logError] = await try_fn_default(async () => {
|
|
14259
14420
|
if (this.config.replicatorLogResource) {
|
|
14260
14421
|
const logRes = await database.createResource({
|
|
14261
14422
|
name: this.config.replicatorLogResource,
|
|
@@ -14272,7 +14433,15 @@ class ReplicatorPlugin extends plugin_class_default {
|
|
|
14272
14433
|
}
|
|
14273
14434
|
});
|
|
14274
14435
|
}
|
|
14275
|
-
}
|
|
14436
|
+
});
|
|
14437
|
+
if (!logOk) {
|
|
14438
|
+
if (this.config.verbose) {
|
|
14439
|
+
console.warn(`[ReplicatorPlugin] Failed to create log resource ${this.config.replicatorLogResource}: ${logError.message}`);
|
|
14440
|
+
}
|
|
14441
|
+
this.emit("replicator_log_resource_creation_error", {
|
|
14442
|
+
resourceName: this.config.replicatorLogResource,
|
|
14443
|
+
error: logError.message
|
|
14444
|
+
});
|
|
14276
14445
|
}
|
|
14277
14446
|
await this.uploadMetadataFile(database);
|
|
14278
14447
|
const originalCreateResource = database.createResource.bind(database);
|
|
@@ -14307,49 +14476,36 @@ class ReplicatorPlugin extends plugin_class_default {
|
|
|
14307
14476
|
}
|
|
14308
14477
|
async stop() {
|
|
14309
14478
|
}
|
|
14310
|
-
filterInternalFields(data) {
|
|
14311
|
-
if (!data || typeof data !== "object") return data;
|
|
14312
|
-
const filtered = {};
|
|
14313
|
-
for (const [key, value] of Object.entries(data)) {
|
|
14314
|
-
if (!key.startsWith("_") && !key.startsWith("$")) {
|
|
14315
|
-
filtered[key] = value;
|
|
14316
|
-
}
|
|
14317
|
-
}
|
|
14318
|
-
return filtered;
|
|
14319
|
-
}
|
|
14320
14479
|
async uploadMetadataFile(database) {
|
|
14321
14480
|
if (typeof database.uploadMetadataFile === "function") {
|
|
14322
14481
|
await database.uploadMetadataFile();
|
|
14323
14482
|
}
|
|
14324
14483
|
}
|
|
14325
|
-
async getCompleteData(resource, data) {
|
|
14326
|
-
try {
|
|
14327
|
-
const [ok, err, record] = await try_fn_default(() => resource.get(data.id));
|
|
14328
|
-
if (ok && record) {
|
|
14329
|
-
return record;
|
|
14330
|
-
}
|
|
14331
|
-
} catch (error) {
|
|
14332
|
-
}
|
|
14333
|
-
return data;
|
|
14334
|
-
}
|
|
14335
14484
|
async retryWithBackoff(operation, maxRetries = 3) {
|
|
14336
14485
|
let lastError;
|
|
14337
14486
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
14338
|
-
|
|
14339
|
-
|
|
14340
|
-
|
|
14487
|
+
const [ok, error] = await try_fn_default(operation);
|
|
14488
|
+
if (ok) {
|
|
14489
|
+
return ok;
|
|
14490
|
+
} else {
|
|
14341
14491
|
lastError = error;
|
|
14492
|
+
if (this.config.verbose) {
|
|
14493
|
+
console.warn(`[ReplicatorPlugin] Retry attempt ${attempt}/${maxRetries} failed: ${error.message}`);
|
|
14494
|
+
}
|
|
14342
14495
|
if (attempt === maxRetries) {
|
|
14343
14496
|
throw error;
|
|
14344
14497
|
}
|
|
14345
14498
|
const delay = Math.pow(2, attempt - 1) * 1e3;
|
|
14499
|
+
if (this.config.verbose) {
|
|
14500
|
+
console.warn(`[ReplicatorPlugin] Waiting ${delay}ms before retry...`);
|
|
14501
|
+
}
|
|
14346
14502
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
14347
14503
|
}
|
|
14348
14504
|
}
|
|
14349
14505
|
throw lastError;
|
|
14350
14506
|
}
|
|
14351
14507
|
async logError(replicator, resourceName, operation, recordId, data, error) {
|
|
14352
|
-
|
|
14508
|
+
const [ok, logError] = await try_fn_default(async () => {
|
|
14353
14509
|
const logResourceName = this.config.replicatorLogResource;
|
|
14354
14510
|
if (this.database && this.database.resources && this.database.resources[logResourceName]) {
|
|
14355
14511
|
const logResource = this.database.resources[logResourceName];
|
|
@@ -14364,7 +14520,19 @@ class ReplicatorPlugin extends plugin_class_default {
|
|
|
14364
14520
|
status: "error"
|
|
14365
14521
|
});
|
|
14366
14522
|
}
|
|
14367
|
-
}
|
|
14523
|
+
});
|
|
14524
|
+
if (!ok) {
|
|
14525
|
+
if (this.config.verbose) {
|
|
14526
|
+
console.warn(`[ReplicatorPlugin] Failed to log error for ${resourceName}: ${logError.message}`);
|
|
14527
|
+
}
|
|
14528
|
+
this.emit("replicator_log_error", {
|
|
14529
|
+
replicator: replicator.name || replicator.id,
|
|
14530
|
+
resourceName,
|
|
14531
|
+
operation,
|
|
14532
|
+
recordId,
|
|
14533
|
+
originalError: error.message,
|
|
14534
|
+
logError: logError.message
|
|
14535
|
+
});
|
|
14368
14536
|
}
|
|
14369
14537
|
}
|
|
14370
14538
|
async processReplicatorEvent(operation, resourceName, recordId, data, beforeData = null) {
|
|
@@ -14377,8 +14545,8 @@ class ReplicatorPlugin extends plugin_class_default {
|
|
|
14377
14545
|
return;
|
|
14378
14546
|
}
|
|
14379
14547
|
const promises = applicableReplicators.map(async (replicator) => {
|
|
14380
|
-
|
|
14381
|
-
const
|
|
14548
|
+
const [ok, error, result] = await try_fn_default(async () => {
|
|
14549
|
+
const result2 = await this.retryWithBackoff(
|
|
14382
14550
|
() => replicator.replicate(resourceName, operation, data, recordId, beforeData),
|
|
14383
14551
|
this.config.maxRetries
|
|
14384
14552
|
);
|
|
@@ -14387,11 +14555,17 @@ class ReplicatorPlugin extends plugin_class_default {
|
|
|
14387
14555
|
resourceName,
|
|
14388
14556
|
operation,
|
|
14389
14557
|
recordId,
|
|
14390
|
-
result,
|
|
14558
|
+
result: result2,
|
|
14391
14559
|
success: true
|
|
14392
14560
|
});
|
|
14561
|
+
return result2;
|
|
14562
|
+
});
|
|
14563
|
+
if (ok) {
|
|
14393
14564
|
return result;
|
|
14394
|
-
}
|
|
14565
|
+
} else {
|
|
14566
|
+
if (this.config.verbose) {
|
|
14567
|
+
console.warn(`[ReplicatorPlugin] Replication failed for ${replicator.name || replicator.id} on ${resourceName}: ${error.message}`);
|
|
14568
|
+
}
|
|
14395
14569
|
this.emit("replicator_error", {
|
|
14396
14570
|
replicator: replicator.name || replicator.id,
|
|
14397
14571
|
resourceName,
|
|
@@ -14416,11 +14590,14 @@ class ReplicatorPlugin extends plugin_class_default {
|
|
|
14416
14590
|
return;
|
|
14417
14591
|
}
|
|
14418
14592
|
const promises = applicableReplicators.map(async (replicator) => {
|
|
14419
|
-
|
|
14593
|
+
const [wrapperOk, wrapperError] = await try_fn_default(async () => {
|
|
14420
14594
|
const [ok, err, result] = await try_fn_default(
|
|
14421
14595
|
() => replicator.replicate(item.resourceName, item.operation, item.data, item.recordId, item.beforeData)
|
|
14422
14596
|
);
|
|
14423
14597
|
if (!ok) {
|
|
14598
|
+
if (this.config.verbose) {
|
|
14599
|
+
console.warn(`[ReplicatorPlugin] Replicator item processing failed for ${replicator.name || replicator.id} on ${item.resourceName}: ${err.message}`);
|
|
14600
|
+
}
|
|
14424
14601
|
this.emit("replicator_error", {
|
|
14425
14602
|
replicator: replicator.name || replicator.id,
|
|
14426
14603
|
resourceName: item.resourceName,
|
|
@@ -14442,18 +14619,24 @@ class ReplicatorPlugin extends plugin_class_default {
|
|
|
14442
14619
|
success: true
|
|
14443
14620
|
});
|
|
14444
14621
|
return { success: true, result };
|
|
14445
|
-
}
|
|
14622
|
+
});
|
|
14623
|
+
if (wrapperOk) {
|
|
14624
|
+
return wrapperOk;
|
|
14625
|
+
} else {
|
|
14626
|
+
if (this.config.verbose) {
|
|
14627
|
+
console.warn(`[ReplicatorPlugin] Wrapper processing failed for ${replicator.name || replicator.id} on ${item.resourceName}: ${wrapperError.message}`);
|
|
14628
|
+
}
|
|
14446
14629
|
this.emit("replicator_error", {
|
|
14447
14630
|
replicator: replicator.name || replicator.id,
|
|
14448
14631
|
resourceName: item.resourceName,
|
|
14449
14632
|
operation: item.operation,
|
|
14450
14633
|
recordId: item.recordId,
|
|
14451
|
-
error:
|
|
14634
|
+
error: wrapperError.message
|
|
14452
14635
|
});
|
|
14453
14636
|
if (this.config.logErrors && this.database) {
|
|
14454
|
-
await this.logError(replicator, item.resourceName, item.operation, item.recordId, item.data,
|
|
14637
|
+
await this.logError(replicator, item.resourceName, item.operation, item.recordId, item.data, wrapperError);
|
|
14455
14638
|
}
|
|
14456
|
-
return { success: false, error:
|
|
14639
|
+
return { success: false, error: wrapperError.message };
|
|
14457
14640
|
}
|
|
14458
14641
|
});
|
|
14459
14642
|
return Promise.allSettled(promises);
|
|
@@ -14475,9 +14658,13 @@ class ReplicatorPlugin extends plugin_class_default {
|
|
|
14475
14658
|
timestamp: typeof item.timestamp === "number" ? item.timestamp : Date.now(),
|
|
14476
14659
|
createdAt: item.createdAt || (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)
|
|
14477
14660
|
};
|
|
14478
|
-
|
|
14661
|
+
const [ok, err] = await try_fn_default(async () => {
|
|
14479
14662
|
await logRes.insert(logItem);
|
|
14480
|
-
}
|
|
14663
|
+
});
|
|
14664
|
+
if (!ok) {
|
|
14665
|
+
if (this.config.verbose) {
|
|
14666
|
+
console.warn(`[ReplicatorPlugin] Failed to log replicator item: ${err.message}`);
|
|
14667
|
+
}
|
|
14481
14668
|
this.emit("replicator.log.failed", { error: err, item });
|
|
14482
14669
|
}
|
|
14483
14670
|
}
|
|
@@ -14583,14 +14770,23 @@ class ReplicatorPlugin extends plugin_class_default {
|
|
|
14583
14770
|
this.emit("replicator.sync.completed", { replicatorId, stats: this.stats });
|
|
14584
14771
|
}
|
|
14585
14772
|
async cleanup() {
|
|
14586
|
-
|
|
14773
|
+
const [ok, error] = await try_fn_default(async () => {
|
|
14587
14774
|
if (this.replicators && this.replicators.length > 0) {
|
|
14588
14775
|
const cleanupPromises = this.replicators.map(async (replicator) => {
|
|
14589
|
-
|
|
14776
|
+
const [replicatorOk, replicatorError] = await try_fn_default(async () => {
|
|
14590
14777
|
if (replicator && typeof replicator.cleanup === "function") {
|
|
14591
14778
|
await replicator.cleanup();
|
|
14592
14779
|
}
|
|
14593
|
-
}
|
|
14780
|
+
});
|
|
14781
|
+
if (!replicatorOk) {
|
|
14782
|
+
if (this.config.verbose) {
|
|
14783
|
+
console.warn(`[ReplicatorPlugin] Failed to cleanup replicator ${replicator.name || replicator.id}: ${replicatorError.message}`);
|
|
14784
|
+
}
|
|
14785
|
+
this.emit("replicator_cleanup_error", {
|
|
14786
|
+
replicator: replicator.name || replicator.id || "unknown",
|
|
14787
|
+
driver: replicator.driver || "unknown",
|
|
14788
|
+
error: replicatorError.message
|
|
14789
|
+
});
|
|
14594
14790
|
}
|
|
14595
14791
|
});
|
|
14596
14792
|
await Promise.allSettled(cleanupPromises);
|
|
@@ -14599,7 +14795,14 @@ class ReplicatorPlugin extends plugin_class_default {
|
|
|
14599
14795
|
this.database = null;
|
|
14600
14796
|
this.eventListenersInstalled.clear();
|
|
14601
14797
|
this.removeAllListeners();
|
|
14602
|
-
}
|
|
14798
|
+
});
|
|
14799
|
+
if (!ok) {
|
|
14800
|
+
if (this.config.verbose) {
|
|
14801
|
+
console.warn(`[ReplicatorPlugin] Failed to cleanup plugin: ${error.message}`);
|
|
14802
|
+
}
|
|
14803
|
+
this.emit("replicator_plugin_cleanup_error", {
|
|
14804
|
+
error: error.message
|
|
14805
|
+
});
|
|
14603
14806
|
}
|
|
14604
14807
|
}
|
|
14605
14808
|
}
|