s3db.js 6.1.0 → 6.2.0
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/README.md +29 -47
- package/dist/s3db.cjs.js +312 -117
- package/dist/s3db.cjs.min.js +1 -1
- package/dist/s3db.es.js +312 -117
- package/dist/s3db.es.min.js +1 -1
- package/dist/s3db.iife.js +312 -117
- package/dist/s3db.iife.min.js +1 -1
- package/package.json +1 -1
package/dist/s3db.es.js
CHANGED
|
@@ -11062,7 +11062,7 @@ class Database extends EventEmitter {
|
|
|
11062
11062
|
this.version = "1";
|
|
11063
11063
|
this.s3dbVersion = (() => {
|
|
11064
11064
|
try {
|
|
11065
|
-
return true ? "6.
|
|
11065
|
+
return true ? "6.1.0" : "latest";
|
|
11066
11066
|
} catch (e) {
|
|
11067
11067
|
return "latest";
|
|
11068
11068
|
}
|
|
@@ -20039,23 +20039,63 @@ class SqsReplicator extends BaseReplicator {
|
|
|
20039
20039
|
}
|
|
20040
20040
|
|
|
20041
20041
|
class BigqueryReplicator extends BaseReplicator {
|
|
20042
|
-
constructor(config = {}, resources =
|
|
20042
|
+
constructor(config = {}, resources = {}) {
|
|
20043
20043
|
super(config);
|
|
20044
|
-
this.resources = resources;
|
|
20045
20044
|
this.projectId = config.projectId;
|
|
20046
20045
|
this.datasetId = config.datasetId;
|
|
20047
|
-
this.tableId = config.tableId;
|
|
20048
|
-
this.tableMap = config.tableMap || {};
|
|
20049
20046
|
this.bigqueryClient = null;
|
|
20050
20047
|
this.credentials = config.credentials;
|
|
20051
20048
|
this.location = config.location || "US";
|
|
20052
|
-
this.
|
|
20049
|
+
this.logTable = config.logTable;
|
|
20050
|
+
this.resources = this.parseResourcesConfig(resources);
|
|
20051
|
+
}
|
|
20052
|
+
parseResourcesConfig(resources) {
|
|
20053
|
+
const parsed = {};
|
|
20054
|
+
for (const [resourceName, config] of Object.entries(resources)) {
|
|
20055
|
+
if (typeof config === "string") {
|
|
20056
|
+
parsed[resourceName] = [{
|
|
20057
|
+
table: config,
|
|
20058
|
+
actions: ["insert"]
|
|
20059
|
+
}];
|
|
20060
|
+
} else if (Array.isArray(config)) {
|
|
20061
|
+
parsed[resourceName] = config.map((item) => {
|
|
20062
|
+
if (typeof item === "string") {
|
|
20063
|
+
return { table: item, actions: ["insert"] };
|
|
20064
|
+
}
|
|
20065
|
+
return {
|
|
20066
|
+
table: item.table,
|
|
20067
|
+
actions: item.actions || ["insert"]
|
|
20068
|
+
};
|
|
20069
|
+
});
|
|
20070
|
+
} else if (typeof config === "object") {
|
|
20071
|
+
parsed[resourceName] = [{
|
|
20072
|
+
table: config.table,
|
|
20073
|
+
actions: config.actions || ["insert"]
|
|
20074
|
+
}];
|
|
20075
|
+
}
|
|
20076
|
+
}
|
|
20077
|
+
return parsed;
|
|
20053
20078
|
}
|
|
20054
20079
|
validateConfig() {
|
|
20055
20080
|
const errors = [];
|
|
20056
20081
|
if (!this.projectId) errors.push("projectId is required");
|
|
20057
20082
|
if (!this.datasetId) errors.push("datasetId is required");
|
|
20058
|
-
if (
|
|
20083
|
+
if (Object.keys(this.resources).length === 0) errors.push("At least one resource must be configured");
|
|
20084
|
+
for (const [resourceName, tables] of Object.entries(this.resources)) {
|
|
20085
|
+
for (const tableConfig of tables) {
|
|
20086
|
+
if (!tableConfig.table) {
|
|
20087
|
+
errors.push(`Table name is required for resource '${resourceName}'`);
|
|
20088
|
+
}
|
|
20089
|
+
if (!Array.isArray(tableConfig.actions) || tableConfig.actions.length === 0) {
|
|
20090
|
+
errors.push(`Actions array is required for resource '${resourceName}'`);
|
|
20091
|
+
}
|
|
20092
|
+
const validActions = ["insert", "update", "delete"];
|
|
20093
|
+
const invalidActions = tableConfig.actions.filter((action) => !validActions.includes(action));
|
|
20094
|
+
if (invalidActions.length > 0) {
|
|
20095
|
+
errors.push(`Invalid actions for resource '${resourceName}': ${invalidActions.join(", ")}. Valid actions: ${validActions.join(", ")}`);
|
|
20096
|
+
}
|
|
20097
|
+
}
|
|
20098
|
+
}
|
|
20059
20099
|
return { isValid: errors.length === 0, errors };
|
|
20060
20100
|
}
|
|
20061
20101
|
async initialize(database) {
|
|
@@ -20071,78 +20111,117 @@ class BigqueryReplicator extends BaseReplicator {
|
|
|
20071
20111
|
replicator: this.name,
|
|
20072
20112
|
projectId: this.projectId,
|
|
20073
20113
|
datasetId: this.datasetId,
|
|
20074
|
-
|
|
20114
|
+
resources: Object.keys(this.resources)
|
|
20075
20115
|
});
|
|
20076
20116
|
} catch (error) {
|
|
20077
20117
|
this.emit("initialization_error", { replicator: this.name, error: error.message });
|
|
20078
20118
|
throw error;
|
|
20079
20119
|
}
|
|
20080
20120
|
}
|
|
20081
|
-
|
|
20082
|
-
return this.
|
|
20121
|
+
shouldReplicateResource(resourceName) {
|
|
20122
|
+
return this.resources.hasOwnProperty(resourceName);
|
|
20123
|
+
}
|
|
20124
|
+
shouldReplicateAction(resourceName, operation) {
|
|
20125
|
+
if (!this.resources[resourceName]) return false;
|
|
20126
|
+
return this.resources[resourceName].some(
|
|
20127
|
+
(tableConfig) => tableConfig.actions.includes(operation)
|
|
20128
|
+
);
|
|
20129
|
+
}
|
|
20130
|
+
getTablesForResource(resourceName, operation) {
|
|
20131
|
+
if (!this.resources[resourceName]) return [];
|
|
20132
|
+
return this.resources[resourceName].filter((tableConfig) => tableConfig.actions.includes(operation)).map((tableConfig) => tableConfig.table);
|
|
20083
20133
|
}
|
|
20084
20134
|
async replicate(resourceName, operation, data, id, beforeData = null) {
|
|
20085
20135
|
if (!this.enabled || !this.shouldReplicateResource(resourceName)) {
|
|
20086
20136
|
return { skipped: true, reason: "resource_not_included" };
|
|
20087
20137
|
}
|
|
20138
|
+
if (!this.shouldReplicateAction(resourceName, operation)) {
|
|
20139
|
+
return { skipped: true, reason: "action_not_included" };
|
|
20140
|
+
}
|
|
20141
|
+
const tables = this.getTablesForResource(resourceName, operation);
|
|
20142
|
+
if (tables.length === 0) {
|
|
20143
|
+
return { skipped: true, reason: "no_tables_for_action" };
|
|
20144
|
+
}
|
|
20145
|
+
const results = [];
|
|
20146
|
+
const errors = [];
|
|
20088
20147
|
try {
|
|
20089
20148
|
const dataset = this.bigqueryClient.dataset(this.datasetId);
|
|
20090
|
-
const tableId
|
|
20091
|
-
|
|
20092
|
-
|
|
20093
|
-
|
|
20094
|
-
|
|
20095
|
-
|
|
20096
|
-
|
|
20097
|
-
|
|
20098
|
-
|
|
20099
|
-
|
|
20100
|
-
|
|
20101
|
-
|
|
20102
|
-
|
|
20103
|
-
|
|
20104
|
-
|
|
20105
|
-
|
|
20106
|
-
|
|
20107
|
-
|
|
20108
|
-
|
|
20109
|
-
|
|
20110
|
-
|
|
20111
|
-
|
|
20112
|
-
|
|
20113
|
-
|
|
20114
|
-
|
|
20115
|
-
|
|
20116
|
-
|
|
20117
|
-
|
|
20118
|
-
|
|
20119
|
-
|
|
20120
|
-
|
|
20121
|
-
|
|
20122
|
-
|
|
20123
|
-
|
|
20124
|
-
|
|
20125
|
-
|
|
20126
|
-
|
|
20127
|
-
|
|
20128
|
-
|
|
20129
|
-
|
|
20130
|
-
|
|
20131
|
-
|
|
20132
|
-
|
|
20133
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
20134
|
-
source: "s3db-replication"
|
|
20135
|
-
}]);
|
|
20149
|
+
for (const tableId of tables) {
|
|
20150
|
+
try {
|
|
20151
|
+
const table = dataset.table(tableId);
|
|
20152
|
+
let job;
|
|
20153
|
+
if (operation === "insert") {
|
|
20154
|
+
const row = { ...data };
|
|
20155
|
+
job = await table.insert([row]);
|
|
20156
|
+
} else if (operation === "update") {
|
|
20157
|
+
const keys = Object.keys(data).filter((k) => k !== "id");
|
|
20158
|
+
const setClause = keys.map((k) => `${k}=@${k}`).join(", ");
|
|
20159
|
+
const params = { id };
|
|
20160
|
+
keys.forEach((k) => {
|
|
20161
|
+
params[k] = data[k];
|
|
20162
|
+
});
|
|
20163
|
+
const query = `UPDATE \`${this.projectId}.${this.datasetId}.${tableId}\` SET ${setClause} WHERE id=@id`;
|
|
20164
|
+
const [updateJob] = await this.bigqueryClient.createQueryJob({
|
|
20165
|
+
query,
|
|
20166
|
+
params
|
|
20167
|
+
});
|
|
20168
|
+
await updateJob.getQueryResults();
|
|
20169
|
+
job = [updateJob];
|
|
20170
|
+
} else if (operation === "delete") {
|
|
20171
|
+
const query = `DELETE FROM \`${this.projectId}.${this.datasetId}.${tableId}\` WHERE id=@id`;
|
|
20172
|
+
const [deleteJob] = await this.bigqueryClient.createQueryJob({
|
|
20173
|
+
query,
|
|
20174
|
+
params: { id }
|
|
20175
|
+
});
|
|
20176
|
+
await deleteJob.getQueryResults();
|
|
20177
|
+
job = [deleteJob];
|
|
20178
|
+
} else {
|
|
20179
|
+
throw new Error(`Unsupported operation: ${operation}`);
|
|
20180
|
+
}
|
|
20181
|
+
results.push({
|
|
20182
|
+
table: tableId,
|
|
20183
|
+
success: true,
|
|
20184
|
+
jobId: job[0]?.id
|
|
20185
|
+
});
|
|
20186
|
+
} catch (error) {
|
|
20187
|
+
errors.push({
|
|
20188
|
+
table: tableId,
|
|
20189
|
+
error: error.message
|
|
20190
|
+
});
|
|
20191
|
+
}
|
|
20136
20192
|
}
|
|
20193
|
+
if (this.logTable) {
|
|
20194
|
+
try {
|
|
20195
|
+
const logTable = dataset.table(this.logTable);
|
|
20196
|
+
await logTable.insert([{
|
|
20197
|
+
resource_name: resourceName,
|
|
20198
|
+
operation,
|
|
20199
|
+
record_id: id,
|
|
20200
|
+
data: JSON.stringify(data),
|
|
20201
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
20202
|
+
source: "s3db-replication"
|
|
20203
|
+
}]);
|
|
20204
|
+
} catch (error) {
|
|
20205
|
+
console.warn(`Failed to log operation to ${this.logTable}:`, error.message);
|
|
20206
|
+
}
|
|
20207
|
+
}
|
|
20208
|
+
const success = errors.length === 0;
|
|
20137
20209
|
this.emit("replicated", {
|
|
20138
20210
|
replicator: this.name,
|
|
20139
20211
|
resourceName,
|
|
20140
20212
|
operation,
|
|
20141
20213
|
id,
|
|
20142
|
-
|
|
20143
|
-
|
|
20214
|
+
tables,
|
|
20215
|
+
results,
|
|
20216
|
+
errors,
|
|
20217
|
+
success
|
|
20144
20218
|
});
|
|
20145
|
-
return {
|
|
20219
|
+
return {
|
|
20220
|
+
success,
|
|
20221
|
+
results,
|
|
20222
|
+
errors,
|
|
20223
|
+
tables
|
|
20224
|
+
};
|
|
20146
20225
|
} catch (error) {
|
|
20147
20226
|
this.emit("replication_error", {
|
|
20148
20227
|
replicator: this.name,
|
|
@@ -20159,13 +20238,23 @@ class BigqueryReplicator extends BaseReplicator {
|
|
|
20159
20238
|
const errors = [];
|
|
20160
20239
|
for (const record of records) {
|
|
20161
20240
|
try {
|
|
20162
|
-
const res = await this.replicate(
|
|
20241
|
+
const res = await this.replicate(
|
|
20242
|
+
resourceName,
|
|
20243
|
+
record.operation,
|
|
20244
|
+
record.data,
|
|
20245
|
+
record.id,
|
|
20246
|
+
record.beforeData
|
|
20247
|
+
);
|
|
20163
20248
|
results.push(res);
|
|
20164
20249
|
} catch (err) {
|
|
20165
20250
|
errors.push({ id: record.id, error: err.message });
|
|
20166
20251
|
}
|
|
20167
20252
|
}
|
|
20168
|
-
return {
|
|
20253
|
+
return {
|
|
20254
|
+
success: errors.length === 0,
|
|
20255
|
+
results,
|
|
20256
|
+
errors
|
|
20257
|
+
};
|
|
20169
20258
|
}
|
|
20170
20259
|
async testConnection() {
|
|
20171
20260
|
try {
|
|
@@ -20180,37 +20269,82 @@ class BigqueryReplicator extends BaseReplicator {
|
|
|
20180
20269
|
}
|
|
20181
20270
|
async cleanup() {
|
|
20182
20271
|
}
|
|
20183
|
-
|
|
20184
|
-
|
|
20185
|
-
|
|
20272
|
+
getStatus() {
|
|
20273
|
+
return {
|
|
20274
|
+
...super.getStatus(),
|
|
20275
|
+
projectId: this.projectId,
|
|
20276
|
+
datasetId: this.datasetId,
|
|
20277
|
+
resources: this.resources,
|
|
20278
|
+
logTable: this.logTable
|
|
20279
|
+
};
|
|
20186
20280
|
}
|
|
20187
20281
|
}
|
|
20188
20282
|
|
|
20189
20283
|
class PostgresReplicator extends BaseReplicator {
|
|
20190
|
-
constructor(config = {}, resources =
|
|
20284
|
+
constructor(config = {}, resources = {}) {
|
|
20191
20285
|
super(config);
|
|
20192
|
-
this.resources = resources;
|
|
20193
20286
|
this.connectionString = config.connectionString;
|
|
20194
20287
|
this.host = config.host;
|
|
20195
20288
|
this.port = config.port || 5432;
|
|
20196
20289
|
this.database = config.database;
|
|
20197
20290
|
this.user = config.user;
|
|
20198
20291
|
this.password = config.password;
|
|
20199
|
-
this.tableName = config.tableName || "s3db_replication";
|
|
20200
|
-
this.tableMap = config.tableMap || {};
|
|
20201
20292
|
this.client = null;
|
|
20202
20293
|
this.ssl = config.ssl;
|
|
20203
|
-
this.
|
|
20294
|
+
this.logTable = config.logTable;
|
|
20295
|
+
this.resources = this.parseResourcesConfig(resources);
|
|
20296
|
+
}
|
|
20297
|
+
parseResourcesConfig(resources) {
|
|
20298
|
+
const parsed = {};
|
|
20299
|
+
for (const [resourceName, config] of Object.entries(resources)) {
|
|
20300
|
+
if (typeof config === "string") {
|
|
20301
|
+
parsed[resourceName] = [{
|
|
20302
|
+
table: config,
|
|
20303
|
+
actions: ["insert"]
|
|
20304
|
+
}];
|
|
20305
|
+
} else if (Array.isArray(config)) {
|
|
20306
|
+
parsed[resourceName] = config.map((item) => {
|
|
20307
|
+
if (typeof item === "string") {
|
|
20308
|
+
return { table: item, actions: ["insert"] };
|
|
20309
|
+
}
|
|
20310
|
+
return {
|
|
20311
|
+
table: item.table,
|
|
20312
|
+
actions: item.actions || ["insert"]
|
|
20313
|
+
};
|
|
20314
|
+
});
|
|
20315
|
+
} else if (typeof config === "object") {
|
|
20316
|
+
parsed[resourceName] = [{
|
|
20317
|
+
table: config.table,
|
|
20318
|
+
actions: config.actions || ["insert"]
|
|
20319
|
+
}];
|
|
20320
|
+
}
|
|
20321
|
+
}
|
|
20322
|
+
return parsed;
|
|
20204
20323
|
}
|
|
20205
20324
|
validateConfig() {
|
|
20206
20325
|
const errors = [];
|
|
20207
20326
|
if (!this.connectionString && (!this.host || !this.database)) {
|
|
20208
20327
|
errors.push("Either connectionString or host+database must be provided");
|
|
20209
20328
|
}
|
|
20210
|
-
|
|
20211
|
-
|
|
20212
|
-
|
|
20213
|
-
|
|
20329
|
+
if (Object.keys(this.resources).length === 0) {
|
|
20330
|
+
errors.push("At least one resource must be configured");
|
|
20331
|
+
}
|
|
20332
|
+
for (const [resourceName, tables] of Object.entries(this.resources)) {
|
|
20333
|
+
for (const tableConfig of tables) {
|
|
20334
|
+
if (!tableConfig.table) {
|
|
20335
|
+
errors.push(`Table name is required for resource '${resourceName}'`);
|
|
20336
|
+
}
|
|
20337
|
+
if (!Array.isArray(tableConfig.actions) || tableConfig.actions.length === 0) {
|
|
20338
|
+
errors.push(`Actions array is required for resource '${resourceName}'`);
|
|
20339
|
+
}
|
|
20340
|
+
const validActions = ["insert", "update", "delete"];
|
|
20341
|
+
const invalidActions = tableConfig.actions.filter((action) => !validActions.includes(action));
|
|
20342
|
+
if (invalidActions.length > 0) {
|
|
20343
|
+
errors.push(`Invalid actions for resource '${resourceName}': ${invalidActions.join(", ")}. Valid actions: ${validActions.join(", ")}`);
|
|
20344
|
+
}
|
|
20345
|
+
}
|
|
20346
|
+
}
|
|
20347
|
+
return { isValid: errors.length === 0, errors };
|
|
20214
20348
|
}
|
|
20215
20349
|
async initialize(database) {
|
|
20216
20350
|
await super.initialize(database);
|
|
@@ -20229,11 +20363,13 @@ class PostgresReplicator extends BaseReplicator {
|
|
|
20229
20363
|
};
|
|
20230
20364
|
this.client = new Client(config);
|
|
20231
20365
|
await this.client.connect();
|
|
20232
|
-
if (this.
|
|
20366
|
+
if (this.logTable) {
|
|
20367
|
+
await this.createLogTableIfNotExists();
|
|
20368
|
+
}
|
|
20233
20369
|
this.emit("initialized", {
|
|
20234
20370
|
replicator: this.name,
|
|
20235
20371
|
database: this.database || "postgres",
|
|
20236
|
-
|
|
20372
|
+
resources: Object.keys(this.resources)
|
|
20237
20373
|
});
|
|
20238
20374
|
} catch (error) {
|
|
20239
20375
|
this.emit("initialization_error", {
|
|
@@ -20243,9 +20379,9 @@ class PostgresReplicator extends BaseReplicator {
|
|
|
20243
20379
|
throw error;
|
|
20244
20380
|
}
|
|
20245
20381
|
}
|
|
20246
|
-
async
|
|
20382
|
+
async createLogTableIfNotExists() {
|
|
20247
20383
|
const createTableQuery = `
|
|
20248
|
-
CREATE TABLE IF NOT EXISTS ${this.
|
|
20384
|
+
CREATE TABLE IF NOT EXISTS ${this.logTable} (
|
|
20249
20385
|
id SERIAL PRIMARY KEY,
|
|
20250
20386
|
resource_name VARCHAR(255) NOT NULL,
|
|
20251
20387
|
operation VARCHAR(50) NOT NULL,
|
|
@@ -20255,58 +20391,103 @@ class PostgresReplicator extends BaseReplicator {
|
|
|
20255
20391
|
source VARCHAR(100) DEFAULT 's3db-replication',
|
|
20256
20392
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
20257
20393
|
);
|
|
20258
|
-
CREATE INDEX IF NOT EXISTS idx_${this.
|
|
20259
|
-
CREATE INDEX IF NOT EXISTS idx_${this.
|
|
20260
|
-
CREATE INDEX IF NOT EXISTS idx_${this.
|
|
20261
|
-
CREATE INDEX IF NOT EXISTS idx_${this.
|
|
20394
|
+
CREATE INDEX IF NOT EXISTS idx_${this.logTable}_resource_name ON ${this.logTable}(resource_name);
|
|
20395
|
+
CREATE INDEX IF NOT EXISTS idx_${this.logTable}_operation ON ${this.logTable}(operation);
|
|
20396
|
+
CREATE INDEX IF NOT EXISTS idx_${this.logTable}_record_id ON ${this.logTable}(record_id);
|
|
20397
|
+
CREATE INDEX IF NOT EXISTS idx_${this.logTable}_timestamp ON ${this.logTable}(timestamp);
|
|
20262
20398
|
`;
|
|
20263
20399
|
await this.client.query(createTableQuery);
|
|
20264
20400
|
}
|
|
20265
|
-
|
|
20266
|
-
return this.
|
|
20401
|
+
shouldReplicateResource(resourceName) {
|
|
20402
|
+
return this.resources.hasOwnProperty(resourceName);
|
|
20403
|
+
}
|
|
20404
|
+
shouldReplicateAction(resourceName, operation) {
|
|
20405
|
+
if (!this.resources[resourceName]) return false;
|
|
20406
|
+
return this.resources[resourceName].some(
|
|
20407
|
+
(tableConfig) => tableConfig.actions.includes(operation)
|
|
20408
|
+
);
|
|
20409
|
+
}
|
|
20410
|
+
getTablesForResource(resourceName, operation) {
|
|
20411
|
+
if (!this.resources[resourceName]) return [];
|
|
20412
|
+
return this.resources[resourceName].filter((tableConfig) => tableConfig.actions.includes(operation)).map((tableConfig) => tableConfig.table);
|
|
20267
20413
|
}
|
|
20268
20414
|
async replicate(resourceName, operation, data, id, beforeData = null) {
|
|
20269
20415
|
if (!this.enabled || !this.shouldReplicateResource(resourceName)) {
|
|
20270
20416
|
return { skipped: true, reason: "resource_not_included" };
|
|
20271
20417
|
}
|
|
20418
|
+
if (!this.shouldReplicateAction(resourceName, operation)) {
|
|
20419
|
+
return { skipped: true, reason: "action_not_included" };
|
|
20420
|
+
}
|
|
20421
|
+
const tables = this.getTablesForResource(resourceName, operation);
|
|
20422
|
+
if (tables.length === 0) {
|
|
20423
|
+
return { skipped: true, reason: "no_tables_for_action" };
|
|
20424
|
+
}
|
|
20425
|
+
const results = [];
|
|
20426
|
+
const errors = [];
|
|
20272
20427
|
try {
|
|
20273
|
-
const table
|
|
20274
|
-
|
|
20275
|
-
|
|
20276
|
-
|
|
20277
|
-
|
|
20278
|
-
|
|
20279
|
-
|
|
20280
|
-
|
|
20281
|
-
|
|
20282
|
-
|
|
20283
|
-
|
|
20284
|
-
|
|
20285
|
-
|
|
20286
|
-
|
|
20287
|
-
|
|
20288
|
-
|
|
20289
|
-
|
|
20290
|
-
|
|
20291
|
-
|
|
20292
|
-
|
|
20293
|
-
|
|
20428
|
+
for (const table of tables) {
|
|
20429
|
+
try {
|
|
20430
|
+
let result;
|
|
20431
|
+
if (operation === "insert") {
|
|
20432
|
+
const keys = Object.keys(data);
|
|
20433
|
+
const values = keys.map((k) => data[k]);
|
|
20434
|
+
const columns = keys.map((k) => `"${k}"`).join(", ");
|
|
20435
|
+
const params = keys.map((_, i) => `$${i + 1}`).join(", ");
|
|
20436
|
+
const sql = `INSERT INTO ${table} (${columns}) VALUES (${params}) ON CONFLICT (id) DO NOTHING RETURNING *`;
|
|
20437
|
+
result = await this.client.query(sql, values);
|
|
20438
|
+
} else if (operation === "update") {
|
|
20439
|
+
const keys = Object.keys(data).filter((k) => k !== "id");
|
|
20440
|
+
const setClause = keys.map((k, i) => `"${k}"=$${i + 1}`).join(", ");
|
|
20441
|
+
const values = keys.map((k) => data[k]);
|
|
20442
|
+
values.push(id);
|
|
20443
|
+
const sql = `UPDATE ${table} SET ${setClause} WHERE id=$${keys.length + 1} RETURNING *`;
|
|
20444
|
+
result = await this.client.query(sql, values);
|
|
20445
|
+
} else if (operation === "delete") {
|
|
20446
|
+
const sql = `DELETE FROM ${table} WHERE id=$1 RETURNING *`;
|
|
20447
|
+
result = await this.client.query(sql, [id]);
|
|
20448
|
+
} else {
|
|
20449
|
+
throw new Error(`Unsupported operation: ${operation}`);
|
|
20450
|
+
}
|
|
20451
|
+
results.push({
|
|
20452
|
+
table,
|
|
20453
|
+
success: true,
|
|
20454
|
+
rows: result.rows,
|
|
20455
|
+
rowCount: result.rowCount
|
|
20456
|
+
});
|
|
20457
|
+
} catch (error) {
|
|
20458
|
+
errors.push({
|
|
20459
|
+
table,
|
|
20460
|
+
error: error.message
|
|
20461
|
+
});
|
|
20462
|
+
}
|
|
20294
20463
|
}
|
|
20295
|
-
if (this.
|
|
20296
|
-
|
|
20297
|
-
|
|
20298
|
-
|
|
20299
|
-
|
|
20464
|
+
if (this.logTable) {
|
|
20465
|
+
try {
|
|
20466
|
+
await this.client.query(
|
|
20467
|
+
`INSERT INTO ${this.logTable} (resource_name, operation, record_id, data, timestamp, source) VALUES ($1, $2, $3, $4, $5, $6)`,
|
|
20468
|
+
[resourceName, operation, id, JSON.stringify(data), (/* @__PURE__ */ new Date()).toISOString(), "s3db-replication"]
|
|
20469
|
+
);
|
|
20470
|
+
} catch (error) {
|
|
20471
|
+
console.warn(`Failed to log operation to ${this.logTable}:`, error.message);
|
|
20472
|
+
}
|
|
20300
20473
|
}
|
|
20474
|
+
const success = errors.length === 0;
|
|
20301
20475
|
this.emit("replicated", {
|
|
20302
20476
|
replicator: this.name,
|
|
20303
20477
|
resourceName,
|
|
20304
20478
|
operation,
|
|
20305
20479
|
id,
|
|
20306
|
-
|
|
20307
|
-
|
|
20480
|
+
tables,
|
|
20481
|
+
results,
|
|
20482
|
+
errors,
|
|
20483
|
+
success
|
|
20308
20484
|
});
|
|
20309
|
-
return {
|
|
20485
|
+
return {
|
|
20486
|
+
success,
|
|
20487
|
+
results,
|
|
20488
|
+
errors,
|
|
20489
|
+
tables
|
|
20490
|
+
};
|
|
20310
20491
|
} catch (error) {
|
|
20311
20492
|
this.emit("replication_error", {
|
|
20312
20493
|
replicator: this.name,
|
|
@@ -20323,13 +20504,23 @@ class PostgresReplicator extends BaseReplicator {
|
|
|
20323
20504
|
const errors = [];
|
|
20324
20505
|
for (const record of records) {
|
|
20325
20506
|
try {
|
|
20326
|
-
const res = await this.replicate(
|
|
20507
|
+
const res = await this.replicate(
|
|
20508
|
+
resourceName,
|
|
20509
|
+
record.operation,
|
|
20510
|
+
record.data,
|
|
20511
|
+
record.id,
|
|
20512
|
+
record.beforeData
|
|
20513
|
+
);
|
|
20327
20514
|
results.push(res);
|
|
20328
20515
|
} catch (err) {
|
|
20329
20516
|
errors.push({ id: record.id, error: err.message });
|
|
20330
20517
|
}
|
|
20331
20518
|
}
|
|
20332
|
-
return {
|
|
20519
|
+
return {
|
|
20520
|
+
success: errors.length === 0,
|
|
20521
|
+
results,
|
|
20522
|
+
errors
|
|
20523
|
+
};
|
|
20333
20524
|
}
|
|
20334
20525
|
async testConnection() {
|
|
20335
20526
|
try {
|
|
@@ -20344,9 +20535,13 @@ class PostgresReplicator extends BaseReplicator {
|
|
|
20344
20535
|
async cleanup() {
|
|
20345
20536
|
if (this.client) await this.client.end();
|
|
20346
20537
|
}
|
|
20347
|
-
|
|
20348
|
-
|
|
20349
|
-
|
|
20538
|
+
getStatus() {
|
|
20539
|
+
return {
|
|
20540
|
+
...super.getStatus(),
|
|
20541
|
+
database: this.database || "postgres",
|
|
20542
|
+
resources: this.resources,
|
|
20543
|
+
logTable: this.logTable
|
|
20544
|
+
};
|
|
20350
20545
|
}
|
|
20351
20546
|
}
|
|
20352
20547
|
|