supastash 0.2.10 → 0.2.11

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.
Files changed (50) hide show
  1. package/dist/desktop/hooks/syncEngine.d.ts.map +1 -1
  2. package/dist/desktop/hooks/syncEngine.js +7 -2
  3. package/dist/desktop/index.d.ts +3 -2
  4. package/dist/desktop/index.d.ts.map +1 -1
  5. package/dist/desktop/index.js +1 -0
  6. package/dist/desktop/utils/sync/pullFromRemote/index.d.ts +2 -1
  7. package/dist/desktop/utils/sync/pullFromRemote/index.d.ts.map +1 -1
  8. package/dist/desktop/utils/sync/pullFromRemote/index.js +3 -2
  9. package/dist/desktop/utils/sync/pullFromRemote/pullFromRemoteBatch.d.ts +9 -0
  10. package/dist/desktop/utils/sync/pullFromRemote/pullFromRemoteBatch.d.ts.map +1 -0
  11. package/dist/desktop/utils/sync/pullFromRemote/pullFromRemoteBatch.js +202 -0
  12. package/dist/index.d.ts +1 -0
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +1 -0
  15. package/dist/native/hooks/syncEngine.d.ts.map +1 -1
  16. package/dist/native/hooks/syncEngine.js +7 -2
  17. package/dist/native/index.d.ts +1 -0
  18. package/dist/native/index.d.ts.map +1 -1
  19. package/dist/native/index.js +1 -0
  20. package/dist/native/utils/sync/pullFromRemote/index.d.ts +2 -1
  21. package/dist/native/utils/sync/pullFromRemote/index.d.ts.map +1 -1
  22. package/dist/native/utils/sync/pullFromRemote/index.js +7 -1
  23. package/dist/native/utils/sync/pullFromRemote/pullFromRemoteBatch.d.ts +9 -0
  24. package/dist/native/utils/sync/pullFromRemote/pullFromRemoteBatch.d.ts.map +1 -0
  25. package/dist/native/utils/sync/pullFromRemote/pullFromRemoteBatch.js +183 -0
  26. package/dist/shared/core/config/index.d.ts.map +1 -1
  27. package/dist/shared/core/config/index.js +2 -0
  28. package/dist/shared/hooks/supastashFilters/index.d.ts +9 -4
  29. package/dist/shared/hooks/supastashFilters/index.d.ts.map +1 -1
  30. package/dist/shared/hooks/supastashFilters/index.js +13 -5
  31. package/dist/shared/store/rpcTableFilters.d.ts +7 -0
  32. package/dist/shared/store/rpcTableFilters.d.ts.map +1 -0
  33. package/dist/shared/store/rpcTableFilters.js +5 -0
  34. package/dist/shared/types/rpcFilter.types.d.ts +23 -0
  35. package/dist/shared/types/supastashConfig.types.d.ts +38 -10
  36. package/dist/shared/utils/schema/createSyncStatus.d.ts.map +1 -1
  37. package/dist/shared/utils/schema/createSyncStatus.js +5 -1
  38. package/dist/shared/utils/sync/pullFromRemote/postgrestToRpc.d.ts +9 -0
  39. package/dist/shared/utils/sync/pullFromRemote/postgrestToRpc.d.ts.map +1 -0
  40. package/dist/shared/utils/sync/pullFromRemote/postgrestToRpc.js +50 -0
  41. package/dist/shared/utils/sync/pullFromRemote/updateFilter.d.ts +8 -5
  42. package/dist/shared/utils/sync/pullFromRemote/updateFilter.d.ts.map +1 -1
  43. package/dist/shared/utils/sync/pullFromRemote/updateFilter.js +11 -5
  44. package/dist/shared/utils/sync/pullFromRemote/updateRpcFilters.d.ts +12 -0
  45. package/dist/shared/utils/sync/pullFromRemote/updateRpcFilters.d.ts.map +1 -0
  46. package/dist/shared/utils/sync/pullFromRemote/updateRpcFilters.js +36 -0
  47. package/dist/shared/utils/sync/status/remoteSchema.d.ts +12 -0
  48. package/dist/shared/utils/sync/status/remoteSchema.d.ts.map +1 -1
  49. package/dist/shared/utils/sync/status/remoteSchema.js +46 -0
  50. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"syncEngine.d.ts","sourceRoot":"","sources":["../../../src/desktop/hooks/syncEngine.ts"],"names":[],"mappings":"AA6BA;;;;;GAKG;AACH,wBAAsB,OAAO,CAAC,KAAK,GAAE,OAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAqCnE;AAuDD;;;;;GAKG;AACH,wBAAgB,aAAa;;;EA2D5B;AAMD;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgC5D;AAED;;GAEG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAEnD"}
1
+ {"version":3,"file":"syncEngine.d.ts","sourceRoot":"","sources":["../../../src/desktop/hooks/syncEngine.ts"],"names":[],"mappings":"AA6BA;;;;;GAKG;AACH,wBAAsB,OAAO,CAAC,KAAK,GAAE,OAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAqCnE;AA2DD;;;;;GAKG;AACH,wBAAgB,aAAa;;;EA2D5B;AAMD;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgC5D;AAED;;GAEG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAEnD"}
@@ -7,8 +7,8 @@ import { isOnline } from "../../shared/utils/connection";
7
7
  import log from "../../shared/utils/logs";
8
8
  import { subscribeToAppVisibility } from "../adapters/appstate";
9
9
  import { pullFromRemote as doPullFromRemote } from "../utils/sync/pullFromRemote";
10
+ import { pullFromRemoteBatch } from "../utils/sync/pullFromRemote/pullFromRemoteBatch";
10
11
  import { updateLocalDb } from "../utils/sync/pullFromRemote/updateLocalDb";
11
- import { pushLocalData as doPushLocalData } from "../utils/sync/pushLocal";
12
12
  import { pushLocalDataToRemote } from "../utils/sync/pushLocal/sendUnsyncedToSupabase";
13
13
  // -----------------------------
14
14
  // Module-scoped state & tunables
@@ -85,7 +85,12 @@ async function pushLocalDataSafe() {
85
85
  return;
86
86
  isPushing = true;
87
87
  try {
88
- await doPushLocalData();
88
+ if (cfg.useBatchPullSync) {
89
+ await pullFromRemoteBatch();
90
+ }
91
+ else {
92
+ await doPullFromRemote();
93
+ }
89
94
  lastPushAt = Date.now();
90
95
  }
91
96
  catch (e) {
@@ -9,6 +9,7 @@ export { refreshScreen } from "../shared/utils/refreshScreenCalls";
9
9
  export { getSupastashRuntimeMode, reinitializeSupastash, } from "../shared/utils/supastashMode";
10
10
  export { getAllTables } from "../shared/utils/sync/getAllTables";
11
11
  export { updateFilters } from "../shared/utils/sync/pullFromRemote/updateFilter";
12
+ export { updateRpcFilters } from "../shared/utils/sync/pullFromRemote/updateRpcFilters";
12
13
  export { refreshAllTables, refreshTable, refreshTableWithPayload, } from "../shared/utils/sync/refreshTables";
13
14
  export { clearSyncCalls, getAllSyncTables, getSyncCall, registerSyncCall, unregisterSyncCall, } from "../shared/utils/sync/registration/syncCalls";
14
15
  export { defineLocalSchema } from "./core/schemaManager";
@@ -21,8 +22,8 @@ export { updateLocalDb, upsertChunkData, upsertData, } from "./utils/sync/pullFr
21
22
  export { clearAllLocalDeleteLog, clearAllLocalSyncLog, clearLocalDeleteLog, clearLocalSyncLog, clearSyncLog, getLocalDeleteLog, getSyncLog, resetSyncLog, setLocalDeleteLog, setLocalSyncLog, setSyncLog, } from "./utils/sync/status/syncStatus";
22
23
  export type { CrudMethods } from "../shared/types/query.types";
23
24
  export type { RealtimeOptions, SupastashDataResult, SupastashFilter, } from "../shared/types/realtimeData.types";
24
- export type { ExpoSQLiteClient, RNSqliteNitroClient, RNStorageSQLiteClient, SupastashConfig, SupastashHookReturn, SupastashSQLiteClientTypes, SupastashSQLiteDatabase, SupastashSQLiteExecutor, TauriSQLiteClient, } from "../shared/types/supastashConfig.types";
25
25
  export type { LocalSchemaDefinition } from "../shared/types/schemaManager.types";
26
- export type { SyncInfo } from "../shared/types/syncEngine.types";
26
+ export type { ExpoSQLiteClient, RNSqliteNitroClient, RNStorageSQLiteClient, SupastashConfig, SupastashHookReturn, SupastashSQLiteClientTypes, SupastashSQLiteDatabase, SupastashSQLiteExecutor, TauriSQLiteClient, } from "../shared/types/supastashConfig.types";
27
27
  export type { SupastashClient, SupastashTransactionClient, } from "../shared/utils/query/builder";
28
+ export type { SyncInfo } from "../shared/types/syncEngine.types";
28
29
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/desktop/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,YAAY,GACb,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACnE,OAAO,EACL,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,kDAAkD,CAAC;AACjF,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,uBAAuB,GACxB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,WAAW,EACX,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,6CAA6C,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EACL,aAAa,EACb,SAAS,EACT,aAAa,EACb,uBAAuB,EACvB,oBAAoB,EACpB,SAAS,GACV,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,MAAM,wCAAwC,CAAC;AACxE,OAAO,EACL,aAAa,EACb,eAAe,EACf,UAAU,GACX,MAAM,2CAA2C,CAAC;AACnD,OAAO,EACL,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,EACjB,UAAU,EACV,YAAY,EACZ,iBAAiB,EACjB,eAAe,EACf,UAAU,GACX,MAAM,gCAAgC,CAAC;AAExC,YAAY,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC/D,YAAY,EACV,eAAe,EACf,mBAAmB,EACnB,eAAe,GAChB,MAAM,oCAAoC,CAAC;AAC5C,YAAY,EACV,gBAAgB,EAChB,mBAAmB,EACnB,qBAAqB,EACrB,eAAe,EACf,mBAAmB,EACnB,0BAA0B,EAC1B,uBAAuB,EACvB,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,uCAAuC,CAAC;AAE/C,YAAY,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAEjF,YAAY,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AACjE,YAAY,EACV,eAAe,EACf,0BAA0B,GAC3B,MAAM,+BAA+B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/desktop/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,YAAY,GACb,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACnE,OAAO,EACL,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,kDAAkD,CAAC;AACjF,OAAO,EAAE,gBAAgB,EAAE,MAAM,sDAAsD,CAAC;AACxF,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,uBAAuB,GACxB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,WAAW,EACX,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,6CAA6C,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EACL,aAAa,EACb,SAAS,EACT,aAAa,EACb,uBAAuB,EACvB,oBAAoB,EACpB,SAAS,GACV,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,MAAM,wCAAwC,CAAC;AACxE,OAAO,EACL,aAAa,EACb,eAAe,EACf,UAAU,GACX,MAAM,2CAA2C,CAAC;AACnD,OAAO,EACL,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,EACjB,UAAU,EACV,YAAY,EACZ,iBAAiB,EACjB,eAAe,EACf,UAAU,GACX,MAAM,gCAAgC,CAAC;AAExC,YAAY,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC/D,YAAY,EACV,eAAe,EACf,mBAAmB,EACnB,eAAe,GAChB,MAAM,oCAAoC,CAAC;AAC5C,YAAY,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AACjF,YAAY,EACV,gBAAgB,EAChB,mBAAmB,EACnB,qBAAqB,EACrB,eAAe,EACf,mBAAmB,EACnB,0BAA0B,EAC1B,uBAAuB,EACvB,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,uCAAuC,CAAC;AAC/C,YAAY,EACV,eAAe,EACf,0BAA0B,GAC3B,MAAM,+BAA+B,CAAC;AAEvC,YAAY,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC"}
@@ -9,6 +9,7 @@ export { refreshScreen } from "../shared/utils/refreshScreenCalls";
9
9
  export { getSupastashRuntimeMode, reinitializeSupastash, } from "../shared/utils/supastashMode";
10
10
  export { getAllTables } from "../shared/utils/sync/getAllTables";
11
11
  export { updateFilters } from "../shared/utils/sync/pullFromRemote/updateFilter";
12
+ export { updateRpcFilters } from "../shared/utils/sync/pullFromRemote/updateRpcFilters";
12
13
  export { refreshAllTables, refreshTable, refreshTableWithPayload, } from "../shared/utils/sync/refreshTables";
13
14
  export { clearSyncCalls, getAllSyncTables, getSyncCall, registerSyncCall, unregisterSyncCall, } from "../shared/utils/sync/registration/syncCalls";
14
15
  export { defineLocalSchema } from "./core/schemaManager";
@@ -1,5 +1,6 @@
1
1
  /**
2
- * Pulls the data from the remote database to the local database
2
+ * Pulls the data from the remote database to the local database (per-table path).
3
+ * For the batch RPC path, see pullFromRemoteBatch — routed via syncEngine.
3
4
  */
4
5
  export declare function pullFromRemote(): Promise<void>;
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/desktop/utils/sync/pullFromRemote/index.ts"],"names":[],"mappings":"AASA;;GAEG;AACH,wBAAsB,cAAc,kBAqEnC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/desktop/utils/sync/pullFromRemote/index.ts"],"names":[],"mappings":"AASA;;;GAGG;AACH,wBAAsB,cAAc,kBAqEnC"}
@@ -5,9 +5,10 @@ import log from "../../../../shared/utils/logs";
5
5
  import { getAllTables } from "../../../../shared/utils/sync/getAllTables";
6
6
  import { runLimitedConcurrency } from "../../../../shared/utils/sync/pullFromRemote/runLimitedConcurrency";
7
7
  import { SyncInfoUpdater } from "../../../../shared/utils/sync/queryStatus";
8
- import { updateLocalDb } from "../../../utils/sync/pullFromRemote/updateLocalDb";
8
+ import { updateLocalDb } from "./updateLocalDb";
9
9
  /**
10
- * Pulls the data from the remote database to the local database
10
+ * Pulls the data from the remote database to the local database (per-table path).
11
+ * For the batch RPC path, see pullFromRemoteBatch — routed via syncEngine.
11
12
  */
12
13
  export async function pullFromRemote() {
13
14
  let numberOfTables = 0;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Batch pull: fetches all tables in a single RPC call per round,
3
+ * looping until `remaining_tables` is empty.
4
+ *
5
+ * Requires `useBatchPullSync: true` in config and the
6
+ * `supastash_pull_sync` Postgres function to be deployed.
7
+ */
8
+ export declare function pullFromRemoteBatch(): Promise<void>;
9
+ //# sourceMappingURL=pullFromRemoteBatch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pullFromRemoteBatch.d.ts","sourceRoot":"","sources":["../../../../../src/desktop/utils/sync/pullFromRemote/pullFromRemoteBatch.ts"],"names":[],"mappings":"AA+CA;;;;;;GAMG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CA4MzD"}
@@ -0,0 +1,202 @@
1
+ import { getSupastashConfig } from "../../../../shared/core/config";
2
+ import { getSupastashDb } from "../../../../shared/db/dbInitializer";
3
+ import { rpcTableFilters } from "../../../../shared/store/rpcTableFilters";
4
+ import { tableFilters } from "../../../../shared/store/tableFilters";
5
+ import log, { logError, logWarn } from "../../../../shared/utils/logs";
6
+ import { refreshScreen } from "../../../../shared/utils/refreshScreenCalls";
7
+ import { supabaseClientErr } from "../../../../shared/utils/supabaseClientErr";
8
+ import { getAllTables } from "../../../../shared/utils/sync/getAllTables";
9
+ import { getMaxSyncLookBack, logNoUpdates, returnMaxDate, } from "../../../../shared/utils/sync/pullFromRemote/helpers";
10
+ import { postgrestFiltersToRpc } from "../../../../shared/utils/sync/pullFromRemote/postgrestToRpc";
11
+ import { SyncInfoUpdater } from "../../../../shared/utils/sync/queryStatus";
12
+ import { prefetchRemoteTableSchemas } from "../../../../shared/utils/sync/status/remoteSchema";
13
+ import { selectSyncStatus } from "../status/repo";
14
+ import { setSupastashSyncStatus } from "../status/services";
15
+ import { upsertChunkData } from "./updateLocalDb";
16
+ const CHUNK_SIZE = 999;
17
+ function buildCursorFilter(tsCol, lastSyncedAt, lastPk) {
18
+ if (lastPk) {
19
+ return {
20
+ or: [
21
+ { col: tsCol, op: "gt", val: lastSyncedAt },
22
+ {
23
+ and: [
24
+ { col: tsCol, op: "eq", val: lastSyncedAt },
25
+ { col: "id", op: "gt", val: lastPk },
26
+ ],
27
+ },
28
+ ],
29
+ };
30
+ }
31
+ return { col: tsCol, op: "gte", val: lastSyncedAt };
32
+ }
33
+ /**
34
+ * Batch pull: fetches all tables in a single RPC call per round,
35
+ * looping until `remaining_tables` is empty.
36
+ *
37
+ * Requires `useBatchPullSync: true` in config and the
38
+ * `supastash_pull_sync` Postgres function to be deployed.
39
+ */
40
+ export async function pullFromRemoteBatch() {
41
+ const cfg = getSupastashConfig();
42
+ const supabase = cfg.supabaseClient;
43
+ if (!supabase)
44
+ throw new Error(`No supabase client found: ${supabaseClientErr}`);
45
+ if (cfg.supastashMode === "ghost")
46
+ return;
47
+ const tables = await getAllTables();
48
+ if (!tables) {
49
+ log("[Supastash] Batch pull: no tables found");
50
+ return;
51
+ }
52
+ const excludeTables = cfg.excludeTables?.pull ?? [];
53
+ const tablesToPull = tables.filter((t) => !excludeTables.includes(t));
54
+ if (!tablesToPull.length)
55
+ return;
56
+ const tsCol = cfg.replicationMode === "server-side" ? "arrived_at" : "updated_at";
57
+ const db = await getSupastashDb();
58
+ const completedTables = new Set();
59
+ SyncInfoUpdater.setInProgress({ action: "start", type: "pull" });
60
+ SyncInfoUpdater.setNumberOfTables({
61
+ amount: tablesToPull.length,
62
+ type: "pull",
63
+ });
64
+ // Warm schema cache for all tables in one call if enabled
65
+ if (cfg.useBatchSchemaFetch) {
66
+ await prefetchRemoteTableSchemas(tablesToPull);
67
+ }
68
+ let remainingTables = tablesToPull;
69
+ try {
70
+ while (remainingTables.length > 0) {
71
+ // ── Build per-table filters: base filters + cursor ──────────────────
72
+ const p_filters = {};
73
+ for (const table of remainingTables) {
74
+ const syncStatus = await selectSyncStatus(db, table, tableFilters.get(table) ?? []);
75
+ // Mirror pageThrough: cap the cursor to maxSyncLookbackDays so a
76
+ // first-time sync doesn't try to pull decades of data.
77
+ // fullSyncTables bypass the cap (getMaxSyncLookBack returns undefined).
78
+ const maxLookBack = getMaxSyncLookBack({ table });
79
+ const effectiveSince = maxLookBack &&
80
+ Date.parse(syncStatus.last_synced_at) < Date.parse(maxLookBack)
81
+ ? maxLookBack
82
+ : syncStatus.last_synced_at;
83
+ const cursorFilter = buildCursorFilter(tsCol, effectiveSince, syncStatus.last_synced_at_pk);
84
+ // Merge explicit RPC filters + PostgREST filters auto-converted at query time
85
+ const rpcBase = rpcTableFilters.get(table) ?? [];
86
+ const converted = postgrestFiltersToRpc(tableFilters.get(table));
87
+ const baseFilters = [...rpcBase, ...converted];
88
+ p_filters[table] = [...baseFilters, cursorFilter];
89
+ }
90
+ // ── Single RPC call ─────────────────────────────────────────────────
91
+ const { data, error } = await supabase.rpc("supastash_pull_sync", {
92
+ p_tables: remainingTables,
93
+ p_filters,
94
+ p_ts_col: tsCol,
95
+ });
96
+ if (error)
97
+ throw error;
98
+ const result = data;
99
+ const nextRemaining = result.remaining_tables ?? [];
100
+ // ── Process each table ──────────────────────────────────────────────
101
+ for (const [table, rows] of Object.entries(result.tables ?? {})) {
102
+ SyncInfoUpdater.markLogStart({ type: "pull", table });
103
+ try {
104
+ if (!rows?.length) {
105
+ logNoUpdates(table);
106
+ SyncInfoUpdater.markLogSuccess({ type: "pull", table });
107
+ continue;
108
+ }
109
+ const toDelete = [];
110
+ const toUpsert = [];
111
+ let prevMaxSyncedAt = null;
112
+ let prevMaxDeletedAt = null;
113
+ for (const row of rows) {
114
+ if (!row?.id) {
115
+ logWarn(`[Supastash] Batch: skipped row without id from "${table}"`);
116
+ continue;
117
+ }
118
+ prevMaxSyncedAt = returnMaxDate({
119
+ row,
120
+ prevMax: prevMaxSyncedAt,
121
+ col: tsCol,
122
+ });
123
+ prevMaxDeletedAt = returnMaxDate({
124
+ row,
125
+ prevMax: prevMaxDeletedAt,
126
+ col: "deleted_at",
127
+ });
128
+ if (row.deleted_at) {
129
+ toDelete.push(row.id);
130
+ }
131
+ else {
132
+ toUpsert.push(row);
133
+ }
134
+ }
135
+ SyncInfoUpdater.setUnsyncedDataCount({
136
+ amount: toUpsert.length,
137
+ type: "pull",
138
+ table,
139
+ });
140
+ SyncInfoUpdater.setUnsyncedDeletedCount({
141
+ amount: toDelete.length,
142
+ type: "pull",
143
+ table,
144
+ });
145
+ // Delete soft-deleted rows
146
+ if (toDelete.length > 0) {
147
+ for (let i = 0; i < toDelete.length; i += CHUNK_SIZE) {
148
+ const slice = toDelete.slice(i, i + CHUNK_SIZE);
149
+ const placeholders = slice.map(() => "?").join(", ");
150
+ await db.runAsync(`DELETE FROM ${table} WHERE id IN (${placeholders})`, slice);
151
+ }
152
+ }
153
+ // Upsert live rows
154
+ if (toUpsert.length > 0) {
155
+ await upsertChunkData({ table, records: toUpsert });
156
+ }
157
+ // Update sync cursor so the next round starts from the right place
158
+ if (prevMaxSyncedAt || prevMaxDeletedAt) {
159
+ await setSupastashSyncStatus(table, tableFilters.get(table) ?? [], {
160
+ lastSyncedAt: prevMaxSyncedAt?.value ?? undefined,
161
+ lastDeletedAt: prevMaxDeletedAt?.value ?? undefined,
162
+ lastSyncedAtPk: prevMaxSyncedAt?.pk ?? null,
163
+ filterNamespace: "global",
164
+ });
165
+ }
166
+ if (toUpsert.length > 0 || toDelete.length > 0) {
167
+ refreshScreen(table);
168
+ }
169
+ log(`[Supastash] Batch received ${rows.length} rows for "${table}" ` +
170
+ `(u${toUpsert.length}/d${toDelete.length})`);
171
+ SyncInfoUpdater.markLogSuccess({ type: "pull", table });
172
+ }
173
+ catch (e) {
174
+ SyncInfoUpdater.markLogError({
175
+ type: "pull",
176
+ table,
177
+ lastError: e,
178
+ errorCount: 1,
179
+ });
180
+ logError(`[Supastash] Batch pull failed for "${table}"`, e);
181
+ }
182
+ finally {
183
+ // Mark table as fully completed only once it leaves remaining_tables
184
+ if (!nextRemaining.includes(table)) {
185
+ completedTables.add(table);
186
+ SyncInfoUpdater.setTablesCompleted({
187
+ amount: completedTables.size,
188
+ type: "pull",
189
+ });
190
+ }
191
+ }
192
+ }
193
+ remainingTables = nextRemaining;
194
+ }
195
+ }
196
+ catch (error) {
197
+ logError("[Supastash] Error in batch pull from remote", error);
198
+ }
199
+ finally {
200
+ SyncInfoUpdater.reset({ type: "pull" });
201
+ }
202
+ }
package/dist/index.d.ts CHANGED
@@ -7,6 +7,7 @@ export { refreshScreen } from "./shared/utils/refreshScreenCalls";
7
7
  export { getSupastashRuntimeMode, reinitializeSupastash, } from "./shared/utils/supastashMode";
8
8
  export { getAllTables } from "./shared/utils/sync/getAllTables";
9
9
  export { updateFilters } from "./shared/utils/sync/pullFromRemote/updateFilter";
10
+ export { updateRpcFilters } from "./shared/utils/sync/pullFromRemote/updateRpcFilters";
10
11
  export { refreshAllTables, refreshTable, refreshTableWithPayload, } from "./shared/utils/sync/refreshTables";
11
12
  export { clearSyncCalls, getAllSyncTables, getSyncCall, registerSyncCall, unregisterSyncCall, } from "./shared/utils/sync/registration/syncCalls";
12
13
  export { supastash } from "./shared/utils/query/builder";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC9E,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,YAAY,GACb,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAClE,OAAO,EACL,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,iDAAiD,CAAC;AAChF,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,uBAAuB,GACxB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,WAAW,EACX,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,4CAA4C,CAAC;AAEpD,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAEzD,YAAY,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC9D,YAAY,EACV,eAAe,EACf,mBAAmB,EACnB,eAAe,GAChB,MAAM,mCAAmC,CAAC;AAC3C,YAAY,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAChF,YAAY,EACV,gBAAgB,EAChB,mBAAmB,EACnB,qBAAqB,EACrB,eAAe,EACf,mBAAmB,EACnB,0BAA0B,EAC1B,uBAAuB,EACvB,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,sCAAsC,CAAC;AAC9C,YAAY,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAChE,YAAY,EACV,eAAe,EACf,0BAA0B,GAC3B,MAAM,8BAA8B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC9E,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,YAAY,GACb,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAClE,OAAO,EACL,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,iDAAiD,CAAC;AAChF,OAAO,EAAE,gBAAgB,EAAE,MAAM,qDAAqD,CAAC;AACvF,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,uBAAuB,GACxB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,WAAW,EACX,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,4CAA4C,CAAC;AAEpD,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAEzD,YAAY,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC9D,YAAY,EACV,eAAe,EACf,mBAAmB,EACnB,eAAe,GAChB,MAAM,mCAAmC,CAAC;AAC3C,YAAY,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAChF,YAAY,EACV,gBAAgB,EAChB,mBAAmB,EACnB,qBAAqB,EACrB,eAAe,EACf,mBAAmB,EACnB,0BAA0B,EAC1B,uBAAuB,EACvB,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,sCAAsC,CAAC;AAC9C,YAAY,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAChE,YAAY,EACV,eAAe,EACf,0BAA0B,GAC3B,MAAM,8BAA8B,CAAC"}
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ export { refreshScreen } from "./shared/utils/refreshScreenCalls";
7
7
  export { getSupastashRuntimeMode, reinitializeSupastash, } from "./shared/utils/supastashMode";
8
8
  export { getAllTables } from "./shared/utils/sync/getAllTables";
9
9
  export { updateFilters } from "./shared/utils/sync/pullFromRemote/updateFilter";
10
+ export { updateRpcFilters } from "./shared/utils/sync/pullFromRemote/updateRpcFilters";
10
11
  export { refreshAllTables, refreshTable, refreshTableWithPayload, } from "./shared/utils/sync/refreshTables";
11
12
  export { clearSyncCalls, getAllSyncTables, getSyncCall, registerSyncCall, unregisterSyncCall, } from "./shared/utils/sync/registration/syncCalls";
12
13
  export { supastash } from "./shared/utils/query/builder";
@@ -1 +1 @@
1
- {"version":3,"file":"syncEngine.d.ts","sourceRoot":"","sources":["../../../src/native/hooks/syncEngine.ts"],"names":[],"mappings":"AA6BA;;;;;GAKG;AACH,wBAAsB,OAAO,CAAC,KAAK,GAAE,OAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAqCnE;AAuDD;;;;;GAKG;AACH,wBAAgB,aAAa;;;EA+D5B;AAMD;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgC5D;AAED;;GAEG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAEnD"}
1
+ {"version":3,"file":"syncEngine.d.ts","sourceRoot":"","sources":["../../../src/native/hooks/syncEngine.ts"],"names":[],"mappings":"AA8BA;;;;;GAKG;AACH,wBAAsB,OAAO,CAAC,KAAK,GAAE,OAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAqCnE;AAyDD;;;;;GAKG;AACH,wBAAgB,aAAa;;;EA+D5B;AAMD;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgC5D;AAED;;GAEG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAEnD"}
@@ -7,6 +7,7 @@ import { tableFilters } from "../../shared/store/tableFilters";
7
7
  import { isOnline } from "../../shared/utils/connection";
8
8
  import log from "../../shared/utils/logs";
9
9
  import { pullFromRemote as doPullFromRemote } from "../utils/sync/pullFromRemote";
10
+ import { pullFromRemoteBatch } from "../utils/sync/pullFromRemote/pullFromRemoteBatch";
10
11
  import { updateLocalDb } from "../utils/sync/pullFromRemote/updateLocalDb";
11
12
  import { pushLocalData as doPushLocalData } from "../utils/sync/pushLocal";
12
13
  import { pushLocalDataToRemote } from "../utils/sync/pushLocal/sendUnsyncedToSupabase";
@@ -103,7 +104,6 @@ async function pullFromRemoteSafe() {
103
104
  return;
104
105
  if (!(await isOnline()))
105
106
  return;
106
- // If in ghost mode, don't pull
107
107
  const cfg = getSupastashConfig();
108
108
  if (cfg.supastashMode === "ghost")
109
109
  return;
@@ -111,7 +111,12 @@ async function pullFromRemoteSafe() {
111
111
  return;
112
112
  isPulling = true;
113
113
  try {
114
- await doPullFromRemote();
114
+ if (cfg.useBatchPullSync) {
115
+ await pullFromRemoteBatch();
116
+ }
117
+ else {
118
+ await doPullFromRemote();
119
+ }
115
120
  lastPullAt = Date.now();
116
121
  }
117
122
  catch (e) {
@@ -9,6 +9,7 @@ export { refreshScreen } from "../shared/utils/refreshScreenCalls";
9
9
  export { getSupastashRuntimeMode, reinitializeSupastash, } from "../shared/utils/supastashMode";
10
10
  export { getAllTables } from "../shared/utils/sync/getAllTables";
11
11
  export { updateFilters } from "../shared/utils/sync/pullFromRemote/updateFilter";
12
+ export { updateRpcFilters } from "../shared/utils/sync/pullFromRemote/updateRpcFilters";
12
13
  export { refreshAllTables, refreshTable, refreshTableWithPayload, } from "../shared/utils/sync/refreshTables";
13
14
  export { clearSyncCalls, getAllSyncTables, getSyncCall, registerSyncCall, unregisterSyncCall, } from "../shared/utils/sync/registration/syncCalls";
14
15
  export { defineLocalSchema } from "./core/schemaManager";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/native/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,YAAY,GACb,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACnE,OAAO,EACL,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,kDAAkD,CAAC;AACjF,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,uBAAuB,GACxB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,WAAW,EACX,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,6CAA6C,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EACL,aAAa,EACb,SAAS,EACT,aAAa,EACb,uBAAuB,EACvB,oBAAoB,EACpB,SAAS,GACV,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,MAAM,wCAAwC,CAAC;AACxE,OAAO,EACL,aAAa,EACb,eAAe,EACf,UAAU,GACX,MAAM,2CAA2C,CAAC;AACnD,OAAO,EACL,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,EACjB,UAAU,EACV,YAAY,EACZ,iBAAiB,EACjB,eAAe,EACf,UAAU,GACX,MAAM,gCAAgC,CAAC;AAExC,YAAY,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC/D,YAAY,EACV,eAAe,EACf,mBAAmB,EACnB,eAAe,GAChB,MAAM,oCAAoC,CAAC;AAC5C,YAAY,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AACjF,YAAY,EACV,gBAAgB,EAChB,mBAAmB,EACnB,qBAAqB,EACrB,eAAe,EACf,mBAAmB,EACnB,0BAA0B,EAC1B,uBAAuB,EACvB,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,uCAAuC,CAAC;AAC/C,YAAY,EACV,eAAe,EACf,0BAA0B,GAC3B,MAAM,+BAA+B,CAAC;AAEvC,YAAY,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/native/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,YAAY,GACb,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACnE,OAAO,EACL,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,kDAAkD,CAAC;AACjF,OAAO,EAAE,gBAAgB,EAAE,MAAM,sDAAsD,CAAC;AACxF,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,uBAAuB,GACxB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,WAAW,EACX,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,6CAA6C,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EACL,aAAa,EACb,SAAS,EACT,aAAa,EACb,uBAAuB,EACvB,oBAAoB,EACpB,SAAS,GACV,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,MAAM,wCAAwC,CAAC;AACxE,OAAO,EACL,aAAa,EACb,eAAe,EACf,UAAU,GACX,MAAM,2CAA2C,CAAC;AACnD,OAAO,EACL,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,EACjB,UAAU,EACV,YAAY,EACZ,iBAAiB,EACjB,eAAe,EACf,UAAU,GACX,MAAM,gCAAgC,CAAC;AAExC,YAAY,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC/D,YAAY,EACV,eAAe,EACf,mBAAmB,EACnB,eAAe,GAChB,MAAM,oCAAoC,CAAC;AAC5C,YAAY,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AACjF,YAAY,EACV,gBAAgB,EAChB,mBAAmB,EACnB,qBAAqB,EACrB,eAAe,EACf,mBAAmB,EACnB,0BAA0B,EAC1B,uBAAuB,EACvB,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,uCAAuC,CAAC;AAC/C,YAAY,EACV,eAAe,EACf,0BAA0B,GAC3B,MAAM,+BAA+B,CAAC;AAEvC,YAAY,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC"}
@@ -9,6 +9,7 @@ export { refreshScreen } from "../shared/utils/refreshScreenCalls";
9
9
  export { getSupastashRuntimeMode, reinitializeSupastash, } from "../shared/utils/supastashMode";
10
10
  export { getAllTables } from "../shared/utils/sync/getAllTables";
11
11
  export { updateFilters } from "../shared/utils/sync/pullFromRemote/updateFilter";
12
+ export { updateRpcFilters } from "../shared/utils/sync/pullFromRemote/updateRpcFilters";
12
13
  export { refreshAllTables, refreshTable, refreshTableWithPayload, } from "../shared/utils/sync/refreshTables";
13
14
  export { clearSyncCalls, getAllSyncTables, getSyncCall, registerSyncCall, unregisterSyncCall, } from "../shared/utils/sync/registration/syncCalls";
14
15
  export { defineLocalSchema } from "./core/schemaManager";
@@ -1,5 +1,6 @@
1
1
  /**
2
- * Pulls the data from the remote database to the local database
2
+ * Pulls the data from the remote database to the local database (per-table path).
3
+ * For the batch RPC path, see pullFromRemoteBatch — routed via syncEngine.
3
4
  */
4
5
  export declare function pullFromRemote(): Promise<void>;
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/native/utils/sync/pullFromRemote/index.ts"],"names":[],"mappings":"AASA;;GAEG;AACH,wBAAsB,cAAc,kBAqEnC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/native/utils/sync/pullFromRemote/index.ts"],"names":[],"mappings":"AAUA;;;GAGG;AACH,wBAAsB,cAAc,kBA0EnC"}
@@ -5,9 +5,11 @@ import log from "../../../../shared/utils/logs";
5
5
  import { getAllTables } from "../../../../shared/utils/sync/getAllTables";
6
6
  import { runLimitedConcurrency } from "../../../../shared/utils/sync/pullFromRemote/runLimitedConcurrency";
7
7
  import { SyncInfoUpdater } from "../../../../shared/utils/sync/queryStatus";
8
+ import { prefetchRemoteTableSchemas } from "../../../../shared/utils/sync/status/remoteSchema";
8
9
  import { updateLocalDb } from "./updateLocalDb";
9
10
  /**
10
- * Pulls the data from the remote database to the local database
11
+ * Pulls the data from the remote database to the local database (per-table path).
12
+ * For the batch RPC path, see pullFromRemoteBatch — routed via syncEngine.
11
13
  */
12
14
  export async function pullFromRemote() {
13
15
  let numberOfTables = 0;
@@ -21,6 +23,10 @@ export async function pullFromRemote() {
21
23
  const excludeTables = getSupastashConfig()?.excludeTables?.pull || [];
22
24
  const tablesToPull = tables.filter((table) => !excludeTables?.includes(table));
23
25
  numberOfTables = tablesToPull.length;
26
+ // Warm schema cache for all tables in one call if enabled
27
+ if (getSupastashConfig().useBatchSchemaFetch) {
28
+ await prefetchRemoteTableSchemas(tablesToPull);
29
+ }
24
30
  SyncInfoUpdater.setInProgress({
25
31
  action: "start",
26
32
  type: "pull",
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Batch pull: fetches all tables in a single RPC call per round,
3
+ * looping until `remaining_tables` is empty.
4
+ *
5
+ * Requires `useBatchPullSync: true` in config and the
6
+ * `supastash_pull_sync` Postgres function to be deployed.
7
+ */
8
+ export declare function pullFromRemoteBatch(): Promise<void>;
9
+ //# sourceMappingURL=pullFromRemoteBatch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pullFromRemoteBatch.d.ts","sourceRoot":"","sources":["../../../../../src/native/utils/sync/pullFromRemote/pullFromRemoteBatch.ts"],"names":[],"mappings":"AA4CA;;;;;;GAMG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CA+KzD"}
@@ -0,0 +1,183 @@
1
+ import { getSupastashConfig } from "../../../../shared/core/config";
2
+ import { getSupastashDb } from "../../../../shared/db/dbInitializer";
3
+ import { rpcTableFilters } from "../../../../shared/store/rpcTableFilters";
4
+ import { tableFilters } from "../../../../shared/store/tableFilters";
5
+ import { postgrestFiltersToRpc } from "../../../../shared/utils/sync/pullFromRemote/postgrestToRpc";
6
+ import { getAllTables } from "../../../../shared/utils/sync/getAllTables";
7
+ import { getMaxSyncLookBack, logNoUpdates, returnMaxDate, } from "../../../../shared/utils/sync/pullFromRemote/helpers";
8
+ import { SyncInfoUpdater } from "../../../../shared/utils/sync/queryStatus";
9
+ import { refreshScreen } from "../../../../shared/utils/refreshScreenCalls";
10
+ import log, { logError, logWarn } from "../../../../shared/utils/logs";
11
+ import { supabaseClientErr } from "../../../../shared/utils/supabaseClientErr";
12
+ import { prefetchRemoteTableSchemas } from "../../../../shared/utils/sync/status/remoteSchema";
13
+ import { setSupastashSyncStatus } from "../status/services";
14
+ import { selectSyncStatus } from "../status/repo";
15
+ import { upsertChunkData } from "./updateLocalDb";
16
+ const CHUNK_SIZE = 999;
17
+ function buildCursorFilter(tsCol, lastSyncedAt, lastPk) {
18
+ if (lastPk) {
19
+ return {
20
+ or: [
21
+ { col: tsCol, op: "gt", val: lastSyncedAt },
22
+ {
23
+ and: [
24
+ { col: tsCol, op: "eq", val: lastSyncedAt },
25
+ { col: "id", op: "gt", val: lastPk },
26
+ ],
27
+ },
28
+ ],
29
+ };
30
+ }
31
+ return { col: tsCol, op: "gte", val: lastSyncedAt };
32
+ }
33
+ /**
34
+ * Batch pull: fetches all tables in a single RPC call per round,
35
+ * looping until `remaining_tables` is empty.
36
+ *
37
+ * Requires `useBatchPullSync: true` in config and the
38
+ * `supastash_pull_sync` Postgres function to be deployed.
39
+ */
40
+ export async function pullFromRemoteBatch() {
41
+ const cfg = getSupastashConfig();
42
+ const supabase = cfg.supabaseClient;
43
+ if (!supabase)
44
+ throw new Error(`No supabase client found: ${supabaseClientErr}`);
45
+ if (cfg.supastashMode === "ghost")
46
+ return;
47
+ const tables = await getAllTables();
48
+ if (!tables) {
49
+ log("[Supastash] Batch pull: no tables found");
50
+ return;
51
+ }
52
+ const excludeTables = cfg.excludeTables?.pull ?? [];
53
+ const tablesToPull = tables.filter((t) => !excludeTables.includes(t));
54
+ if (!tablesToPull.length)
55
+ return;
56
+ const tsCol = cfg.replicationMode === "server-side" ? "arrived_at" : "updated_at";
57
+ const db = await getSupastashDb();
58
+ const completedTables = new Set();
59
+ SyncInfoUpdater.setInProgress({ action: "start", type: "pull" });
60
+ SyncInfoUpdater.setNumberOfTables({ amount: tablesToPull.length, type: "pull" });
61
+ // Warm schema cache for all tables in one call if enabled
62
+ if (cfg.useBatchSchemaFetch) {
63
+ await prefetchRemoteTableSchemas(tablesToPull);
64
+ }
65
+ let remainingTables = tablesToPull;
66
+ try {
67
+ while (remainingTables.length > 0) {
68
+ // ── Build per-table filters: base filters + cursor ──────────────────
69
+ const p_filters = {};
70
+ for (const table of remainingTables) {
71
+ const syncStatus = await selectSyncStatus(db, table, tableFilters.get(table) ?? []);
72
+ // Mirror pageThrough: cap the cursor to maxSyncLookbackDays so a
73
+ // first-time sync doesn't try to pull decades of data.
74
+ // fullSyncTables bypass the cap (getMaxSyncLookBack returns undefined).
75
+ const maxLookBack = getMaxSyncLookBack({ table });
76
+ const effectiveSince = maxLookBack &&
77
+ Date.parse(syncStatus.last_synced_at) < Date.parse(maxLookBack)
78
+ ? maxLookBack
79
+ : syncStatus.last_synced_at;
80
+ const cursorFilter = buildCursorFilter(tsCol, effectiveSince, syncStatus.last_synced_at_pk);
81
+ // Merge explicit RPC filters + PostgREST filters auto-converted at query time
82
+ const rpcBase = rpcTableFilters.get(table) ?? [];
83
+ const converted = postgrestFiltersToRpc(tableFilters.get(table));
84
+ const baseFilters = [...rpcBase, ...converted];
85
+ p_filters[table] = [...baseFilters, cursorFilter];
86
+ }
87
+ // ── Single RPC call ─────────────────────────────────────────────────
88
+ const { data, error } = await supabase.rpc("supastash_pull_sync", {
89
+ p_tables: remainingTables,
90
+ p_filters,
91
+ p_ts_col: tsCol,
92
+ });
93
+ if (error)
94
+ throw error;
95
+ const result = data;
96
+ const nextRemaining = result.remaining_tables ?? [];
97
+ // ── Process each table ──────────────────────────────────────────────
98
+ for (const [table, rows] of Object.entries(result.tables ?? {})) {
99
+ SyncInfoUpdater.markLogStart({ type: "pull", table });
100
+ try {
101
+ if (!rows?.length) {
102
+ logNoUpdates(table);
103
+ SyncInfoUpdater.markLogSuccess({ type: "pull", table });
104
+ continue;
105
+ }
106
+ const toDelete = [];
107
+ const toUpsert = [];
108
+ let prevMaxSyncedAt = null;
109
+ let prevMaxDeletedAt = null;
110
+ for (const row of rows) {
111
+ if (!row?.id) {
112
+ logWarn(`[Supastash] Batch: skipped row without id from "${table}"`);
113
+ continue;
114
+ }
115
+ prevMaxSyncedAt = returnMaxDate({ row, prevMax: prevMaxSyncedAt, col: tsCol });
116
+ prevMaxDeletedAt = returnMaxDate({ row, prevMax: prevMaxDeletedAt, col: "deleted_at" });
117
+ if (row.deleted_at) {
118
+ toDelete.push(row.id);
119
+ }
120
+ else {
121
+ toUpsert.push(row);
122
+ }
123
+ }
124
+ SyncInfoUpdater.setUnsyncedDataCount({ amount: toUpsert.length, type: "pull", table });
125
+ SyncInfoUpdater.setUnsyncedDeletedCount({ amount: toDelete.length, type: "pull", table });
126
+ // Delete soft-deleted rows
127
+ if (toDelete.length > 0) {
128
+ for (let i = 0; i < toDelete.length; i += CHUNK_SIZE) {
129
+ const slice = toDelete.slice(i, i + CHUNK_SIZE);
130
+ const placeholders = slice.map(() => "?").join(", ");
131
+ await db.runAsync(`DELETE FROM ${table} WHERE id IN (${placeholders})`, slice);
132
+ }
133
+ }
134
+ // Upsert live rows
135
+ if (toUpsert.length > 0) {
136
+ await upsertChunkData({ table, records: toUpsert });
137
+ }
138
+ // Update sync cursor so the next round starts from the right place
139
+ if (prevMaxSyncedAt || prevMaxDeletedAt) {
140
+ await setSupastashSyncStatus(table, tableFilters.get(table) ?? [], {
141
+ lastSyncedAt: prevMaxSyncedAt?.value ?? undefined,
142
+ lastDeletedAt: prevMaxDeletedAt?.value ?? undefined,
143
+ lastSyncedAtPk: prevMaxSyncedAt?.pk ?? null,
144
+ filterNamespace: "global",
145
+ });
146
+ }
147
+ if (toUpsert.length > 0 || toDelete.length > 0) {
148
+ refreshScreen(table);
149
+ }
150
+ log(`[Supastash] Batch received ${rows.length} rows for "${table}" ` +
151
+ `(u${toUpsert.length}/d${toDelete.length})`);
152
+ SyncInfoUpdater.markLogSuccess({ type: "pull", table });
153
+ }
154
+ catch (e) {
155
+ SyncInfoUpdater.markLogError({
156
+ type: "pull",
157
+ table,
158
+ lastError: e,
159
+ errorCount: 1,
160
+ });
161
+ logError(`[Supastash] Batch pull failed for "${table}"`, e);
162
+ }
163
+ finally {
164
+ // Mark table as fully completed only once it leaves remaining_tables
165
+ if (!nextRemaining.includes(table)) {
166
+ completedTables.add(table);
167
+ SyncInfoUpdater.setTablesCompleted({
168
+ amount: completedTables.size,
169
+ type: "pull",
170
+ });
171
+ }
172
+ }
173
+ }
174
+ remainingTables = nextRemaining;
175
+ }
176
+ }
177
+ catch (error) {
178
+ logError("[Supastash] Error in batch pull from remote", error);
179
+ }
180
+ finally {
181
+ SyncInfoUpdater.reset({ type: "pull" });
182
+ }
183
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/shared/core/config/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,eAAe,EACf,0BAA0B,EAC3B,MAAM,mCAAmC,CAAC;AAmC3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAEH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,0BAA0B,EACrE,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG;IAAE,gBAAgB,EAAE,CAAC,CAAA;CAAE,QAwCrD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAChC,CAAC,SAAS,0BAA0B,KACjC,eAAe,CAAC,CAAC,CAAC,CAEtB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,qBAAqB,CACnC,CAAC,SAAS,0BAA0B,EACpC,MAAM,EAAE;IACR,SAAS,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG;QAC9B,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;KAC3B,CAAC;CACH,QAEA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/shared/core/config/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,eAAe,EACf,0BAA0B,EAC3B,MAAM,mCAAmC,CAAC;AAqC3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAEH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,0BAA0B,EACrE,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG;IAAE,gBAAgB,EAAE,CAAC,CAAA;CAAE,QAwCrD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAChC,CAAC,SAAS,0BAA0B,KACjC,eAAe,CAAC,CAAC,CAAC,CAEtB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,qBAAqB,CACnC,CAAC,SAAS,0BAA0B,EACpC,MAAM,EAAE;IACR,SAAS,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG;QAC9B,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;KAC3B,CAAC;CACH,QAEA"}
@@ -16,6 +16,8 @@ let _config = {
16
16
  },
17
17
  platform: "native",
18
18
  replicationMode: "client-side",
19
+ useBatchPullSync: false,
20
+ useBatchSchemaFetch: false,
19
21
  hasEnabledSimpleNullHandling: false,
20
22
  maxSyncLookbackDays: 365,
21
23
  perTableSyncLookbackDays: undefined,
@@ -1,4 +1,5 @@
1
1
  import { SupastashFilter } from "../../types/realtimeData.types";
2
+ import { RpcTableFilters } from "../../types/rpcFilter.types";
2
3
  /**
3
4
  * useSupastashFilters
4
5
  *
@@ -26,11 +27,15 @@ import { SupastashFilter } from "../../types/realtimeData.types";
26
27
  * });
27
28
  * ```
28
29
  *
29
- * @param {SupastashFilter} filters - An object where each key is a table name, and its value is
30
- * an array of `SupastashFilter` objects that define the filter criteria for that table's pull sync.
30
+ * @param {Record<string, SupastashFilter[]>} filters - Per-table filters applied to both the
31
+ * per-table pull path and (automatically converted) the batch RPC pull path.
32
+ * Covers eq, neq, gt, gte, lt, lte, in, is (null / not-null), and or-groups.
33
+ * @param {RpcTableFilters} rpcFilters - Optional supplemental RPC filter nodes for the batch
34
+ * pull path only. Only needed when you require `and` groups, which `SupastashFilter` doesn't
35
+ * support. These are merged with the auto-converted `filters` before the RPC call.
31
36
  *
32
- * @note This hook does not re-run unless the `filters` object reference changes.
37
+ * @note This hook does not re-run unless the `filters` or `rpcFilters` object reference changes.
33
38
  * To force re-evaluation, pass a fresh object (not just mutated data).
34
39
  */
35
- export declare function useSupastashFilters(filters?: Record<string, SupastashFilter[]>): void;
40
+ export declare function useSupastashFilters(filters?: Record<string, SupastashFilter[]>, rpcFilters?: RpcTableFilters): void;
36
41
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/shared/hooks/supastashFilters/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAMjE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,EAAE,CAAC,QAyD5C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/shared/hooks/supastashFilters/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAO9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,EAAE,CAAC,EAC3C,UAAU,CAAC,EAAE,eAAe,QA6D7B"}
@@ -2,6 +2,7 @@ import { useEffect } from "react";
2
2
  import { filterTracker, tableFilters, tableFiltersUsed, } from "../../store/tableFilters";
3
3
  import { logWarn } from "../../utils/logs";
4
4
  import { ReusedHelpers } from "../../utils/reusedHelpers";
5
+ import { updateRpcFilters } from "../../utils/sync/pullFromRemote/updateRpcFilters";
5
6
  import { warnOnMisMatch } from "../../utils/sync/pullFromRemote/validateFilters";
6
7
  import { checkIfTableExist } from "../../utils/tableValidator";
7
8
  /**
@@ -31,16 +32,23 @@ import { checkIfTableExist } from "../../utils/tableValidator";
31
32
  * });
32
33
  * ```
33
34
  *
34
- * @param {SupastashFilter} filters - An object where each key is a table name, and its value is
35
- * an array of `SupastashFilter` objects that define the filter criteria for that table's pull sync.
35
+ * @param {Record<string, SupastashFilter[]>} filters - Per-table filters applied to both the
36
+ * per-table pull path and (automatically converted) the batch RPC pull path.
37
+ * Covers eq, neq, gt, gte, lt, lte, in, is (null / not-null), and or-groups.
38
+ * @param {RpcTableFilters} rpcFilters - Optional supplemental RPC filter nodes for the batch
39
+ * pull path only. Only needed when you require `and` groups, which `SupastashFilter` doesn't
40
+ * support. These are merged with the auto-converted `filters` before the RPC call.
36
41
  *
37
- * @note This hook does not re-run unless the `filters` object reference changes.
42
+ * @note This hook does not re-run unless the `filters` or `rpcFilters` object reference changes.
38
43
  * To force re-evaluation, pass a fresh object (not just mutated data).
39
44
  */
40
- export function useSupastashFilters(filters) {
45
+ export function useSupastashFilters(filters, rpcFilters) {
41
46
  useEffect(() => {
42
47
  let cancelled = false;
43
48
  async function run() {
49
+ if (rpcFilters) {
50
+ await updateRpcFilters(rpcFilters);
51
+ }
44
52
  if (!filters)
45
53
  return;
46
54
  const incoming = Object.keys(filters);
@@ -82,5 +90,5 @@ export function useSupastashFilters(filters) {
82
90
  return () => {
83
91
  cancelled = true;
84
92
  };
85
- }, [filters]);
93
+ }, [filters, rpcFilters]);
86
94
  }
@@ -0,0 +1,7 @@
1
+ import { RpcFilterNode } from "../types/rpcFilter.types";
2
+ /**
3
+ * Stores per-table RPC filter nodes used by the batch pull sync path.
4
+ * Keyed by table name, value is the array of RpcFilterNode to apply.
5
+ */
6
+ export declare const rpcTableFilters: Map<string, RpcFilterNode[]>;
7
+ //# sourceMappingURL=rpcTableFilters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpcTableFilters.d.ts","sourceRoot":"","sources":["../../../src/shared/store/rpcTableFilters.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEzD;;;GAGG;AACH,eAAO,MAAM,eAAe,8BAAqC,CAAC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Stores per-table RPC filter nodes used by the batch pull sync path.
3
+ * Keyed by table name, value is the array of RpcFilterNode to apply.
4
+ */
5
+ export const rpcTableFilters = new Map();
@@ -0,0 +1,23 @@
1
+ export type RpcFilterOp =
2
+ | "eq"
3
+ | "neq"
4
+ | "gt"
5
+ | "gte"
6
+ | "lt"
7
+ | "lte"
8
+ | "in"
9
+ | "is_null"
10
+ | "is_not_null";
11
+
12
+ export type RpcSimpleFilter = {
13
+ col: string;
14
+ op: RpcFilterOp;
15
+ val?: string | number;
16
+ };
17
+
18
+ export type RpcFilterNode =
19
+ | RpcSimpleFilter
20
+ | { or: RpcFilterNode[] }
21
+ | { and: RpcFilterNode[] };
22
+
23
+ export type RpcTableFilters = Record<string, RpcFilterNode[]>;
@@ -89,12 +89,12 @@ export type SupastashConfig<T extends SupastashSQLiteClientTypes> = {
89
89
  sqliteClient: T extends "expo"
90
90
  ? ExpoSQLiteClient
91
91
  : T extends "rn-storage"
92
- ? RNStorageSQLiteClient
93
- : T extends "rn-nitro"
94
- ? RNSqliteNitroClient
95
- : T extends "tauri"
96
- ? TauriSQLiteClient
97
- : null;
92
+ ? RNStorageSQLiteClient
93
+ : T extends "rn-nitro"
94
+ ? RNSqliteNitroClient
95
+ : T extends "tauri"
96
+ ? TauriSQLiteClient
97
+ : null;
98
98
 
99
99
  /**
100
100
  * Runtime platform.
@@ -319,6 +319,34 @@ export type SupastashConfig<T extends SupastashSQLiteClientTypes> = {
319
319
  * Supastash docs: https://0xzekea.github.io/supastash/docs/sync-calls#%EF%B8%8F-pushrpcpath-custom-batch-sync-rpc
320
320
  */
321
321
  pushRPCPath?: string;
322
+ /**
323
+ * When true, pull sync uses a single `supastash_pull_sync` RPC call to fetch
324
+ * all tables in one round trip instead of per-table queries.
325
+ *
326
+ * The RPC handles pagination internally via `remaining_tables` — Supastash
327
+ * will keep calling until all tables are fully synced.
328
+ *
329
+ * Use `updateRpcFilters` to set per-table filters for this mode.
330
+ *
331
+ * ⚠️ Requires the `supastash_pull_sync` Postgres function to be deployed
332
+ * and RLS to be enabled on every table you expose.
333
+ *
334
+ * @default false
335
+ */
336
+ useBatchPullSync?: boolean;
337
+ /**
338
+ * When true, Supastash fetches column metadata for all tables in a single
339
+ * `get_table_schemas` RPC call at the start of each sync cycle instead of
340
+ * calling `get_table_schema` once per table on demand.
341
+ *
342
+ * Warms the in-memory and SQLite schema caches up front so every subsequent
343
+ * per-table lookup is served from cache with zero extra network calls.
344
+ *
345
+ * Requires the `get_table_schemas` Postgres function to be deployed.
346
+ *
347
+ * @default false
348
+ */
349
+ useBatchSchemaFetch?: boolean;
322
350
  /**
323
351
  * Controls how Supastash operates at runtime.
324
352
  *
@@ -443,7 +471,7 @@ export interface SupastashSQLiteExecutor {
443
471
  */
444
472
  queryOne<T = any>(
445
473
  sql: string,
446
- params?: Record<string, any>
474
+ params?: Record<string, any>,
447
475
  ): Promise<T | null>;
448
476
  /**
449
477
  * Executes a write or mutation statement.
@@ -500,14 +528,14 @@ export interface SupastashSQLiteDatabase extends SupastashSQLiteExecutor {
500
528
  * @returns A Promise that resolves when the transaction is complete
501
529
  */
502
530
  withTransaction(
503
- fn: (tx: SupastashSQLiteExecutor) => Promise<void> | void
531
+ fn: (tx: SupastashSQLiteExecutor) => Promise<void> | void,
504
532
  ): Promise<void>;
505
533
  }
506
534
 
507
535
  export interface SupastashSQLiteAdapter<TClient = any> {
508
536
  openDatabaseAsync(
509
537
  name: string,
510
- sqliteClient: TClient
538
+ sqliteClient: TClient,
511
539
  ): Promise<SupastashSQLiteDatabase>;
512
540
  }
513
541
 
@@ -515,7 +543,7 @@ export interface ExpoSQLiteClient {
515
543
  openDatabaseAsync: (
516
544
  databaseName: string,
517
545
  options?: SQLiteOpenOptions | undefined,
518
- directory?: string | undefined
546
+ directory?: string | undefined,
519
547
  ) => Promise<ExpoSQLiteDatabase>;
520
548
  }
521
549
 
@@ -1 +1 @@
1
- {"version":3,"file":"createSyncStatus.d.ts","sourceRoot":"","sources":["../../../../src/shared/utils/schema/createSyncStatus.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,wBAAsB,wBAAwB,kBAE7C;AAED,eAAO,MAAM,sBAAsB,0ZAUhC,CAAC;AAEJ,eAAO,MAAM,6BAA6B,6eAezC,CAAC;AAEF,eAAO,MAAM,wBAAwB,mFAEpC,CAAC;AAEF,eAAO,MAAM,oBAAoB,2GAGhC,CAAC;AAEF,eAAO,MAAM,2BAA2B,yHAGvC,CAAC;AAIF;;;;GAIG;AACH,wBAAsB,qBAAqB,kBAuB1C;AAED,wEAAwE;AACxE,wBAAgB,yBAAyB,SAExC"}
1
+ {"version":3,"file":"createSyncStatus.d.ts","sourceRoot":"","sources":["../../../../src/shared/utils/schema/createSyncStatus.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,wBAAsB,wBAAwB,kBAE7C;AAED,eAAO,MAAM,sBAAsB,0ZAUhC,CAAC;AAEJ,eAAO,MAAM,6BAA6B,6eAezC,CAAC;AAEF,eAAO,MAAM,wBAAwB,mFAEpC,CAAC;AAEF,eAAO,MAAM,oBAAoB,2GAGhC,CAAC;AAEF,eAAO,MAAM,2BAA2B,yHAGvC,CAAC;AAIF;;;;GAIG;AACH,wBAAsB,qBAAqB,kBAiC1C;AAED,wEAAwE;AACxE,wBAAgB,yBAAyB,SAExC"}
@@ -68,7 +68,11 @@ export async function createSyncStatusTable() {
68
68
  await db.execAsync(INDEX_SYNC_MARKS_SQL);
69
69
  }
70
70
  try {
71
- await db.execAsync(ADD_PK_TO_SYNC_MARKS_SQL);
71
+ const columns = await db.getAllAsync(`SELECT name FROM pragma_table_info('supastash_sync_marks')`);
72
+ const hasColumn = columns.some((column) => column.name === "last_synced_at_pk");
73
+ if (!hasColumn) {
74
+ await db.execAsync(ADD_PK_TO_SYNC_MARKS_SQL);
75
+ }
72
76
  }
73
77
  catch {
74
78
  // Ignore — column already exists
@@ -0,0 +1,9 @@
1
+ import { SupastashFilter } from "../../../types/realtimeData.types";
2
+ import { RpcFilterNode } from "../../../types/rpcFilter.types";
3
+ /**
4
+ * Converts PostgREST-style SupastashFilter[] to RpcFilterNode[].
5
+ * Supports simple filters and { or: [...] } groups.
6
+ * Returns an empty array for an empty/undefined input.
7
+ */
8
+ export declare function postgrestFiltersToRpc(filters: SupastashFilter[] | undefined): RpcFilterNode[];
9
+ //# sourceMappingURL=postgrestToRpc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgrestToRpc.d.ts","sourceRoot":"","sources":["../../../../../src/shared/utils/sync/pullFromRemote/postgrestToRpc.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAC5E,OAAO,EAAE,aAAa,EAAe,MAAM,gCAAgC,CAAC;AA+B5E;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,eAAe,EAAE,GAAG,SAAS,GACrC,aAAa,EAAE,CAgBjB"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Converts a single PostgREST Filter to an RpcFilterNode.
3
+ * Used so that tableFilters registered via useSupastashFilters are
4
+ * automatically applied in the batch pull RPC path.
5
+ */
6
+ function convertFilter(f) {
7
+ const col = String(f.column);
8
+ if (f.operator === "is") {
9
+ return f.value === null
10
+ ? { col, op: "is_null" }
11
+ : { col, op: "is_not_null" };
12
+ }
13
+ // For "in", the SQL compiler expects a comma-separated string
14
+ if (f.operator === "in") {
15
+ const val = Array.isArray(f.value)
16
+ ? f.value.join(",")
17
+ : String(f.value ?? "");
18
+ return { col, op: "in", val };
19
+ }
20
+ return {
21
+ col,
22
+ op: f.operator,
23
+ val: f.value,
24
+ };
25
+ }
26
+ /**
27
+ * Converts PostgREST-style SupastashFilter[] to RpcFilterNode[].
28
+ * Supports simple filters and { or: [...] } groups.
29
+ * Returns an empty array for an empty/undefined input.
30
+ */
31
+ export function postgrestFiltersToRpc(filters) {
32
+ if (!filters?.length)
33
+ return [];
34
+ const result = [];
35
+ for (const f of filters) {
36
+ if ("or" in f) {
37
+ const nodes = f.or
38
+ .map(convertFilter)
39
+ .filter((n) => n !== null);
40
+ if (nodes.length)
41
+ result.push({ or: nodes });
42
+ }
43
+ else {
44
+ const node = convertFilter(f);
45
+ if (node)
46
+ result.push(node);
47
+ }
48
+ }
49
+ return result;
50
+ }
@@ -1,10 +1,13 @@
1
1
  import { SupastashFilter } from "../../../types/realtimeData.types";
2
+ import { RpcTableFilters } from "../../../types/rpcFilter.types";
2
3
  /**
3
- * Updates the filter for the given table
4
- * Non-hook version of useSupastashFilters
4
+ * Updates the filter for the given table.
5
+ * Non-hook version of useSupastashFilters.
5
6
  *
6
- * Filters are validated and stored in the tableFilters store
7
- * @param filters - The filters to update
7
+ * @param filters - PostgREST filters for the standard pull path. Automatically converted
8
+ * and applied in the batch RPC pull path too.
9
+ * @param rpcFilters - Optional supplemental RPC filter nodes. Only needed for `and` groups
10
+ * or other constructs SupastashFilter can't express.
8
11
  */
9
- export declare function updateFilters(filters: SupastashFilter): Promise<void>;
12
+ export declare function updateFilters(filters: SupastashFilter, rpcFilters?: RpcTableFilters): Promise<void>;
10
13
  //# sourceMappingURL=updateFilter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"updateFilter.d.ts","sourceRoot":"","sources":["../../../../../src/shared/utils/sync/pullFromRemote/updateFilter.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAMpE;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,eAAe,iBAwC3D"}
1
+ {"version":3,"file":"updateFilter.d.ts","sourceRoot":"","sources":["../../../../../src/shared/utils/sync/pullFromRemote/updateFilter.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAOjE;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,eAAe,EACxB,UAAU,CAAC,EAAE,eAAe,iBA6C7B"}
@@ -2,15 +2,21 @@ import { filterTracker, tableFilters, tableFiltersUsed, } from "../../../store/t
2
2
  import { logWarn } from "../../logs";
3
3
  import { ReusedHelpers } from "../../reusedHelpers";
4
4
  import { checkIfTableExist } from "../../tableValidator";
5
+ import { updateRpcFilters } from "./updateRpcFilters";
5
6
  import { warnOnMisMatch } from "./validateFilters";
6
7
  /**
7
- * Updates the filter for the given table
8
- * Non-hook version of useSupastashFilters
8
+ * Updates the filter for the given table.
9
+ * Non-hook version of useSupastashFilters.
9
10
  *
10
- * Filters are validated and stored in the tableFilters store
11
- * @param filters - The filters to update
11
+ * @param filters - PostgREST filters for the standard pull path. Automatically converted
12
+ * and applied in the batch RPC pull path too.
13
+ * @param rpcFilters - Optional supplemental RPC filter nodes. Only needed for `and` groups
14
+ * or other constructs SupastashFilter can't express.
12
15
  */
13
- export async function updateFilters(filters) {
16
+ export async function updateFilters(filters, rpcFilters) {
17
+ if (rpcFilters) {
18
+ await updateRpcFilters(rpcFilters);
19
+ }
14
20
  const incoming = Object.keys(filters ?? {});
15
21
  // Remove stale tables
16
22
  for (const t of Array.from(tableFilters.keys())) {
@@ -0,0 +1,12 @@
1
+ import { RpcTableFilters } from "../../../types/rpcFilter.types";
2
+ /**
3
+ * Registers explicit RPC filter nodes for the batch pull path (useBatchPullSync: true).
4
+ *
5
+ * Only needed for constructs SupastashFilter can't express (e.g. `and` groups).
6
+ * Filters registered via useSupastashFilters / updateFilters are automatically
7
+ * converted and applied in the batch path — no separate call needed for those.
8
+ *
9
+ * Called automatically by useSupastashFilters / updateFilters.
10
+ */
11
+ export declare function updateRpcFilters(filters?: RpcTableFilters): Promise<void>;
12
+ //# sourceMappingURL=updateRpcFilters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"updateRpcFilters.d.ts","sourceRoot":"","sources":["../../../../../src/shared/utils/sync/pullFromRemote/updateRpcFilters.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAIhF;;;;;;;;GAQG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CA8B/E"}
@@ -0,0 +1,36 @@
1
+ import { rpcTableFilters } from "../../../store/rpcTableFilters";
2
+ import { logWarn } from "../../logs";
3
+ import { checkIfTableExist } from "../../tableValidator";
4
+ /**
5
+ * Registers explicit RPC filter nodes for the batch pull path (useBatchPullSync: true).
6
+ *
7
+ * Only needed for constructs SupastashFilter can't express (e.g. `and` groups).
8
+ * Filters registered via useSupastashFilters / updateFilters are automatically
9
+ * converted and applied in the batch path — no separate call needed for those.
10
+ *
11
+ * Called automatically by useSupastashFilters / updateFilters.
12
+ */
13
+ export async function updateRpcFilters(filters) {
14
+ const incoming = Object.keys(filters ?? {});
15
+ // Remove stale tables no longer in the incoming set
16
+ for (const t of Array.from(rpcTableFilters.keys())) {
17
+ if (!incoming.includes(t)) {
18
+ rpcTableFilters.delete(t);
19
+ }
20
+ }
21
+ if (!incoming.length)
22
+ return;
23
+ const existence = await Promise.all(incoming.map(async (t) => [t, await checkIfTableExist(t)]));
24
+ for (const [table, exists] of existence) {
25
+ if (!exists) {
26
+ logWarn(`[Supastash] Table '${table}' does not exist; skipping RPC filters`);
27
+ continue;
28
+ }
29
+ const nodes = (filters[table] ?? []);
30
+ if (!nodes.length) {
31
+ rpcTableFilters.delete(table);
32
+ continue;
33
+ }
34
+ rpcTableFilters.set(table, nodes.map((n) => ({ ...n })));
35
+ }
36
+ }
@@ -1,4 +1,16 @@
1
1
  import { TableSchema } from "../../../types/realtimeData.types";
2
2
  export declare function appendSyncedAt(schema: TableSchema[]): TableSchema[];
3
+ /**
4
+ * Fetches column metadata for all supplied tables in a single RPC call
5
+ * and warms both the in-memory cache and the SQLite fallback store.
6
+ *
7
+ * Requires `useBatchSchemaFetch: true` in config and the
8
+ * `get_table_schemas` Postgres function to be deployed.
9
+ *
10
+ * Tables already in the memory cache are skipped.
11
+ * Validation errors for individual tables are logged and skipped —
12
+ * they will surface as normal errors when that table is first used.
13
+ */
14
+ export declare function prefetchRemoteTableSchemas(tables: string[]): Promise<void>;
3
15
  export declare function getRemoteTableSchema(table: string): Promise<TableSchema[] | null>;
4
16
  //# sourceMappingURL=remoteSchema.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"remoteSchema.d.ts","sourceRoot":"","sources":["../../../../../src/shared/utils/sync/status/remoteSchema.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,mCAAmC,CAAC;AA4HhE,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,iBASnD;AAID,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,CAkD/B"}
1
+ {"version":3,"file":"remoteSchema.d.ts","sourceRoot":"","sources":["../../../../../src/shared/utils/sync/status/remoteSchema.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,mCAAmC,CAAC;AA4HhE,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,iBASnD;AAID;;;;;;;;;;GAUG;AACH,wBAAsB,0BAA0B,CAC9C,MAAM,EAAE,MAAM,EAAE,GACf,OAAO,CAAC,IAAI,CAAC,CAyCf;AAED,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,CAkD/B"}
@@ -98,6 +98,52 @@ export function appendSyncedAt(schema) {
98
98
  ];
99
99
  }
100
100
  const localSchemaCache = new Map();
101
+ /**
102
+ * Fetches column metadata for all supplied tables in a single RPC call
103
+ * and warms both the in-memory cache and the SQLite fallback store.
104
+ *
105
+ * Requires `useBatchSchemaFetch: true` in config and the
106
+ * `get_table_schemas` Postgres function to be deployed.
107
+ *
108
+ * Tables already in the memory cache are skipped.
109
+ * Validation errors for individual tables are logged and skipped —
110
+ * they will surface as normal errors when that table is first used.
111
+ */
112
+ export async function prefetchRemoteTableSchemas(tables) {
113
+ const config = getSupastashConfig();
114
+ const supabase = config?.supabaseClient;
115
+ if (!supabase)
116
+ return;
117
+ const online = await isOnline();
118
+ if (!online)
119
+ return;
120
+ const toFetch = tables.filter((t) => !tableSchemaData.has(t));
121
+ if (!toFetch.length)
122
+ return;
123
+ const { data, error } = await supabase.rpc("get_table_schemas", {
124
+ p_tables: toFetch,
125
+ });
126
+ if (error || !data) {
127
+ if (error && !isNetworkError(error)) {
128
+ log(`[Supastash] Error batch-fetching table schemas: ${error.message}
129
+ You can find more information in the Supastash docs: ${SERVER_SIDE_DOCS_URL}`);
130
+ }
131
+ return;
132
+ }
133
+ await ensureRemoteSchemaTableExists();
134
+ for (const [table, schema] of Object.entries(data)) {
135
+ if (!Array.isArray(schema))
136
+ continue;
137
+ try {
138
+ validatePayloadForTable(schema, table);
139
+ await upsertRemoteSchema(table, schema);
140
+ tableSchemaData.set(table, schema);
141
+ }
142
+ catch (e) {
143
+ logWarn(`[Supastash] Schema validation failed for "${table}" during batch fetch: ${e?.message}`);
144
+ }
145
+ }
146
+ }
101
147
  export async function getRemoteTableSchema(table) {
102
148
  const config = getSupastashConfig();
103
149
  const supabase = config?.supabaseClient;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supastash",
3
- "version": "0.2.10",
3
+ "version": "0.2.11",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "type": "module",