@hasna/todos 0.11.6 → 0.11.7

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
@@ -10538,6 +10538,8 @@ import {
10538
10538
  import { homedir as homedir3 } from "os";
10539
10539
  import { join as join9, relative as relative2 } from "path";
10540
10540
  import { hostname } from "os";
10541
+ import { homedir as homedir32 } from "os";
10542
+ import { join as join32 } from "path";
10541
10543
  function __accessProp2(key) {
10542
10544
  return this[key];
10543
10545
  }
@@ -10605,6 +10607,9 @@ class SqliteAdapter {
10605
10607
  exec(sql) {
10606
10608
  this.db.exec(sql);
10607
10609
  }
10610
+ query(sql) {
10611
+ return this.db.query(sql);
10612
+ }
10608
10613
  prepare(sql) {
10609
10614
  const stmt = this.db.prepare(sql);
10610
10615
  return {
@@ -10762,6 +10767,62 @@ class PgAdapter {
10762
10767
  return this.pool;
10763
10768
  }
10764
10769
  }
10770
+
10771
+ class PgAdapterAsync {
10772
+ pool;
10773
+ constructor(arg) {
10774
+ if (typeof arg === "string") {
10775
+ this.pool = new esm_default.Pool({ connectionString: arg });
10776
+ } else {
10777
+ this.pool = arg;
10778
+ }
10779
+ }
10780
+ async run(sql, ...params) {
10781
+ const pgSql = translateSql(sql, "pg");
10782
+ const pgParams = translateParams(params);
10783
+ const res = await this.pool.query(pgSql, pgParams);
10784
+ return {
10785
+ changes: res.rowCount ?? 0,
10786
+ lastInsertRowid: res.rows?.[0]?.id ?? 0
10787
+ };
10788
+ }
10789
+ async get(sql, ...params) {
10790
+ const pgSql = translateSql(sql, "pg");
10791
+ const pgParams = translateParams(params);
10792
+ const res = await this.pool.query(pgSql, pgParams);
10793
+ return res.rows[0] ?? null;
10794
+ }
10795
+ async all(sql, ...params) {
10796
+ const pgSql = translateSql(sql, "pg");
10797
+ const pgParams = translateParams(params);
10798
+ const res = await this.pool.query(pgSql, pgParams);
10799
+ return res.rows;
10800
+ }
10801
+ async exec(sql) {
10802
+ const pgSql = translateSql(sql, "pg");
10803
+ await this.pool.query(pgSql);
10804
+ }
10805
+ async close() {
10806
+ await this.pool.end();
10807
+ }
10808
+ async transaction(fn) {
10809
+ const client = await this.pool.connect();
10810
+ try {
10811
+ await client.query("BEGIN");
10812
+ const result = await fn(client);
10813
+ await client.query("COMMIT");
10814
+ return result;
10815
+ } catch (err) {
10816
+ await client.query("ROLLBACK");
10817
+ throw err;
10818
+ } finally {
10819
+ client.release();
10820
+ }
10821
+ }
10822
+ get raw() {
10823
+ return this.pool;
10824
+ }
10825
+ }
10765
10826
  function setErrorMap2(map) {
10766
10827
  overrideErrorMap2 = map;
10767
10828
  }
@@ -11373,19 +11434,133 @@ function createDatabase(options) {
11373
11434
  const dbPath = options.sqlitePath ?? getDbPath2(options.service);
11374
11435
  return new SqliteAdapter(dbPath);
11375
11436
  }
11376
- function syncPush(local, cloud, options) {
11377
- return syncTransfer(local, cloud, options, "push");
11437
+ async function syncPush(local, remote, options) {
11438
+ const orderedTables = await getTableOrder(remote, options.tables);
11439
+ return syncTransfer(local, remote, { ...options, tables: orderedTables }, "push");
11440
+ }
11441
+ async function syncPull(remote, local, options) {
11442
+ const orderedTables = await getTableOrder(remote, options.tables);
11443
+ return syncTransfer(remote, local, { ...options, tables: orderedTables }, "pull");
11444
+ }
11445
+ async function getTableOrder(remote, tables) {
11446
+ if (tables.length <= 1)
11447
+ return tables;
11448
+ try {
11449
+ const fks = await remote.all(`
11450
+ SELECT DISTINCT
11451
+ tc.table_name AS source_table,
11452
+ ccu.table_name AS referenced_table
11453
+ FROM information_schema.table_constraints tc
11454
+ JOIN information_schema.constraint_column_usage ccu
11455
+ ON tc.constraint_name = ccu.constraint_name
11456
+ AND tc.table_schema = ccu.table_schema
11457
+ WHERE tc.constraint_type = 'FOREIGN KEY'
11458
+ AND tc.table_schema = 'public'
11459
+ `);
11460
+ if (fks.length > 0) {
11461
+ return topoSort(tables, fks);
11462
+ }
11463
+ } catch {}
11464
+ return heuristicOrder(tables);
11465
+ }
11466
+ function topoSort(tables, fks) {
11467
+ const tableSet = new Set(tables);
11468
+ const deps = new Map;
11469
+ for (const t of tables) {
11470
+ deps.set(t, new Set);
11471
+ }
11472
+ for (const fk of fks) {
11473
+ if (tableSet.has(fk.source_table) && tableSet.has(fk.referenced_table)) {
11474
+ deps.get(fk.source_table).add(fk.referenced_table);
11475
+ }
11476
+ }
11477
+ const sorted = [];
11478
+ const visited = new Set;
11479
+ const visiting = new Set;
11480
+ function visit(table) {
11481
+ if (visited.has(table))
11482
+ return;
11483
+ if (visiting.has(table)) {
11484
+ sorted.push(table);
11485
+ visited.add(table);
11486
+ return;
11487
+ }
11488
+ visiting.add(table);
11489
+ const tableDeps = deps.get(table) ?? new Set;
11490
+ for (const dep of tableDeps) {
11491
+ visit(dep);
11492
+ }
11493
+ visiting.delete(table);
11494
+ visited.add(table);
11495
+ sorted.push(table);
11496
+ }
11497
+ for (const t of tables) {
11498
+ visit(t);
11499
+ }
11500
+ return sorted;
11501
+ }
11502
+ function heuristicOrder(tables) {
11503
+ const sorted = [...tables].sort((a, b) => {
11504
+ const aIsChild = a.includes("_") && tables.some((t) => a.startsWith(t + "_") || a.endsWith("_" + t));
11505
+ const bIsChild = b.includes("_") && tables.some((t) => b.startsWith(t + "_") || b.endsWith("_" + t));
11506
+ if (aIsChild && !bIsChild)
11507
+ return 1;
11508
+ if (!aIsChild && bIsChild)
11509
+ return -1;
11510
+ return a.localeCompare(b);
11511
+ });
11512
+ return sorted;
11513
+ }
11514
+ function getSqlitePrimaryKeys(adapter, table) {
11515
+ try {
11516
+ const cols = adapter.all(`PRAGMA table_info("${table}")`);
11517
+ const pkCols = cols.filter((c) => c.pk > 0).sort((a, b) => a.pk - b.pk).map((c) => c.name);
11518
+ return pkCols;
11519
+ } catch {
11520
+ return [];
11521
+ }
11378
11522
  }
11379
- function syncPull(local, cloud, options) {
11380
- return syncTransfer(cloud, local, options, "pull");
11523
+ async function getPgPrimaryKeys(adapter, table) {
11524
+ try {
11525
+ const rows = await adapter.all(`
11526
+ SELECT kcu.column_name, kcu.ordinal_position
11527
+ FROM information_schema.table_constraints tc
11528
+ JOIN information_schema.key_column_usage kcu
11529
+ ON tc.constraint_name = kcu.constraint_name
11530
+ AND tc.table_schema = kcu.table_schema
11531
+ WHERE tc.constraint_type = 'PRIMARY KEY'
11532
+ AND tc.table_schema = 'public'
11533
+ AND tc.table_name = '${table}'
11534
+ ORDER BY kcu.ordinal_position
11535
+ `);
11536
+ return rows.map((r) => r.column_name);
11537
+ } catch {
11538
+ return [];
11539
+ }
11540
+ }
11541
+ async function detectPrimaryKeys(adapter, table) {
11542
+ if (isAsyncAdapter(adapter)) {
11543
+ return getPgPrimaryKeys(adapter, table);
11544
+ }
11545
+ return getSqlitePrimaryKeys(adapter, table);
11546
+ }
11547
+ async function resolvePrimaryKeys(source, target, table, pkOption) {
11548
+ if (pkOption) {
11549
+ return Array.isArray(pkOption) ? pkOption : [pkOption];
11550
+ }
11551
+ let pks = await detectPrimaryKeys(source, table);
11552
+ if (pks.length === 0) {
11553
+ pks = await detectPrimaryKeys(target, table);
11554
+ }
11555
+ return pks;
11381
11556
  }
11382
- function syncTransfer(source, target, options, _direction) {
11557
+ async function syncTransfer(source, target, options, _direction) {
11383
11558
  const {
11384
11559
  tables,
11385
11560
  onProgress,
11386
- batchSize = 500,
11561
+ batchSize = 100,
11387
11562
  conflictColumn = "updated_at",
11388
- primaryKey = "id"
11563
+ primaryKey: pkOption
11389
11564
  } = options;
11390
11565
  const results = [];
11391
11566
  for (let i = 0;i < tables.length; i++) {
@@ -11406,7 +11581,7 @@ function syncTransfer(source, target, options, _direction) {
11406
11581
  totalTables: tables.length,
11407
11582
  currentTableIndex: i
11408
11583
  });
11409
- const rows = source.all(`SELECT * FROM "${table}"`);
11584
+ const rows = await readAll(source, `SELECT * FROM "${table}"`);
11410
11585
  result.rowsRead = rows.length;
11411
11586
  if (rows.length === 0) {
11412
11587
  onProgress?.({
@@ -11420,11 +11595,45 @@ function syncTransfer(source, target, options, _direction) {
11420
11595
  results.push(result);
11421
11596
  continue;
11422
11597
  }
11598
+ const pkColumns = await resolvePrimaryKeys(source, target, table, pkOption);
11423
11599
  const columns = Object.keys(rows[0]);
11424
- const hasConflictCol = columns.includes(conflictColumn);
11425
- const hasPrimaryKey = columns.includes(primaryKey);
11426
- if (!hasPrimaryKey) {
11427
- result.errors.push(`Table "${table}" has no "${primaryKey}" column \u2014 skipping`);
11600
+ if (pkColumns.length === 0) {
11601
+ result.errors.push(`Table "${table}" has no primary key \u2014 inserting without conflict handling`);
11602
+ onProgress?.({
11603
+ table,
11604
+ phase: "writing",
11605
+ rowsRead: result.rowsRead,
11606
+ rowsWritten: 0,
11607
+ totalTables: tables.length,
11608
+ currentTableIndex: i
11609
+ });
11610
+ for (let offset = 0;offset < rows.length; offset += batchSize) {
11611
+ const batch = rows.slice(offset, offset + batchSize);
11612
+ try {
11613
+ if (isAsyncAdapter(target)) {
11614
+ await batchInsertPg(target, table, columns, batch);
11615
+ } else {
11616
+ batchInsertSqlite(target, table, columns, batch);
11617
+ }
11618
+ result.rowsWritten += batch.length;
11619
+ } catch (err) {
11620
+ result.errors.push(`Batch at offset ${offset}: ${err?.message ?? String(err)}`);
11621
+ }
11622
+ }
11623
+ onProgress?.({
11624
+ table,
11625
+ phase: "done",
11626
+ rowsRead: result.rowsRead,
11627
+ rowsWritten: result.rowsWritten,
11628
+ totalTables: tables.length,
11629
+ currentTableIndex: i
11630
+ });
11631
+ results.push(result);
11632
+ continue;
11633
+ }
11634
+ const missingPks = pkColumns.filter((pk) => !columns.includes(pk));
11635
+ if (missingPks.length > 0) {
11636
+ result.errors.push(`Table "${table}" missing PK columns in data: ${missingPks.join(", ")} \u2014 skipping`);
11428
11637
  results.push(result);
11429
11638
  continue;
11430
11639
  }
@@ -11436,34 +11645,18 @@ function syncTransfer(source, target, options, _direction) {
11436
11645
  totalTables: tables.length,
11437
11646
  currentTableIndex: i
11438
11647
  });
11648
+ const updateCols = columns.filter((c) => !pkColumns.includes(c));
11439
11649
  for (let offset = 0;offset < rows.length; offset += batchSize) {
11440
11650
  const batch = rows.slice(offset, offset + batchSize);
11441
- for (const row of batch) {
11442
- try {
11443
- const existing = target.get(`SELECT "${primaryKey}"${hasConflictCol ? `, "${conflictColumn}"` : ""} FROM "${table}" WHERE "${primaryKey}" = ?`, row[primaryKey]);
11444
- if (existing) {
11445
- if (hasConflictCol && existing[conflictColumn] && row[conflictColumn]) {
11446
- const existingTime = new Date(existing[conflictColumn]).getTime();
11447
- const incomingTime = new Date(row[conflictColumn]).getTime();
11448
- if (existingTime >= incomingTime) {
11449
- result.rowsSkipped++;
11450
- continue;
11451
- }
11452
- }
11453
- const setClauses = columns.filter((c) => c !== primaryKey).map((c) => `"${c}" = ?`).join(", ");
11454
- const values = columns.filter((c) => c !== primaryKey).map((c) => row[c]);
11455
- values.push(row[primaryKey]);
11456
- target.run(`UPDATE "${table}" SET ${setClauses} WHERE "${primaryKey}" = ?`, ...values);
11457
- } else {
11458
- const placeholders = columns.map(() => "?").join(", ");
11459
- const colList = columns.map((c) => `"${c}"`).join(", ");
11460
- const values = columns.map((c) => row[c]);
11461
- target.run(`INSERT INTO "${table}" (${colList}) VALUES (${placeholders})`, ...values);
11462
- }
11463
- result.rowsWritten++;
11464
- } catch (err) {
11465
- result.errors.push(`Row ${row[primaryKey]}: ${err?.message ?? String(err)}`);
11651
+ try {
11652
+ if (isAsyncAdapter(target)) {
11653
+ await batchUpsertPg(target, table, columns, updateCols, pkColumns, batch);
11654
+ } else {
11655
+ batchUpsertSqlite(target, table, columns, updateCols, pkColumns, batch);
11466
11656
  }
11657
+ result.rowsWritten += batch.length;
11658
+ } catch (err) {
11659
+ result.errors.push(`Batch at offset ${offset}: ${err?.message ?? String(err)}`);
11467
11660
  }
11468
11661
  onProgress?.({
11469
11662
  table,
@@ -11489,10 +11682,69 @@ function syncTransfer(source, target, options, _direction) {
11489
11682
  }
11490
11683
  return results;
11491
11684
  }
11685
+ async function batchUpsertPg(target, table, columns, updateCols, primaryKeys, batch) {
11686
+ if (batch.length === 0)
11687
+ return;
11688
+ const colList = columns.map((c) => `"${c}"`).join(", ");
11689
+ const valuePlaceholders = batch.map((_, rowIdx) => {
11690
+ const offset = rowIdx * columns.length;
11691
+ return `(${columns.map((_2, colIdx) => `$${offset + colIdx + 1}`).join(", ")})`;
11692
+ }).join(", ");
11693
+ const pkList = primaryKeys.map((c) => `"${c}"`).join(", ");
11694
+ const setClause = updateCols.length > 0 ? updateCols.map((c) => `"${c}" = EXCLUDED."${c}"`).join(", ") : `"${primaryKeys[0]}" = EXCLUDED."${primaryKeys[0]}"`;
11695
+ const sql = `INSERT INTO "${table}" (${colList}) VALUES ${valuePlaceholders}
11696
+ ON CONFLICT (${pkList}) DO UPDATE SET ${setClause}`;
11697
+ const params = batch.flatMap((row) => columns.map((c) => row[c] ?? null));
11698
+ await target.run(sql, ...params);
11699
+ }
11700
+ function batchUpsertSqlite(target, table, columns, updateCols, primaryKeys, batch) {
11701
+ if (batch.length === 0)
11702
+ return;
11703
+ const colList = columns.map((c) => `"${c}"`).join(", ");
11704
+ const valuePlaceholders = batch.map(() => `(${columns.map(() => "?").join(", ")})`).join(", ");
11705
+ const pkList = primaryKeys.map((c) => `"${c}"`).join(", ");
11706
+ const setClause = updateCols.length > 0 ? updateCols.map((c) => `"${c}" = EXCLUDED."${c}"`).join(", ") : `"${primaryKeys[0]}" = EXCLUDED."${primaryKeys[0]}"`;
11707
+ const sql = `INSERT INTO "${table}" (${colList}) VALUES ${valuePlaceholders}
11708
+ ON CONFLICT (${pkList}) DO UPDATE SET ${setClause}`;
11709
+ const params = batch.flatMap((row) => columns.map((c) => row[c] ?? null));
11710
+ target.run(sql, ...params);
11711
+ }
11712
+ async function batchInsertPg(target, table, columns, batch) {
11713
+ if (batch.length === 0)
11714
+ return;
11715
+ const colList = columns.map((c) => `"${c}"`).join(", ");
11716
+ const valuePlaceholders = batch.map((_, rowIdx) => {
11717
+ const offset = rowIdx * columns.length;
11718
+ return `(${columns.map((_2, colIdx) => `$${offset + colIdx + 1}`).join(", ")})`;
11719
+ }).join(", ");
11720
+ const sql = `INSERT INTO "${table}" (${colList}) VALUES ${valuePlaceholders}`;
11721
+ const params = batch.flatMap((row) => columns.map((c) => row[c] ?? null));
11722
+ await target.run(sql, ...params);
11723
+ }
11724
+ function batchInsertSqlite(target, table, columns, batch) {
11725
+ if (batch.length === 0)
11726
+ return;
11727
+ const colList = columns.map((c) => `"${c}"`).join(", ");
11728
+ const valuePlaceholders = batch.map(() => `(${columns.map(() => "?").join(", ")})`).join(", ");
11729
+ const sql = `INSERT INTO "${table}" (${colList}) VALUES ${valuePlaceholders}`;
11730
+ const params = batch.flatMap((row) => columns.map((c) => row[c] ?? null));
11731
+ target.run(sql, ...params);
11732
+ }
11733
+ function isAsyncAdapter(adapter) {
11734
+ return adapter.constructor.name === "PgAdapterAsync" || typeof adapter.raw?.connect === "function";
11735
+ }
11736
+ async function readAll(adapter, sql) {
11737
+ const result = adapter.all(sql);
11738
+ return result instanceof Promise ? await result : result;
11739
+ }
11492
11740
  function listSqliteTables(db) {
11493
11741
  const rows = db.all(`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name`);
11494
11742
  return rows.map((r) => r.name);
11495
11743
  }
11744
+ async function listPgTables(db) {
11745
+ const rows = await db.all(`SELECT tablename FROM pg_tables WHERE schemaname = 'public' ORDER BY tablename`);
11746
+ return rows.map((r) => r.tablename);
11747
+ }
11496
11748
  function ensureFeedbackTable(db) {
11497
11749
  db.exec(FEEDBACK_TABLE_SQL);
11498
11750
  }
@@ -11545,6 +11797,126 @@ async function sendFeedback(feedback, db) {
11545
11797
  return { sent: false, id, error: errorMsg };
11546
11798
  }
11547
11799
  }
11800
+
11801
+ class SyncProgressTracker {
11802
+ db;
11803
+ progress = new Map;
11804
+ startTimes = new Map;
11805
+ callback;
11806
+ constructor(db, callback) {
11807
+ this.db = db;
11808
+ this.callback = callback;
11809
+ this.ensureResumeTable();
11810
+ }
11811
+ ensureResumeTable() {
11812
+ this.db.exec(`
11813
+ CREATE TABLE IF NOT EXISTS _sync_resume (
11814
+ table_name TEXT PRIMARY KEY,
11815
+ last_row_id TEXT,
11816
+ direction TEXT,
11817
+ started_at TEXT,
11818
+ status TEXT DEFAULT 'in_progress'
11819
+ )
11820
+ `);
11821
+ }
11822
+ start(table, total, direction) {
11823
+ const resumed = this.canResume(table);
11824
+ const now2 = Date.now();
11825
+ this.startTimes.set(table, now2);
11826
+ const status = resumed ? "resumed" : "in_progress";
11827
+ const info = {
11828
+ table,
11829
+ total,
11830
+ done: 0,
11831
+ percent: 0,
11832
+ elapsed_ms: 0,
11833
+ eta_ms: 0,
11834
+ status
11835
+ };
11836
+ this.progress.set(table, info);
11837
+ this.db.run(`INSERT INTO _sync_resume (table_name, last_row_id, direction, started_at, status)
11838
+ VALUES (?, ?, ?, datetime('now'), ?)
11839
+ ON CONFLICT (table_name) DO UPDATE SET
11840
+ direction = excluded.direction,
11841
+ started_at = datetime('now'),
11842
+ status = excluded.status`, table, "", direction, status);
11843
+ this.notify(table);
11844
+ }
11845
+ update(table, done, lastRowId) {
11846
+ const info = this.progress.get(table);
11847
+ if (!info)
11848
+ return;
11849
+ const startTime = this.startTimes.get(table) ?? Date.now();
11850
+ const elapsed = Date.now() - startTime;
11851
+ const rate = done > 0 ? elapsed / done : 0;
11852
+ const remaining = info.total - done;
11853
+ const eta = remaining > 0 ? Math.round(rate * remaining) : 0;
11854
+ info.done = done;
11855
+ info.percent = info.total > 0 ? Math.round(done / info.total * 100) : 0;
11856
+ info.elapsed_ms = elapsed;
11857
+ info.eta_ms = eta;
11858
+ info.status = "in_progress";
11859
+ this.db.run(`UPDATE _sync_resume SET last_row_id = ?, status = 'in_progress' WHERE table_name = ?`, lastRowId, table);
11860
+ this.notify(table);
11861
+ }
11862
+ markComplete(table) {
11863
+ const info = this.progress.get(table);
11864
+ if (info) {
11865
+ const startTime = this.startTimes.get(table) ?? Date.now();
11866
+ info.elapsed_ms = Date.now() - startTime;
11867
+ info.done = info.total;
11868
+ info.percent = 100;
11869
+ info.eta_ms = 0;
11870
+ info.status = "completed";
11871
+ this.notify(table);
11872
+ }
11873
+ this.db.run(`UPDATE _sync_resume SET status = 'completed' WHERE table_name = ?`, table);
11874
+ }
11875
+ markFailed(table, _error) {
11876
+ const info = this.progress.get(table);
11877
+ if (info) {
11878
+ const startTime = this.startTimes.get(table) ?? Date.now();
11879
+ info.elapsed_ms = Date.now() - startTime;
11880
+ info.status = "failed";
11881
+ this.notify(table);
11882
+ }
11883
+ this.db.run(`UPDATE _sync_resume SET status = 'failed' WHERE table_name = ?`, table);
11884
+ }
11885
+ canResume(table) {
11886
+ const row = this.db.get(`SELECT status FROM _sync_resume WHERE table_name = ?`, table);
11887
+ if (!row)
11888
+ return false;
11889
+ return row.status === "in_progress" || row.status === "resumed";
11890
+ }
11891
+ getResumePoint(table) {
11892
+ const row = this.db.get(`SELECT table_name, last_row_id, direction, started_at, status FROM _sync_resume WHERE table_name = ?`, table);
11893
+ if (!row)
11894
+ return null;
11895
+ if (row.status !== "in_progress" && row.status !== "resumed")
11896
+ return null;
11897
+ return row;
11898
+ }
11899
+ clearResume(table) {
11900
+ this.db.run(`DELETE FROM _sync_resume WHERE table_name = ?`, table);
11901
+ this.progress.delete(table);
11902
+ this.startTimes.delete(table);
11903
+ }
11904
+ getProgress(table) {
11905
+ return this.progress.get(table) ?? null;
11906
+ }
11907
+ getAllProgress() {
11908
+ return Array.from(this.progress.values());
11909
+ }
11910
+ listResumeRecords() {
11911
+ return this.db.all(`SELECT table_name, last_row_id, direction, started_at, status FROM _sync_resume ORDER BY started_at DESC`);
11912
+ }
11913
+ notify(table) {
11914
+ const info = this.progress.get(table);
11915
+ if (info && this.callback) {
11916
+ this.callback({ ...info });
11917
+ }
11918
+ }
11919
+ }
11548
11920
  function registerCloudTools(server, serviceName) {
11549
11921
  server.tool(`${serviceName}_cloud_status`, "Show cloud configuration and connection health", {}, async () => {
11550
11922
  const config = getCloudConfig();
@@ -11555,10 +11927,10 @@ function registerCloudTools(server, serviceName) {
11555
11927
  ];
11556
11928
  if (config.rds.host && config.rds.username) {
11557
11929
  try {
11558
- const pg2 = new PgAdapter(getConnectionString("postgres"));
11559
- pg2.get("SELECT 1 as ok");
11930
+ const pg2 = new PgAdapterAsync(getConnectionString("postgres"));
11931
+ await pg2.get("SELECT 1 as ok");
11560
11932
  lines.push("PostgreSQL: connected");
11561
- pg2.close();
11933
+ await pg2.close();
11562
11934
  } catch (err) {
11563
11935
  lines.push(`PostgreSQL: failed \u2014 ${err?.message}`);
11564
11936
  }
@@ -11579,11 +11951,11 @@ function registerCloudTools(server, serviceName) {
11579
11951
  };
11580
11952
  }
11581
11953
  const local = new SqliteAdapter(getDbPath2(serviceName));
11582
- const cloud = new PgAdapter(getConnectionString(serviceName));
11954
+ const cloud = new PgAdapterAsync(getConnectionString(serviceName));
11583
11955
  const tableList = tablesStr ? tablesStr.split(",").map((t) => t.trim()) : listSqliteTables(local);
11584
- const results = syncPush(local, cloud, { tables: tableList });
11956
+ const results = await syncPush(local, cloud, { tables: tableList });
11585
11957
  local.close();
11586
- cloud.close();
11958
+ await cloud.close();
11587
11959
  const total = results.reduce((s, r) => s + r.rowsWritten, 0);
11588
11960
  return {
11589
11961
  content: [{ type: "text", text: `Pushed ${total} rows across ${tableList.length} table(s).` }]
@@ -11602,17 +11974,16 @@ function registerCloudTools(server, serviceName) {
11602
11974
  };
11603
11975
  }
11604
11976
  const local = new SqliteAdapter(getDbPath2(serviceName));
11605
- const cloud = new PgAdapter(getConnectionString(serviceName));
11977
+ const cloud = new PgAdapterAsync(getConnectionString(serviceName));
11606
11978
  let tableList;
11607
11979
  if (tablesStr) {
11608
11980
  tableList = tablesStr.split(",").map((t) => t.trim());
11609
11981
  } else {
11610
11982
  try {
11611
- const rows = cloud.all(`SELECT tablename FROM pg_tables WHERE schemaname = 'public'`);
11612
- tableList = rows.map((r) => r.tablename);
11983
+ tableList = await listPgTables(cloud);
11613
11984
  } catch {
11614
11985
  local.close();
11615
- cloud.close();
11986
+ await cloud.close();
11616
11987
  return {
11617
11988
  content: [
11618
11989
  { type: "text", text: "Error: failed to list cloud tables." }
@@ -11621,9 +11992,9 @@ function registerCloudTools(server, serviceName) {
11621
11992
  };
11622
11993
  }
11623
11994
  }
11624
- const results = syncPull(local, cloud, { tables: tableList });
11995
+ const results = await syncPull(cloud, local, { tables: tableList });
11625
11996
  local.close();
11626
- cloud.close();
11997
+ await cloud.close();
11627
11998
  const total = results.reduce((s, r) => s + r.rowsWritten, 0);
11628
11999
  return {
11629
12000
  content: [{ type: "text", text: `Pulled ${total} rows across ${tableList.length} table(s).` }]
@@ -11898,7 +12269,7 @@ CREATE TABLE IF NOT EXISTS feedback (
11898
12269
  email TEXT DEFAULT '',
11899
12270
  machine_id TEXT DEFAULT '',
11900
12271
  created_at TEXT DEFAULT (datetime('now'))
11901
- )`;
12272
+ )`, AUTO_SYNC_CONFIG_PATH;
11902
12273
  var init_dist = __esm(() => {
11903
12274
  __create2 = Object.create;
11904
12275
  __getProtoOf2 = Object.getPrototypeOf;
@@ -19841,6 +20212,7 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
19841
20212
  });
19842
20213
  CONFIG_DIR2 = join22(homedir22(), ".hasna", "cloud");
19843
20214
  CONFIG_PATH2 = join22(CONFIG_DIR2, "config.json");
20215
+ AUTO_SYNC_CONFIG_PATH = join32(homedir32(), ".hasna", "cloud", "config.json");
19844
20216
  });
19845
20217
 
19846
20218
  // src/db/traces.ts