@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.
package/dist/mcp/index.js CHANGED
@@ -6629,7 +6629,7 @@ var require_arrayParser = __commonJS((exports, module) => {
6629
6629
  };
6630
6630
  });
6631
6631
 
6632
- // node_modules/postgres-date/index.js
6632
+ // node_modules/pg-types/node_modules/postgres-date/index.js
6633
6633
  var require_postgres_date = __commonJS((exports, module) => {
6634
6634
  var DATE_TIME = /(\d{1,})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})(\.\d{1,})?.*?( BC)?$/;
6635
6635
  var DATE = /^(\d{1,})-(\d{2})-(\d{2})( BC)?$/;
@@ -6731,7 +6731,7 @@ var require_mutable = __commonJS((exports, module) => {
6731
6731
  }
6732
6732
  });
6733
6733
 
6734
- // node_modules/postgres-interval/index.js
6734
+ // node_modules/pg-types/node_modules/postgres-interval/index.js
6735
6735
  var require_postgres_interval = __commonJS((exports, module) => {
6736
6736
  var extend2 = require_mutable();
6737
6737
  module.exports = PostgresInterval;
@@ -6823,7 +6823,7 @@ var require_postgres_interval = __commonJS((exports, module) => {
6823
6823
  }
6824
6824
  });
6825
6825
 
6826
- // node_modules/postgres-bytea/index.js
6826
+ // node_modules/pg-types/node_modules/postgres-bytea/index.js
6827
6827
  var require_postgres_bytea = __commonJS((exports, module) => {
6828
6828
  var bufferFrom = Buffer.from || Buffer;
6829
6829
  module.exports = function parseBytea(input) {
@@ -24656,17 +24656,88 @@ function createDatabase(options) {
24656
24656
  }
24657
24657
 
24658
24658
  // src/sync.ts
24659
- function syncPush(local, cloud, options) {
24660
- return syncTransfer(local, cloud, options, "push");
24659
+ async function syncPush(local, remote, options) {
24660
+ const orderedTables = await getTableOrder(remote, options.tables);
24661
+ return syncTransfer(local, remote, { ...options, tables: orderedTables }, "push");
24661
24662
  }
24662
- function syncPull(local, cloud, options) {
24663
- return syncTransfer(cloud, local, options, "pull");
24663
+ async function syncPull(remote, local, options) {
24664
+ const orderedTables = await getTableOrder(remote, options.tables);
24665
+ return syncTransfer(remote, local, { ...options, tables: orderedTables }, "pull");
24664
24666
  }
24665
- function syncTransfer(source, target, options, _direction) {
24667
+ async function getTableOrder(remote, tables) {
24668
+ if (tables.length <= 1)
24669
+ return tables;
24670
+ try {
24671
+ const fks = await remote.all(`
24672
+ SELECT DISTINCT
24673
+ tc.table_name AS source_table,
24674
+ ccu.table_name AS referenced_table
24675
+ FROM information_schema.table_constraints tc
24676
+ JOIN information_schema.constraint_column_usage ccu
24677
+ ON tc.constraint_name = ccu.constraint_name
24678
+ AND tc.table_schema = ccu.table_schema
24679
+ WHERE tc.constraint_type = 'FOREIGN KEY'
24680
+ AND tc.table_schema = 'public'
24681
+ `);
24682
+ if (fks.length > 0) {
24683
+ return topoSort(tables, fks);
24684
+ }
24685
+ } catch {}
24686
+ return heuristicOrder(tables);
24687
+ }
24688
+ function topoSort(tables, fks) {
24689
+ const tableSet = new Set(tables);
24690
+ const deps = new Map;
24691
+ for (const t of tables) {
24692
+ deps.set(t, new Set);
24693
+ }
24694
+ for (const fk of fks) {
24695
+ if (tableSet.has(fk.source_table) && tableSet.has(fk.referenced_table)) {
24696
+ deps.get(fk.source_table).add(fk.referenced_table);
24697
+ }
24698
+ }
24699
+ const sorted = [];
24700
+ const visited = new Set;
24701
+ const visiting = new Set;
24702
+ function visit(table) {
24703
+ if (visited.has(table))
24704
+ return;
24705
+ if (visiting.has(table)) {
24706
+ sorted.push(table);
24707
+ visited.add(table);
24708
+ return;
24709
+ }
24710
+ visiting.add(table);
24711
+ const tableDeps = deps.get(table) ?? new Set;
24712
+ for (const dep of tableDeps) {
24713
+ visit(dep);
24714
+ }
24715
+ visiting.delete(table);
24716
+ visited.add(table);
24717
+ sorted.push(table);
24718
+ }
24719
+ for (const t of tables) {
24720
+ visit(t);
24721
+ }
24722
+ return sorted;
24723
+ }
24724
+ function heuristicOrder(tables) {
24725
+ const sorted = [...tables].sort((a, b) => {
24726
+ const aIsChild = a.includes("_") && tables.some((t) => a.startsWith(t + "_") || a.endsWith("_" + t));
24727
+ const bIsChild = b.includes("_") && tables.some((t) => b.startsWith(t + "_") || b.endsWith("_" + t));
24728
+ if (aIsChild && !bIsChild)
24729
+ return 1;
24730
+ if (!aIsChild && bIsChild)
24731
+ return -1;
24732
+ return a.localeCompare(b);
24733
+ });
24734
+ return sorted;
24735
+ }
24736
+ async function syncTransfer(source, target, options, _direction) {
24666
24737
  const {
24667
24738
  tables,
24668
24739
  onProgress,
24669
- batchSize = 500,
24740
+ batchSize = 100,
24670
24741
  conflictColumn = "updated_at",
24671
24742
  primaryKey = "id"
24672
24743
  } = options;
@@ -24689,7 +24760,7 @@ function syncTransfer(source, target, options, _direction) {
24689
24760
  totalTables: tables.length,
24690
24761
  currentTableIndex: i
24691
24762
  });
24692
- const rows = source.all(`SELECT * FROM "${table}"`);
24763
+ const rows = await readAll(source, `SELECT * FROM "${table}"`);
24693
24764
  result.rowsRead = rows.length;
24694
24765
  if (rows.length === 0) {
24695
24766
  onProgress?.({
@@ -24704,7 +24775,6 @@ function syncTransfer(source, target, options, _direction) {
24704
24775
  continue;
24705
24776
  }
24706
24777
  const columns = Object.keys(rows[0]);
24707
- const hasConflictCol = columns.includes(conflictColumn);
24708
24778
  const hasPrimaryKey = columns.includes(primaryKey);
24709
24779
  if (!hasPrimaryKey) {
24710
24780
  result.errors.push(`Table "${table}" has no "${primaryKey}" column — skipping`);
@@ -24719,34 +24789,18 @@ function syncTransfer(source, target, options, _direction) {
24719
24789
  totalTables: tables.length,
24720
24790
  currentTableIndex: i
24721
24791
  });
24792
+ const updateCols = columns.filter((c) => c !== primaryKey);
24722
24793
  for (let offset = 0;offset < rows.length; offset += batchSize) {
24723
24794
  const batch = rows.slice(offset, offset + batchSize);
24724
- for (const row of batch) {
24725
- try {
24726
- const existing = target.get(`SELECT "${primaryKey}"${hasConflictCol ? `, "${conflictColumn}"` : ""} FROM "${table}" WHERE "${primaryKey}" = ?`, row[primaryKey]);
24727
- if (existing) {
24728
- if (hasConflictCol && existing[conflictColumn] && row[conflictColumn]) {
24729
- const existingTime = new Date(existing[conflictColumn]).getTime();
24730
- const incomingTime = new Date(row[conflictColumn]).getTime();
24731
- if (existingTime >= incomingTime) {
24732
- result.rowsSkipped++;
24733
- continue;
24734
- }
24735
- }
24736
- const setClauses = columns.filter((c) => c !== primaryKey).map((c) => `"${c}" = ?`).join(", ");
24737
- const values = columns.filter((c) => c !== primaryKey).map((c) => row[c]);
24738
- values.push(row[primaryKey]);
24739
- target.run(`UPDATE "${table}" SET ${setClauses} WHERE "${primaryKey}" = ?`, ...values);
24740
- } else {
24741
- const placeholders = columns.map(() => "?").join(", ");
24742
- const colList = columns.map((c) => `"${c}"`).join(", ");
24743
- const values = columns.map((c) => row[c]);
24744
- target.run(`INSERT INTO "${table}" (${colList}) VALUES (${placeholders})`, ...values);
24745
- }
24746
- result.rowsWritten++;
24747
- } catch (err) {
24748
- result.errors.push(`Row ${row[primaryKey]}: ${err?.message ?? String(err)}`);
24795
+ try {
24796
+ if (isAsyncAdapter(target)) {
24797
+ await batchUpsertPg(target, table, columns, updateCols, primaryKey, batch);
24798
+ } else {
24799
+ batchUpsertSqlite(target, table, columns, updateCols, primaryKey, batch);
24749
24800
  }
24801
+ result.rowsWritten += batch.length;
24802
+ } catch (err) {
24803
+ result.errors.push(`Batch at offset ${offset}: ${err?.message ?? String(err)}`);
24750
24804
  }
24751
24805
  onProgress?.({
24752
24806
  table,
@@ -24772,10 +24826,46 @@ function syncTransfer(source, target, options, _direction) {
24772
24826
  }
24773
24827
  return results;
24774
24828
  }
24829
+ async function batchUpsertPg(target, table, columns, updateCols, primaryKey, batch) {
24830
+ if (batch.length === 0)
24831
+ return;
24832
+ const colList = columns.map((c) => `"${c}"`).join(", ");
24833
+ const valuePlaceholders = batch.map((_, rowIdx) => {
24834
+ const offset = rowIdx * columns.length;
24835
+ return `(${columns.map((_2, colIdx) => `$${offset + colIdx + 1}`).join(", ")})`;
24836
+ }).join(", ");
24837
+ const setClause = updateCols.length > 0 ? updateCols.map((c) => `"${c}" = EXCLUDED."${c}"`).join(", ") : `"${primaryKey}" = EXCLUDED."${primaryKey}"`;
24838
+ const sql = `INSERT INTO "${table}" (${colList}) VALUES ${valuePlaceholders}
24839
+ ON CONFLICT ("${primaryKey}") DO UPDATE SET ${setClause}`;
24840
+ const params = batch.flatMap((row) => columns.map((c) => row[c] ?? null));
24841
+ await target.run(sql, ...params);
24842
+ }
24843
+ function batchUpsertSqlite(target, table, columns, updateCols, primaryKey, batch) {
24844
+ if (batch.length === 0)
24845
+ return;
24846
+ const colList = columns.map((c) => `"${c}"`).join(", ");
24847
+ const valuePlaceholders = batch.map(() => `(${columns.map(() => "?").join(", ")})`).join(", ");
24848
+ const setClause = updateCols.length > 0 ? updateCols.map((c) => `"${c}" = EXCLUDED."${c}"`).join(", ") : `"${primaryKey}" = EXCLUDED."${primaryKey}"`;
24849
+ const sql = `INSERT INTO "${table}" (${colList}) VALUES ${valuePlaceholders}
24850
+ ON CONFLICT ("${primaryKey}") DO UPDATE SET ${setClause}`;
24851
+ const params = batch.flatMap((row) => columns.map((c) => row[c] ?? null));
24852
+ target.run(sql, ...params);
24853
+ }
24854
+ function isAsyncAdapter(adapter) {
24855
+ return adapter.constructor.name === "PgAdapterAsync" || typeof adapter.raw?.connect === "function";
24856
+ }
24857
+ async function readAll(adapter, sql) {
24858
+ const result = adapter.all(sql);
24859
+ return result instanceof Promise ? await result : result;
24860
+ }
24775
24861
  function listSqliteTables(db) {
24776
24862
  const rows = db.all(`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name`);
24777
24863
  return rows.map((r) => r.name);
24778
24864
  }
24865
+ async function listPgTables(db) {
24866
+ const rows = await db.all(`SELECT tablename FROM pg_tables WHERE schemaname = 'public' ORDER BY tablename`);
24867
+ return rows.map((r) => r.tablename);
24868
+ }
24779
24869
 
24780
24870
  // src/feedback.ts
24781
24871
  import { hostname as hostname2 } from "os";
@@ -24951,10 +25041,8 @@ class SqliteAdapter2 {
24951
25041
  return this.db;
24952
25042
  }
24953
25043
  }
24954
-
24955
- class PgAdapter2 {
25044
+ class PgAdapterAsync {
24956
25045
  pool;
24957
- _client = null;
24958
25046
  constructor(arg) {
24959
25047
  if (typeof arg === "string") {
24960
25048
  this.pool = new esm_default.Pool({ connectionString: arg });
@@ -24962,118 +25050,47 @@ class PgAdapter2 {
24962
25050
  this.pool = arg;
24963
25051
  }
24964
25052
  }
24965
- runSync(fn) {
24966
- let result;
24967
- let error2;
24968
- let done = false;
24969
- fn().then((r) => {
24970
- result = r;
24971
- done = true;
24972
- }).catch((e) => {
24973
- error2 = e;
24974
- done = true;
24975
- });
24976
- const deadline = Date.now() + 30000;
24977
- while (!done && Date.now() < deadline) {
24978
- Bun.sleepSync(1);
24979
- }
24980
- if (error2)
24981
- throw error2;
24982
- if (!done)
24983
- throw new Error("PgAdapter: query timed out (30s)");
24984
- return result;
24985
- }
24986
- run(sql, ...params) {
25053
+ async run(sql, ...params) {
24987
25054
  const pgSql = translateSql(sql, "pg");
24988
25055
  const pgParams = translateParams(params);
24989
- return this.runSync(async () => {
24990
- const res = await this.pool.query(pgSql, pgParams);
24991
- return {
24992
- changes: res.rowCount ?? 0,
24993
- lastInsertRowid: res.rows?.[0]?.id ?? 0
24994
- };
24995
- });
25056
+ const res = await this.pool.query(pgSql, pgParams);
25057
+ return {
25058
+ changes: res.rowCount ?? 0,
25059
+ lastInsertRowid: res.rows?.[0]?.id ?? 0
25060
+ };
24996
25061
  }
24997
- get(sql, ...params) {
25062
+ async get(sql, ...params) {
24998
25063
  const pgSql = translateSql(sql, "pg");
24999
25064
  const pgParams = translateParams(params);
25000
- return this.runSync(async () => {
25001
- const res = await this.pool.query(pgSql, pgParams);
25002
- return res.rows[0] ?? null;
25003
- });
25065
+ const res = await this.pool.query(pgSql, pgParams);
25066
+ return res.rows[0] ?? null;
25004
25067
  }
25005
- all(sql, ...params) {
25068
+ async all(sql, ...params) {
25006
25069
  const pgSql = translateSql(sql, "pg");
25007
25070
  const pgParams = translateParams(params);
25008
- return this.runSync(async () => {
25009
- const res = await this.pool.query(pgSql, pgParams);
25010
- return res.rows;
25011
- });
25071
+ const res = await this.pool.query(pgSql, pgParams);
25072
+ return res.rows;
25012
25073
  }
25013
- exec(sql) {
25074
+ async exec(sql) {
25014
25075
  const pgSql = translateSql(sql, "pg");
25015
- this.runSync(async () => {
25016
- await this.pool.query(pgSql);
25017
- });
25076
+ await this.pool.query(pgSql);
25018
25077
  }
25019
- prepare(sql) {
25020
- const pgSql = translateSql(sql, "pg");
25021
- const adapter = this;
25022
- return {
25023
- run(...params) {
25024
- const pgParams = translateParams(params);
25025
- return adapter.runSync(async () => {
25026
- const res = await adapter.pool.query(pgSql, pgParams);
25027
- return {
25028
- changes: res.rowCount ?? 0,
25029
- lastInsertRowid: res.rows?.[0]?.id ?? 0
25030
- };
25031
- });
25032
- },
25033
- get(...params) {
25034
- const pgParams = translateParams(params);
25035
- return adapter.runSync(async () => {
25036
- const res = await adapter.pool.query(pgSql, pgParams);
25037
- return res.rows[0] ?? null;
25038
- });
25039
- },
25040
- all(...params) {
25041
- const pgParams = translateParams(params);
25042
- return adapter.runSync(async () => {
25043
- const res = await adapter.pool.query(pgSql, pgParams);
25044
- return res.rows;
25045
- });
25046
- },
25047
- finalize() {}
25048
- };
25049
- }
25050
- close() {
25051
- this.runSync(async () => {
25052
- await this.pool.end();
25053
- });
25078
+ async close() {
25079
+ await this.pool.end();
25054
25080
  }
25055
- transaction(fn) {
25056
- return this.runSync(async () => {
25057
- const client = await this.pool.connect();
25058
- try {
25059
- await client.query("BEGIN");
25060
- const origQuery = this.pool.query.bind(this.pool);
25061
- this.pool.query = client.query.bind(client);
25062
- let result;
25063
- try {
25064
- result = fn();
25065
- } finally {
25066
- this.pool.query = origQuery;
25067
- }
25068
- await client.query("COMMIT");
25069
- return result;
25070
- } catch (err) {
25071
- await client.query("ROLLBACK");
25072
- throw err;
25073
- } finally {
25074
- client.release();
25075
- }
25076
- });
25081
+ async transaction(fn) {
25082
+ const client = await this.pool.connect();
25083
+ try {
25084
+ await client.query("BEGIN");
25085
+ const result = await fn(client);
25086
+ await client.query("COMMIT");
25087
+ return result;
25088
+ } catch (err) {
25089
+ await client.query("ROLLBACK");
25090
+ throw err;
25091
+ } finally {
25092
+ client.release();
25093
+ }
25077
25094
  }
25078
25095
  get raw() {
25079
25096
  return this.pool;
@@ -25098,10 +25115,10 @@ server.tool("cloud_status", "Show cloud configuration and connection health", {}
25098
25115
  if (config2.rds.host && config2.rds.username) {
25099
25116
  try {
25100
25117
  const connStr = getConnectionString("postgres");
25101
- const pg2 = new PgAdapter2(connStr);
25102
- pg2.get("SELECT 1 as ok");
25118
+ const pg2 = new PgAdapterAsync(connStr);
25119
+ await pg2.get("SELECT 1 as ok");
25103
25120
  lines.push("PostgreSQL: connected");
25104
- pg2.close();
25121
+ await pg2.close();
25105
25122
  } catch (err) {
25106
25123
  lines.push(`PostgreSQL: connection failed \u2014 ${err?.message}`);
25107
25124
  }
@@ -25128,16 +25145,16 @@ server.tool("sync_push", "Push local SQLite data to cloud PostgreSQL", {
25128
25145
  const dbPath = getDbPath2(service);
25129
25146
  const local = new SqliteAdapter2(dbPath);
25130
25147
  const connStr = getConnectionString(service);
25131
- const cloud = new PgAdapter2(connStr);
25148
+ const cloud = new PgAdapterAsync(connStr);
25132
25149
  let tableList;
25133
25150
  if (tablesStr) {
25134
25151
  tableList = tablesStr.split(",").map((t) => t.trim());
25135
25152
  } else {
25136
25153
  tableList = listSqliteTables(local);
25137
25154
  }
25138
- const results = syncPush(local, cloud, { tables: tableList });
25155
+ const results = await syncPush(local, cloud, { tables: tableList });
25139
25156
  local.close();
25140
- cloud.close();
25157
+ await cloud.close();
25141
25158
  const totalWritten = results.reduce((s, r) => s + r.rowsWritten, 0);
25142
25159
  const totalErrors = results.reduce((s, r) => s + r.errors.length, 0);
25143
25160
  const lines = [
@@ -25171,17 +25188,16 @@ server.tool("sync_pull", "Pull cloud PostgreSQL data to local SQLite", {
25171
25188
  const dbPath = getDbPath2(service);
25172
25189
  const local = new SqliteAdapter2(dbPath);
25173
25190
  const connStr = getConnectionString(service);
25174
- const cloud = new PgAdapter2(connStr);
25191
+ const cloud = new PgAdapterAsync(connStr);
25175
25192
  let tableList;
25176
25193
  if (tablesStr) {
25177
25194
  tableList = tablesStr.split(",").map((t) => t.trim());
25178
25195
  } else {
25179
25196
  try {
25180
- const rows = cloud.all(`SELECT tablename FROM pg_tables WHERE schemaname = 'public' ORDER BY tablename`);
25181
- tableList = rows.map((r) => r.tablename);
25197
+ tableList = await listPgTables(cloud);
25182
25198
  } catch {
25183
25199
  local.close();
25184
- cloud.close();
25200
+ await cloud.close();
25185
25201
  return {
25186
25202
  content: [
25187
25203
  { type: "text", text: "Error: failed to list tables from cloud." }
@@ -25190,9 +25206,9 @@ server.tool("sync_pull", "Pull cloud PostgreSQL data to local SQLite", {
25190
25206
  };
25191
25207
  }
25192
25208
  }
25193
- const results = syncPull(local, cloud, { tables: tableList });
25209
+ const results = await syncPull(cloud, local, { tables: tableList });
25194
25210
  local.close();
25195
- cloud.close();
25211
+ await cloud.close();
25196
25212
  const totalWritten = results.reduce((s, r) => s + r.rowsWritten, 0);
25197
25213
  const totalErrors = results.reduce((s, r) => s + r.errors.length, 0);
25198
25214
  const lines = [
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-helpers.d.ts","sourceRoot":"","sources":["../src/mcp-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAYzE;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,MAAM,GAClB,IAAI,CAuJN"}
1
+ {"version":3,"file":"mcp-helpers.d.ts","sourceRoot":"","sources":["../src/mcp-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAYzE;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,MAAM,GAClB,IAAI,CAoJN"}
@@ -0,0 +1,76 @@
1
+ import type { DbAdapter } from "./adapter.js";
2
+ export interface SyncConflict {
3
+ table: string;
4
+ row_id: string;
5
+ local_updated_at: string;
6
+ remote_updated_at: string;
7
+ local_data: Record<string, any>;
8
+ remote_data: Record<string, any>;
9
+ resolved: boolean;
10
+ resolution?: "local-wins" | "remote-wins" | "newest-wins" | "manual";
11
+ }
12
+ export type ConflictStrategy = "local-wins" | "remote-wins" | "newest-wins";
13
+ export interface StoredConflict {
14
+ id: string;
15
+ table_name: string;
16
+ row_id: string;
17
+ local_data: string;
18
+ remote_data: string;
19
+ local_updated_at: string;
20
+ remote_updated_at: string;
21
+ resolution: string | null;
22
+ resolved_at: string | null;
23
+ created_at: string;
24
+ }
25
+ /**
26
+ * Find rows that exist in BOTH local and remote datasets with DIFFERENT
27
+ * `updated_at` values. Returns a list of SyncConflict objects.
28
+ *
29
+ * @param local - Array of rows from the local database
30
+ * @param remote - Array of rows from the remote database
31
+ * @param table - The table name these rows belong to
32
+ * @param primaryKey - Column used as primary key (default: "id")
33
+ * @param conflictColumn - Column used for timestamp comparison (default: "updated_at")
34
+ */
35
+ export declare function detectConflicts(local: Record<string, any>[], remote: Record<string, any>[], table: string, primaryKey?: string, conflictColumn?: string): SyncConflict[];
36
+ /**
37
+ * Resolve a list of conflicts using the given strategy.
38
+ * Returns the winning row data for each conflict.
39
+ *
40
+ * @param conflicts - The conflicts to resolve
41
+ * @param strategy - Resolution strategy (default: "newest-wins")
42
+ * @returns Array of resolved conflicts with `resolved: true` and `resolution` set
43
+ */
44
+ export declare function resolveConflicts(conflicts: SyncConflict[], strategy?: ConflictStrategy): SyncConflict[];
45
+ /**
46
+ * Get the winning data for a resolved conflict.
47
+ */
48
+ export declare function getWinningData(conflict: SyncConflict): Record<string, any>;
49
+ /**
50
+ * Ensure the _sync_conflicts table exists.
51
+ */
52
+ export declare function ensureConflictsTable(db: DbAdapter): void;
53
+ /**
54
+ * Store unresolved conflicts in the database for later review.
55
+ */
56
+ export declare function storeConflicts(db: DbAdapter, conflicts: SyncConflict[]): void;
57
+ /**
58
+ * List all stored conflicts, optionally filtered by resolved status.
59
+ */
60
+ export declare function listConflicts(db: DbAdapter, opts?: {
61
+ resolved?: boolean;
62
+ table?: string;
63
+ }): StoredConflict[];
64
+ /**
65
+ * Resolve a stored conflict by ID using the given strategy.
66
+ */
67
+ export declare function resolveConflict(db: DbAdapter, conflictId: string, strategy: ConflictStrategy | "manual"): StoredConflict | null;
68
+ /**
69
+ * Get a single stored conflict by ID.
70
+ */
71
+ export declare function getConflict(db: DbAdapter, conflictId: string): StoredConflict | null;
72
+ /**
73
+ * Delete all resolved conflicts (cleanup).
74
+ */
75
+ export declare function purgeResolvedConflicts(db: DbAdapter): number;
76
+ //# sourceMappingURL=sync-conflicts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-conflicts.d.ts","sourceRoot":"","sources":["../src/sync-conflicts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAM9C,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACjC,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,YAAY,GAAG,aAAa,GAAG,aAAa,GAAG,QAAQ,CAAC;CACtE;AAED,MAAM,MAAM,gBAAgB,GAAG,YAAY,GAAG,aAAa,GAAG,aAAa,CAAC;AAE5E,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;CACpB;AAMD;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EAC5B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EAC7B,KAAK,EAAE,MAAM,EACb,UAAU,SAAO,EACjB,cAAc,SAAe,GAC5B,YAAY,EAAE,CAkChB;AAMD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,YAAY,EAAE,EACzB,QAAQ,GAAE,gBAAgC,GACzC,YAAY,EAAE,CA4BhB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAyB1E;AAMD;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI,CAexD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,GAAG,IAAI,CAmB7E;AAMD;;GAEG;AACH,wBAAgB,aAAa,CAC3B,EAAE,EAAE,SAAS,EACb,IAAI,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5C,cAAc,EAAE,CAsBlB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,EAAE,EAAE,SAAS,EACb,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,gBAAgB,GAAG,QAAQ,GACpC,cAAc,GAAG,IAAI,CAoBvB;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAMpF;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,SAAS,GAAG,MAAM,CAM5D"}
@@ -0,0 +1,62 @@
1
+ import type { DbAdapter } from "./adapter.js";
2
+ export interface IncrementalSyncStats {
3
+ table: string;
4
+ total_rows: number;
5
+ synced_rows: number;
6
+ skipped_rows: number;
7
+ errors: string[];
8
+ first_sync: boolean;
9
+ }
10
+ export interface SyncMeta {
11
+ table_name: string;
12
+ last_synced_at: string;
13
+ last_synced_row_count: number;
14
+ direction: "push" | "pull";
15
+ }
16
+ export interface IncrementalSyncOptions {
17
+ /** Primary key column name (default: "id"). */
18
+ primaryKey?: string;
19
+ /** Conflict resolution column (default: "updated_at"). */
20
+ conflictColumn?: string;
21
+ /** Batch size for writes (default: 500). */
22
+ batchSize?: number;
23
+ }
24
+ /**
25
+ * Ensure the `_sync_meta` table exists in the given database.
26
+ */
27
+ export declare function ensureSyncMetaTable(db: DbAdapter): void;
28
+ /**
29
+ * Push only changed rows (since last sync) from local to remote.
30
+ *
31
+ * - Checks `_sync_meta` in the local DB for `last_synced_at`.
32
+ * - If found: only selects rows where `updated_at > last_synced_at`.
33
+ * - If not found: full push (first-time sync).
34
+ * - After push, updates `_sync_meta` with current timestamp and row count.
35
+ */
36
+ export declare function incrementalSyncPush(local: DbAdapter, remote: DbAdapter, tables: string[], options?: IncrementalSyncOptions): IncrementalSyncStats[];
37
+ /**
38
+ * Pull only changed rows (since last sync) from remote to local.
39
+ *
40
+ * - Checks `_sync_meta` in the local DB for `last_synced_at`.
41
+ * - If found: only selects rows where `updated_at > last_synced_at`.
42
+ * - If not found: full pull (first-time sync).
43
+ * - After pull, updates `_sync_meta` with current timestamp and row count.
44
+ */
45
+ export declare function incrementalSyncPull(remote: DbAdapter, local: DbAdapter, tables: string[], options?: IncrementalSyncOptions): IncrementalSyncStats[];
46
+ /**
47
+ * Get the sync metadata for all tables or a specific table.
48
+ */
49
+ export declare function getSyncMetaAll(db: DbAdapter): SyncMeta[];
50
+ /**
51
+ * Get sync metadata for a specific table.
52
+ */
53
+ export declare function getSyncMetaForTable(db: DbAdapter, table: string): SyncMeta | null;
54
+ /**
55
+ * Reset sync metadata for a table (forces full re-sync on next run).
56
+ */
57
+ export declare function resetSyncMeta(db: DbAdapter, table: string): void;
58
+ /**
59
+ * Reset all sync metadata (forces full re-sync for all tables).
60
+ */
61
+ export declare function resetAllSyncMeta(db: DbAdapter): void;
62
+ //# sourceMappingURL=sync-incremental.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-incremental.d.ts","sourceRoot":"","sources":["../src/sync-incremental.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAM9C,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,QAAQ;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,sBAAsB;IACrC,+CAA+C;IAC/C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0DAA0D;IAC1D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAcD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI,CAEvD;AAkID;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,MAAM,EAAE,EAChB,OAAO,GAAE,sBAA2B,GACnC,oBAAoB,EAAE,CAyExB;AAMD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,MAAM,EAAE,EAChB,OAAO,GAAE,sBAA2B,GACnC,oBAAoB,EAAE,CAyExB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,SAAS,GAAG,QAAQ,EAAE,CAKxD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,EAAE,EAAE,SAAS,EACb,KAAK,EAAE,MAAM,GACZ,QAAQ,GAAG,IAAI,CAEjB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAGhE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI,CAGpD"}
@@ -0,0 +1,68 @@
1
+ import type { DbAdapter } from "./adapter.js";
2
+ export interface SyncProgressInfo {
3
+ table: string;
4
+ total: number;
5
+ done: number;
6
+ percent: number;
7
+ elapsed_ms: number;
8
+ eta_ms: number;
9
+ status: "pending" | "in_progress" | "completed" | "failed" | "resumed";
10
+ }
11
+ export type ProgressCallback = (progress: SyncProgressInfo) => void;
12
+ export interface ResumePoint {
13
+ table_name: string;
14
+ last_row_id: string;
15
+ direction: string;
16
+ started_at: string;
17
+ status: string;
18
+ }
19
+ export declare class SyncProgressTracker {
20
+ private db;
21
+ private progress;
22
+ private startTimes;
23
+ private callback?;
24
+ constructor(db: DbAdapter, callback?: ProgressCallback);
25
+ private ensureResumeTable;
26
+ /**
27
+ * Start tracking a table sync. Sets status to in_progress or resumed.
28
+ */
29
+ start(table: string, total: number, direction: string): void;
30
+ /**
31
+ * Update progress for a table after processing rows.
32
+ */
33
+ update(table: string, done: number, lastRowId: string): void;
34
+ /**
35
+ * Mark a table sync as completed.
36
+ */
37
+ markComplete(table: string): void;
38
+ /**
39
+ * Mark a table sync as failed.
40
+ */
41
+ markFailed(table: string, _error: string): void;
42
+ /**
43
+ * Check if a previous sync was interrupted (status is 'in_progress' or 'resumed').
44
+ */
45
+ canResume(table: string): boolean;
46
+ /**
47
+ * Returns the last successfully synced row ID for a table, or null.
48
+ */
49
+ getResumePoint(table: string): ResumePoint | null;
50
+ /**
51
+ * Clear resume state for a table (e.g., after a fresh sync starts).
52
+ */
53
+ clearResume(table: string): void;
54
+ /**
55
+ * Get current progress info for a table.
56
+ */
57
+ getProgress(table: string): SyncProgressInfo | null;
58
+ /**
59
+ * Get progress info for all tracked tables.
60
+ */
61
+ getAllProgress(): SyncProgressInfo[];
62
+ /**
63
+ * List all resume records from the database (including historical).
64
+ */
65
+ listResumeRecords(): ResumePoint[];
66
+ private notify;
67
+ }
68
+ //# sourceMappingURL=sync-progress.d.ts.map