@hasna/cloud 0.1.11 → 0.1.13

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/cli/index.js CHANGED
@@ -11443,6 +11443,30 @@ async function resolvePrimaryKeys(source, target, table, pkOption) {
11443
11443
  }
11444
11444
  return pks;
11445
11445
  }
11446
+ async function filterColumnsForTarget(target, table, sourceColumns) {
11447
+ try {
11448
+ if (!isAsyncAdapter(target)) {
11449
+ const colInfo = target.all(`PRAGMA table_info("${table}")`);
11450
+ if (Array.isArray(colInfo) && colInfo.length > 0) {
11451
+ const targetCols = new Set(colInfo.map((c) => c.name));
11452
+ const filtered = sourceColumns.filter((c) => targetCols.has(c));
11453
+ if (filtered.length < sourceColumns.length) {
11454
+ const dropped = sourceColumns.filter((c) => !targetCols.has(c));
11455
+ process.stderr.write(` [sync] ${table}: dropping ${dropped.length} columns not in target: ${dropped.join(", ")}
11456
+ `);
11457
+ }
11458
+ return filtered;
11459
+ }
11460
+ } else {
11461
+ const colInfo = await target.all(`SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = '${table}'`);
11462
+ if (colInfo.length > 0) {
11463
+ const targetCols = new Set(colInfo.map((c) => c.column_name));
11464
+ return sourceColumns.filter((c) => targetCols.has(c));
11465
+ }
11466
+ }
11467
+ } catch {}
11468
+ return sourceColumns;
11469
+ }
11446
11470
  async function syncTransfer(source, target, options, _direction) {
11447
11471
  const {
11448
11472
  tables,
@@ -11452,60 +11476,88 @@ async function syncTransfer(source, target, options, _direction) {
11452
11476
  primaryKey: pkOption
11453
11477
  } = options;
11454
11478
  const results = [];
11455
- if (!isAsyncAdapter(target)) {
11479
+ const sqliteTarget = !isAsyncAdapter(target) ? target : null;
11480
+ if (sqliteTarget) {
11456
11481
  try {
11457
- target.run("PRAGMA foreign_keys = OFF");
11482
+ sqliteTarget.exec("PRAGMA foreign_keys = OFF");
11458
11483
  } catch {}
11459
11484
  }
11460
- for (let i = 0;i < tables.length; i++) {
11461
- const table = tables[i];
11462
- const result = {
11463
- table,
11464
- rowsRead: 0,
11465
- rowsWritten: 0,
11466
- rowsSkipped: 0,
11467
- errors: []
11468
- };
11469
- try {
11470
- onProgress?.({
11485
+ try {
11486
+ for (let i = 0;i < tables.length; i++) {
11487
+ const table = tables[i];
11488
+ const result = {
11471
11489
  table,
11472
- phase: "reading",
11473
11490
  rowsRead: 0,
11474
11491
  rowsWritten: 0,
11475
- totalTables: tables.length,
11476
- currentTableIndex: i
11477
- });
11478
- const rows = await readAll(source, `SELECT * FROM "${table}"`);
11479
- result.rowsRead = rows.length;
11480
- if (rows.length === 0) {
11492
+ rowsSkipped: 0,
11493
+ errors: []
11494
+ };
11495
+ try {
11481
11496
  onProgress?.({
11482
11497
  table,
11483
- phase: "done",
11498
+ phase: "reading",
11484
11499
  rowsRead: 0,
11485
11500
  rowsWritten: 0,
11486
11501
  totalTables: tables.length,
11487
11502
  currentTableIndex: i
11488
11503
  });
11489
- results.push(result);
11490
- continue;
11491
- }
11492
- const pkColumns = await resolvePrimaryKeys(source, target, table, pkOption);
11493
- const sourceColumns = Object.keys(rows[0]);
11494
- let targetColumns = null;
11495
- if (!isAsyncAdapter(target)) {
11496
- try {
11497
- const colInfo = target.all(`PRAGMA table_info("${table}")`);
11498
- targetColumns = new Set(colInfo.map((c) => c.name));
11499
- } catch {}
11500
- } else {
11501
- try {
11502
- const colInfo = await target.all(`SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = '${table}'`);
11503
- targetColumns = new Set(colInfo.map((c) => c.column_name));
11504
- } catch {}
11505
- }
11506
- const columns = targetColumns ? sourceColumns.filter((c) => targetColumns.has(c)) : sourceColumns;
11507
- if (pkColumns.length === 0) {
11508
- result.errors.push(`Table "${table}" has no primary key — inserting without conflict handling`);
11504
+ const rows = await readAll(source, `SELECT * FROM "${table}"`);
11505
+ result.rowsRead = rows.length;
11506
+ if (rows.length === 0) {
11507
+ onProgress?.({
11508
+ table,
11509
+ phase: "done",
11510
+ rowsRead: 0,
11511
+ rowsWritten: 0,
11512
+ totalTables: tables.length,
11513
+ currentTableIndex: i
11514
+ });
11515
+ results.push(result);
11516
+ continue;
11517
+ }
11518
+ const pkColumns = await resolvePrimaryKeys(source, target, table, pkOption);
11519
+ const sourceColumns = Object.keys(rows[0]);
11520
+ const columns = await filterColumnsForTarget(target, table, sourceColumns);
11521
+ if (pkColumns.length === 0) {
11522
+ result.errors.push(`Table "${table}" has no primary key — inserting without conflict handling`);
11523
+ onProgress?.({
11524
+ table,
11525
+ phase: "writing",
11526
+ rowsRead: result.rowsRead,
11527
+ rowsWritten: 0,
11528
+ totalTables: tables.length,
11529
+ currentTableIndex: i
11530
+ });
11531
+ for (let offset = 0;offset < rows.length; offset += batchSize) {
11532
+ const batch = rows.slice(offset, offset + batchSize);
11533
+ try {
11534
+ if (isAsyncAdapter(target)) {
11535
+ await batchInsertPg(target, table, columns, batch);
11536
+ } else {
11537
+ batchInsertSqlite(target, table, columns, batch);
11538
+ }
11539
+ result.rowsWritten += batch.length;
11540
+ } catch (err) {
11541
+ result.errors.push(`Batch at offset ${offset}: ${err?.message ?? String(err)}`);
11542
+ }
11543
+ }
11544
+ onProgress?.({
11545
+ table,
11546
+ phase: "done",
11547
+ rowsRead: result.rowsRead,
11548
+ rowsWritten: result.rowsWritten,
11549
+ totalTables: tables.length,
11550
+ currentTableIndex: i
11551
+ });
11552
+ results.push(result);
11553
+ continue;
11554
+ }
11555
+ const missingPks = pkColumns.filter((pk) => !columns.includes(pk));
11556
+ if (missingPks.length > 0) {
11557
+ result.errors.push(`Table "${table}" missing PK columns in data: ${missingPks.join(", ")} — skipping`);
11558
+ results.push(result);
11559
+ continue;
11560
+ }
11509
11561
  onProgress?.({
11510
11562
  table,
11511
11563
  phase: "writing",
@@ -11514,18 +11566,27 @@ async function syncTransfer(source, target, options, _direction) {
11514
11566
  totalTables: tables.length,
11515
11567
  currentTableIndex: i
11516
11568
  });
11569
+ const updateCols = columns.filter((c) => !pkColumns.includes(c));
11517
11570
  for (let offset = 0;offset < rows.length; offset += batchSize) {
11518
11571
  const batch = rows.slice(offset, offset + batchSize);
11519
11572
  try {
11520
11573
  if (isAsyncAdapter(target)) {
11521
- await batchInsertPg(target, table, columns, batch);
11574
+ await batchUpsertPg(target, table, columns, updateCols, pkColumns, batch);
11522
11575
  } else {
11523
- batchInsertSqlite(target, table, columns, batch);
11576
+ batchUpsertSqlite(target, table, columns, updateCols, pkColumns, batch);
11524
11577
  }
11525
11578
  result.rowsWritten += batch.length;
11526
11579
  } catch (err) {
11527
11580
  result.errors.push(`Batch at offset ${offset}: ${err?.message ?? String(err)}`);
11528
11581
  }
11582
+ onProgress?.({
11583
+ table,
11584
+ phase: "writing",
11585
+ rowsRead: result.rowsRead,
11586
+ rowsWritten: result.rowsWritten,
11587
+ totalTables: tables.length,
11588
+ currentTableIndex: i
11589
+ });
11529
11590
  }
11530
11591
  onProgress?.({
11531
11592
  table,
@@ -11535,62 +11596,27 @@ async function syncTransfer(source, target, options, _direction) {
11535
11596
  totalTables: tables.length,
11536
11597
  currentTableIndex: i
11537
11598
  });
11538
- results.push(result);
11539
- continue;
11540
- }
11541
- const missingPks = pkColumns.filter((pk) => !columns.includes(pk));
11542
- if (missingPks.length > 0) {
11543
- result.errors.push(`Table "${table}" missing PK columns in data: ${missingPks.join(", ")} — skipping`);
11544
- results.push(result);
11545
- continue;
11599
+ } catch (err) {
11600
+ result.errors.push(`Table "${table}": ${err?.message ?? String(err)}`);
11546
11601
  }
11547
- onProgress?.({
11548
- table,
11549
- phase: "writing",
11550
- rowsRead: result.rowsRead,
11551
- rowsWritten: 0,
11552
- totalTables: tables.length,
11553
- currentTableIndex: i
11554
- });
11555
- const updateCols = columns.filter((c) => !pkColumns.includes(c));
11556
- for (let offset = 0;offset < rows.length; offset += batchSize) {
11557
- const batch = rows.slice(offset, offset + batchSize);
11558
- try {
11559
- if (isAsyncAdapter(target)) {
11560
- await batchUpsertPg(target, table, columns, updateCols, pkColumns, batch);
11561
- } else {
11562
- batchUpsertSqlite(target, table, columns, updateCols, pkColumns, batch);
11602
+ results.push(result);
11603
+ }
11604
+ } finally {
11605
+ if (sqliteTarget) {
11606
+ try {
11607
+ sqliteTarget.exec("PRAGMA foreign_keys = ON");
11608
+ } catch {}
11609
+ try {
11610
+ const violations = sqliteTarget.all("PRAGMA foreign_key_check");
11611
+ if (violations.length > 0) {
11612
+ const tables2 = [...new Set(violations.map((v) => v.table))];
11613
+ const msg = `FK integrity check: ${violations.length} violation(s) in table(s): ${tables2.join(", ")}`;
11614
+ if (results.length > 0) {
11615
+ results[results.length - 1].errors.push(msg);
11563
11616
  }
11564
- result.rowsWritten += batch.length;
11565
- } catch (err) {
11566
- result.errors.push(`Batch at offset ${offset}: ${err?.message ?? String(err)}`);
11567
11617
  }
11568
- onProgress?.({
11569
- table,
11570
- phase: "writing",
11571
- rowsRead: result.rowsRead,
11572
- rowsWritten: result.rowsWritten,
11573
- totalTables: tables.length,
11574
- currentTableIndex: i
11575
- });
11576
- }
11577
- onProgress?.({
11578
- table,
11579
- phase: "done",
11580
- rowsRead: result.rowsRead,
11581
- rowsWritten: result.rowsWritten,
11582
- totalTables: tables.length,
11583
- currentTableIndex: i
11584
- });
11585
- } catch (err) {
11586
- result.errors.push(`Table "${table}": ${err?.message ?? String(err)}`);
11618
+ } catch {}
11587
11619
  }
11588
- results.push(result);
11589
- }
11590
- if (!isAsyncAdapter(target)) {
11591
- try {
11592
- target.run("PRAGMA foreign_keys = ON");
11593
- } catch {}
11594
11620
  }
11595
11621
  return results;
11596
11622
  }
package/dist/index.js CHANGED
@@ -9435,6 +9435,30 @@ async function resolvePrimaryKeys(source, target, table, pkOption) {
9435
9435
  }
9436
9436
  return pks;
9437
9437
  }
9438
+ async function filterColumnsForTarget(target, table, sourceColumns) {
9439
+ try {
9440
+ if (!isAsyncAdapter(target)) {
9441
+ const colInfo = target.all(`PRAGMA table_info("${table}")`);
9442
+ if (Array.isArray(colInfo) && colInfo.length > 0) {
9443
+ const targetCols = new Set(colInfo.map((c) => c.name));
9444
+ const filtered = sourceColumns.filter((c) => targetCols.has(c));
9445
+ if (filtered.length < sourceColumns.length) {
9446
+ const dropped = sourceColumns.filter((c) => !targetCols.has(c));
9447
+ process.stderr.write(` [sync] ${table}: dropping ${dropped.length} columns not in target: ${dropped.join(", ")}
9448
+ `);
9449
+ }
9450
+ return filtered;
9451
+ }
9452
+ } else {
9453
+ const colInfo = await target.all(`SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = '${table}'`);
9454
+ if (colInfo.length > 0) {
9455
+ const targetCols = new Set(colInfo.map((c) => c.column_name));
9456
+ return sourceColumns.filter((c) => targetCols.has(c));
9457
+ }
9458
+ }
9459
+ } catch {}
9460
+ return sourceColumns;
9461
+ }
9438
9462
  async function syncTransfer(source, target, options, _direction) {
9439
9463
  const {
9440
9464
  tables,
@@ -9444,60 +9468,88 @@ async function syncTransfer(source, target, options, _direction) {
9444
9468
  primaryKey: pkOption
9445
9469
  } = options;
9446
9470
  const results = [];
9447
- if (!isAsyncAdapter(target)) {
9471
+ const sqliteTarget = !isAsyncAdapter(target) ? target : null;
9472
+ if (sqliteTarget) {
9448
9473
  try {
9449
- target.run("PRAGMA foreign_keys = OFF");
9474
+ sqliteTarget.exec("PRAGMA foreign_keys = OFF");
9450
9475
  } catch {}
9451
9476
  }
9452
- for (let i = 0;i < tables.length; i++) {
9453
- const table = tables[i];
9454
- const result = {
9455
- table,
9456
- rowsRead: 0,
9457
- rowsWritten: 0,
9458
- rowsSkipped: 0,
9459
- errors: []
9460
- };
9461
- try {
9462
- onProgress?.({
9477
+ try {
9478
+ for (let i = 0;i < tables.length; i++) {
9479
+ const table = tables[i];
9480
+ const result = {
9463
9481
  table,
9464
- phase: "reading",
9465
9482
  rowsRead: 0,
9466
9483
  rowsWritten: 0,
9467
- totalTables: tables.length,
9468
- currentTableIndex: i
9469
- });
9470
- const rows = await readAll(source, `SELECT * FROM "${table}"`);
9471
- result.rowsRead = rows.length;
9472
- if (rows.length === 0) {
9484
+ rowsSkipped: 0,
9485
+ errors: []
9486
+ };
9487
+ try {
9473
9488
  onProgress?.({
9474
9489
  table,
9475
- phase: "done",
9490
+ phase: "reading",
9476
9491
  rowsRead: 0,
9477
9492
  rowsWritten: 0,
9478
9493
  totalTables: tables.length,
9479
9494
  currentTableIndex: i
9480
9495
  });
9481
- results.push(result);
9482
- continue;
9483
- }
9484
- const pkColumns = await resolvePrimaryKeys(source, target, table, pkOption);
9485
- const sourceColumns = Object.keys(rows[0]);
9486
- let targetColumns = null;
9487
- if (!isAsyncAdapter(target)) {
9488
- try {
9489
- const colInfo = target.all(`PRAGMA table_info("${table}")`);
9490
- targetColumns = new Set(colInfo.map((c) => c.name));
9491
- } catch {}
9492
- } else {
9493
- try {
9494
- const colInfo = await target.all(`SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = '${table}'`);
9495
- targetColumns = new Set(colInfo.map((c) => c.column_name));
9496
- } catch {}
9497
- }
9498
- const columns = targetColumns ? sourceColumns.filter((c) => targetColumns.has(c)) : sourceColumns;
9499
- if (pkColumns.length === 0) {
9500
- result.errors.push(`Table "${table}" has no primary key — inserting without conflict handling`);
9496
+ const rows = await readAll(source, `SELECT * FROM "${table}"`);
9497
+ result.rowsRead = rows.length;
9498
+ if (rows.length === 0) {
9499
+ onProgress?.({
9500
+ table,
9501
+ phase: "done",
9502
+ rowsRead: 0,
9503
+ rowsWritten: 0,
9504
+ totalTables: tables.length,
9505
+ currentTableIndex: i
9506
+ });
9507
+ results.push(result);
9508
+ continue;
9509
+ }
9510
+ const pkColumns = await resolvePrimaryKeys(source, target, table, pkOption);
9511
+ const sourceColumns = Object.keys(rows[0]);
9512
+ const columns = await filterColumnsForTarget(target, table, sourceColumns);
9513
+ if (pkColumns.length === 0) {
9514
+ result.errors.push(`Table "${table}" has no primary key — inserting without conflict handling`);
9515
+ onProgress?.({
9516
+ table,
9517
+ phase: "writing",
9518
+ rowsRead: result.rowsRead,
9519
+ rowsWritten: 0,
9520
+ totalTables: tables.length,
9521
+ currentTableIndex: i
9522
+ });
9523
+ for (let offset = 0;offset < rows.length; offset += batchSize) {
9524
+ const batch = rows.slice(offset, offset + batchSize);
9525
+ try {
9526
+ if (isAsyncAdapter(target)) {
9527
+ await batchInsertPg(target, table, columns, batch);
9528
+ } else {
9529
+ batchInsertSqlite(target, table, columns, batch);
9530
+ }
9531
+ result.rowsWritten += batch.length;
9532
+ } catch (err) {
9533
+ result.errors.push(`Batch at offset ${offset}: ${err?.message ?? String(err)}`);
9534
+ }
9535
+ }
9536
+ onProgress?.({
9537
+ table,
9538
+ phase: "done",
9539
+ rowsRead: result.rowsRead,
9540
+ rowsWritten: result.rowsWritten,
9541
+ totalTables: tables.length,
9542
+ currentTableIndex: i
9543
+ });
9544
+ results.push(result);
9545
+ continue;
9546
+ }
9547
+ const missingPks = pkColumns.filter((pk) => !columns.includes(pk));
9548
+ if (missingPks.length > 0) {
9549
+ result.errors.push(`Table "${table}" missing PK columns in data: ${missingPks.join(", ")} — skipping`);
9550
+ results.push(result);
9551
+ continue;
9552
+ }
9501
9553
  onProgress?.({
9502
9554
  table,
9503
9555
  phase: "writing",
@@ -9506,18 +9558,27 @@ async function syncTransfer(source, target, options, _direction) {
9506
9558
  totalTables: tables.length,
9507
9559
  currentTableIndex: i
9508
9560
  });
9561
+ const updateCols = columns.filter((c) => !pkColumns.includes(c));
9509
9562
  for (let offset = 0;offset < rows.length; offset += batchSize) {
9510
9563
  const batch = rows.slice(offset, offset + batchSize);
9511
9564
  try {
9512
9565
  if (isAsyncAdapter(target)) {
9513
- await batchInsertPg(target, table, columns, batch);
9566
+ await batchUpsertPg(target, table, columns, updateCols, pkColumns, batch);
9514
9567
  } else {
9515
- batchInsertSqlite(target, table, columns, batch);
9568
+ batchUpsertSqlite(target, table, columns, updateCols, pkColumns, batch);
9516
9569
  }
9517
9570
  result.rowsWritten += batch.length;
9518
9571
  } catch (err) {
9519
9572
  result.errors.push(`Batch at offset ${offset}: ${err?.message ?? String(err)}`);
9520
9573
  }
9574
+ onProgress?.({
9575
+ table,
9576
+ phase: "writing",
9577
+ rowsRead: result.rowsRead,
9578
+ rowsWritten: result.rowsWritten,
9579
+ totalTables: tables.length,
9580
+ currentTableIndex: i
9581
+ });
9521
9582
  }
9522
9583
  onProgress?.({
9523
9584
  table,
@@ -9527,62 +9588,27 @@ async function syncTransfer(source, target, options, _direction) {
9527
9588
  totalTables: tables.length,
9528
9589
  currentTableIndex: i
9529
9590
  });
9530
- results.push(result);
9531
- continue;
9532
- }
9533
- const missingPks = pkColumns.filter((pk) => !columns.includes(pk));
9534
- if (missingPks.length > 0) {
9535
- result.errors.push(`Table "${table}" missing PK columns in data: ${missingPks.join(", ")} — skipping`);
9536
- results.push(result);
9537
- continue;
9591
+ } catch (err) {
9592
+ result.errors.push(`Table "${table}": ${err?.message ?? String(err)}`);
9538
9593
  }
9539
- onProgress?.({
9540
- table,
9541
- phase: "writing",
9542
- rowsRead: result.rowsRead,
9543
- rowsWritten: 0,
9544
- totalTables: tables.length,
9545
- currentTableIndex: i
9546
- });
9547
- const updateCols = columns.filter((c) => !pkColumns.includes(c));
9548
- for (let offset = 0;offset < rows.length; offset += batchSize) {
9549
- const batch = rows.slice(offset, offset + batchSize);
9550
- try {
9551
- if (isAsyncAdapter(target)) {
9552
- await batchUpsertPg(target, table, columns, updateCols, pkColumns, batch);
9553
- } else {
9554
- batchUpsertSqlite(target, table, columns, updateCols, pkColumns, batch);
9594
+ results.push(result);
9595
+ }
9596
+ } finally {
9597
+ if (sqliteTarget) {
9598
+ try {
9599
+ sqliteTarget.exec("PRAGMA foreign_keys = ON");
9600
+ } catch {}
9601
+ try {
9602
+ const violations = sqliteTarget.all("PRAGMA foreign_key_check");
9603
+ if (violations.length > 0) {
9604
+ const tables2 = [...new Set(violations.map((v) => v.table))];
9605
+ const msg = `FK integrity check: ${violations.length} violation(s) in table(s): ${tables2.join(", ")}`;
9606
+ if (results.length > 0) {
9607
+ results[results.length - 1].errors.push(msg);
9555
9608
  }
9556
- result.rowsWritten += batch.length;
9557
- } catch (err) {
9558
- result.errors.push(`Batch at offset ${offset}: ${err?.message ?? String(err)}`);
9559
9609
  }
9560
- onProgress?.({
9561
- table,
9562
- phase: "writing",
9563
- rowsRead: result.rowsRead,
9564
- rowsWritten: result.rowsWritten,
9565
- totalTables: tables.length,
9566
- currentTableIndex: i
9567
- });
9568
- }
9569
- onProgress?.({
9570
- table,
9571
- phase: "done",
9572
- rowsRead: result.rowsRead,
9573
- rowsWritten: result.rowsWritten,
9574
- totalTables: tables.length,
9575
- currentTableIndex: i
9576
- });
9577
- } catch (err) {
9578
- result.errors.push(`Table "${table}": ${err?.message ?? String(err)}`);
9610
+ } catch {}
9579
9611
  }
9580
- results.push(result);
9581
- }
9582
- if (!isAsyncAdapter(target)) {
9583
- try {
9584
- target.run("PRAGMA foreign_keys = ON");
9585
- } catch {}
9586
9612
  }
9587
9613
  return results;
9588
9614
  }
package/dist/mcp/index.js CHANGED
@@ -24782,6 +24782,30 @@ async function resolvePrimaryKeys(source, target, table, pkOption) {
24782
24782
  }
24783
24783
  return pks;
24784
24784
  }
24785
+ async function filterColumnsForTarget(target, table, sourceColumns) {
24786
+ try {
24787
+ if (!isAsyncAdapter(target)) {
24788
+ const colInfo = target.all(`PRAGMA table_info("${table}")`);
24789
+ if (Array.isArray(colInfo) && colInfo.length > 0) {
24790
+ const targetCols = new Set(colInfo.map((c) => c.name));
24791
+ const filtered = sourceColumns.filter((c) => targetCols.has(c));
24792
+ if (filtered.length < sourceColumns.length) {
24793
+ const dropped = sourceColumns.filter((c) => !targetCols.has(c));
24794
+ process.stderr.write(` [sync] ${table}: dropping ${dropped.length} columns not in target: ${dropped.join(", ")}
24795
+ `);
24796
+ }
24797
+ return filtered;
24798
+ }
24799
+ } else {
24800
+ const colInfo = await target.all(`SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = '${table}'`);
24801
+ if (colInfo.length > 0) {
24802
+ const targetCols = new Set(colInfo.map((c) => c.column_name));
24803
+ return sourceColumns.filter((c) => targetCols.has(c));
24804
+ }
24805
+ }
24806
+ } catch {}
24807
+ return sourceColumns;
24808
+ }
24785
24809
  async function syncTransfer(source, target, options, _direction) {
24786
24810
  const {
24787
24811
  tables,
@@ -24791,60 +24815,88 @@ async function syncTransfer(source, target, options, _direction) {
24791
24815
  primaryKey: pkOption
24792
24816
  } = options;
24793
24817
  const results = [];
24794
- if (!isAsyncAdapter(target)) {
24818
+ const sqliteTarget = !isAsyncAdapter(target) ? target : null;
24819
+ if (sqliteTarget) {
24795
24820
  try {
24796
- target.run("PRAGMA foreign_keys = OFF");
24821
+ sqliteTarget.exec("PRAGMA foreign_keys = OFF");
24797
24822
  } catch {}
24798
24823
  }
24799
- for (let i = 0;i < tables.length; i++) {
24800
- const table = tables[i];
24801
- const result = {
24802
- table,
24803
- rowsRead: 0,
24804
- rowsWritten: 0,
24805
- rowsSkipped: 0,
24806
- errors: []
24807
- };
24808
- try {
24809
- onProgress?.({
24824
+ try {
24825
+ for (let i = 0;i < tables.length; i++) {
24826
+ const table = tables[i];
24827
+ const result = {
24810
24828
  table,
24811
- phase: "reading",
24812
24829
  rowsRead: 0,
24813
24830
  rowsWritten: 0,
24814
- totalTables: tables.length,
24815
- currentTableIndex: i
24816
- });
24817
- const rows = await readAll(source, `SELECT * FROM "${table}"`);
24818
- result.rowsRead = rows.length;
24819
- if (rows.length === 0) {
24831
+ rowsSkipped: 0,
24832
+ errors: []
24833
+ };
24834
+ try {
24820
24835
  onProgress?.({
24821
24836
  table,
24822
- phase: "done",
24837
+ phase: "reading",
24823
24838
  rowsRead: 0,
24824
24839
  rowsWritten: 0,
24825
24840
  totalTables: tables.length,
24826
24841
  currentTableIndex: i
24827
24842
  });
24828
- results.push(result);
24829
- continue;
24830
- }
24831
- const pkColumns = await resolvePrimaryKeys(source, target, table, pkOption);
24832
- const sourceColumns = Object.keys(rows[0]);
24833
- let targetColumns = null;
24834
- if (!isAsyncAdapter(target)) {
24835
- try {
24836
- const colInfo = target.all(`PRAGMA table_info("${table}")`);
24837
- targetColumns = new Set(colInfo.map((c) => c.name));
24838
- } catch {}
24839
- } else {
24840
- try {
24841
- const colInfo = await target.all(`SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = '${table}'`);
24842
- targetColumns = new Set(colInfo.map((c) => c.column_name));
24843
- } catch {}
24844
- }
24845
- const columns = targetColumns ? sourceColumns.filter((c) => targetColumns.has(c)) : sourceColumns;
24846
- if (pkColumns.length === 0) {
24847
- result.errors.push(`Table "${table}" has no primary key — inserting without conflict handling`);
24843
+ const rows = await readAll(source, `SELECT * FROM "${table}"`);
24844
+ result.rowsRead = rows.length;
24845
+ if (rows.length === 0) {
24846
+ onProgress?.({
24847
+ table,
24848
+ phase: "done",
24849
+ rowsRead: 0,
24850
+ rowsWritten: 0,
24851
+ totalTables: tables.length,
24852
+ currentTableIndex: i
24853
+ });
24854
+ results.push(result);
24855
+ continue;
24856
+ }
24857
+ const pkColumns = await resolvePrimaryKeys(source, target, table, pkOption);
24858
+ const sourceColumns = Object.keys(rows[0]);
24859
+ const columns = await filterColumnsForTarget(target, table, sourceColumns);
24860
+ if (pkColumns.length === 0) {
24861
+ result.errors.push(`Table "${table}" has no primary key — inserting without conflict handling`);
24862
+ onProgress?.({
24863
+ table,
24864
+ phase: "writing",
24865
+ rowsRead: result.rowsRead,
24866
+ rowsWritten: 0,
24867
+ totalTables: tables.length,
24868
+ currentTableIndex: i
24869
+ });
24870
+ for (let offset = 0;offset < rows.length; offset += batchSize) {
24871
+ const batch = rows.slice(offset, offset + batchSize);
24872
+ try {
24873
+ if (isAsyncAdapter(target)) {
24874
+ await batchInsertPg(target, table, columns, batch);
24875
+ } else {
24876
+ batchInsertSqlite(target, table, columns, batch);
24877
+ }
24878
+ result.rowsWritten += batch.length;
24879
+ } catch (err) {
24880
+ result.errors.push(`Batch at offset ${offset}: ${err?.message ?? String(err)}`);
24881
+ }
24882
+ }
24883
+ onProgress?.({
24884
+ table,
24885
+ phase: "done",
24886
+ rowsRead: result.rowsRead,
24887
+ rowsWritten: result.rowsWritten,
24888
+ totalTables: tables.length,
24889
+ currentTableIndex: i
24890
+ });
24891
+ results.push(result);
24892
+ continue;
24893
+ }
24894
+ const missingPks = pkColumns.filter((pk) => !columns.includes(pk));
24895
+ if (missingPks.length > 0) {
24896
+ result.errors.push(`Table "${table}" missing PK columns in data: ${missingPks.join(", ")} — skipping`);
24897
+ results.push(result);
24898
+ continue;
24899
+ }
24848
24900
  onProgress?.({
24849
24901
  table,
24850
24902
  phase: "writing",
@@ -24853,18 +24905,27 @@ async function syncTransfer(source, target, options, _direction) {
24853
24905
  totalTables: tables.length,
24854
24906
  currentTableIndex: i
24855
24907
  });
24908
+ const updateCols = columns.filter((c) => !pkColumns.includes(c));
24856
24909
  for (let offset = 0;offset < rows.length; offset += batchSize) {
24857
24910
  const batch = rows.slice(offset, offset + batchSize);
24858
24911
  try {
24859
24912
  if (isAsyncAdapter(target)) {
24860
- await batchInsertPg(target, table, columns, batch);
24913
+ await batchUpsertPg(target, table, columns, updateCols, pkColumns, batch);
24861
24914
  } else {
24862
- batchInsertSqlite(target, table, columns, batch);
24915
+ batchUpsertSqlite(target, table, columns, updateCols, pkColumns, batch);
24863
24916
  }
24864
24917
  result.rowsWritten += batch.length;
24865
24918
  } catch (err) {
24866
24919
  result.errors.push(`Batch at offset ${offset}: ${err?.message ?? String(err)}`);
24867
24920
  }
24921
+ onProgress?.({
24922
+ table,
24923
+ phase: "writing",
24924
+ rowsRead: result.rowsRead,
24925
+ rowsWritten: result.rowsWritten,
24926
+ totalTables: tables.length,
24927
+ currentTableIndex: i
24928
+ });
24868
24929
  }
24869
24930
  onProgress?.({
24870
24931
  table,
@@ -24874,62 +24935,27 @@ async function syncTransfer(source, target, options, _direction) {
24874
24935
  totalTables: tables.length,
24875
24936
  currentTableIndex: i
24876
24937
  });
24877
- results.push(result);
24878
- continue;
24879
- }
24880
- const missingPks = pkColumns.filter((pk) => !columns.includes(pk));
24881
- if (missingPks.length > 0) {
24882
- result.errors.push(`Table "${table}" missing PK columns in data: ${missingPks.join(", ")} — skipping`);
24883
- results.push(result);
24884
- continue;
24938
+ } catch (err) {
24939
+ result.errors.push(`Table "${table}": ${err?.message ?? String(err)}`);
24885
24940
  }
24886
- onProgress?.({
24887
- table,
24888
- phase: "writing",
24889
- rowsRead: result.rowsRead,
24890
- rowsWritten: 0,
24891
- totalTables: tables.length,
24892
- currentTableIndex: i
24893
- });
24894
- const updateCols = columns.filter((c) => !pkColumns.includes(c));
24895
- for (let offset = 0;offset < rows.length; offset += batchSize) {
24896
- const batch = rows.slice(offset, offset + batchSize);
24897
- try {
24898
- if (isAsyncAdapter(target)) {
24899
- await batchUpsertPg(target, table, columns, updateCols, pkColumns, batch);
24900
- } else {
24901
- batchUpsertSqlite(target, table, columns, updateCols, pkColumns, batch);
24941
+ results.push(result);
24942
+ }
24943
+ } finally {
24944
+ if (sqliteTarget) {
24945
+ try {
24946
+ sqliteTarget.exec("PRAGMA foreign_keys = ON");
24947
+ } catch {}
24948
+ try {
24949
+ const violations = sqliteTarget.all("PRAGMA foreign_key_check");
24950
+ if (violations.length > 0) {
24951
+ const tables2 = [...new Set(violations.map((v) => v.table))];
24952
+ const msg = `FK integrity check: ${violations.length} violation(s) in table(s): ${tables2.join(", ")}`;
24953
+ if (results.length > 0) {
24954
+ results[results.length - 1].errors.push(msg);
24902
24955
  }
24903
- result.rowsWritten += batch.length;
24904
- } catch (err) {
24905
- result.errors.push(`Batch at offset ${offset}: ${err?.message ?? String(err)}`);
24906
24956
  }
24907
- onProgress?.({
24908
- table,
24909
- phase: "writing",
24910
- rowsRead: result.rowsRead,
24911
- rowsWritten: result.rowsWritten,
24912
- totalTables: tables.length,
24913
- currentTableIndex: i
24914
- });
24915
- }
24916
- onProgress?.({
24917
- table,
24918
- phase: "done",
24919
- rowsRead: result.rowsRead,
24920
- rowsWritten: result.rowsWritten,
24921
- totalTables: tables.length,
24922
- currentTableIndex: i
24923
- });
24924
- } catch (err) {
24925
- result.errors.push(`Table "${table}": ${err?.message ?? String(err)}`);
24957
+ } catch {}
24926
24958
  }
24927
- results.push(result);
24928
- }
24929
- if (!isAsyncAdapter(target)) {
24930
- try {
24931
- target.run("PRAGMA foreign_keys = ON");
24932
- } catch {}
24933
24959
  }
24934
24960
  return results;
24935
24961
  }
@@ -1 +1 @@
1
- {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAMnD,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,MAAM,oBAAoB,GAAG,CAAC,QAAQ,EAAE,YAAY,KAAK,IAAI,CAAC;AAEpE,MAAM,WAAW,WAAW;IAC1B,sBAAsB;IACtB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,kCAAkC;IAClC,UAAU,CAAC,EAAE,oBAAoB,CAAC;IAClC,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAMD;;;GAGG;AACH,wBAAsB,QAAQ,CAC5B,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,UAAU,EAAE,CAAC,CAGvB;AAMD;;;GAGG;AACH,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,SAAS,EAChB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,UAAU,EAAE,CAAC,CAGvB;AAslBD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,SAAS,GAAG,MAAM,EAAE,CAKxD;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,EAAE,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAKxE"}
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAMnD,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,MAAM,oBAAoB,GAAG,CAAC,QAAQ,EAAE,YAAY,KAAK,IAAI,CAAC;AAEpE,MAAM,WAAW,WAAW;IAC1B,sBAAsB;IACtB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,kCAAkC;IAClC,UAAU,CAAC,EAAE,oBAAoB,CAAC;IAClC,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAMD;;;GAGG;AACH,wBAAsB,QAAQ,CAC5B,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,UAAU,EAAE,CAAC,CAGvB;AAMD;;;GAGG;AACH,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,SAAS,EAChB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,UAAU,EAAE,CAAC,CAGvB;AAqoBD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,SAAS,GAAG,MAAM,EAAE,CAKxD;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,EAAE,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAKxE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/cloud",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "Shared cloud infrastructure — database adapter (SQLite + PostgreSQL), sync engine, feedback system, unified dotfile config",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",