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