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