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 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 = 9;
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
- let def = `${quoteIdentifier(col.name, engine)} ${col.dataType}`;
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
- let sql = `ALTER TABLE ${quoteTable(schema, table, engine)} ADD COLUMN ${quoteIdentifier(col.name, engine)} ${col.dataType}`;
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 = primaryKeyColumns.map((c) => quoteIdentifier2(c, sourceConnection.engine)).join(", ");
84989
- const targetOrderBy = primaryKeyColumns.map((c) => quoteIdentifier2(c, targetConnection.engine)).join(", ");
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) => primaryKeyColumns.map((c) => String(row[c])).join("|");
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 diff = await this.getTableDataDiff(sourceConnectionId, targetConnectionId, schema, table, primaryKeyColumns);
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
- await targetConnector.execute(`INSERT INTO ${tableRef} (${quotedColumns}) VALUES (${placeholders})`, values);
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) => !primaryKeyColumns.includes(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 = primaryKeyColumns.map((c, i) => `${quoteIdentifier2(c, engine)} = ${getPlaceholder(nonPkColumns.length + i + 1, engine)}`).join(" AND ");
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
- ...primaryKeyColumns.map((c) => serializeValue(c, source[c]))
85737
+ ...effectivePkColumns.map((c) => serializeValue(c, source[c]))
85066
85738
  ];
85067
- await targetConnector.execute(`UPDATE ${tableRef} SET ${setClause} WHERE ${whereClause}`, values);
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 = primaryKeyColumns.map((c, i) => `${quoteIdentifier2(c, engine)} = ${getPlaceholder(i + 1, engine)}`).join(" AND ");
85078
- const values = primaryKeyColumns.map((c) => row[c]);
85079
- await targetConnector.execute(`DELETE FROM ${tableRef} WHERE ${whereClause}`, values);
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
- constructor(syncService) {
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", [SyncService])
86269
+ __metadata11("design:paramtypes", [
86270
+ SyncService,
86271
+ MetadataService
86272
+ ])
85560
86273
  ], SyncController);
85561
86274
 
85562
86275
  // apps/api/dist/sync/sync.module.js