@hasna/cloud 0.1.3 → 0.1.5

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) {
@@ -24430,6 +24430,9 @@ class SqliteAdapter {
24430
24430
  exec(sql) {
24431
24431
  this.db.exec(sql);
24432
24432
  }
24433
+ query(sql) {
24434
+ return this.db.query(sql);
24435
+ }
24433
24436
  prepare(sql) {
24434
24437
  const stmt = this.db.prepare(sql);
24435
24438
  return {
@@ -24656,17 +24659,88 @@ function createDatabase(options) {
24656
24659
  }
24657
24660
 
24658
24661
  // src/sync.ts
24659
- function syncPush(local, cloud, options) {
24660
- return syncTransfer(local, cloud, options, "push");
24662
+ async function syncPush(local, remote, options) {
24663
+ const orderedTables = await getTableOrder(remote, options.tables);
24664
+ return syncTransfer(local, remote, { ...options, tables: orderedTables }, "push");
24665
+ }
24666
+ async function syncPull(remote, local, options) {
24667
+ const orderedTables = await getTableOrder(remote, options.tables);
24668
+ return syncTransfer(remote, local, { ...options, tables: orderedTables }, "pull");
24669
+ }
24670
+ async function getTableOrder(remote, tables) {
24671
+ if (tables.length <= 1)
24672
+ return tables;
24673
+ try {
24674
+ const fks = await remote.all(`
24675
+ SELECT DISTINCT
24676
+ tc.table_name AS source_table,
24677
+ ccu.table_name AS referenced_table
24678
+ FROM information_schema.table_constraints tc
24679
+ JOIN information_schema.constraint_column_usage ccu
24680
+ ON tc.constraint_name = ccu.constraint_name
24681
+ AND tc.table_schema = ccu.table_schema
24682
+ WHERE tc.constraint_type = 'FOREIGN KEY'
24683
+ AND tc.table_schema = 'public'
24684
+ `);
24685
+ if (fks.length > 0) {
24686
+ return topoSort(tables, fks);
24687
+ }
24688
+ } catch {}
24689
+ return heuristicOrder(tables);
24661
24690
  }
24662
- function syncPull(local, cloud, options) {
24663
- return syncTransfer(cloud, local, options, "pull");
24691
+ function topoSort(tables, fks) {
24692
+ const tableSet = new Set(tables);
24693
+ const deps = new Map;
24694
+ for (const t of tables) {
24695
+ deps.set(t, new Set);
24696
+ }
24697
+ for (const fk of fks) {
24698
+ if (tableSet.has(fk.source_table) && tableSet.has(fk.referenced_table)) {
24699
+ deps.get(fk.source_table).add(fk.referenced_table);
24700
+ }
24701
+ }
24702
+ const sorted = [];
24703
+ const visited = new Set;
24704
+ const visiting = new Set;
24705
+ function visit(table) {
24706
+ if (visited.has(table))
24707
+ return;
24708
+ if (visiting.has(table)) {
24709
+ sorted.push(table);
24710
+ visited.add(table);
24711
+ return;
24712
+ }
24713
+ visiting.add(table);
24714
+ const tableDeps = deps.get(table) ?? new Set;
24715
+ for (const dep of tableDeps) {
24716
+ visit(dep);
24717
+ }
24718
+ visiting.delete(table);
24719
+ visited.add(table);
24720
+ sorted.push(table);
24721
+ }
24722
+ for (const t of tables) {
24723
+ visit(t);
24724
+ }
24725
+ return sorted;
24726
+ }
24727
+ function heuristicOrder(tables) {
24728
+ const sorted = [...tables].sort((a, b) => {
24729
+ const aIsChild = a.includes("_") && tables.some((t) => a.startsWith(t + "_") || a.endsWith("_" + t));
24730
+ const bIsChild = b.includes("_") && tables.some((t) => b.startsWith(t + "_") || b.endsWith("_" + t));
24731
+ if (aIsChild && !bIsChild)
24732
+ return 1;
24733
+ if (!aIsChild && bIsChild)
24734
+ return -1;
24735
+ return a.localeCompare(b);
24736
+ });
24737
+ return sorted;
24664
24738
  }
24665
- function syncTransfer(source, target, options, _direction) {
24739
+ async function syncTransfer(source, target, options, _direction) {
24666
24740
  const {
24667
24741
  tables,
24668
24742
  onProgress,
24669
- batchSize = 500,
24743
+ batchSize = 100,
24670
24744
  conflictColumn = "updated_at",
24671
24745
  primaryKey = "id"
24672
24746
  } = options;
@@ -24689,7 +24763,7 @@ function syncTransfer(source, target, options, _direction) {
24689
24763
  totalTables: tables.length,
24690
24764
  currentTableIndex: i
24691
24765
  });
24692
- const rows = source.all(`SELECT * FROM "${table}"`);
24766
+ const rows = await readAll(source, `SELECT * FROM "${table}"`);
24693
24767
  result.rowsRead = rows.length;
24694
24768
  if (rows.length === 0) {
24695
24769
  onProgress?.({
@@ -24704,7 +24778,6 @@ function syncTransfer(source, target, options, _direction) {
24704
24778
  continue;
24705
24779
  }
24706
24780
  const columns = Object.keys(rows[0]);
24707
- const hasConflictCol = columns.includes(conflictColumn);
24708
24781
  const hasPrimaryKey = columns.includes(primaryKey);
24709
24782
  if (!hasPrimaryKey) {
24710
24783
  result.errors.push(`Table "${table}" has no "${primaryKey}" column — skipping`);
@@ -24719,34 +24792,18 @@ function syncTransfer(source, target, options, _direction) {
24719
24792
  totalTables: tables.length,
24720
24793
  currentTableIndex: i
24721
24794
  });
24795
+ const updateCols = columns.filter((c) => c !== primaryKey);
24722
24796
  for (let offset = 0;offset < rows.length; offset += batchSize) {
24723
24797
  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)}`);
24798
+ try {
24799
+ if (isAsyncAdapter(target)) {
24800
+ await batchUpsertPg(target, table, columns, updateCols, primaryKey, batch);
24801
+ } else {
24802
+ batchUpsertSqlite(target, table, columns, updateCols, primaryKey, batch);
24749
24803
  }
24804
+ result.rowsWritten += batch.length;
24805
+ } catch (err) {
24806
+ result.errors.push(`Batch at offset ${offset}: ${err?.message ?? String(err)}`);
24750
24807
  }
24751
24808
  onProgress?.({
24752
24809
  table,
@@ -24772,10 +24829,46 @@ function syncTransfer(source, target, options, _direction) {
24772
24829
  }
24773
24830
  return results;
24774
24831
  }
24832
+ async function batchUpsertPg(target, table, columns, updateCols, primaryKey, batch) {
24833
+ if (batch.length === 0)
24834
+ return;
24835
+ const colList = columns.map((c) => `"${c}"`).join(", ");
24836
+ const valuePlaceholders = batch.map((_, rowIdx) => {
24837
+ const offset = rowIdx * columns.length;
24838
+ return `(${columns.map((_2, colIdx) => `$${offset + colIdx + 1}`).join(", ")})`;
24839
+ }).join(", ");
24840
+ const setClause = updateCols.length > 0 ? updateCols.map((c) => `"${c}" = EXCLUDED."${c}"`).join(", ") : `"${primaryKey}" = EXCLUDED."${primaryKey}"`;
24841
+ const sql = `INSERT INTO "${table}" (${colList}) VALUES ${valuePlaceholders}
24842
+ ON CONFLICT ("${primaryKey}") DO UPDATE SET ${setClause}`;
24843
+ const params = batch.flatMap((row) => columns.map((c) => row[c] ?? null));
24844
+ await target.run(sql, ...params);
24845
+ }
24846
+ function batchUpsertSqlite(target, table, columns, updateCols, primaryKey, batch) {
24847
+ if (batch.length === 0)
24848
+ return;
24849
+ const colList = columns.map((c) => `"${c}"`).join(", ");
24850
+ const valuePlaceholders = batch.map(() => `(${columns.map(() => "?").join(", ")})`).join(", ");
24851
+ const setClause = updateCols.length > 0 ? updateCols.map((c) => `"${c}" = EXCLUDED."${c}"`).join(", ") : `"${primaryKey}" = EXCLUDED."${primaryKey}"`;
24852
+ const sql = `INSERT INTO "${table}" (${colList}) VALUES ${valuePlaceholders}
24853
+ ON CONFLICT ("${primaryKey}") DO UPDATE SET ${setClause}`;
24854
+ const params = batch.flatMap((row) => columns.map((c) => row[c] ?? null));
24855
+ target.run(sql, ...params);
24856
+ }
24857
+ function isAsyncAdapter(adapter) {
24858
+ return adapter.constructor.name === "PgAdapterAsync" || typeof adapter.raw?.connect === "function";
24859
+ }
24860
+ async function readAll(adapter, sql) {
24861
+ const result = adapter.all(sql);
24862
+ return result instanceof Promise ? await result : result;
24863
+ }
24775
24864
  function listSqliteTables(db) {
24776
24865
  const rows = db.all(`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name`);
24777
24866
  return rows.map((r) => r.name);
24778
24867
  }
24868
+ async function listPgTables(db) {
24869
+ const rows = await db.all(`SELECT tablename FROM pg_tables WHERE schemaname = 'public' ORDER BY tablename`);
24870
+ return rows.map((r) => r.tablename);
24871
+ }
24779
24872
 
24780
24873
  // src/feedback.ts
24781
24874
  import { hostname as hostname2 } from "os";
@@ -24922,6 +25015,9 @@ class SqliteAdapter2 {
24922
25015
  exec(sql) {
24923
25016
  this.db.exec(sql);
24924
25017
  }
25018
+ query(sql) {
25019
+ return this.db.query(sql);
25020
+ }
24925
25021
  prepare(sql) {
24926
25022
  const stmt = this.db.prepare(sql);
24927
25023
  return {
@@ -24951,10 +25047,8 @@ class SqliteAdapter2 {
24951
25047
  return this.db;
24952
25048
  }
24953
25049
  }
24954
-
24955
- class PgAdapter2 {
25050
+ class PgAdapterAsync {
24956
25051
  pool;
24957
- _client = null;
24958
25052
  constructor(arg) {
24959
25053
  if (typeof arg === "string") {
24960
25054
  this.pool = new esm_default.Pool({ connectionString: arg });
@@ -24962,118 +25056,47 @@ class PgAdapter2 {
24962
25056
  this.pool = arg;
24963
25057
  }
24964
25058
  }
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) {
25059
+ async run(sql, ...params) {
24987
25060
  const pgSql = translateSql(sql, "pg");
24988
25061
  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
- });
25062
+ const res = await this.pool.query(pgSql, pgParams);
25063
+ return {
25064
+ changes: res.rowCount ?? 0,
25065
+ lastInsertRowid: res.rows?.[0]?.id ?? 0
25066
+ };
24996
25067
  }
24997
- get(sql, ...params) {
25068
+ async get(sql, ...params) {
24998
25069
  const pgSql = translateSql(sql, "pg");
24999
25070
  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
- });
25071
+ const res = await this.pool.query(pgSql, pgParams);
25072
+ return res.rows[0] ?? null;
25004
25073
  }
25005
- all(sql, ...params) {
25074
+ async all(sql, ...params) {
25006
25075
  const pgSql = translateSql(sql, "pg");
25007
25076
  const pgParams = translateParams(params);
25008
- return this.runSync(async () => {
25009
- const res = await this.pool.query(pgSql, pgParams);
25010
- return res.rows;
25011
- });
25012
- }
25013
- exec(sql) {
25014
- const pgSql = translateSql(sql, "pg");
25015
- this.runSync(async () => {
25016
- await this.pool.query(pgSql);
25017
- });
25077
+ const res = await this.pool.query(pgSql, pgParams);
25078
+ return res.rows;
25018
25079
  }
25019
- prepare(sql) {
25080
+ async exec(sql) {
25020
25081
  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
- };
25082
+ await this.pool.query(pgSql);
25049
25083
  }
25050
- close() {
25051
- this.runSync(async () => {
25052
- await this.pool.end();
25053
- });
25084
+ async close() {
25085
+ await this.pool.end();
25054
25086
  }
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
- });
25087
+ async transaction(fn) {
25088
+ const client = await this.pool.connect();
25089
+ try {
25090
+ await client.query("BEGIN");
25091
+ const result = await fn(client);
25092
+ await client.query("COMMIT");
25093
+ return result;
25094
+ } catch (err) {
25095
+ await client.query("ROLLBACK");
25096
+ throw err;
25097
+ } finally {
25098
+ client.release();
25099
+ }
25077
25100
  }
25078
25101
  get raw() {
25079
25102
  return this.pool;
@@ -25098,10 +25121,10 @@ server.tool("cloud_status", "Show cloud configuration and connection health", {}
25098
25121
  if (config2.rds.host && config2.rds.username) {
25099
25122
  try {
25100
25123
  const connStr = getConnectionString("postgres");
25101
- const pg2 = new PgAdapter2(connStr);
25102
- pg2.get("SELECT 1 as ok");
25124
+ const pg2 = new PgAdapterAsync(connStr);
25125
+ await pg2.get("SELECT 1 as ok");
25103
25126
  lines.push("PostgreSQL: connected");
25104
- pg2.close();
25127
+ await pg2.close();
25105
25128
  } catch (err) {
25106
25129
  lines.push(`PostgreSQL: connection failed \u2014 ${err?.message}`);
25107
25130
  }
@@ -25128,16 +25151,16 @@ server.tool("sync_push", "Push local SQLite data to cloud PostgreSQL", {
25128
25151
  const dbPath = getDbPath2(service);
25129
25152
  const local = new SqliteAdapter2(dbPath);
25130
25153
  const connStr = getConnectionString(service);
25131
- const cloud = new PgAdapter2(connStr);
25154
+ const cloud = new PgAdapterAsync(connStr);
25132
25155
  let tableList;
25133
25156
  if (tablesStr) {
25134
25157
  tableList = tablesStr.split(",").map((t) => t.trim());
25135
25158
  } else {
25136
25159
  tableList = listSqliteTables(local);
25137
25160
  }
25138
- const results = syncPush(local, cloud, { tables: tableList });
25161
+ const results = await syncPush(local, cloud, { tables: tableList });
25139
25162
  local.close();
25140
- cloud.close();
25163
+ await cloud.close();
25141
25164
  const totalWritten = results.reduce((s, r) => s + r.rowsWritten, 0);
25142
25165
  const totalErrors = results.reduce((s, r) => s + r.errors.length, 0);
25143
25166
  const lines = [
@@ -25171,17 +25194,16 @@ server.tool("sync_pull", "Pull cloud PostgreSQL data to local SQLite", {
25171
25194
  const dbPath = getDbPath2(service);
25172
25195
  const local = new SqliteAdapter2(dbPath);
25173
25196
  const connStr = getConnectionString(service);
25174
- const cloud = new PgAdapter2(connStr);
25197
+ const cloud = new PgAdapterAsync(connStr);
25175
25198
  let tableList;
25176
25199
  if (tablesStr) {
25177
25200
  tableList = tablesStr.split(",").map((t) => t.trim());
25178
25201
  } else {
25179
25202
  try {
25180
- const rows = cloud.all(`SELECT tablename FROM pg_tables WHERE schemaname = 'public' ORDER BY tablename`);
25181
- tableList = rows.map((r) => r.tablename);
25203
+ tableList = await listPgTables(cloud);
25182
25204
  } catch {
25183
25205
  local.close();
25184
- cloud.close();
25206
+ await cloud.close();
25185
25207
  return {
25186
25208
  content: [
25187
25209
  { type: "text", text: "Error: failed to list tables from cloud." }
@@ -25190,9 +25212,9 @@ server.tool("sync_pull", "Pull cloud PostgreSQL data to local SQLite", {
25190
25212
  };
25191
25213
  }
25192
25214
  }
25193
- const results = syncPull(local, cloud, { tables: tableList });
25215
+ const results = await syncPull(cloud, local, { tables: tableList });
25194
25216
  local.close();
25195
- cloud.close();
25217
+ await cloud.close();
25196
25218
  const totalWritten = results.reduce((s, r) => s + r.rowsWritten, 0);
25197
25219
  const totalErrors = results.reduce((s, r) => s + r.errors.length, 0);
25198
25220
  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"}