supastash 0.1.62 → 0.2.0

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 (151) hide show
  1. package/README.md +17 -5
  2. package/dist/core/config/index.d.ts.map +1 -1
  3. package/dist/core/config/index.js +5 -1
  4. package/dist/db/adapters/expo_sqlite.d.ts.map +1 -1
  5. package/dist/db/adapters/expo_sqlite.js +11 -0
  6. package/dist/db/adapters/rn_nitro.d.ts.map +1 -1
  7. package/dist/db/adapters/rn_nitro.js +20 -0
  8. package/dist/db/adapters/rn_sqlite_storage.d.ts.map +1 -1
  9. package/dist/db/adapters/rn_sqlite_storage.js +27 -0
  10. package/dist/hooks/supastashData/fetchCalls.d.ts.map +1 -1
  11. package/dist/hooks/supastashData/index.d.ts.map +1 -1
  12. package/dist/hooks/supastashData/index.js +12 -4
  13. package/dist/hooks/supastashData/realtimeSubscription.js +4 -4
  14. package/dist/hooks/supastashData/registerSub.d.ts +3 -3
  15. package/dist/hooks/supastashData/registerSub.d.ts.map +1 -1
  16. package/dist/hooks/supastashData/registerSub.js +3 -3
  17. package/dist/hooks/supastashFilters/index.d.ts +2 -2
  18. package/dist/hooks/supastashFilters/index.d.ts.map +1 -1
  19. package/dist/hooks/supastashFilters/index.js +6 -4
  20. package/dist/hooks/supastashLiteQuery/index.d.ts.map +1 -1
  21. package/dist/hooks/supastashLiteQuery/index.js +12 -2
  22. package/dist/hooks/syncEngine/pushLocal/index.js +1 -1
  23. package/dist/index.d.ts +3 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +1 -0
  26. package/dist/store/tableFilters.d.ts +3 -3
  27. package/dist/store/tableFilters.d.ts.map +1 -1
  28. package/dist/store/tx.d.ts +3 -0
  29. package/dist/store/tx.d.ts.map +1 -0
  30. package/dist/store/tx.js +1 -0
  31. package/dist/types/expoSqlite.types.d.ts +3 -13
  32. package/dist/types/index.d.ts +1 -1
  33. package/dist/types/index.d.ts.map +1 -1
  34. package/dist/types/liteQuery.types.d.ts +2 -2
  35. package/dist/types/query.types.d.ts +24 -1
  36. package/dist/types/realtimeData.types.d.ts +8 -4
  37. package/dist/types/supastashConfig.types.d.ts +83 -6
  38. package/dist/types/syncEngine.types.d.ts +49 -7
  39. package/dist/utils/errorHandler.d.ts +6 -0
  40. package/dist/utils/errorHandler.d.ts.map +1 -0
  41. package/dist/utils/errorHandler.js +8 -0
  42. package/dist/utils/fetchData/buildFilter.d.ts +8 -4
  43. package/dist/utils/fetchData/buildFilter.d.ts.map +1 -1
  44. package/dist/utils/fetchData/createTable.d.ts.map +1 -1
  45. package/dist/utils/fetchData/createTable.js +3 -46
  46. package/dist/utils/fetchData/deleteData.d.ts.map +1 -1
  47. package/dist/utils/fetchData/deleteData.js +6 -3
  48. package/dist/utils/fetchData/fetchLocalData.d.ts +2 -2
  49. package/dist/utils/fetchData/fetchLocalData.d.ts.map +1 -1
  50. package/dist/utils/fetchData/initialFetch.d.ts +2 -2
  51. package/dist/utils/fetchData/initialFetch.d.ts.map +1 -1
  52. package/dist/utils/fetchData/liteHelpers.d.ts +2 -2
  53. package/dist/utils/fetchData/liteHelpers.d.ts.map +1 -1
  54. package/dist/utils/fetchData/liteHelpers.js +14 -119
  55. package/dist/utils/fetchData/realTimeCall.js +2 -2
  56. package/dist/utils/fetchData/receiveData.js +1 -1
  57. package/dist/utils/query/builder/crud.d.ts +5 -5
  58. package/dist/utils/query/builder/crud.d.ts.map +1 -1
  59. package/dist/utils/query/builder/filters.d.ts +36 -12
  60. package/dist/utils/query/builder/filters.d.ts.map +1 -1
  61. package/dist/utils/query/builder/filters.js +32 -0
  62. package/dist/utils/query/builder/index.d.ts +29 -1
  63. package/dist/utils/query/builder/index.d.ts.map +1 -1
  64. package/dist/utils/query/builder/index.js +77 -1
  65. package/dist/utils/query/builder/mainQuery.d.ts.map +1 -1
  66. package/dist/utils/query/builder/mainQuery.js +17 -2
  67. package/dist/utils/query/helpers/localDb/getLocalMethod.d.ts +2 -2
  68. package/dist/utils/query/helpers/localDb/getLocalMethod.d.ts.map +1 -1
  69. package/dist/utils/query/helpers/localDb/getLocalMethod.js +7 -6
  70. package/dist/utils/query/helpers/localDb/insertMany.d.ts +3 -0
  71. package/dist/utils/query/helpers/localDb/insertMany.d.ts.map +1 -1
  72. package/dist/utils/query/helpers/localDb/insertMany.js +10 -3
  73. package/dist/utils/query/helpers/localDb/localQueryBuilder.d.ts +6 -6
  74. package/dist/utils/query/helpers/localDb/localQueryBuilder.d.ts.map +1 -1
  75. package/dist/utils/query/helpers/localDb/localQueryBuilder.js +17 -10
  76. package/dist/utils/query/helpers/localDb/upsertMany.d.ts +3 -0
  77. package/dist/utils/query/helpers/localDb/upsertMany.d.ts.map +1 -1
  78. package/dist/utils/query/helpers/localDb/upsertMany.js +9 -2
  79. package/dist/utils/query/helpers/mainQueryHelpers.d.ts.map +1 -1
  80. package/dist/utils/query/helpers/mainQueryHelpers.js +38 -8
  81. package/dist/utils/query/helpers/queueRemote.d.ts.map +1 -1
  82. package/dist/utils/query/helpers/queueRemote.js +33 -24
  83. package/dist/utils/query/localDbQuery/delete.d.ts +9 -3
  84. package/dist/utils/query/localDbQuery/delete.d.ts.map +1 -1
  85. package/dist/utils/query/localDbQuery/delete.js +16 -5
  86. package/dist/utils/query/localDbQuery/index.d.ts.map +1 -1
  87. package/dist/utils/query/localDbQuery/index.js +3 -3
  88. package/dist/utils/query/localDbQuery/insert.d.ts +2 -2
  89. package/dist/utils/query/localDbQuery/insert.d.ts.map +1 -1
  90. package/dist/utils/query/localDbQuery/insert.js +8 -5
  91. package/dist/utils/query/localDbQuery/select.d.ts +2 -2
  92. package/dist/utils/query/localDbQuery/select.d.ts.map +1 -1
  93. package/dist/utils/query/localDbQuery/select.js +5 -2
  94. package/dist/utils/query/localDbQuery/update.d.ts +2 -2
  95. package/dist/utils/query/localDbQuery/update.d.ts.map +1 -1
  96. package/dist/utils/query/localDbQuery/update.js +5 -2
  97. package/dist/utils/query/localDbQuery/upsert.d.ts +2 -2
  98. package/dist/utils/query/localDbQuery/upsert.d.ts.map +1 -1
  99. package/dist/utils/query/localDbQuery/upsert.js +8 -3
  100. package/dist/utils/query/remoteQuery/supabaseQuery.d.ts.map +1 -1
  101. package/dist/utils/query/remoteQuery/supabaseQuery.js +4 -1
  102. package/dist/utils/reusedHelpers.d.ts +8 -0
  103. package/dist/utils/reusedHelpers.d.ts.map +1 -0
  104. package/dist/utils/reusedHelpers.js +162 -0
  105. package/dist/utils/schema/createSyncStatus.d.ts +3 -1
  106. package/dist/utils/schema/createSyncStatus.d.ts.map +1 -1
  107. package/dist/utils/schema/createSyncStatus.js +30 -3
  108. package/dist/utils/sync/pullFromRemote/fetchOlder.d.ts +44 -0
  109. package/dist/utils/sync/pullFromRemote/fetchOlder.d.ts.map +1 -0
  110. package/dist/utils/sync/pullFromRemote/fetchOlder.js +55 -0
  111. package/dist/utils/sync/pullFromRemote/fetchOlderHelpers.d.ts +33 -0
  112. package/dist/utils/sync/pullFromRemote/fetchOlderHelpers.d.ts.map +1 -0
  113. package/dist/utils/sync/pullFromRemote/fetchOlderHelpers.js +110 -0
  114. package/dist/utils/sync/pullFromRemote/helpers.d.ts +10 -7
  115. package/dist/utils/sync/pullFromRemote/helpers.d.ts.map +1 -1
  116. package/dist/utils/sync/pullFromRemote/helpers.js +20 -14
  117. package/dist/utils/sync/pullFromRemote/pullData.d.ts +2 -3
  118. package/dist/utils/sync/pullFromRemote/pullData.d.ts.map +1 -1
  119. package/dist/utils/sync/pullFromRemote/pullData.js +4 -9
  120. package/dist/utils/sync/pullFromRemote/pullDeletedData.d.ts +8 -5
  121. package/dist/utils/sync/pullFromRemote/updateFilter.d.ts +1 -1
  122. package/dist/utils/sync/pullFromRemote/updateFilter.d.ts.map +1 -1
  123. package/dist/utils/sync/pullFromRemote/updateFilter.js +5 -3
  124. package/dist/utils/sync/pullFromRemote/updateLocalDb.d.ts +4 -3
  125. package/dist/utils/sync/pullFromRemote/updateLocalDb.d.ts.map +1 -1
  126. package/dist/utils/sync/pullFromRemote/updateLocalDb.js +51 -46
  127. package/dist/utils/sync/pullFromRemote/validateFilters.d.ts +2 -4
  128. package/dist/utils/sync/pullFromRemote/validateFilters.d.ts.map +1 -1
  129. package/dist/utils/sync/pullFromRemote/validateFilters.js +9 -63
  130. package/dist/utils/sync/pushLocal/deleteChunks.d.ts.map +1 -1
  131. package/dist/utils/sync/pushLocal/deleteChunks.js +7 -5
  132. package/dist/utils/sync/pushLocal/getAllUnsyncedData.d.ts.map +1 -1
  133. package/dist/utils/sync/pushLocal/getAllUnsyncedData.js +12 -44
  134. package/dist/utils/sync/pushLocal/uploadHelpers.js +1 -1
  135. package/dist/utils/sync/status/filterKey.d.ts +3 -3
  136. package/dist/utils/sync/status/filterKey.d.ts.map +1 -1
  137. package/dist/utils/sync/status/filterKey.js +5 -2
  138. package/dist/utils/sync/status/remoteSchema.d.ts +4 -0
  139. package/dist/utils/sync/status/remoteSchema.d.ts.map +1 -0
  140. package/dist/utils/sync/status/remoteSchema.js +140 -0
  141. package/dist/utils/sync/status/repo.d.ts +5 -5
  142. package/dist/utils/sync/status/repo.d.ts.map +1 -1
  143. package/dist/utils/sync/status/repo.js +29 -23
  144. package/dist/utils/sync/status/services.d.ts +5 -6
  145. package/dist/utils/sync/status/services.d.ts.map +1 -1
  146. package/dist/utils/sync/status/services.js +1 -6
  147. package/dist/utils/sync/status/syncStatus.d.ts +5 -7
  148. package/dist/utils/sync/status/syncStatus.d.ts.map +1 -1
  149. package/dist/utils/sync/status/syncStatus.js +11 -3
  150. package/package.json +2 -2
  151. package/dist/types/supastashFilters.types.d.ts +0 -3
@@ -29,8 +29,10 @@ export async function pullData({ table, filters, batchId, }) {
29
29
  });
30
30
  const db = await getSupastashDb();
31
31
  const syncStatus = await selectSyncStatus(db, table, filters);
32
+ const cfg = getSupastashConfig();
33
+ const tsCol = cfg.replicationMode === "server-side" ? "arrived_at" : "updated_at";
32
34
  const updatedRows = await pageThrough({
33
- tsCol: "updated_at",
35
+ tsCol,
34
36
  since: syncStatus.last_synced_at,
35
37
  table,
36
38
  filters,
@@ -39,7 +41,6 @@ export async function pullData({ table, filters, batchId, }) {
39
41
  });
40
42
  const updatedData = [];
41
43
  let deletedIds = [];
42
- let createdMax = null;
43
44
  let updatedMax = null;
44
45
  let deletedMax = null;
45
46
  for (const r of updatedRows) {
@@ -47,15 +48,10 @@ export async function pullData({ table, filters, batchId, }) {
47
48
  logWarn(`[Supastash] Skipped row without id from "${table}"`);
48
49
  continue;
49
50
  }
50
- createdMax = returnMaxDate({
51
- row: r,
52
- prevMax: createdMax,
53
- col: "created_at",
54
- });
55
51
  updatedMax = returnMaxDate({
56
52
  row: r,
57
53
  prevMax: updatedMax,
58
- col: "updated_at",
54
+ col: tsCol,
59
55
  });
60
56
  // If the row is deleted, add the id to the deleted ids and update the deleted max
61
57
  if (r.deleted_at) {
@@ -75,7 +71,6 @@ export async function pullData({ table, filters, batchId, }) {
75
71
  return null;
76
72
  }
77
73
  const timestamps = {
78
- createdMax: createdMax?.value ?? null,
79
74
  updatedMax: updatedMax?.value ?? null,
80
75
  deletedMax: deletedMax?.value ?? null,
81
76
  updatedMaxPk: updatedMax?.pk ?? null,
@@ -1,12 +1,15 @@
1
1
  import { PayloadData } from "../../../types/query.types";
2
- import { RealtimeFilter } from "../../../types/realtimeData.types";
2
+ import { SupastashFilter } from "../../../types/realtimeData.types";
3
3
  /**
4
4
  * Pulls deleted data from the remote database for a given table
5
5
  * @param table - The table to pull deleted data from
6
6
  * @returns The deleted data from the table as a map of id to record and the reordered data
7
7
  */
8
- export declare function pullDeletedData(table: string, filters?: RealtimeFilter[]): Promise<{
9
- deletedDataMap: Map<string, PayloadData>;
10
- records: PayloadData[];
8
+ export declare function pullDeletedData(
9
+ table: string,
10
+ filters?: SupastashFilter[]
11
+ ): Promise<{
12
+ deletedDataMap: Map<string, PayloadData>;
13
+ records: PayloadData[];
11
14
  } | null>;
12
- //# sourceMappingURL=pullDeletedData.d.ts.map
15
+ //# sourceMappingURL=pullDeletedData.d.ts.map
@@ -1,4 +1,4 @@
1
- import { SupastashFilter } from "../../../types/supastashFilters.types";
1
+ import { SupastashFilter } from "../../../types/realtimeData.types";
2
2
  /**
3
3
  * Updates the filter for the given table
4
4
  * Non-hook version of useSupastashFilters
@@ -1 +1 @@
1
- {"version":3,"file":"updateFilter.d.ts","sourceRoot":"","sources":["../../../../src/utils/sync/pullFromRemote/updateFilter.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;AAKxE;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,eAAe,iBAuC3D"}
1
+ {"version":3,"file":"updateFilter.d.ts","sourceRoot":"","sources":["../../../../src/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,7 +1,8 @@
1
1
  import { filterTracker, tableFilters, tableFiltersUsed, } from "../../../store/tableFilters";
2
2
  import { logWarn } from "../../logs";
3
+ import { ReusedHelpers } from "../../reusedHelpers";
3
4
  import { checkIfTableExist } from "../../tableValidator";
4
- import isValidFilter, { warnOnMisMatch } from "./validateFilters";
5
+ import { warnOnMisMatch } from "./validateFilters";
5
6
  /**
6
7
  * Updates the filter for the given table
7
8
  * Non-hook version of useSupastashFilters
@@ -26,8 +27,9 @@ export async function updateFilters(filters) {
26
27
  logWarn(`Table '${table}' does not exist; skipping filters`);
27
28
  continue;
28
29
  }
29
- const raw = filters[table] ?? [];
30
- const valid = raw.filter((f) => isValidFilter([f]));
30
+ const raw = (filters[table] ??
31
+ []);
32
+ const valid = raw.filter((f) => ReusedHelpers.isValidFilter([f]));
31
33
  if (!valid.length) {
32
34
  tableFilters.delete(table);
33
35
  tableFiltersUsed.delete(table);
@@ -1,14 +1,15 @@
1
- import { RealtimeFilter } from "../../../types/realtimeData.types";
1
+ import { SupastashFilter } from "../../../types/realtimeData.types";
2
+ import { UpsertDataParams } from "../../../types/syncEngine.types";
2
3
  /**
3
4
  * Updates the local database with the remote changes
4
5
  * @param table - The table to update
5
6
  */
6
- export declare function updateLocalDb(table: string, filters?: RealtimeFilter[], onReceiveData?: (payload: any) => Promise<void>): Promise<void>;
7
+ export declare function updateLocalDb(table: string, filters?: SupastashFilter[], onReceiveData?: (payload: any) => Promise<void>): Promise<void>;
7
8
  /**
8
9
  * Upserts a record into the local database
9
10
  * @param table - The table to upsert the record into
10
11
  * @param record - The record to upsert
11
12
  * @param exists - Whether the record already exists in the database
12
13
  */
13
- export declare function upsertData(table: string, record: any, doesExist?: boolean): Promise<void>;
14
+ export declare function upsertData({ tx, table, record, doesExist, }: UpsertDataParams): Promise<void>;
14
15
  //# sourceMappingURL=updateLocalDb.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"updateLocalDb.d.ts","sourceRoot":"","sources":["../../../../src/utils/sync/pullFromRemote/updateLocalDb.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAkBnE;;;GAGG;AACH,wBAAsB,aAAa,CACjC,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,cAAc,EAAE,EAC1B,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,iBAwHhD;AAID;;;;;GAKG;AACH,wBAAsB,UAAU,CAC9B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,GAAG,EACX,SAAS,CAAC,EAAE,OAAO,iBAsEpB"}
1
+ {"version":3,"file":"updateLocalDb.d.ts","sourceRoot":"","sources":["../../../../src/utils/sync/pullFromRemote/updateLocalDb.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAkBnE;;;GAGG;AACH,wBAAsB,aAAa,CACjC,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,eAAe,EAAE,EAC3B,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,iBA6HhD;AAID;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,EAC/B,EAAE,EACF,KAAK,EACL,MAAM,EACN,SAAS,GACV,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqElC"}
@@ -34,9 +34,8 @@ export async function updateLocalDb(table, filters, onReceiveData) {
34
34
  const db = await getSupastashDb();
35
35
  // Initialize the batch completed map
36
36
  RECEIVED_DATA_COMPLETED_MAP[batchId] = {
37
- created_at: DEFAULT_RECEIVED_DATA_COMPLETED,
37
+ arrived_at: DEFAULT_RECEIVED_DATA_COMPLETED,
38
38
  updated_at: DEFAULT_RECEIVED_DATA_COMPLETED,
39
- deleted_at: DEFAULT_RECEIVED_DATA_COMPLETED,
40
39
  };
41
40
  let refreshNeeded = false;
42
41
  while (true) {
@@ -59,55 +58,61 @@ export async function updateLocalDb(table, filters, onReceiveData) {
59
58
  });
60
59
  refreshNeeded = !!data?.length || !!deletedIds?.length;
61
60
  // Delete records that are no longer in the remote data
62
- if (deletedIds && deletedIds.length > 0) {
63
- const ids = deletedIds;
64
- for (let i = 0; i < ids.length; i += DELETE_CHUNK) {
65
- const slice = ids.slice(i, i + DELETE_CHUNK);
66
- const placeholders = slice.map(() => "?").join(", ");
67
- await db.runAsync(`DELETE FROM ${table} WHERE id IN (${placeholders})`, slice);
68
- }
69
- }
70
- // Get the local stamp of the records
71
- let localStamp = new Map();
72
- if (data?.length) {
73
- const ids = data.map((r) => r.id).filter(Boolean);
74
- for (let i = 0; i < ids.length; i += DELETE_CHUNK) {
75
- const slice = ids.slice(i, i + DELETE_CHUNK);
76
- const placeholders = slice.map(() => "?").join(", ");
77
- const rows = await db.getAllAsync(`SELECT id, updated_at FROM ${table} WHERE id IN (${placeholders})`, slice);
78
- for (const row of rows ?? []) {
79
- localStamp.set(row.id, row.updated_at ?? null);
61
+ await db.withTransaction(async (tx) => {
62
+ if (deletedIds && deletedIds.length > 0) {
63
+ const ids = deletedIds;
64
+ for (let i = 0; i < ids.length; i += DELETE_CHUNK) {
65
+ const slice = ids.slice(i, i + DELETE_CHUNK);
66
+ const placeholders = slice.map(() => "?").join(", ");
67
+ await tx.runAsync(`DELETE FROM ${table} WHERE id IN (${placeholders})`, slice);
80
68
  }
81
69
  }
82
- }
83
- // Update local database with remote changes
84
- if (data?.length) {
85
- for (let i = 0; i < data.length; i++) {
86
- const record = data[i];
87
- if (!record?.id)
88
- continue;
89
- if (deletedIdSet.has(record.id))
90
- continue;
91
- const localUpdated = localStamp.get(record.id) ?? DEFAULT_DATE;
92
- const remoteUpdated = record.updated_at ?? DEFAULT_DATE;
93
- if (new Date(localUpdated) >= new Date(remoteUpdated)) {
94
- // local is newer or same — skip
95
- continue;
96
- }
97
- if (onReceiveData) {
98
- await onReceiveData(record);
99
- }
100
- else {
101
- await upsertData(table, record, localStamp.has(record.id));
70
+ // Get the local stamp of the records
71
+ let localStamp = new Map();
72
+ if (data?.length) {
73
+ const ids = data.map((r) => r.id).filter(Boolean);
74
+ for (let i = 0; i < ids.length; i += DELETE_CHUNK) {
75
+ const slice = ids.slice(i, i + DELETE_CHUNK);
76
+ const placeholders = slice.map(() => "?").join(", ");
77
+ const rows = await db.getAllAsync(`SELECT id, updated_at FROM ${table} WHERE id IN (${placeholders})`, slice);
78
+ for (const row of rows ?? []) {
79
+ localStamp.set(row.id, row.updated_at ?? null);
80
+ }
102
81
  }
103
- if ((i + 1) % BATCH_SIZE === 0) {
104
- await new Promise((res) => setTimeout(res, 0));
82
+ }
83
+ // Update local database with remote changes
84
+ if (data?.length) {
85
+ for (let i = 0; i < data.length; i++) {
86
+ const record = data[i];
87
+ if (!record?.id)
88
+ continue;
89
+ if (deletedIdSet.has(record.id))
90
+ continue;
91
+ const localUpdated = localStamp.get(record.id) ?? DEFAULT_DATE;
92
+ const remoteUpdated = record.updated_at ?? DEFAULT_DATE;
93
+ if (new Date(localUpdated) >= new Date(remoteUpdated)) {
94
+ // local is newer or same — skip
95
+ continue;
96
+ }
97
+ if (onReceiveData) {
98
+ await onReceiveData(record);
99
+ }
100
+ else {
101
+ await upsertData({
102
+ tx,
103
+ table,
104
+ record,
105
+ doesExist: localStamp.has(record.id),
106
+ });
107
+ }
108
+ if ((i + 1) % BATCH_SIZE === 0) {
109
+ await new Promise((res) => setTimeout(res, 0));
110
+ }
105
111
  }
106
112
  }
107
- }
113
+ });
108
114
  if (timestamps) {
109
115
  await setSupastashSyncStatus(table, filters, {
110
- lastCreatedAt: timestamps.createdMax,
111
116
  lastSyncedAt: timestamps.updatedMax,
112
117
  lastDeletedAt: timestamps.deletedMax,
113
118
  lastSyncedAtPk: timestamps.updatedMaxPk,
@@ -138,7 +143,7 @@ const warned = new Map();
138
143
  * @param record - The record to upsert
139
144
  * @param exists - Whether the record already exists in the database
140
145
  */
141
- export async function upsertData(table, record, doesExist) {
146
+ export async function upsertData({ tx, table, record, doesExist, }) {
142
147
  if (!record?.id)
143
148
  return;
144
149
  let itemExists = !!doesExist;
@@ -150,7 +155,7 @@ export async function upsertData(table, record, doesExist) {
150
155
  itemExists = exists;
151
156
  }
152
157
  try {
153
- const db = await getSupastashDb();
158
+ const db = tx ?? (await getSupastashDb());
154
159
  const columns = await getTableSchema(table);
155
160
  const recordToSave = {
156
161
  ...record,
@@ -1,5 +1,3 @@
1
- import { RealtimeFilter } from "../../../types/realtimeData.types";
2
- declare function isValidFilter<R = any>(filters: RealtimeFilter<R>[]): boolean;
3
- export default isValidFilter;
4
- export declare function warnOnMisMatch<R = any>(table: string, filters: RealtimeFilter<R>[]): void;
1
+ import { SupastashFilter } from "../../../types/realtimeData.types";
2
+ export declare function warnOnMisMatch<R = any>(table: string, filters: SupastashFilter<R>[]): void;
5
3
  //# sourceMappingURL=validateFilters.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validateFilters.d.ts","sourceRoot":"","sources":["../../../../src/utils/sync/pullFromRemote/validateFilters.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAcnE,iBAAS,aAAa,CAAC,CAAC,GAAG,GAAG,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,CA+DrE;AAED,eAAe,aAAa,CAAC;AAM7B,wBAAgB,cAAc,CAAC,CAAC,GAAG,GAAG,EACpC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,QAkD7B"}
1
+ {"version":3,"file":"validateFilters.d.ts","sourceRoot":"","sources":["../../../../src/utils/sync/pullFromRemote/validateFilters.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAOpE,wBAAgB,cAAc,CAAC,CAAC,GAAG,GAAG,EACpC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC,EAAE,QA2D9B"}
@@ -1,68 +1,5 @@
1
1
  import { filterTracker } from "../../../store/tableFilters";
2
2
  import { logWarn } from "../../logs";
3
- const validOperators = new Set([
4
- "eq",
5
- "neq",
6
- "gt",
7
- "lt",
8
- "gte",
9
- "lte",
10
- "is",
11
- "in",
12
- ]);
13
- function isValidFilter(filters) {
14
- for (const filter of filters ?? []) {
15
- if (!filter || typeof filter !== "object") {
16
- return false;
17
- }
18
- const { column, operator, value } = filter;
19
- if (typeof column !== "string" || column.trim() === "") {
20
- return false;
21
- }
22
- if (!validOperators.has(operator)) {
23
- return false;
24
- }
25
- switch (operator) {
26
- case "is":
27
- if (typeof value === "boolean") {
28
- // normalize
29
- filter.value = value ? "true" : "false";
30
- break;
31
- }
32
- if (!(value === null ||
33
- value === "null" ||
34
- value === "true" ||
35
- value === "false")) {
36
- return false;
37
- }
38
- break;
39
- case "in":
40
- if (Array.isArray(value)) {
41
- if (value.length === 0) {
42
- return false;
43
- }
44
- }
45
- else if (typeof value === "string") {
46
- const trimmed = value.trim();
47
- if (trimmed === "" ||
48
- trimmed.split(",").filter((item) => item.trim() !== "").length === 0) {
49
- return false;
50
- }
51
- }
52
- else {
53
- return false;
54
- }
55
- break;
56
- default:
57
- if (value === undefined || value === null) {
58
- return false;
59
- }
60
- break;
61
- }
62
- }
63
- return true;
64
- }
65
- export default isValidFilter;
66
3
  const tablesWarned = new Set();
67
4
  const debounceWarnTime = 2000;
68
5
  let debounceWarnTimeout = null;
@@ -74,6 +11,15 @@ export function warnOnMisMatch(table, filters) {
74
11
  for (let i = 0; i < maxLength; i++) {
75
12
  const oldFilter = existingFilters[i];
76
13
  const newFilter = filters[i];
14
+ if ("or" in oldFilter || "or" in newFilter) {
15
+ if ("or" in oldFilter && "or" in newFilter) {
16
+ if (oldFilter.or.length !== newFilter.or.length) {
17
+ hasMismatch = true;
18
+ break;
19
+ }
20
+ }
21
+ continue;
22
+ }
77
23
  if (!oldFilter || !newFilter) {
78
24
  hasMismatch = true;
79
25
  break;
@@ -1 +1 @@
1
- {"version":3,"file":"deleteChunks.d.ts","sourceRoot":"","sources":["../../../../src/utils/sync/pushLocal/deleteChunks.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AA2DzD;;;;GAIG;AACH,wBAAsB,UAAU,CAC9B,KAAK,EAAE,MAAM,EACb,eAAe,EAAE,WAAW,EAAE,iBAc/B"}
1
+ {"version":3,"file":"deleteChunks.d.ts","sourceRoot":"","sources":["../../../../src/utils/sync/pushLocal/deleteChunks.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AA6DzD;;;;GAIG;AACH,wBAAsB,UAAU,CAC9B,KAAK,EAAE,MAAM,EACb,eAAe,EAAE,WAAW,EAAE,iBAc/B"}
@@ -12,11 +12,13 @@ const SQL_CHUNK = 999;
12
12
  */
13
13
  async function permanentlyDeleteLocally(table, ids) {
14
14
  const db = await getSupastashDb();
15
- for (let i = 0; i < ids.length; i += SQL_CHUNK) {
16
- const slice = ids.slice(i, i + SQL_CHUNK);
17
- const placeholders = slice.map(() => "?").join(", ");
18
- await db.runAsync(`DELETE FROM ${table} WHERE id IN (${placeholders})`, slice);
19
- }
15
+ await db.withTransaction(async (tx) => {
16
+ for (let i = 0; i < ids.length; i += SQL_CHUNK) {
17
+ const slice = ids.slice(i, i + SQL_CHUNK);
18
+ const placeholders = slice.map(() => "?").join(", ");
19
+ await tx.runAsync(`DELETE FROM ${table} WHERE id IN (${placeholders})`, slice);
20
+ }
21
+ });
20
22
  }
21
23
  /**
22
24
  * Deletes a chunk of data from the remote database
@@ -1 +1 @@
1
- {"version":3,"file":"getAllUnsyncedData.d.ts","sourceRoot":"","sources":["../../../../src/utils/sync/pushLocal/getAllUnsyncedData.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AA6EzD;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,CAe/B;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,CAM/B"}
1
+ {"version":3,"file":"getAllUnsyncedData.d.ts","sourceRoot":"","sources":["../../../../src/utils/sync/pushLocal/getAllUnsyncedData.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAwCzD;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,CAe/B;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,CAM/B"}
@@ -1,59 +1,27 @@
1
- import { getSupastashConfig } from "../../../core/config";
2
1
  import { getSupastashDb } from "../../../db/dbInitializer";
3
- import { tableSchemaData } from "../../../store/tableSchemaData";
4
2
  import { getTableSchema } from "../../../utils/getTableSchema";
5
3
  import log from "../../../utils/logs";
6
- import { isNetworkError, isOnline } from "../../connection";
7
- import { supabaseClientErr } from "../../supabaseClientErr";
8
- const numberOfErrors = new Map();
4
+ import { getRemoteTableSchema } from "../status/remoteSchema";
9
5
  const sharedKeysCache = new Map();
10
6
  async function getRemoteKeys(table) {
11
- const config = getSupastashConfig();
12
- const supabase = config?.supabaseClient;
13
- if (!supabase) {
14
- throw new Error(`Supabase client not found, ${supabaseClientErr}`);
15
- }
16
7
  if (sharedKeysCache.has(table)) {
17
8
  return sharedKeysCache.get(table);
18
9
  }
19
- if (numberOfErrors.get(table) && (numberOfErrors.get(table) || 0) > 3) {
10
+ const remoteSchema = await getRemoteTableSchema(table);
11
+ if (!remoteSchema)
20
12
  return null;
21
- }
22
- if (!tableSchemaData.has(table)) {
23
- const isConnected = await isOnline();
24
- if (!isConnected) {
25
- return null;
26
- }
27
- const { data, error } = await supabase.rpc("get_table_schema", {
28
- table_name: table,
29
- });
30
- if (error) {
31
- if (!isNetworkError(error)) {
32
- log(`[Supastash] Error getting remote keys for table ${table} on public schema: ${error.message}
33
- You can find more information in the Supastash docs: https://0xzekea.github.io/supastash/docs/getting-started#%EF%B8%8F-server-side-setup-for-filtered-pulls`);
34
- numberOfErrors.set(table, (numberOfErrors.get(table) || 0) + 1);
35
- return null;
36
- }
37
- }
38
- tableSchemaData.set(table, data);
39
- }
40
- if (!tableSchemaData.get(table)) {
13
+ const remoteKeys = remoteSchema.map((col) => col.column_name);
14
+ const localSchema = await getTableSchema(table);
15
+ if (!localSchema)
41
16
  return null;
42
- }
43
- const keys = tableSchemaData.get(table)?.map((item) => item.column_name);
44
- const columns = await getTableSchema(table);
45
- const sharedKeys = keys?.filter((key) => columns.includes(key));
46
- const missingKeys = columns.filter((column) => !keys?.includes(column) && column !== "synced_at");
47
- // Inform user of missing keys
17
+ const localKeys = localSchema;
18
+ const sharedKeys = remoteKeys.filter((key) => localKeys.includes(key));
19
+ const missingKeys = remoteKeys.filter((key) => !localKeys.includes(key) && key !== "synced_at" && key !== "arrived_at");
48
20
  if (missingKeys.length > 0) {
49
- log(`[Supastash] Missing keys for table ${table} on public schema: ${missingKeys.join(", ")}`);
50
- }
51
- // Return shared keys if they exist
52
- if (sharedKeys) {
53
- sharedKeysCache.set(table, sharedKeys);
54
- return sharedKeys;
21
+ log(`[Supastash] Missing keys for table ${table}: ${missingKeys.join(", ")}`);
55
22
  }
56
- return null;
23
+ sharedKeysCache.set(table, sharedKeys);
24
+ return sharedKeys.length ? sharedKeys : null;
57
25
  }
58
26
  /**
59
27
  * Gets all unsynced data from a table
@@ -220,7 +220,7 @@ export async function rewindAndDropLocal(table, rowId, supabase) {
220
220
  }
221
221
  }
222
222
  async function replaceLocalWithServer(table, serverRow) {
223
- await upsertData(table, serverRow);
223
+ await upsertData({ table, record: serverRow });
224
224
  await updateLocalSyncedAt(table, [serverRow.id]);
225
225
  setQueryStatus(serverRow.id, table, "success");
226
226
  supastashEventBus.emit("updateSyncStatus");
@@ -1,15 +1,15 @@
1
- import { RealtimeFilter } from "../../../types/realtimeData.types";
1
+ import { SupastashFilter } from "../../../types/realtimeData.types";
2
2
  /**
3
3
  * Canonicalizes a set of filters
4
4
  * @param filters - The filters to canonicalize
5
5
  * @returns The canonicalized filters
6
6
  */
7
- export declare function canonicalizeFilters(filters?: RealtimeFilter[] | null): string;
7
+ export declare function canonicalizeFilters(filters?: SupastashFilter[] | null): string;
8
8
  /**
9
9
  * Computes the filter key for a given set of filters
10
10
  * @param filters - The filters to compute the key for
11
11
  * @param ns - The namespace to use for the key
12
12
  * @returns The computed filter key
13
13
  */
14
- export declare function computeFilterKey(filters?: RealtimeFilter[] | null, ns?: string): Promise<string>;
14
+ export declare function computeFilterKey(filters?: SupastashFilter[] | null, ns?: string): Promise<string>;
15
15
  //# sourceMappingURL=filterKey.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"filterKey.d.ts","sourceRoot":"","sources":["../../../../src/utils/sync/status/filterKey.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AA4BnE;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,CAAC,EAAE,cAAc,EAAE,GAAG,IAAI,GAAG,MAAM,CAc7E;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,CAAC,EAAE,cAAc,EAAE,GAAG,IAAI,EACjC,EAAE,SAAW,mBAQd"}
1
+ {"version":3,"file":"filterKey.d.ts","sourceRoot":"","sources":["../../../../src/utils/sync/status/filterKey.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AA4BpE;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,CAAC,EAAE,eAAe,EAAE,GAAG,IAAI,GACjC,MAAM,CAuBR;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,CAAC,EAAE,eAAe,EAAE,GAAG,IAAI,EAClC,EAAE,SAAW,mBAQd"}
@@ -31,7 +31,9 @@ function normVal(v) {
31
31
  * @returns The canonicalized filters
32
32
  */
33
33
  export function canonicalizeFilters(filters) {
34
- const list = (filters ?? []).map((f) => ({
34
+ const list = (filters ?? [])
35
+ .filter((f) => !!f && typeof f === "object" && "column" in f && "operator" in f)
36
+ .map((f) => ({
35
37
  column: String(f.column),
36
38
  operator: f.operator,
37
39
  value: normVal(f.value),
@@ -41,7 +43,8 @@ export function canonicalizeFilters(filters) {
41
43
  return a.column < b.column ? -1 : 1;
42
44
  if (a.operator !== b.operator)
43
45
  return a.operator < b.operator ? -1 : 1;
44
- const av = JSON.stringify(a.value), bv = JSON.stringify(b.value);
46
+ const av = JSON.stringify(a.value);
47
+ const bv = JSON.stringify(b.value);
45
48
  return av < bv ? -1 : av > bv ? 1 : 0;
46
49
  });
47
50
  return JSON.stringify(list);
@@ -0,0 +1,4 @@
1
+ import { TableSchema } from "../../../types/realtimeData.types";
2
+ export declare function appendSyncedAt(schema: TableSchema[]): TableSchema[];
3
+ export declare function getRemoteTableSchema(table: string): Promise<TableSchema[] | null>;
4
+ //# sourceMappingURL=remoteSchema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remoteSchema.d.ts","sourceRoot":"","sources":["../../../../src/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"}