@hasna/cloud 0.1.3 → 0.1.4

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.
@@ -0,0 +1,57 @@
1
+ import type { DbAdapter } from "./adapter.js";
2
+ export interface AutoSyncConfig {
3
+ auto_sync_on_start: boolean;
4
+ auto_sync_on_stop: boolean;
5
+ }
6
+ export interface AutoSyncContext {
7
+ serviceName: string;
8
+ local: DbAdapter;
9
+ remote: DbAdapter;
10
+ tables: string[];
11
+ config: AutoSyncConfig;
12
+ }
13
+ export interface AutoSyncResult {
14
+ event: "start" | "stop";
15
+ direction: "pull" | "push";
16
+ success: boolean;
17
+ tables_synced: number;
18
+ total_rows_synced: number;
19
+ errors: string[];
20
+ }
21
+ /**
22
+ * Read auto-sync configuration from `~/.hasna/cloud/config.json`.
23
+ * Falls back to defaults if the file does not exist or is malformed.
24
+ */
25
+ export declare function getAutoSyncConfig(): AutoSyncConfig;
26
+ /**
27
+ * Set up auto-sync hooks for a service's MCP server.
28
+ *
29
+ * - On connect: if `auto_sync_on_start` and mode is `hybrid` or `cloud`,
30
+ * pull from cloud to local.
31
+ * - On disconnect/SIGTERM: if `auto_sync_on_stop` and mode is `hybrid` or `cloud`,
32
+ * push from local to cloud.
33
+ *
34
+ * @param serviceName - The service identifier.
35
+ * @param server - The MCP server instance (any object with `onconnect`/`ondisconnect` events).
36
+ * @param local - The local database adapter.
37
+ * @param remote - The remote database adapter.
38
+ * @param tables - Tables to sync.
39
+ * @returns An object with methods to manually trigger start/stop syncs.
40
+ */
41
+ export declare function setupAutoSync(serviceName: string, server: any, local: DbAdapter, remote: DbAdapter, tables: string[]): {
42
+ syncOnStart: () => AutoSyncResult | null;
43
+ syncOnStop: () => AutoSyncResult | null;
44
+ config: AutoSyncConfig;
45
+ };
46
+ /**
47
+ * Enable auto-sync for a service. Simplified entry point that services
48
+ * can call with minimal configuration.
49
+ *
50
+ * @param serviceName - The service name (used for logging context).
51
+ * @param mcpServer - The MCP server instance.
52
+ * @param local - The local database adapter.
53
+ * @param remote - The remote database adapter.
54
+ * @param tables - Tables to sync on start/stop.
55
+ */
56
+ export declare function enableAutoSync(serviceName: string, mcpServer: any, local: DbAdapter, remote: DbAdapter, tables: string[]): void;
57
+ //# sourceMappingURL=auto-sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-sync.d.ts","sourceRoot":"","sources":["../src/auto-sync.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAQ9C,MAAM,WAAW,cAAc;IAC7B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,iBAAiB,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,SAAS,CAAC;IACjB,MAAM,EAAE,SAAS,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAaD;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,cAAc,CAmBlD;AAwFD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAC3B,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,GAAG,EACX,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,MAAM,EAAE,GACf;IACD,WAAW,EAAE,MAAM,cAAc,GAAG,IAAI,CAAC;IACzC,UAAU,EAAE,MAAM,cAAc,GAAG,IAAI,CAAC;IACxC,MAAM,EAAE,cAAc,CAAC;CACxB,CAiDA;AAMD;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAC5B,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,GAAG,EACd,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,MAAM,EAAE,GACf,IAAI,CAEN"}
package/dist/cli/index.js CHANGED
@@ -2191,7 +2191,7 @@ var require_arrayParser = __commonJS((exports, module) => {
2191
2191
  };
2192
2192
  });
2193
2193
 
2194
- // node_modules/postgres-date/index.js
2194
+ // node_modules/pg-types/node_modules/postgres-date/index.js
2195
2195
  var require_postgres_date = __commonJS((exports, module) => {
2196
2196
  var DATE_TIME = /(\d{1,})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})(\.\d{1,})?.*?( BC)?$/;
2197
2197
  var DATE = /^(\d{1,})-(\d{2})-(\d{2})( BC)?$/;
@@ -2293,7 +2293,7 @@ var require_mutable = __commonJS((exports, module) => {
2293
2293
  }
2294
2294
  });
2295
2295
 
2296
- // node_modules/postgres-interval/index.js
2296
+ // node_modules/pg-types/node_modules/postgres-interval/index.js
2297
2297
  var require_postgres_interval = __commonJS((exports, module) => {
2298
2298
  var extend = require_mutable();
2299
2299
  module.exports = PostgresInterval;
@@ -2385,7 +2385,7 @@ var require_postgres_interval = __commonJS((exports, module) => {
2385
2385
  }
2386
2386
  });
2387
2387
 
2388
- // node_modules/postgres-bytea/index.js
2388
+ // node_modules/pg-types/node_modules/postgres-bytea/index.js
2389
2389
  var require_postgres_bytea = __commonJS((exports, module) => {
2390
2390
  var bufferFrom = Buffer.from || Buffer;
2391
2391
  module.exports = function parseBytea(input) {
@@ -11257,17 +11257,88 @@ function createDatabase(options) {
11257
11257
  }
11258
11258
 
11259
11259
  // src/sync.ts
11260
- function syncPush(local, cloud, options) {
11261
- return syncTransfer(local, cloud, options, "push");
11260
+ async function syncPush(local, remote, options) {
11261
+ const orderedTables = await getTableOrder(remote, options.tables);
11262
+ return syncTransfer(local, remote, { ...options, tables: orderedTables }, "push");
11262
11263
  }
11263
- function syncPull(local, cloud, options) {
11264
- return syncTransfer(cloud, local, options, "pull");
11264
+ async function syncPull(remote, local, options) {
11265
+ const orderedTables = await getTableOrder(remote, options.tables);
11266
+ return syncTransfer(remote, local, { ...options, tables: orderedTables }, "pull");
11265
11267
  }
11266
- function syncTransfer(source, target, options, _direction) {
11268
+ async function getTableOrder(remote, tables) {
11269
+ if (tables.length <= 1)
11270
+ return tables;
11271
+ try {
11272
+ const fks = await remote.all(`
11273
+ SELECT DISTINCT
11274
+ tc.table_name AS source_table,
11275
+ ccu.table_name AS referenced_table
11276
+ FROM information_schema.table_constraints tc
11277
+ JOIN information_schema.constraint_column_usage ccu
11278
+ ON tc.constraint_name = ccu.constraint_name
11279
+ AND tc.table_schema = ccu.table_schema
11280
+ WHERE tc.constraint_type = 'FOREIGN KEY'
11281
+ AND tc.table_schema = 'public'
11282
+ `);
11283
+ if (fks.length > 0) {
11284
+ return topoSort(tables, fks);
11285
+ }
11286
+ } catch {}
11287
+ return heuristicOrder(tables);
11288
+ }
11289
+ function topoSort(tables, fks) {
11290
+ const tableSet = new Set(tables);
11291
+ const deps = new Map;
11292
+ for (const t of tables) {
11293
+ deps.set(t, new Set);
11294
+ }
11295
+ for (const fk of fks) {
11296
+ if (tableSet.has(fk.source_table) && tableSet.has(fk.referenced_table)) {
11297
+ deps.get(fk.source_table).add(fk.referenced_table);
11298
+ }
11299
+ }
11300
+ const sorted = [];
11301
+ const visited = new Set;
11302
+ const visiting = new Set;
11303
+ function visit(table) {
11304
+ if (visited.has(table))
11305
+ return;
11306
+ if (visiting.has(table)) {
11307
+ sorted.push(table);
11308
+ visited.add(table);
11309
+ return;
11310
+ }
11311
+ visiting.add(table);
11312
+ const tableDeps = deps.get(table) ?? new Set;
11313
+ for (const dep of tableDeps) {
11314
+ visit(dep);
11315
+ }
11316
+ visiting.delete(table);
11317
+ visited.add(table);
11318
+ sorted.push(table);
11319
+ }
11320
+ for (const t of tables) {
11321
+ visit(t);
11322
+ }
11323
+ return sorted;
11324
+ }
11325
+ function heuristicOrder(tables) {
11326
+ const sorted = [...tables].sort((a, b) => {
11327
+ const aIsChild = a.includes("_") && tables.some((t) => a.startsWith(t + "_") || a.endsWith("_" + t));
11328
+ const bIsChild = b.includes("_") && tables.some((t) => b.startsWith(t + "_") || b.endsWith("_" + t));
11329
+ if (aIsChild && !bIsChild)
11330
+ return 1;
11331
+ if (!aIsChild && bIsChild)
11332
+ return -1;
11333
+ return a.localeCompare(b);
11334
+ });
11335
+ return sorted;
11336
+ }
11337
+ async function syncTransfer(source, target, options, _direction) {
11267
11338
  const {
11268
11339
  tables,
11269
11340
  onProgress,
11270
- batchSize = 500,
11341
+ batchSize = 100,
11271
11342
  conflictColumn = "updated_at",
11272
11343
  primaryKey = "id"
11273
11344
  } = options;
@@ -11290,7 +11361,7 @@ function syncTransfer(source, target, options, _direction) {
11290
11361
  totalTables: tables.length,
11291
11362
  currentTableIndex: i
11292
11363
  });
11293
- const rows = source.all(`SELECT * FROM "${table}"`);
11364
+ const rows = await readAll(source, `SELECT * FROM "${table}"`);
11294
11365
  result.rowsRead = rows.length;
11295
11366
  if (rows.length === 0) {
11296
11367
  onProgress?.({
@@ -11305,7 +11376,6 @@ function syncTransfer(source, target, options, _direction) {
11305
11376
  continue;
11306
11377
  }
11307
11378
  const columns = Object.keys(rows[0]);
11308
- const hasConflictCol = columns.includes(conflictColumn);
11309
11379
  const hasPrimaryKey = columns.includes(primaryKey);
11310
11380
  if (!hasPrimaryKey) {
11311
11381
  result.errors.push(`Table "${table}" has no "${primaryKey}" column — skipping`);
@@ -11320,34 +11390,18 @@ function syncTransfer(source, target, options, _direction) {
11320
11390
  totalTables: tables.length,
11321
11391
  currentTableIndex: i
11322
11392
  });
11393
+ const updateCols = columns.filter((c) => c !== primaryKey);
11323
11394
  for (let offset = 0;offset < rows.length; offset += batchSize) {
11324
11395
  const batch = rows.slice(offset, offset + batchSize);
11325
- for (const row of batch) {
11326
- try {
11327
- const existing = target.get(`SELECT "${primaryKey}"${hasConflictCol ? `, "${conflictColumn}"` : ""} FROM "${table}" WHERE "${primaryKey}" = ?`, row[primaryKey]);
11328
- if (existing) {
11329
- if (hasConflictCol && existing[conflictColumn] && row[conflictColumn]) {
11330
- const existingTime = new Date(existing[conflictColumn]).getTime();
11331
- const incomingTime = new Date(row[conflictColumn]).getTime();
11332
- if (existingTime >= incomingTime) {
11333
- result.rowsSkipped++;
11334
- continue;
11335
- }
11336
- }
11337
- const setClauses = columns.filter((c) => c !== primaryKey).map((c) => `"${c}" = ?`).join(", ");
11338
- const values = columns.filter((c) => c !== primaryKey).map((c) => row[c]);
11339
- values.push(row[primaryKey]);
11340
- target.run(`UPDATE "${table}" SET ${setClauses} WHERE "${primaryKey}" = ?`, ...values);
11341
- } else {
11342
- const placeholders = columns.map(() => "?").join(", ");
11343
- const colList = columns.map((c) => `"${c}"`).join(", ");
11344
- const values = columns.map((c) => row[c]);
11345
- target.run(`INSERT INTO "${table}" (${colList}) VALUES (${placeholders})`, ...values);
11346
- }
11347
- result.rowsWritten++;
11348
- } catch (err) {
11349
- result.errors.push(`Row ${row[primaryKey]}: ${err?.message ?? String(err)}`);
11396
+ try {
11397
+ if (isAsyncAdapter(target)) {
11398
+ await batchUpsertPg(target, table, columns, updateCols, primaryKey, batch);
11399
+ } else {
11400
+ batchUpsertSqlite(target, table, columns, updateCols, primaryKey, batch);
11350
11401
  }
11402
+ result.rowsWritten += batch.length;
11403
+ } catch (err) {
11404
+ result.errors.push(`Batch at offset ${offset}: ${err?.message ?? String(err)}`);
11351
11405
  }
11352
11406
  onProgress?.({
11353
11407
  table,
@@ -11373,10 +11427,46 @@ function syncTransfer(source, target, options, _direction) {
11373
11427
  }
11374
11428
  return results;
11375
11429
  }
11430
+ async function batchUpsertPg(target, table, columns, updateCols, primaryKey, batch) {
11431
+ if (batch.length === 0)
11432
+ return;
11433
+ const colList = columns.map((c) => `"${c}"`).join(", ");
11434
+ const valuePlaceholders = batch.map((_, rowIdx) => {
11435
+ const offset = rowIdx * columns.length;
11436
+ return `(${columns.map((_2, colIdx) => `$${offset + colIdx + 1}`).join(", ")})`;
11437
+ }).join(", ");
11438
+ const setClause = updateCols.length > 0 ? updateCols.map((c) => `"${c}" = EXCLUDED."${c}"`).join(", ") : `"${primaryKey}" = EXCLUDED."${primaryKey}"`;
11439
+ const sql = `INSERT INTO "${table}" (${colList}) VALUES ${valuePlaceholders}
11440
+ ON CONFLICT ("${primaryKey}") DO UPDATE SET ${setClause}`;
11441
+ const params = batch.flatMap((row) => columns.map((c) => row[c] ?? null));
11442
+ await target.run(sql, ...params);
11443
+ }
11444
+ function batchUpsertSqlite(target, table, columns, updateCols, primaryKey, batch) {
11445
+ if (batch.length === 0)
11446
+ return;
11447
+ const colList = columns.map((c) => `"${c}"`).join(", ");
11448
+ const valuePlaceholders = batch.map(() => `(${columns.map(() => "?").join(", ")})`).join(", ");
11449
+ const setClause = updateCols.length > 0 ? updateCols.map((c) => `"${c}" = EXCLUDED."${c}"`).join(", ") : `"${primaryKey}" = EXCLUDED."${primaryKey}"`;
11450
+ const sql = `INSERT INTO "${table}" (${colList}) VALUES ${valuePlaceholders}
11451
+ ON CONFLICT ("${primaryKey}") DO UPDATE SET ${setClause}`;
11452
+ const params = batch.flatMap((row) => columns.map((c) => row[c] ?? null));
11453
+ target.run(sql, ...params);
11454
+ }
11455
+ function isAsyncAdapter(adapter) {
11456
+ return adapter.constructor.name === "PgAdapterAsync" || typeof adapter.raw?.connect === "function";
11457
+ }
11458
+ async function readAll(adapter, sql) {
11459
+ const result = adapter.all(sql);
11460
+ return result instanceof Promise ? await result : result;
11461
+ }
11376
11462
  function listSqliteTables(db) {
11377
11463
  const rows = db.all(`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name`);
11378
11464
  return rows.map((r) => r.name);
11379
11465
  }
11466
+ async function listPgTables(db) {
11467
+ const rows = await db.all(`SELECT tablename FROM pg_tables WHERE schemaname = 'public' ORDER BY tablename`);
11468
+ return rows.map((r) => r.tablename);
11469
+ }
11380
11470
 
11381
11471
  // src/feedback.ts
11382
11472
  import { hostname } from "os";
@@ -11578,10 +11668,8 @@ class SqliteAdapter2 {
11578
11668
  return this.db;
11579
11669
  }
11580
11670
  }
11581
-
11582
- class PgAdapter2 {
11671
+ class PgAdapterAsync {
11583
11672
  pool;
11584
- _client = null;
11585
11673
  constructor(arg) {
11586
11674
  if (typeof arg === "string") {
11587
11675
  this.pool = new esm_default.Pool({ connectionString: arg });
@@ -11589,118 +11677,47 @@ class PgAdapter2 {
11589
11677
  this.pool = arg;
11590
11678
  }
11591
11679
  }
11592
- runSync(fn) {
11593
- let result;
11594
- let error;
11595
- let done = false;
11596
- fn().then((r) => {
11597
- result = r;
11598
- done = true;
11599
- }).catch((e) => {
11600
- error = e;
11601
- done = true;
11602
- });
11603
- const deadline = Date.now() + 30000;
11604
- while (!done && Date.now() < deadline) {
11605
- Bun.sleepSync(1);
11606
- }
11607
- if (error)
11608
- throw error;
11609
- if (!done)
11610
- throw new Error("PgAdapter: query timed out (30s)");
11611
- return result;
11612
- }
11613
- run(sql, ...params) {
11680
+ async run(sql, ...params) {
11614
11681
  const pgSql = translateSql(sql, "pg");
11615
11682
  const pgParams = translateParams(params);
11616
- return this.runSync(async () => {
11617
- const res = await this.pool.query(pgSql, pgParams);
11618
- return {
11619
- changes: res.rowCount ?? 0,
11620
- lastInsertRowid: res.rows?.[0]?.id ?? 0
11621
- };
11622
- });
11683
+ const res = await this.pool.query(pgSql, pgParams);
11684
+ return {
11685
+ changes: res.rowCount ?? 0,
11686
+ lastInsertRowid: res.rows?.[0]?.id ?? 0
11687
+ };
11623
11688
  }
11624
- get(sql, ...params) {
11689
+ async get(sql, ...params) {
11625
11690
  const pgSql = translateSql(sql, "pg");
11626
11691
  const pgParams = translateParams(params);
11627
- return this.runSync(async () => {
11628
- const res = await this.pool.query(pgSql, pgParams);
11629
- return res.rows[0] ?? null;
11630
- });
11692
+ const res = await this.pool.query(pgSql, pgParams);
11693
+ return res.rows[0] ?? null;
11631
11694
  }
11632
- all(sql, ...params) {
11695
+ async all(sql, ...params) {
11633
11696
  const pgSql = translateSql(sql, "pg");
11634
11697
  const pgParams = translateParams(params);
11635
- return this.runSync(async () => {
11636
- const res = await this.pool.query(pgSql, pgParams);
11637
- return res.rows;
11638
- });
11698
+ const res = await this.pool.query(pgSql, pgParams);
11699
+ return res.rows;
11639
11700
  }
11640
- exec(sql) {
11641
- const pgSql = translateSql(sql, "pg");
11642
- this.runSync(async () => {
11643
- await this.pool.query(pgSql);
11644
- });
11645
- }
11646
- prepare(sql) {
11701
+ async exec(sql) {
11647
11702
  const pgSql = translateSql(sql, "pg");
11648
- const adapter = this;
11649
- return {
11650
- run(...params) {
11651
- const pgParams = translateParams(params);
11652
- return adapter.runSync(async () => {
11653
- const res = await adapter.pool.query(pgSql, pgParams);
11654
- return {
11655
- changes: res.rowCount ?? 0,
11656
- lastInsertRowid: res.rows?.[0]?.id ?? 0
11657
- };
11658
- });
11659
- },
11660
- get(...params) {
11661
- const pgParams = translateParams(params);
11662
- return adapter.runSync(async () => {
11663
- const res = await adapter.pool.query(pgSql, pgParams);
11664
- return res.rows[0] ?? null;
11665
- });
11666
- },
11667
- all(...params) {
11668
- const pgParams = translateParams(params);
11669
- return adapter.runSync(async () => {
11670
- const res = await adapter.pool.query(pgSql, pgParams);
11671
- return res.rows;
11672
- });
11673
- },
11674
- finalize() {}
11675
- };
11703
+ await this.pool.query(pgSql);
11676
11704
  }
11677
- close() {
11678
- this.runSync(async () => {
11679
- await this.pool.end();
11680
- });
11705
+ async close() {
11706
+ await this.pool.end();
11681
11707
  }
11682
- transaction(fn) {
11683
- return this.runSync(async () => {
11684
- const client = await this.pool.connect();
11685
- try {
11686
- await client.query("BEGIN");
11687
- const origQuery = this.pool.query.bind(this.pool);
11688
- this.pool.query = client.query.bind(client);
11689
- let result;
11690
- try {
11691
- result = fn();
11692
- } finally {
11693
- this.pool.query = origQuery;
11694
- }
11695
- await client.query("COMMIT");
11696
- return result;
11697
- } catch (err) {
11698
- await client.query("ROLLBACK");
11699
- throw err;
11700
- } finally {
11701
- client.release();
11702
- }
11703
- });
11708
+ async transaction(fn) {
11709
+ const client = await this.pool.connect();
11710
+ try {
11711
+ await client.query("BEGIN");
11712
+ const result = await fn(client);
11713
+ await client.query("COMMIT");
11714
+ return result;
11715
+ } catch (err) {
11716
+ await client.query("ROLLBACK");
11717
+ throw err;
11718
+ } finally {
11719
+ client.release();
11720
+ }
11704
11721
  }
11705
11722
  get raw() {
11706
11723
  return this.pool;
@@ -11742,19 +11759,19 @@ program2.command("status").description("Show current cloud configuration and con
11742
11759
  Checking PostgreSQL connection...`);
11743
11760
  try {
11744
11761
  const connStr = getConnectionString("postgres");
11745
- const pg2 = new PgAdapter2(connStr);
11746
- const row = pg2.get("SELECT 1 as ok");
11762
+ const pg2 = new PgAdapterAsync(connStr);
11763
+ const row = await pg2.get("SELECT 1 as ok");
11747
11764
  if (row?.ok === 1) {
11748
11765
  console.log("PostgreSQL: connected");
11749
11766
  }
11750
- pg2.close();
11767
+ await pg2.close();
11751
11768
  } catch (err) {
11752
11769
  console.log("PostgreSQL: connection failed \u2014", err?.message);
11753
11770
  }
11754
11771
  }
11755
11772
  });
11756
11773
  var syncCmd = program2.command("sync").description("Sync data between local and cloud");
11757
- syncCmd.command("push").description("Push local data to cloud").requiredOption("--service <name>", "Service name").option("--tables <tables>", "Comma-separated table names (default: all)").action((opts) => {
11774
+ syncCmd.command("push").description("Push local data to cloud").requiredOption("--service <name>", "Service name").option("--tables <tables>", "Comma-separated table names (default: all)").action(async (opts) => {
11758
11775
  const config = getCloudConfig();
11759
11776
  if (config.mode === "local") {
11760
11777
  console.error("Error: mode is 'local'. Run `cloud setup --mode hybrid` or `--mode cloud` first.");
@@ -11775,8 +11792,8 @@ syncCmd.command("push").description("Push local data to cloud").requiredOption("
11775
11792
  }
11776
11793
  console.log(`Pushing ${tables.length} table(s) to cloud...`);
11777
11794
  const connStr = getConnectionString(opts.service);
11778
- const cloud = new PgAdapter2(connStr);
11779
- const results = syncPush(local, cloud, {
11795
+ const cloud = new PgAdapterAsync(connStr);
11796
+ const results = await syncPush(local, cloud, {
11780
11797
  tables,
11781
11798
  onProgress: (p) => {
11782
11799
  if (p.phase === "done") {
@@ -11785,7 +11802,7 @@ syncCmd.command("push").description("Push local data to cloud").requiredOption("
11785
11802
  }
11786
11803
  });
11787
11804
  local.close();
11788
- cloud.close();
11805
+ await cloud.close();
11789
11806
  const totalWritten = results.reduce((s, r) => s + r.rowsWritten, 0);
11790
11807
  const totalErrors = results.reduce((s, r) => s + r.errors.length, 0);
11791
11808
  console.log(`
@@ -11798,7 +11815,7 @@ Done. ${totalWritten} rows pushed, ${totalErrors} errors.`);
11798
11815
  }
11799
11816
  }
11800
11817
  });
11801
- syncCmd.command("pull").description("Pull cloud data to local").requiredOption("--service <name>", "Service name").option("--tables <tables>", "Comma-separated table names (default: all)").action((opts) => {
11818
+ syncCmd.command("pull").description("Pull cloud data to local").requiredOption("--service <name>", "Service name").option("--tables <tables>", "Comma-separated table names (default: all)").action(async (opts) => {
11802
11819
  const config = getCloudConfig();
11803
11820
  if (config.mode === "local") {
11804
11821
  console.error("Error: mode is 'local'. Run `cloud setup --mode hybrid` or `--mode cloud` first.");
@@ -11807,18 +11824,17 @@ syncCmd.command("pull").description("Pull cloud data to local").requiredOption("
11807
11824
  const dbPath = getDbPath2(opts.service);
11808
11825
  const local = new SqliteAdapter2(dbPath);
11809
11826
  const connStr = getConnectionString(opts.service);
11810
- const cloud = new PgAdapter2(connStr);
11827
+ const cloud = new PgAdapterAsync(connStr);
11811
11828
  let tables;
11812
11829
  if (opts.tables) {
11813
11830
  tables = opts.tables.split(",").map((t) => t.trim());
11814
11831
  } else {
11815
11832
  try {
11816
- const rows = cloud.all(`SELECT tablename FROM pg_tables WHERE schemaname = 'public' ORDER BY tablename`);
11817
- tables = rows.map((r) => r.tablename);
11833
+ tables = await listPgTables(cloud);
11818
11834
  } catch {
11819
11835
  console.error("Failed to list tables from cloud.");
11820
11836
  local.close();
11821
- cloud.close();
11837
+ await cloud.close();
11822
11838
  process.exit(1);
11823
11839
  return;
11824
11840
  }
@@ -11826,11 +11842,11 @@ syncCmd.command("pull").description("Pull cloud data to local").requiredOption("
11826
11842
  if (tables.length === 0) {
11827
11843
  console.log("No tables found to sync.");
11828
11844
  local.close();
11829
- cloud.close();
11845
+ await cloud.close();
11830
11846
  return;
11831
11847
  }
11832
11848
  console.log(`Pulling ${tables.length} table(s) from cloud...`);
11833
- const results = syncPull(local, cloud, {
11849
+ const results = await syncPull(cloud, local, {
11834
11850
  tables,
11835
11851
  onProgress: (p) => {
11836
11852
  if (p.phase === "done") {
@@ -11839,7 +11855,7 @@ syncCmd.command("pull").description("Pull cloud data to local").requiredOption("
11839
11855
  }
11840
11856
  });
11841
11857
  local.close();
11842
- cloud.close();
11858
+ await cloud.close();
11843
11859
  const totalWritten = results.reduce((s, r) => s + r.rowsWritten, 0);
11844
11860
  const totalErrors = results.reduce((s, r) => s + r.errors.length, 0);
11845
11861
  console.log(`
@@ -1 +1 @@
1
- {"version":3,"file":"cli-helpers.d.ts","sourceRoot":"","sources":["../src/cli-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAWzC;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,MAAM,GAClB,IAAI,CAwHN"}
1
+ {"version":3,"file":"cli-helpers.d.ts","sourceRoot":"","sources":["../src/cli-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAWzC;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,MAAM,GAClB,IAAI,CAqHN"}
package/dist/index.d.ts CHANGED
@@ -4,6 +4,10 @@ export { getCloudConfig, saveCloudConfig, getConnectionString, createDatabase, g
4
4
  export { syncPush, syncPull, listSqliteTables, listPgTables, type SyncOptions, type SyncResult, type SyncProgress, type SyncProgressCallback, } from "./sync.js";
5
5
  export { saveFeedback, sendFeedback, listFeedback, ensureFeedbackTable, type Feedback, } from "./feedback.js";
6
6
  export { migrateDotfile, getDataDir, getDbPath, hasLegacyDotfile, getHasnaDir, } from "./dotfile.js";
7
+ export { SyncProgressTracker, type SyncProgressInfo, type ProgressCallback, type ResumePoint, } from "./sync-progress.js";
8
+ export { detectConflicts, resolveConflicts, getWinningData, ensureConflictsTable, storeConflicts, listConflicts, resolveConflict, getConflict, purgeResolvedConflicts, type SyncConflict, type ConflictStrategy, type StoredConflict, } from "./sync-conflicts.js";
9
+ export { incrementalSyncPush, incrementalSyncPull, ensureSyncMetaTable, getSyncMetaAll, getSyncMetaForTable, resetSyncMeta, resetAllSyncMeta, type IncrementalSyncStats, type IncrementalSyncOptions, type SyncMeta, } from "./sync-incremental.js";
10
+ export { setupAutoSync, enableAutoSync, getAutoSyncConfig, type AutoSyncConfig, type AutoSyncContext, type AutoSyncResult, } from "./auto-sync.js";
7
11
  export { registerCloudTools } from "./mcp-helpers.js";
8
12
  export { registerCloudCommands } from "./cli-helpers.js";
9
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,aAAa,EACb,SAAS,EACT,cAAc,EACd,KAAK,SAAS,EACd,KAAK,iBAAiB,EACtB,KAAK,SAAS,GACf,MAAM,cAAc,CAAC;AAGtB,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,KAAK,OAAO,GACb,MAAM,cAAc,CAAC;AAGtB,OAAO,EACL,cAAc,EACd,eAAe,EACf,mBAAmB,EACnB,cAAc,EACd,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,qBAAqB,GAC3B,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,gBAAgB,EAChB,YAAY,EACZ,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,oBAAoB,GAC1B,MAAM,WAAW,CAAC;AAGnB,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACnB,KAAK,QAAQ,GACd,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,cAAc,EACd,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,WAAW,GACZ,MAAM,cAAc,CAAC;AAGtB,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,aAAa,EACb,SAAS,EACT,cAAc,EACd,KAAK,SAAS,EACd,KAAK,iBAAiB,EACtB,KAAK,SAAS,GACf,MAAM,cAAc,CAAC;AAGtB,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,KAAK,OAAO,GACb,MAAM,cAAc,CAAC;AAGtB,OAAO,EACL,cAAc,EACd,eAAe,EACf,mBAAmB,EACnB,cAAc,EACd,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,qBAAqB,GAC3B,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,gBAAgB,EAChB,YAAY,EACZ,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,oBAAoB,GAC1B,MAAM,WAAW,CAAC;AAGnB,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACnB,KAAK,QAAQ,GACd,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,cAAc,EACd,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,WAAW,GACZ,MAAM,cAAc,CAAC;AAGtB,OAAO,EACL,mBAAmB,EACnB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,WAAW,GACjB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,aAAa,EACb,eAAe,EACf,WAAW,EACX,sBAAsB,EACtB,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,KAAK,cAAc,GACpB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACd,mBAAmB,EACnB,aAAa,EACb,gBAAgB,EAChB,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,QAAQ,GACd,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EACL,aAAa,EACb,cAAc,EACd,iBAAiB,EACjB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,cAAc,GACpB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC"}