dbnexus 0.1.11 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.js +735 -22
- package/dist/cli.js +46 -2
- package/dist/web/assets/index-CT2xRuCn.js +381 -0
- package/dist/web/index.html +1 -1
- package/package.json +2 -2
- package/dist/web/assets/index-B64mvoX2.js +0 -379
package/dist/api.js
CHANGED
|
@@ -80952,7 +80952,7 @@ var import_common = __toESM(require_common(), 1);
|
|
|
80952
80952
|
import Database from "better-sqlite3";
|
|
80953
80953
|
|
|
80954
80954
|
// packages/metadata/dist/schema.js
|
|
80955
|
-
var SCHEMA_VERSION =
|
|
80955
|
+
var SCHEMA_VERSION = 11;
|
|
80956
80956
|
var MIGRATIONS = [
|
|
80957
80957
|
// Version 1: Initial schema
|
|
80958
80958
|
`
|
|
@@ -81169,6 +81169,50 @@ var MIGRATIONS = [
|
|
|
81169
81169
|
ALTER TABLE connections ADD COLUMN connection_type TEXT NOT NULL DEFAULT 'local' CHECK(connection_type IN ('local', 'docker', 'remote'));
|
|
81170
81170
|
|
|
81171
81171
|
UPDATE schema_version SET version = 9;
|
|
81172
|
+
`,
|
|
81173
|
+
// Version 10: Refactor sync_runs to work without sync_configs (add direct connection/table info)
|
|
81174
|
+
`
|
|
81175
|
+
-- Create new sync_runs table without FK constraint
|
|
81176
|
+
CREATE TABLE IF NOT EXISTS sync_runs_new (
|
|
81177
|
+
id TEXT PRIMARY KEY,
|
|
81178
|
+
source_connection_id TEXT,
|
|
81179
|
+
target_connection_id TEXT,
|
|
81180
|
+
schema_name TEXT,
|
|
81181
|
+
table_name TEXT,
|
|
81182
|
+
group_id TEXT,
|
|
81183
|
+
started_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
81184
|
+
completed_at TEXT,
|
|
81185
|
+
status TEXT NOT NULL DEFAULT 'running',
|
|
81186
|
+
inserts INTEGER NOT NULL DEFAULT 0,
|
|
81187
|
+
updates INTEGER NOT NULL DEFAULT 0,
|
|
81188
|
+
deletes INTEGER NOT NULL DEFAULT 0,
|
|
81189
|
+
errors_json TEXT,
|
|
81190
|
+
FOREIGN KEY (source_connection_id) REFERENCES connections(id) ON DELETE SET NULL,
|
|
81191
|
+
FOREIGN KEY (target_connection_id) REFERENCES connections(id) ON DELETE SET NULL,
|
|
81192
|
+
FOREIGN KEY (group_id) REFERENCES database_groups(id) ON DELETE SET NULL
|
|
81193
|
+
);
|
|
81194
|
+
|
|
81195
|
+
-- Copy existing data (old sync_config_id will be lost, but that's ok)
|
|
81196
|
+
INSERT INTO sync_runs_new (id, started_at, completed_at, status, inserts, updates, deletes, errors_json)
|
|
81197
|
+
SELECT id, started_at, completed_at, status, inserts, updates, deletes, errors_json FROM sync_runs;
|
|
81198
|
+
|
|
81199
|
+
-- Drop old table and rename
|
|
81200
|
+
DROP TABLE sync_runs;
|
|
81201
|
+
ALTER TABLE sync_runs_new RENAME TO sync_runs;
|
|
81202
|
+
|
|
81203
|
+
-- Create indexes
|
|
81204
|
+
CREATE INDEX IF NOT EXISTS idx_sync_runs_source ON sync_runs(source_connection_id);
|
|
81205
|
+
CREATE INDEX IF NOT EXISTS idx_sync_runs_target ON sync_runs(target_connection_id);
|
|
81206
|
+
CREATE INDEX IF NOT EXISTS idx_sync_runs_group ON sync_runs(group_id);
|
|
81207
|
+
CREATE INDEX IF NOT EXISTS idx_sync_runs_started ON sync_runs(started_at DESC);
|
|
81208
|
+
|
|
81209
|
+
UPDATE schema_version SET version = 10;
|
|
81210
|
+
`,
|
|
81211
|
+
// Version 11: Add sql_statements to sync_runs for tracking executed SQL
|
|
81212
|
+
`
|
|
81213
|
+
ALTER TABLE sync_runs ADD COLUMN sql_statements TEXT;
|
|
81214
|
+
|
|
81215
|
+
UPDATE schema_version SET version = 11;
|
|
81172
81216
|
`
|
|
81173
81217
|
];
|
|
81174
81218
|
|
|
@@ -81986,6 +82030,524 @@ var DatabaseGroupRepository = class {
|
|
|
81986
82030
|
}
|
|
81987
82031
|
};
|
|
81988
82032
|
|
|
82033
|
+
// packages/metadata/dist/repositories/sync-run.repository.js
|
|
82034
|
+
var SyncRunRepository = class {
|
|
82035
|
+
db;
|
|
82036
|
+
constructor(db) {
|
|
82037
|
+
this.db = db;
|
|
82038
|
+
}
|
|
82039
|
+
/**
|
|
82040
|
+
* Create a new sync run (starts in 'running' status)
|
|
82041
|
+
*/
|
|
82042
|
+
create(input) {
|
|
82043
|
+
const id = crypto.randomUUID();
|
|
82044
|
+
this.db.getDb().prepare(`
|
|
82045
|
+
INSERT INTO sync_runs (id, source_connection_id, target_connection_id, schema_name, table_name, group_id, status)
|
|
82046
|
+
VALUES (?, ?, ?, ?, ?, ?, 'running')
|
|
82047
|
+
`).run(id, input.sourceConnectionId, input.targetConnectionId, input.schemaName || null, input.tableName || null, input.groupId || null);
|
|
82048
|
+
return this.findById(id);
|
|
82049
|
+
}
|
|
82050
|
+
/**
|
|
82051
|
+
* Find a sync run by ID
|
|
82052
|
+
*/
|
|
82053
|
+
findById(id) {
|
|
82054
|
+
const row = this.db.getDb().prepare(`
|
|
82055
|
+
SELECT
|
|
82056
|
+
sr.*,
|
|
82057
|
+
src.name as source_connection_name,
|
|
82058
|
+
tgt.name as target_connection_name,
|
|
82059
|
+
dg.name as group_name
|
|
82060
|
+
FROM sync_runs sr
|
|
82061
|
+
LEFT JOIN connections src ON sr.source_connection_id = src.id
|
|
82062
|
+
LEFT JOIN connections tgt ON sr.target_connection_id = tgt.id
|
|
82063
|
+
LEFT JOIN database_groups dg ON sr.group_id = dg.id
|
|
82064
|
+
WHERE sr.id = ?
|
|
82065
|
+
`).get(id);
|
|
82066
|
+
return row ? this.rowToRun(row) : null;
|
|
82067
|
+
}
|
|
82068
|
+
/**
|
|
82069
|
+
* Find sync runs by group ID
|
|
82070
|
+
*/
|
|
82071
|
+
findByGroupId(groupId, limit) {
|
|
82072
|
+
let query = `
|
|
82073
|
+
SELECT
|
|
82074
|
+
sr.*,
|
|
82075
|
+
src.name as source_connection_name,
|
|
82076
|
+
tgt.name as target_connection_name,
|
|
82077
|
+
dg.name as group_name
|
|
82078
|
+
FROM sync_runs sr
|
|
82079
|
+
LEFT JOIN connections src ON sr.source_connection_id = src.id
|
|
82080
|
+
LEFT JOIN connections tgt ON sr.target_connection_id = tgt.id
|
|
82081
|
+
LEFT JOIN database_groups dg ON sr.group_id = dg.id
|
|
82082
|
+
WHERE sr.group_id = ?
|
|
82083
|
+
ORDER BY sr.started_at DESC
|
|
82084
|
+
`;
|
|
82085
|
+
if (limit) {
|
|
82086
|
+
query += ` LIMIT ${limit}`;
|
|
82087
|
+
}
|
|
82088
|
+
const rows = this.db.getDb().prepare(query).all(groupId);
|
|
82089
|
+
return rows.map((row) => this.rowToRun(row));
|
|
82090
|
+
}
|
|
82091
|
+
/**
|
|
82092
|
+
* Find sync runs by connection ID (either source or target)
|
|
82093
|
+
*/
|
|
82094
|
+
findByConnectionId(connectionId, limit) {
|
|
82095
|
+
let query = `
|
|
82096
|
+
SELECT
|
|
82097
|
+
sr.*,
|
|
82098
|
+
src.name as source_connection_name,
|
|
82099
|
+
tgt.name as target_connection_name,
|
|
82100
|
+
dg.name as group_name
|
|
82101
|
+
FROM sync_runs sr
|
|
82102
|
+
LEFT JOIN connections src ON sr.source_connection_id = src.id
|
|
82103
|
+
LEFT JOIN connections tgt ON sr.target_connection_id = tgt.id
|
|
82104
|
+
LEFT JOIN database_groups dg ON sr.group_id = dg.id
|
|
82105
|
+
WHERE sr.source_connection_id = ? OR sr.target_connection_id = ?
|
|
82106
|
+
ORDER BY sr.started_at DESC
|
|
82107
|
+
`;
|
|
82108
|
+
if (limit) {
|
|
82109
|
+
query += ` LIMIT ${limit}`;
|
|
82110
|
+
}
|
|
82111
|
+
const rows = this.db.getDb().prepare(query).all(connectionId, connectionId);
|
|
82112
|
+
return rows.map((row) => this.rowToRun(row));
|
|
82113
|
+
}
|
|
82114
|
+
/**
|
|
82115
|
+
* Find recent sync runs
|
|
82116
|
+
*/
|
|
82117
|
+
findRecent(limit = 50) {
|
|
82118
|
+
const rows = this.db.getDb().prepare(`
|
|
82119
|
+
SELECT
|
|
82120
|
+
sr.*,
|
|
82121
|
+
src.name as source_connection_name,
|
|
82122
|
+
tgt.name as target_connection_name,
|
|
82123
|
+
dg.name as group_name
|
|
82124
|
+
FROM sync_runs sr
|
|
82125
|
+
LEFT JOIN connections src ON sr.source_connection_id = src.id
|
|
82126
|
+
LEFT JOIN connections tgt ON sr.target_connection_id = tgt.id
|
|
82127
|
+
LEFT JOIN database_groups dg ON sr.group_id = dg.id
|
|
82128
|
+
ORDER BY sr.started_at DESC
|
|
82129
|
+
LIMIT ?
|
|
82130
|
+
`).all(limit);
|
|
82131
|
+
return rows.map((row) => this.rowToRun(row));
|
|
82132
|
+
}
|
|
82133
|
+
/**
|
|
82134
|
+
* Find running sync runs
|
|
82135
|
+
*/
|
|
82136
|
+
findRunning() {
|
|
82137
|
+
const rows = this.db.getDb().prepare(`
|
|
82138
|
+
SELECT
|
|
82139
|
+
sr.*,
|
|
82140
|
+
src.name as source_connection_name,
|
|
82141
|
+
tgt.name as target_connection_name,
|
|
82142
|
+
dg.name as group_name
|
|
82143
|
+
FROM sync_runs sr
|
|
82144
|
+
LEFT JOIN connections src ON sr.source_connection_id = src.id
|
|
82145
|
+
LEFT JOIN connections tgt ON sr.target_connection_id = tgt.id
|
|
82146
|
+
LEFT JOIN database_groups dg ON sr.group_id = dg.id
|
|
82147
|
+
WHERE sr.status = 'running'
|
|
82148
|
+
ORDER BY sr.started_at DESC
|
|
82149
|
+
`).all();
|
|
82150
|
+
return rows.map((row) => this.rowToRun(row));
|
|
82151
|
+
}
|
|
82152
|
+
/**
|
|
82153
|
+
* Update a sync run
|
|
82154
|
+
*/
|
|
82155
|
+
update(id, input) {
|
|
82156
|
+
const updates = [];
|
|
82157
|
+
const params = [];
|
|
82158
|
+
if (input.status !== void 0) {
|
|
82159
|
+
updates.push("status = ?");
|
|
82160
|
+
params.push(input.status);
|
|
82161
|
+
if (["completed", "failed", "cancelled"].includes(input.status)) {
|
|
82162
|
+
updates.push("completed_at = datetime('now')");
|
|
82163
|
+
}
|
|
82164
|
+
}
|
|
82165
|
+
if (input.inserts !== void 0) {
|
|
82166
|
+
updates.push("inserts = ?");
|
|
82167
|
+
params.push(input.inserts);
|
|
82168
|
+
}
|
|
82169
|
+
if (input.updates !== void 0) {
|
|
82170
|
+
updates.push("updates = ?");
|
|
82171
|
+
params.push(input.updates);
|
|
82172
|
+
}
|
|
82173
|
+
if (input.deletes !== void 0) {
|
|
82174
|
+
updates.push("deletes = ?");
|
|
82175
|
+
params.push(input.deletes);
|
|
82176
|
+
}
|
|
82177
|
+
if (input.errors !== void 0) {
|
|
82178
|
+
updates.push("errors_json = ?");
|
|
82179
|
+
params.push(JSON.stringify(input.errors));
|
|
82180
|
+
}
|
|
82181
|
+
if (input.sqlStatements !== void 0) {
|
|
82182
|
+
updates.push("sql_statements = ?");
|
|
82183
|
+
params.push(JSON.stringify(input.sqlStatements));
|
|
82184
|
+
}
|
|
82185
|
+
if (updates.length === 0) {
|
|
82186
|
+
return this.findById(id);
|
|
82187
|
+
}
|
|
82188
|
+
params.push(id);
|
|
82189
|
+
this.db.getDb().prepare(`UPDATE sync_runs SET ${updates.join(", ")} WHERE id = ?`).run(...params);
|
|
82190
|
+
return this.findById(id);
|
|
82191
|
+
}
|
|
82192
|
+
/**
|
|
82193
|
+
* Complete a sync run with results
|
|
82194
|
+
*/
|
|
82195
|
+
complete(id, results) {
|
|
82196
|
+
return this.update(id, {
|
|
82197
|
+
status: results.errors.length > 0 ? "failed" : "completed",
|
|
82198
|
+
inserts: results.inserts,
|
|
82199
|
+
updates: results.updates,
|
|
82200
|
+
deletes: results.deletes,
|
|
82201
|
+
errors: results.errors
|
|
82202
|
+
});
|
|
82203
|
+
}
|
|
82204
|
+
/**
|
|
82205
|
+
* Delete a sync run
|
|
82206
|
+
*/
|
|
82207
|
+
delete(id) {
|
|
82208
|
+
const result = this.db.getDb().prepare("DELETE FROM sync_runs WHERE id = ?").run(id);
|
|
82209
|
+
return result.changes > 0;
|
|
82210
|
+
}
|
|
82211
|
+
/**
|
|
82212
|
+
* Delete old sync runs (cleanup)
|
|
82213
|
+
*/
|
|
82214
|
+
deleteOlderThan(days) {
|
|
82215
|
+
const result = this.db.getDb().prepare(`
|
|
82216
|
+
DELETE FROM sync_runs
|
|
82217
|
+
WHERE started_at < datetime('now', '-' || ? || ' days')
|
|
82218
|
+
`).run(days);
|
|
82219
|
+
return result.changes;
|
|
82220
|
+
}
|
|
82221
|
+
/**
|
|
82222
|
+
* Convert database row to SyncRun
|
|
82223
|
+
*/
|
|
82224
|
+
rowToRun(row) {
|
|
82225
|
+
return {
|
|
82226
|
+
id: row.id,
|
|
82227
|
+
sourceConnectionId: row.source_connection_id || void 0,
|
|
82228
|
+
targetConnectionId: row.target_connection_id || void 0,
|
|
82229
|
+
schemaName: row.schema_name || void 0,
|
|
82230
|
+
tableName: row.table_name || void 0,
|
|
82231
|
+
groupId: row.group_id || void 0,
|
|
82232
|
+
startedAt: new Date(row.started_at),
|
|
82233
|
+
completedAt: row.completed_at ? new Date(row.completed_at) : void 0,
|
|
82234
|
+
status: row.status,
|
|
82235
|
+
inserts: row.inserts,
|
|
82236
|
+
updates: row.updates,
|
|
82237
|
+
deletes: row.deletes,
|
|
82238
|
+
errors: row.errors_json ? JSON.parse(row.errors_json) : [],
|
|
82239
|
+
sqlStatements: row.sql_statements ? JSON.parse(row.sql_statements) : [],
|
|
82240
|
+
sourceConnectionName: row.source_connection_name,
|
|
82241
|
+
targetConnectionName: row.target_connection_name,
|
|
82242
|
+
groupName: row.group_name
|
|
82243
|
+
};
|
|
82244
|
+
}
|
|
82245
|
+
};
|
|
82246
|
+
|
|
82247
|
+
// packages/metadata/dist/repositories/schema-snapshot.repository.js
|
|
82248
|
+
var SchemaSnapshotRepository = class {
|
|
82249
|
+
db;
|
|
82250
|
+
constructor(db) {
|
|
82251
|
+
this.db = db;
|
|
82252
|
+
}
|
|
82253
|
+
/**
|
|
82254
|
+
* Create a new schema snapshot
|
|
82255
|
+
*/
|
|
82256
|
+
create(input) {
|
|
82257
|
+
const id = crypto.randomUUID();
|
|
82258
|
+
const schemaJsonStr = JSON.stringify(input.schemaJson);
|
|
82259
|
+
this.db.getDb().prepare(`
|
|
82260
|
+
INSERT INTO schema_snapshots (id, connection_id, schema_json)
|
|
82261
|
+
VALUES (?, ?, ?)
|
|
82262
|
+
`).run(id, input.connectionId, schemaJsonStr);
|
|
82263
|
+
return this.findById(id);
|
|
82264
|
+
}
|
|
82265
|
+
/**
|
|
82266
|
+
* Find a snapshot by ID
|
|
82267
|
+
*/
|
|
82268
|
+
findById(id) {
|
|
82269
|
+
const row = this.db.getDb().prepare(`
|
|
82270
|
+
SELECT
|
|
82271
|
+
ss.*,
|
|
82272
|
+
c.name as connection_name
|
|
82273
|
+
FROM schema_snapshots ss
|
|
82274
|
+
LEFT JOIN connections c ON ss.connection_id = c.id
|
|
82275
|
+
WHERE ss.id = ?
|
|
82276
|
+
`).get(id);
|
|
82277
|
+
return row ? this.rowToSnapshot(row) : null;
|
|
82278
|
+
}
|
|
82279
|
+
/**
|
|
82280
|
+
* Find snapshots by connection ID
|
|
82281
|
+
*/
|
|
82282
|
+
findByConnectionId(connectionId, limit) {
|
|
82283
|
+
let query = `
|
|
82284
|
+
SELECT
|
|
82285
|
+
ss.*,
|
|
82286
|
+
c.name as connection_name
|
|
82287
|
+
FROM schema_snapshots ss
|
|
82288
|
+
LEFT JOIN connections c ON ss.connection_id = c.id
|
|
82289
|
+
WHERE ss.connection_id = ?
|
|
82290
|
+
ORDER BY ss.captured_at DESC
|
|
82291
|
+
`;
|
|
82292
|
+
if (limit) {
|
|
82293
|
+
query += ` LIMIT ${limit}`;
|
|
82294
|
+
}
|
|
82295
|
+
const rows = this.db.getDb().prepare(query).all(connectionId);
|
|
82296
|
+
return rows.map((row) => this.rowToSnapshot(row));
|
|
82297
|
+
}
|
|
82298
|
+
/**
|
|
82299
|
+
* Find the latest snapshot for a connection
|
|
82300
|
+
*/
|
|
82301
|
+
findLatest(connectionId) {
|
|
82302
|
+
const row = this.db.getDb().prepare(`
|
|
82303
|
+
SELECT
|
|
82304
|
+
ss.*,
|
|
82305
|
+
c.name as connection_name
|
|
82306
|
+
FROM schema_snapshots ss
|
|
82307
|
+
LEFT JOIN connections c ON ss.connection_id = c.id
|
|
82308
|
+
WHERE ss.connection_id = ?
|
|
82309
|
+
ORDER BY ss.captured_at DESC
|
|
82310
|
+
LIMIT 1
|
|
82311
|
+
`).get(connectionId);
|
|
82312
|
+
return row ? this.rowToSnapshot(row) : null;
|
|
82313
|
+
}
|
|
82314
|
+
/**
|
|
82315
|
+
* Find all snapshots
|
|
82316
|
+
*/
|
|
82317
|
+
findAll(limit = 100) {
|
|
82318
|
+
const rows = this.db.getDb().prepare(`
|
|
82319
|
+
SELECT
|
|
82320
|
+
ss.*,
|
|
82321
|
+
c.name as connection_name
|
|
82322
|
+
FROM schema_snapshots ss
|
|
82323
|
+
LEFT JOIN connections c ON ss.connection_id = c.id
|
|
82324
|
+
ORDER BY ss.captured_at DESC
|
|
82325
|
+
LIMIT ?
|
|
82326
|
+
`).all(limit);
|
|
82327
|
+
return rows.map((row) => this.rowToSnapshot(row));
|
|
82328
|
+
}
|
|
82329
|
+
/**
|
|
82330
|
+
* Delete a snapshot
|
|
82331
|
+
*/
|
|
82332
|
+
delete(id) {
|
|
82333
|
+
const result = this.db.getDb().prepare("DELETE FROM schema_snapshots WHERE id = ?").run(id);
|
|
82334
|
+
return result.changes > 0;
|
|
82335
|
+
}
|
|
82336
|
+
/**
|
|
82337
|
+
* Delete snapshots older than N days
|
|
82338
|
+
*/
|
|
82339
|
+
deleteOlderThan(days) {
|
|
82340
|
+
const result = this.db.getDb().prepare(`
|
|
82341
|
+
DELETE FROM schema_snapshots
|
|
82342
|
+
WHERE captured_at < datetime('now', '-' || ? || ' days')
|
|
82343
|
+
`).run(days);
|
|
82344
|
+
return result.changes;
|
|
82345
|
+
}
|
|
82346
|
+
/**
|
|
82347
|
+
* Delete all snapshots for a connection except the N most recent
|
|
82348
|
+
*/
|
|
82349
|
+
keepLatest(connectionId, count) {
|
|
82350
|
+
const result = this.db.getDb().prepare(`
|
|
82351
|
+
DELETE FROM schema_snapshots
|
|
82352
|
+
WHERE connection_id = ?
|
|
82353
|
+
AND id NOT IN (
|
|
82354
|
+
SELECT id FROM schema_snapshots
|
|
82355
|
+
WHERE connection_id = ?
|
|
82356
|
+
ORDER BY captured_at DESC
|
|
82357
|
+
LIMIT ?
|
|
82358
|
+
)
|
|
82359
|
+
`).run(connectionId, connectionId, count);
|
|
82360
|
+
return result.changes;
|
|
82361
|
+
}
|
|
82362
|
+
/**
|
|
82363
|
+
* Convert database row to SchemaSnapshot
|
|
82364
|
+
*/
|
|
82365
|
+
rowToSnapshot(row) {
|
|
82366
|
+
return {
|
|
82367
|
+
id: row.id,
|
|
82368
|
+
connectionId: row.connection_id,
|
|
82369
|
+
capturedAt: new Date(row.captured_at),
|
|
82370
|
+
schemaJson: JSON.parse(row.schema_json),
|
|
82371
|
+
connectionName: row.connection_name
|
|
82372
|
+
};
|
|
82373
|
+
}
|
|
82374
|
+
};
|
|
82375
|
+
|
|
82376
|
+
// packages/metadata/dist/repositories/audit-log.repository.js
|
|
82377
|
+
var AuditLogRepository = class {
|
|
82378
|
+
db;
|
|
82379
|
+
constructor(db) {
|
|
82380
|
+
this.db = db;
|
|
82381
|
+
}
|
|
82382
|
+
/**
|
|
82383
|
+
* Create a new audit log entry
|
|
82384
|
+
*/
|
|
82385
|
+
create(input) {
|
|
82386
|
+
const id = crypto.randomUUID();
|
|
82387
|
+
const detailsJson = input.details ? JSON.stringify(input.details) : null;
|
|
82388
|
+
this.db.getDb().prepare(`
|
|
82389
|
+
INSERT INTO audit_log (id, action, entity_type, entity_id, connection_id, details_json)
|
|
82390
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
82391
|
+
`).run(id, input.action, input.entityType, input.entityId || null, input.connectionId || null, detailsJson);
|
|
82392
|
+
return this.findById(id);
|
|
82393
|
+
}
|
|
82394
|
+
/**
|
|
82395
|
+
* Log a sync operation start
|
|
82396
|
+
*/
|
|
82397
|
+
logSyncStart(syncRunId, connectionId, details) {
|
|
82398
|
+
return this.create({
|
|
82399
|
+
action: "data_sync_started",
|
|
82400
|
+
entityType: "sync_run",
|
|
82401
|
+
entityId: syncRunId,
|
|
82402
|
+
connectionId,
|
|
82403
|
+
details
|
|
82404
|
+
});
|
|
82405
|
+
}
|
|
82406
|
+
/**
|
|
82407
|
+
* Log a sync operation completion
|
|
82408
|
+
*/
|
|
82409
|
+
logSyncComplete(syncRunId, connectionId, results) {
|
|
82410
|
+
const hasErrors = results.errors.length > 0;
|
|
82411
|
+
return this.create({
|
|
82412
|
+
action: hasErrors ? "data_sync_failed" : "data_sync_completed",
|
|
82413
|
+
entityType: "sync_run",
|
|
82414
|
+
entityId: syncRunId,
|
|
82415
|
+
connectionId,
|
|
82416
|
+
details: results
|
|
82417
|
+
});
|
|
82418
|
+
}
|
|
82419
|
+
/**
|
|
82420
|
+
* Log a schema migration
|
|
82421
|
+
*/
|
|
82422
|
+
logMigration(migrationId, targetConnectionId, details) {
|
|
82423
|
+
return this.create({
|
|
82424
|
+
action: "schema_migration_applied",
|
|
82425
|
+
entityType: "migration",
|
|
82426
|
+
entityId: migrationId,
|
|
82427
|
+
connectionId: targetConnectionId,
|
|
82428
|
+
details
|
|
82429
|
+
});
|
|
82430
|
+
}
|
|
82431
|
+
/**
|
|
82432
|
+
* Log a query execution
|
|
82433
|
+
*/
|
|
82434
|
+
logQuery(connectionId, details) {
|
|
82435
|
+
return this.create({
|
|
82436
|
+
action: "query_executed",
|
|
82437
|
+
entityType: "query",
|
|
82438
|
+
connectionId,
|
|
82439
|
+
details
|
|
82440
|
+
});
|
|
82441
|
+
}
|
|
82442
|
+
/**
|
|
82443
|
+
* Find an entry by ID
|
|
82444
|
+
*/
|
|
82445
|
+
findById(id) {
|
|
82446
|
+
const row = this.db.getDb().prepare(`
|
|
82447
|
+
SELECT
|
|
82448
|
+
al.*,
|
|
82449
|
+
c.name as connection_name
|
|
82450
|
+
FROM audit_log al
|
|
82451
|
+
LEFT JOIN connections c ON al.connection_id = c.id
|
|
82452
|
+
WHERE al.id = ?
|
|
82453
|
+
`).get(id);
|
|
82454
|
+
return row ? this.rowToEntry(row) : null;
|
|
82455
|
+
}
|
|
82456
|
+
/**
|
|
82457
|
+
* Find entries by connection ID
|
|
82458
|
+
*/
|
|
82459
|
+
findByConnectionId(connectionId, limit) {
|
|
82460
|
+
let query = `
|
|
82461
|
+
SELECT
|
|
82462
|
+
al.*,
|
|
82463
|
+
c.name as connection_name
|
|
82464
|
+
FROM audit_log al
|
|
82465
|
+
LEFT JOIN connections c ON al.connection_id = c.id
|
|
82466
|
+
WHERE al.connection_id = ?
|
|
82467
|
+
ORDER BY al.created_at DESC
|
|
82468
|
+
`;
|
|
82469
|
+
if (limit) {
|
|
82470
|
+
query += ` LIMIT ${limit}`;
|
|
82471
|
+
}
|
|
82472
|
+
const rows = this.db.getDb().prepare(query).all(connectionId);
|
|
82473
|
+
return rows.map((row) => this.rowToEntry(row));
|
|
82474
|
+
}
|
|
82475
|
+
/**
|
|
82476
|
+
* Find entries by action type
|
|
82477
|
+
*/
|
|
82478
|
+
findByAction(action, limit) {
|
|
82479
|
+
let query = `
|
|
82480
|
+
SELECT
|
|
82481
|
+
al.*,
|
|
82482
|
+
c.name as connection_name
|
|
82483
|
+
FROM audit_log al
|
|
82484
|
+
LEFT JOIN connections c ON al.connection_id = c.id
|
|
82485
|
+
WHERE al.action = ?
|
|
82486
|
+
ORDER BY al.created_at DESC
|
|
82487
|
+
`;
|
|
82488
|
+
if (limit) {
|
|
82489
|
+
query += ` LIMIT ${limit}`;
|
|
82490
|
+
}
|
|
82491
|
+
const rows = this.db.getDb().prepare(query).all(action);
|
|
82492
|
+
return rows.map((row) => this.rowToEntry(row));
|
|
82493
|
+
}
|
|
82494
|
+
/**
|
|
82495
|
+
* Find entries by entity
|
|
82496
|
+
*/
|
|
82497
|
+
findByEntity(entityType, entityId) {
|
|
82498
|
+
const rows = this.db.getDb().prepare(`
|
|
82499
|
+
SELECT
|
|
82500
|
+
al.*,
|
|
82501
|
+
c.name as connection_name
|
|
82502
|
+
FROM audit_log al
|
|
82503
|
+
LEFT JOIN connections c ON al.connection_id = c.id
|
|
82504
|
+
WHERE al.entity_type = ? AND al.entity_id = ?
|
|
82505
|
+
ORDER BY al.created_at DESC
|
|
82506
|
+
`).all(entityType, entityId);
|
|
82507
|
+
return rows.map((row) => this.rowToEntry(row));
|
|
82508
|
+
}
|
|
82509
|
+
/**
|
|
82510
|
+
* Find recent entries
|
|
82511
|
+
*/
|
|
82512
|
+
findRecent(limit = 100) {
|
|
82513
|
+
const rows = this.db.getDb().prepare(`
|
|
82514
|
+
SELECT
|
|
82515
|
+
al.*,
|
|
82516
|
+
c.name as connection_name
|
|
82517
|
+
FROM audit_log al
|
|
82518
|
+
LEFT JOIN connections c ON al.connection_id = c.id
|
|
82519
|
+
ORDER BY al.created_at DESC
|
|
82520
|
+
LIMIT ?
|
|
82521
|
+
`).all(limit);
|
|
82522
|
+
return rows.map((row) => this.rowToEntry(row));
|
|
82523
|
+
}
|
|
82524
|
+
/**
|
|
82525
|
+
* Delete entries older than N days
|
|
82526
|
+
*/
|
|
82527
|
+
deleteOlderThan(days) {
|
|
82528
|
+
const result = this.db.getDb().prepare(`
|
|
82529
|
+
DELETE FROM audit_log
|
|
82530
|
+
WHERE created_at < datetime('now', '-' || ? || ' days')
|
|
82531
|
+
`).run(days);
|
|
82532
|
+
return result.changes;
|
|
82533
|
+
}
|
|
82534
|
+
/**
|
|
82535
|
+
* Convert database row to AuditLogEntry
|
|
82536
|
+
*/
|
|
82537
|
+
rowToEntry(row) {
|
|
82538
|
+
return {
|
|
82539
|
+
id: row.id,
|
|
82540
|
+
action: row.action,
|
|
82541
|
+
entityType: row.entity_type,
|
|
82542
|
+
entityId: row.entity_id || void 0,
|
|
82543
|
+
connectionId: row.connection_id || void 0,
|
|
82544
|
+
details: row.details_json ? JSON.parse(row.details_json) : void 0,
|
|
82545
|
+
createdAt: new Date(row.created_at),
|
|
82546
|
+
connectionName: row.connection_name
|
|
82547
|
+
};
|
|
82548
|
+
}
|
|
82549
|
+
};
|
|
82550
|
+
|
|
81989
82551
|
// apps/api/dist/metadata/metadata.service.js
|
|
81990
82552
|
import * as path from "node:path";
|
|
81991
82553
|
import * as fs from "node:fs";
|
|
@@ -82004,6 +82566,9 @@ var MetadataService = MetadataService_1 = class MetadataService2 {
|
|
|
82004
82566
|
_migrationHistoryRepository;
|
|
82005
82567
|
_projectRepository;
|
|
82006
82568
|
_databaseGroupRepository;
|
|
82569
|
+
_syncRunRepository;
|
|
82570
|
+
_schemaSnapshotRepository;
|
|
82571
|
+
_auditLogRepository;
|
|
82007
82572
|
onModuleInit() {
|
|
82008
82573
|
let dbnexusDir;
|
|
82009
82574
|
if (process.env["DBNEXUS_DATA_DIR"]) {
|
|
@@ -82026,6 +82591,9 @@ var MetadataService = MetadataService_1 = class MetadataService2 {
|
|
|
82026
82591
|
this._migrationHistoryRepository = new MigrationHistoryRepository(this.db);
|
|
82027
82592
|
this._projectRepository = new ProjectRepository(this.db);
|
|
82028
82593
|
this._databaseGroupRepository = new DatabaseGroupRepository(this.db);
|
|
82594
|
+
this._syncRunRepository = new SyncRunRepository(this.db);
|
|
82595
|
+
this._schemaSnapshotRepository = new SchemaSnapshotRepository(this.db);
|
|
82596
|
+
this._auditLogRepository = new AuditLogRepository(this.db);
|
|
82029
82597
|
this.logger.log(`\u{1F4E6} Metadata database initialized at ${dbPath}`);
|
|
82030
82598
|
}
|
|
82031
82599
|
onModuleDestroy() {
|
|
@@ -82046,6 +82614,15 @@ var MetadataService = MetadataService_1 = class MetadataService2 {
|
|
|
82046
82614
|
get databaseGroupRepository() {
|
|
82047
82615
|
return this._databaseGroupRepository;
|
|
82048
82616
|
}
|
|
82617
|
+
get syncRunRepository() {
|
|
82618
|
+
return this._syncRunRepository;
|
|
82619
|
+
}
|
|
82620
|
+
get schemaSnapshotRepository() {
|
|
82621
|
+
return this._schemaSnapshotRepository;
|
|
82622
|
+
}
|
|
82623
|
+
get auditLogRepository() {
|
|
82624
|
+
return this._auditLogRepository;
|
|
82625
|
+
}
|
|
82049
82626
|
get database() {
|
|
82050
82627
|
return this.db;
|
|
82051
82628
|
}
|
|
@@ -83801,16 +84378,36 @@ function normalizeDefaultForComparison(defaultValue) {
|
|
|
83801
84378
|
}
|
|
83802
84379
|
return defaultValue;
|
|
83803
84380
|
}
|
|
84381
|
+
function getColumnDefForCreate(col, engine) {
|
|
84382
|
+
const quotedName = quoteIdentifier(col.name, engine);
|
|
84383
|
+
if (isIdentityOrSerialDefault(col.defaultValue)) {
|
|
84384
|
+
if (engine === "postgres") {
|
|
84385
|
+
const isBigInt = col.dataType.toLowerCase().includes("big");
|
|
84386
|
+
const baseType = isBigInt ? "BIGINT" : "INTEGER";
|
|
84387
|
+
return `${quotedName} ${baseType} GENERATED BY DEFAULT AS IDENTITY`;
|
|
84388
|
+
} else if (engine === "mysql" || engine === "mariadb") {
|
|
84389
|
+
const isBigInt = col.dataType.toLowerCase().includes("big");
|
|
84390
|
+
const baseType = isBigInt ? "BIGINT" : "INT";
|
|
84391
|
+
let def2 = `${quotedName} ${baseType} AUTO_INCREMENT`;
|
|
84392
|
+
if (!col.nullable)
|
|
84393
|
+
def2 += " NOT NULL";
|
|
84394
|
+
return def2;
|
|
84395
|
+
} else {
|
|
84396
|
+
return `${quotedName} INTEGER`;
|
|
84397
|
+
}
|
|
84398
|
+
}
|
|
84399
|
+
let def = `${quotedName} ${col.dataType}`;
|
|
84400
|
+
if (!col.nullable)
|
|
84401
|
+
def += " NOT NULL";
|
|
84402
|
+
if (col.defaultValue !== null)
|
|
84403
|
+
def += ` DEFAULT ${col.defaultValue}`;
|
|
84404
|
+
return def;
|
|
84405
|
+
}
|
|
83804
84406
|
function generateCreateTableSql(table, engine) {
|
|
83805
84407
|
const sql = [];
|
|
83806
84408
|
const columnDefs = [];
|
|
83807
84409
|
for (const col of table.columns) {
|
|
83808
|
-
|
|
83809
|
-
if (!col.nullable)
|
|
83810
|
-
def += " NOT NULL";
|
|
83811
|
-
if (col.defaultValue !== null)
|
|
83812
|
-
def += ` DEFAULT ${col.defaultValue}`;
|
|
83813
|
-
columnDefs.push(def);
|
|
84410
|
+
columnDefs.push(getColumnDefForCreate(col, engine));
|
|
83814
84411
|
}
|
|
83815
84412
|
const primaryKey = parseColumnArray(table.primaryKey);
|
|
83816
84413
|
if (primaryKey.length > 0) {
|
|
@@ -83833,7 +84430,22 @@ function generateDropTableSql(schema, table, engine) {
|
|
|
83833
84430
|
return `DROP TABLE IF EXISTS ${quoteTable(schema, table, engine)};`;
|
|
83834
84431
|
}
|
|
83835
84432
|
function generateAddColumnSql(schema, table, col, engine) {
|
|
83836
|
-
|
|
84433
|
+
const prefix = `ALTER TABLE ${quoteTable(schema, table, engine)} ADD COLUMN`;
|
|
84434
|
+
if (isIdentityOrSerialDefault(col.defaultValue)) {
|
|
84435
|
+
if (engine === "postgres") {
|
|
84436
|
+
const isBigInt = col.dataType.toLowerCase().includes("big");
|
|
84437
|
+
const baseType = isBigInt ? "BIGINT" : "INTEGER";
|
|
84438
|
+
return `${prefix} ${quoteIdentifier(col.name, engine)} ${baseType} GENERATED BY DEFAULT AS IDENTITY;`;
|
|
84439
|
+
} else if (engine === "mysql" || engine === "mariadb") {
|
|
84440
|
+
const isBigInt = col.dataType.toLowerCase().includes("big");
|
|
84441
|
+
const baseType = isBigInt ? "BIGINT" : "INT";
|
|
84442
|
+
let sql2 = `${prefix} ${quoteIdentifier(col.name, engine)} ${baseType} AUTO_INCREMENT`;
|
|
84443
|
+
if (!col.nullable)
|
|
84444
|
+
sql2 += " NOT NULL";
|
|
84445
|
+
return sql2 + ";";
|
|
84446
|
+
}
|
|
84447
|
+
}
|
|
84448
|
+
let sql = `${prefix} ${quoteIdentifier(col.name, engine)} ${col.dataType}`;
|
|
83837
84449
|
if (!col.nullable)
|
|
83838
84450
|
sql += " NOT NULL";
|
|
83839
84451
|
if (col.defaultValue !== null)
|
|
@@ -84779,6 +85391,40 @@ function isJsonColumn(dataType) {
|
|
|
84779
85391
|
const typeLower = dataType.toLowerCase();
|
|
84780
85392
|
return typeLower === "json" || typeLower === "jsonb";
|
|
84781
85393
|
}
|
|
85394
|
+
function formatValueForSql(value) {
|
|
85395
|
+
if (value === null || value === void 0) {
|
|
85396
|
+
return "NULL";
|
|
85397
|
+
}
|
|
85398
|
+
if (typeof value === "string") {
|
|
85399
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
85400
|
+
}
|
|
85401
|
+
if (typeof value === "number" || typeof value === "bigint") {
|
|
85402
|
+
return String(value);
|
|
85403
|
+
}
|
|
85404
|
+
if (typeof value === "boolean") {
|
|
85405
|
+
return value ? "TRUE" : "FALSE";
|
|
85406
|
+
}
|
|
85407
|
+
if (value instanceof Date) {
|
|
85408
|
+
return `'${value.toISOString()}'`;
|
|
85409
|
+
}
|
|
85410
|
+
if (typeof value === "object") {
|
|
85411
|
+
return `'${JSON.stringify(value).replace(/'/g, "''")}'`;
|
|
85412
|
+
}
|
|
85413
|
+
return String(value);
|
|
85414
|
+
}
|
|
85415
|
+
function buildReadableSql(template, values, engine) {
|
|
85416
|
+
let sql = template;
|
|
85417
|
+
if (engine === "mysql" || engine === "mariadb") {
|
|
85418
|
+
for (const value of values) {
|
|
85419
|
+
sql = sql.replace("?", formatValueForSql(value));
|
|
85420
|
+
}
|
|
85421
|
+
} else {
|
|
85422
|
+
for (let i = values.length; i >= 1; i--) {
|
|
85423
|
+
sql = sql.replace(`$${i}`, formatValueForSql(values[i - 1]));
|
|
85424
|
+
}
|
|
85425
|
+
}
|
|
85426
|
+
return sql;
|
|
85427
|
+
}
|
|
84782
85428
|
function serializeValueForInsert(value, dataType) {
|
|
84783
85429
|
if (value === null || value === void 0) {
|
|
84784
85430
|
return value;
|
|
@@ -84979,17 +85625,21 @@ var SyncService = SyncService_1 = class SyncService2 {
|
|
|
84979
85625
|
* Get detailed data diff for a specific table
|
|
84980
85626
|
*/
|
|
84981
85627
|
async getTableDataDiff(sourceConnectionId, targetConnectionId, schema, table, primaryKeyColumns) {
|
|
85628
|
+
const pkColumns = parseColumnArray(primaryKeyColumns);
|
|
85629
|
+
if (pkColumns.length === 0) {
|
|
85630
|
+
throw new Error("No primary key columns provided");
|
|
85631
|
+
}
|
|
84982
85632
|
const sourceConnection = this.connectionsService.findById(sourceConnectionId);
|
|
84983
85633
|
const targetConnection = this.connectionsService.findById(targetConnectionId);
|
|
84984
85634
|
const sourceConnector = await this.connectionsService.getConnector(sourceConnectionId);
|
|
84985
85635
|
const targetConnector = await this.connectionsService.getConnector(targetConnectionId);
|
|
84986
85636
|
const sourceTableRef = quoteTableRef(schema, table, sourceConnection.engine);
|
|
84987
85637
|
const targetTableRef = quoteTableRef(schema, table, targetConnection.engine);
|
|
84988
|
-
const sourceOrderBy =
|
|
84989
|
-
const targetOrderBy =
|
|
85638
|
+
const sourceOrderBy = pkColumns.map((c) => quoteIdentifier2(c, sourceConnection.engine)).join(", ");
|
|
85639
|
+
const targetOrderBy = pkColumns.map((c) => quoteIdentifier2(c, targetConnection.engine)).join(", ");
|
|
84990
85640
|
const sourceResult = await sourceConnector.query(`SELECT * FROM ${sourceTableRef} ORDER BY ${sourceOrderBy}`);
|
|
84991
85641
|
const targetResult = await targetConnector.query(`SELECT * FROM ${targetTableRef} ORDER BY ${targetOrderBy}`);
|
|
84992
|
-
const getPkValue = (row) =>
|
|
85642
|
+
const getPkValue = (row) => pkColumns.map((c) => String(row[c])).join("|");
|
|
84993
85643
|
const sourceMap = /* @__PURE__ */ new Map();
|
|
84994
85644
|
const targetMap = /* @__PURE__ */ new Map();
|
|
84995
85645
|
for (const row of sourceResult.rows) {
|
|
@@ -85030,7 +85680,26 @@ var SyncService = SyncService_1 = class SyncService2 {
|
|
|
85030
85680
|
};
|
|
85031
85681
|
const targetConnection = this.connectionsService.findById(targetConnectionId);
|
|
85032
85682
|
const targetConnector = await this.connectionsService.getConnector(targetConnectionId);
|
|
85033
|
-
const
|
|
85683
|
+
const parsedPkColumns = parseColumnArray(primaryKeyColumns);
|
|
85684
|
+
let effectivePkColumns = parsedPkColumns;
|
|
85685
|
+
if (parsedPkColumns.length === 0 || parsedPkColumns.length === 1 && parsedPkColumns[0] === "id") {
|
|
85686
|
+
const tableSchema2 = await targetConnector.getTableSchema(schema, table);
|
|
85687
|
+
const schemaPks = parseColumnArray(tableSchema2.primaryKey);
|
|
85688
|
+
if (schemaPks.length > 0) {
|
|
85689
|
+
effectivePkColumns = schemaPks;
|
|
85690
|
+
this.logger.debug(`Auto-detected primary keys for ${schema}.${table}: ${effectivePkColumns.join(", ")}`);
|
|
85691
|
+
} else {
|
|
85692
|
+
effectivePkColumns = ["id"];
|
|
85693
|
+
this.logger.warn(`No primary key found for ${schema}.${table}, falling back to 'id'`);
|
|
85694
|
+
}
|
|
85695
|
+
}
|
|
85696
|
+
const syncRun = this.metadataService.syncRunRepository.create({
|
|
85697
|
+
sourceConnectionId,
|
|
85698
|
+
targetConnectionId,
|
|
85699
|
+
schemaName: schema,
|
|
85700
|
+
tableName: table
|
|
85701
|
+
});
|
|
85702
|
+
const diff = await this.getTableDataDiff(sourceConnectionId, targetConnectionId, schema, table, effectivePkColumns);
|
|
85034
85703
|
const tableSchema = await targetConnector.getTableSchema(schema, table);
|
|
85035
85704
|
const columns = tableSchema.columns.map((c) => c.name);
|
|
85036
85705
|
const columnTypes = new Map(tableSchema.columns.map((c) => [c.name, c.dataType]));
|
|
@@ -85041,13 +85710,16 @@ var SyncService = SyncService_1 = class SyncService2 {
|
|
|
85041
85710
|
const dataType = columnTypes.get(colName) || "";
|
|
85042
85711
|
return serializeValueForInsert(value, dataType);
|
|
85043
85712
|
};
|
|
85713
|
+
const executedSql = [];
|
|
85044
85714
|
if (options.insertMissing && diff.missingInTarget.length > 0) {
|
|
85045
85715
|
for (const row of diff.missingInTarget) {
|
|
85046
85716
|
try {
|
|
85047
85717
|
const values = insertableColumns.map((c) => serializeValue(c, row[c]));
|
|
85048
85718
|
const placeholders = insertableColumns.map((_, i) => getPlaceholder(i + 1, engine)).join(", ");
|
|
85049
85719
|
const quotedColumns = insertableColumns.map((c) => quoteIdentifier2(c, engine)).join(", ");
|
|
85050
|
-
|
|
85720
|
+
const sqlTemplate = `INSERT INTO ${tableRef} (${quotedColumns}) VALUES (${placeholders})`;
|
|
85721
|
+
executedSql.push(buildReadableSql(sqlTemplate, values, engine));
|
|
85722
|
+
await targetConnector.execute(sqlTemplate, values);
|
|
85051
85723
|
result.inserted++;
|
|
85052
85724
|
} catch (error) {
|
|
85053
85725
|
result.errors.push(`Insert failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -85057,14 +85729,16 @@ var SyncService = SyncService_1 = class SyncService2 {
|
|
|
85057
85729
|
if (options.updateDifferent && diff.different.length > 0) {
|
|
85058
85730
|
for (const { source } of diff.different) {
|
|
85059
85731
|
try {
|
|
85060
|
-
const nonPkColumns = columns.filter((c) => !
|
|
85732
|
+
const nonPkColumns = columns.filter((c) => !effectivePkColumns.includes(c));
|
|
85061
85733
|
const setClause = nonPkColumns.map((c, i) => `${quoteIdentifier2(c, engine)} = ${getPlaceholder(i + 1, engine)}`).join(", ");
|
|
85062
|
-
const whereClause =
|
|
85734
|
+
const whereClause = effectivePkColumns.map((c, i) => `${quoteIdentifier2(c, engine)} = ${getPlaceholder(nonPkColumns.length + i + 1, engine)}`).join(" AND ");
|
|
85063
85735
|
const values = [
|
|
85064
85736
|
...nonPkColumns.map((c) => serializeValue(c, source[c])),
|
|
85065
|
-
...
|
|
85737
|
+
...effectivePkColumns.map((c) => serializeValue(c, source[c]))
|
|
85066
85738
|
];
|
|
85067
|
-
|
|
85739
|
+
const sqlTemplate = `UPDATE ${tableRef} SET ${setClause} WHERE ${whereClause}`;
|
|
85740
|
+
executedSql.push(buildReadableSql(sqlTemplate, values, engine));
|
|
85741
|
+
await targetConnector.execute(sqlTemplate, values);
|
|
85068
85742
|
result.updated++;
|
|
85069
85743
|
} catch (error) {
|
|
85070
85744
|
result.errors.push(`Update failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -85074,15 +85748,25 @@ var SyncService = SyncService_1 = class SyncService2 {
|
|
|
85074
85748
|
if (options.deleteExtra && diff.missingInSource.length > 0) {
|
|
85075
85749
|
for (const row of diff.missingInSource) {
|
|
85076
85750
|
try {
|
|
85077
|
-
const whereClause =
|
|
85078
|
-
const values =
|
|
85079
|
-
|
|
85751
|
+
const whereClause = effectivePkColumns.map((c, i) => `${quoteIdentifier2(c, engine)} = ${getPlaceholder(i + 1, engine)}`).join(" AND ");
|
|
85752
|
+
const values = effectivePkColumns.map((c) => row[c]);
|
|
85753
|
+
const sqlTemplate = `DELETE FROM ${tableRef} WHERE ${whereClause}`;
|
|
85754
|
+
executedSql.push(buildReadableSql(sqlTemplate, values, engine));
|
|
85755
|
+
await targetConnector.execute(sqlTemplate, values);
|
|
85080
85756
|
result.deleted++;
|
|
85081
85757
|
} catch (error) {
|
|
85082
85758
|
result.errors.push(`Delete failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
85083
85759
|
}
|
|
85084
85760
|
}
|
|
85085
85761
|
}
|
|
85762
|
+
this.metadataService.syncRunRepository.update(syncRun.id, {
|
|
85763
|
+
status: result.errors.length > 0 ? "failed" : "completed",
|
|
85764
|
+
inserts: result.inserted,
|
|
85765
|
+
updates: result.updated,
|
|
85766
|
+
deletes: result.deleted,
|
|
85767
|
+
errors: result.errors,
|
|
85768
|
+
sqlStatements: executedSql
|
|
85769
|
+
});
|
|
85086
85770
|
return result;
|
|
85087
85771
|
}
|
|
85088
85772
|
/**
|
|
@@ -85233,6 +85917,11 @@ var SyncService = SyncService_1 = class SyncService2 {
|
|
|
85233
85917
|
errors: [],
|
|
85234
85918
|
tableResults: []
|
|
85235
85919
|
};
|
|
85920
|
+
const syncRun = this.metadataService.syncRunRepository.create({
|
|
85921
|
+
sourceConnectionId,
|
|
85922
|
+
targetConnectionId,
|
|
85923
|
+
schemaName: schema
|
|
85924
|
+
});
|
|
85236
85925
|
const sourceConnection = this.connectionsService.findById(sourceConnectionId);
|
|
85237
85926
|
const targetConnection = this.connectionsService.findById(targetConnectionId);
|
|
85238
85927
|
const sourceConnector = await this.connectionsService.getConnector(sourceConnectionId);
|
|
@@ -85327,6 +86016,12 @@ var SyncService = SyncService_1 = class SyncService2 {
|
|
|
85327
86016
|
} finally {
|
|
85328
86017
|
await this.setForeignKeyChecks(targetConnector, targetEngine, true);
|
|
85329
86018
|
}
|
|
86019
|
+
this.metadataService.syncRunRepository.complete(syncRun.id, {
|
|
86020
|
+
inserts: result.rowsCopied,
|
|
86021
|
+
updates: 0,
|
|
86022
|
+
deletes: 0,
|
|
86023
|
+
errors: result.errors
|
|
86024
|
+
});
|
|
85330
86025
|
return result;
|
|
85331
86026
|
}
|
|
85332
86027
|
/**
|
|
@@ -85372,8 +86067,16 @@ var __param6 = function(paramIndex, decorator) {
|
|
|
85372
86067
|
};
|
|
85373
86068
|
var SyncController = class SyncController2 {
|
|
85374
86069
|
syncService;
|
|
85375
|
-
|
|
86070
|
+
metadataService;
|
|
86071
|
+
constructor(syncService, metadataService) {
|
|
85376
86072
|
this.syncService = syncService;
|
|
86073
|
+
this.metadataService = metadataService;
|
|
86074
|
+
}
|
|
86075
|
+
/**
|
|
86076
|
+
* Get recent sync runs (for activity log)
|
|
86077
|
+
*/
|
|
86078
|
+
getSyncRuns(limit) {
|
|
86079
|
+
return this.metadataService.syncRunRepository.findRecent(limit ? parseInt(limit, 10) : 500);
|
|
85377
86080
|
}
|
|
85378
86081
|
/**
|
|
85379
86082
|
* Get sync status for an instance group
|
|
@@ -85472,6 +86175,13 @@ var SyncController = class SyncController2 {
|
|
|
85472
86175
|
});
|
|
85473
86176
|
}
|
|
85474
86177
|
};
|
|
86178
|
+
__decorate17([
|
|
86179
|
+
(0, import_common16.Get)("runs"),
|
|
86180
|
+
__param6(0, (0, import_common16.Query)("limit")),
|
|
86181
|
+
__metadata11("design:type", Function),
|
|
86182
|
+
__metadata11("design:paramtypes", [String]),
|
|
86183
|
+
__metadata11("design:returntype", void 0)
|
|
86184
|
+
], SyncController.prototype, "getSyncRuns", null);
|
|
85475
86185
|
__decorate17([
|
|
85476
86186
|
(0, import_common16.Get)("groups/:groupId/status"),
|
|
85477
86187
|
__param6(0, (0, import_common16.Param)("groupId")),
|
|
@@ -85556,7 +86266,10 @@ __decorate17([
|
|
|
85556
86266
|
], SyncController.prototype, "dumpAndRestore", null);
|
|
85557
86267
|
SyncController = __decorate17([
|
|
85558
86268
|
(0, import_common16.Controller)("sync"),
|
|
85559
|
-
__metadata11("design:paramtypes", [
|
|
86269
|
+
__metadata11("design:paramtypes", [
|
|
86270
|
+
SyncService,
|
|
86271
|
+
MetadataService
|
|
86272
|
+
])
|
|
85560
86273
|
], SyncController);
|
|
85561
86274
|
|
|
85562
86275
|
// apps/api/dist/sync/sync.module.js
|