supastash 0.2.14 → 0.2.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/desktop/hooks/syncEngine.d.ts.map +1 -1
- package/dist/desktop/hooks/syncEngine.js +14 -9
- package/dist/desktop/utils/sync/pullFromRemote/pullFromRemoteBatch.d.ts +1 -1
- package/dist/desktop/utils/sync/pullFromRemote/pullFromRemoteBatch.d.ts.map +1 -1
- package/dist/desktop/utils/sync/pullFromRemote/pullFromRemoteBatch.js +2 -2
- package/dist/desktop/utils/sync/pullFromRemote/updateLocalDb.js +2 -2
- package/dist/desktop/utils/sync/pushLocal/uploadChunk.js +1 -1
- package/dist/native/hooks/syncEngine.d.ts.map +1 -1
- package/dist/native/hooks/syncEngine.js +6 -1
- package/dist/native/utils/sync/pullFromRemote/pullFromRemoteBatch.d.ts +1 -1
- package/dist/native/utils/sync/pullFromRemote/pullFromRemoteBatch.d.ts.map +1 -1
- package/dist/native/utils/sync/pullFromRemote/pullFromRemoteBatch.js +31 -12
- package/dist/native/utils/sync/pullFromRemote/updateLocalDb.js +2 -2
- package/dist/native/utils/sync/pushLocal/uploadChunk.js +1 -1
- package/dist/shared/core/config/index.js +4 -4
- package/dist/shared/types/supastashConfig.types.d.ts +2 -2
- package/dist/shared/utils/sync/pushLocal/getAllUnsyncedData.d.ts +9 -0
- package/dist/shared/utils/sync/pushLocal/getAllUnsyncedData.d.ts.map +1 -1
- package/dist/shared/utils/sync/pushLocal/getAllUnsyncedData.js +100 -5
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncEngine.d.ts","sourceRoot":"","sources":["../../../src/desktop/hooks/syncEngine.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"syncEngine.d.ts","sourceRoot":"","sources":["../../../src/desktop/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;;;EA2D5B;AAMD;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoC5D;AAED;;GAEG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAEnD"}
|
|
@@ -9,6 +9,7 @@ import { subscribeToAppVisibility } from "../adapters/appstate";
|
|
|
9
9
|
import { pullFromRemote as doPullFromRemote } from "../utils/sync/pullFromRemote";
|
|
10
10
|
import { pullFromRemoteBatch } from "../utils/sync/pullFromRemote/pullFromRemoteBatch";
|
|
11
11
|
import { updateLocalDb } from "../utils/sync/pullFromRemote/updateLocalDb";
|
|
12
|
+
import { pushLocalData as doPushLocalData } from "../utils/sync/pushLocal";
|
|
12
13
|
import { pushLocalDataToRemote } from "../utils/sync/pushLocal/sendUnsyncedToSupabase";
|
|
13
14
|
// -----------------------------
|
|
14
15
|
// Module-scoped state & tunables
|
|
@@ -85,12 +86,7 @@ async function pushLocalDataSafe() {
|
|
|
85
86
|
return;
|
|
86
87
|
isPushing = true;
|
|
87
88
|
try {
|
|
88
|
-
|
|
89
|
-
await pullFromRemoteBatch();
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
await doPullFromRemote();
|
|
93
|
-
}
|
|
89
|
+
await doPushLocalData();
|
|
94
90
|
lastPushAt = Date.now();
|
|
95
91
|
}
|
|
96
92
|
catch (e) {
|
|
@@ -108,7 +104,6 @@ async function pullFromRemoteSafe() {
|
|
|
108
104
|
return;
|
|
109
105
|
if (!(await isOnline()))
|
|
110
106
|
return;
|
|
111
|
-
// If in ghost mode, don't pull
|
|
112
107
|
const cfg = getSupastashConfig();
|
|
113
108
|
if (cfg.supastashMode === "ghost")
|
|
114
109
|
return;
|
|
@@ -116,7 +111,12 @@ async function pullFromRemoteSafe() {
|
|
|
116
111
|
return;
|
|
117
112
|
isPulling = true;
|
|
118
113
|
try {
|
|
119
|
-
|
|
114
|
+
if (cfg.useBatchPullSync) {
|
|
115
|
+
await pullFromRemoteBatch();
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
await doPullFromRemote();
|
|
119
|
+
}
|
|
120
120
|
lastPullAt = Date.now();
|
|
121
121
|
}
|
|
122
122
|
catch (e) {
|
|
@@ -206,7 +206,12 @@ export async function syncTable(table) {
|
|
|
206
206
|
// Pull
|
|
207
207
|
if (cfg.syncEngine?.pull) {
|
|
208
208
|
try {
|
|
209
|
-
|
|
209
|
+
if (cfg.useBatchPullSync) {
|
|
210
|
+
await pullFromRemoteBatch([table]);
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
await updateLocalDb(table, filter, syncCalls.get(table)?.pull);
|
|
214
|
+
}
|
|
210
215
|
}
|
|
211
216
|
catch (e) {
|
|
212
217
|
log("[Supastash] syncTable pull error", {
|
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
* Requires `useBatchPullSync: true` in config and the
|
|
6
6
|
* `supastash_pull_sync` Postgres function to be deployed.
|
|
7
7
|
*/
|
|
8
|
-
export declare function pullFromRemoteBatch(): Promise<void>;
|
|
8
|
+
export declare function pullFromRemoteBatch(specificTables?: string[]): Promise<void>;
|
|
9
9
|
//# sourceMappingURL=pullFromRemoteBatch.d.ts.map
|
|
@@ -1 +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,
|
|
1
|
+
{"version":3,"file":"pullFromRemoteBatch.d.ts","sourceRoot":"","sources":["../../../../../src/desktop/utils/sync/pullFromRemote/pullFromRemoteBatch.ts"],"names":[],"mappings":"AA+CA;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,cAAc,CAAC,EAAE,MAAM,EAAE,GACxB,OAAO,CAAC,IAAI,CAAC,CA4Mf"}
|
|
@@ -37,14 +37,14 @@ function buildCursorFilter(tsCol, lastSyncedAt, lastPk) {
|
|
|
37
37
|
* Requires `useBatchPullSync: true` in config and the
|
|
38
38
|
* `supastash_pull_sync` Postgres function to be deployed.
|
|
39
39
|
*/
|
|
40
|
-
export async function pullFromRemoteBatch() {
|
|
40
|
+
export async function pullFromRemoteBatch(specificTables) {
|
|
41
41
|
const cfg = getSupastashConfig();
|
|
42
42
|
const supabase = cfg.supabaseClient;
|
|
43
43
|
if (!supabase)
|
|
44
44
|
throw new Error(`No supabase client found: ${supabaseClientErr}`);
|
|
45
45
|
if (cfg.supastashMode === "ghost")
|
|
46
46
|
return;
|
|
47
|
-
const tables = await getAllTables();
|
|
47
|
+
const tables = specificTables ?? (await getAllTables());
|
|
48
48
|
if (!tables) {
|
|
49
49
|
log("[Supastash] Batch pull: no tables found");
|
|
50
50
|
return;
|
|
@@ -129,7 +129,7 @@ export async function upsertData({ tx, table, record, doesExist, }) {
|
|
|
129
129
|
try {
|
|
130
130
|
const db = tx ?? (await getSupastashDb());
|
|
131
131
|
const allColumns = await getTableSchema(table);
|
|
132
|
-
const pullFilterColumns = new Set(cfg.
|
|
132
|
+
const pullFilterColumns = new Set(cfg.ignoredColumns?.pull?.[table] ?? []);
|
|
133
133
|
const columns = pullFilterColumns.size
|
|
134
134
|
? allColumns.filter((c) => !pullFilterColumns.has(c))
|
|
135
135
|
: allColumns;
|
|
@@ -200,7 +200,7 @@ export async function upsertChunkData({ tx, table, records, }) {
|
|
|
200
200
|
return;
|
|
201
201
|
const db = tx ?? (await getSupastashDb());
|
|
202
202
|
const allColumns = await getTableSchema(table);
|
|
203
|
-
const pullFilterColumns = new Set(cfg.
|
|
203
|
+
const pullFilterColumns = new Set(cfg.ignoredColumns?.pull?.[table] ?? []);
|
|
204
204
|
const columns = pullFilterColumns.size
|
|
205
205
|
? allColumns.filter((c) => !pullFilterColumns.has(c))
|
|
206
206
|
: allColumns;
|
|
@@ -204,7 +204,7 @@ export async function uploadData(table, unsyncedRecords, onPushToRemote) {
|
|
|
204
204
|
const supabase = cfg.supabaseClient;
|
|
205
205
|
if (!supabase)
|
|
206
206
|
throw new Error("[Supastash] Supabase client not configured");
|
|
207
|
-
const pushFilterColumns = cfg.
|
|
207
|
+
const pushFilterColumns = cfg.ignoredColumns?.push?.[table] ?? [];
|
|
208
208
|
const cleaned = unsyncedRecords.map(({ synced_at, deleted_at, arrived_at, ...rest }) => {
|
|
209
209
|
const row = enforceTimestamps(normalizeForSupabase(rest));
|
|
210
210
|
for (const col of pushFilterColumns)
|
|
@@ -1 +1 @@
|
|
|
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,
|
|
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,CAoC5D;AAED;;GAEG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAEnD"}
|
|
@@ -208,7 +208,12 @@ export async function syncTable(table) {
|
|
|
208
208
|
// Pull
|
|
209
209
|
if (cfg.syncEngine?.pull) {
|
|
210
210
|
try {
|
|
211
|
-
|
|
211
|
+
if (cfg.useBatchPullSync) {
|
|
212
|
+
await pullFromRemoteBatch([table]);
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
await updateLocalDb(table, filter, syncCalls.get(table)?.pull);
|
|
216
|
+
}
|
|
212
217
|
}
|
|
213
218
|
catch (e) {
|
|
214
219
|
log("[Supastash] syncTable pull error", {
|
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
* Requires `useBatchPullSync: true` in config and the
|
|
6
6
|
* `supastash_pull_sync` Postgres function to be deployed.
|
|
7
7
|
*/
|
|
8
|
-
export declare function pullFromRemoteBatch(): Promise<void>;
|
|
8
|
+
export declare function pullFromRemoteBatch(specificTables?: string[]): Promise<void>;
|
|
9
9
|
//# sourceMappingURL=pullFromRemoteBatch.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pullFromRemoteBatch.d.ts","sourceRoot":"","sources":["../../../../../src/native/utils/sync/pullFromRemote/pullFromRemoteBatch.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pullFromRemoteBatch.d.ts","sourceRoot":"","sources":["../../../../../src/native/utils/sync/pullFromRemote/pullFromRemoteBatch.ts"],"names":[],"mappings":"AA+CA;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,cAAc,CAAC,EAAE,MAAM,EAAE,GACxB,OAAO,CAAC,IAAI,CAAC,CA4Mf"}
|
|
@@ -2,16 +2,16 @@ import { getSupastashConfig } from "../../../../shared/core/config";
|
|
|
2
2
|
import { getSupastashDb } from "../../../../shared/db/dbInitializer";
|
|
3
3
|
import { rpcTableFilters } from "../../../../shared/store/rpcTableFilters";
|
|
4
4
|
import { tableFilters } from "../../../../shared/store/tableFilters";
|
|
5
|
-
import {
|
|
5
|
+
import log, { logError, logWarn } from "../../../../shared/utils/logs";
|
|
6
|
+
import { refreshScreen } from "../../../../shared/utils/refreshScreenCalls";
|
|
7
|
+
import { supabaseClientErr } from "../../../../shared/utils/supabaseClientErr";
|
|
6
8
|
import { getAllTables } from "../../../../shared/utils/sync/getAllTables";
|
|
7
9
|
import { getMaxSyncLookBack, logNoUpdates, returnMaxDate, } from "../../../../shared/utils/sync/pullFromRemote/helpers";
|
|
10
|
+
import { postgrestFiltersToRpc } from "../../../../shared/utils/sync/pullFromRemote/postgrestToRpc";
|
|
8
11
|
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
12
|
import { prefetchRemoteTableSchemas } from "../../../../shared/utils/sync/status/remoteSchema";
|
|
13
|
-
import { setSupastashSyncStatus } from "../status/services";
|
|
14
13
|
import { selectSyncStatus } from "../status/repo";
|
|
14
|
+
import { setSupastashSyncStatus } from "../status/services";
|
|
15
15
|
import { upsertChunkData } from "./updateLocalDb";
|
|
16
16
|
const CHUNK_SIZE = 999;
|
|
17
17
|
function buildCursorFilter(tsCol, lastSyncedAt, lastPk) {
|
|
@@ -37,14 +37,14 @@ function buildCursorFilter(tsCol, lastSyncedAt, lastPk) {
|
|
|
37
37
|
* Requires `useBatchPullSync: true` in config and the
|
|
38
38
|
* `supastash_pull_sync` Postgres function to be deployed.
|
|
39
39
|
*/
|
|
40
|
-
export async function pullFromRemoteBatch() {
|
|
40
|
+
export async function pullFromRemoteBatch(specificTables) {
|
|
41
41
|
const cfg = getSupastashConfig();
|
|
42
42
|
const supabase = cfg.supabaseClient;
|
|
43
43
|
if (!supabase)
|
|
44
44
|
throw new Error(`No supabase client found: ${supabaseClientErr}`);
|
|
45
45
|
if (cfg.supastashMode === "ghost")
|
|
46
46
|
return;
|
|
47
|
-
const tables = await getAllTables();
|
|
47
|
+
const tables = specificTables ?? (await getAllTables());
|
|
48
48
|
if (!tables) {
|
|
49
49
|
log("[Supastash] Batch pull: no tables found");
|
|
50
50
|
return;
|
|
@@ -57,7 +57,10 @@ export async function pullFromRemoteBatch() {
|
|
|
57
57
|
const db = await getSupastashDb();
|
|
58
58
|
const completedTables = new Set();
|
|
59
59
|
SyncInfoUpdater.setInProgress({ action: "start", type: "pull" });
|
|
60
|
-
SyncInfoUpdater.setNumberOfTables({
|
|
60
|
+
SyncInfoUpdater.setNumberOfTables({
|
|
61
|
+
amount: tablesToPull.length,
|
|
62
|
+
type: "pull",
|
|
63
|
+
});
|
|
61
64
|
// Warm schema cache for all tables in one call if enabled
|
|
62
65
|
if (cfg.useBatchSchemaFetch) {
|
|
63
66
|
await prefetchRemoteTableSchemas(tablesToPull);
|
|
@@ -112,8 +115,16 @@ export async function pullFromRemoteBatch() {
|
|
|
112
115
|
logWarn(`[Supastash] Batch: skipped row without id from "${table}"`);
|
|
113
116
|
continue;
|
|
114
117
|
}
|
|
115
|
-
prevMaxSyncedAt = returnMaxDate({
|
|
116
|
-
|
|
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
|
+
});
|
|
117
128
|
if (row.deleted_at) {
|
|
118
129
|
toDelete.push(row.id);
|
|
119
130
|
}
|
|
@@ -121,8 +132,16 @@ export async function pullFromRemoteBatch() {
|
|
|
121
132
|
toUpsert.push(row);
|
|
122
133
|
}
|
|
123
134
|
}
|
|
124
|
-
SyncInfoUpdater.setUnsyncedDataCount({
|
|
125
|
-
|
|
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
|
+
});
|
|
126
145
|
// Delete soft-deleted rows
|
|
127
146
|
if (toDelete.length > 0) {
|
|
128
147
|
for (let i = 0; i < toDelete.length; i += CHUNK_SIZE) {
|
|
@@ -128,7 +128,7 @@ export async function upsertData({ tx, table, record, doesExist, }) {
|
|
|
128
128
|
try {
|
|
129
129
|
const db = tx ?? (await getSupastashDb());
|
|
130
130
|
const allColumns = await getTableSchema(table);
|
|
131
|
-
const pullFilterColumns = new Set(cfg.
|
|
131
|
+
const pullFilterColumns = new Set(cfg.ignoredColumns?.pull?.[table] ?? []);
|
|
132
132
|
const columns = pullFilterColumns.size
|
|
133
133
|
? allColumns.filter((c) => !pullFilterColumns.has(c))
|
|
134
134
|
: allColumns;
|
|
@@ -199,7 +199,7 @@ export async function upsertChunkData({ tx, table, records, }) {
|
|
|
199
199
|
return;
|
|
200
200
|
const db = tx ?? (await getSupastashDb());
|
|
201
201
|
const allColumns = await getTableSchema(table);
|
|
202
|
-
const pullFilterColumns = new Set(cfg.
|
|
202
|
+
const pullFilterColumns = new Set(cfg.ignoredColumns?.pull?.[table] ?? []);
|
|
203
203
|
const columns = pullFilterColumns.size
|
|
204
204
|
? allColumns.filter((c) => !pullFilterColumns.has(c))
|
|
205
205
|
: allColumns;
|
|
@@ -207,7 +207,7 @@ export async function uploadData(table, unsyncedRecords, onPushToRemote) {
|
|
|
207
207
|
const supabase = cfg.supabaseClient;
|
|
208
208
|
if (!supabase)
|
|
209
209
|
throw new Error("[Supastash] Supabase client not configured");
|
|
210
|
-
const pushFilterColumns = cfg.
|
|
210
|
+
const pushFilterColumns = cfg.ignoredColumns?.push?.[table] ?? [];
|
|
211
211
|
const cleaned = unsyncedRecords.map(({ synced_at, deleted_at, arrived_at, ...rest }) => {
|
|
212
212
|
const row = enforceTimestamps(normalizeForSupabase(rest));
|
|
213
213
|
for (const col of pushFilterColumns)
|
|
@@ -30,7 +30,7 @@ let _config = {
|
|
|
30
30
|
deleteConflictedRows: false,
|
|
31
31
|
pushRPCPath: undefined,
|
|
32
32
|
supastashMode: "live",
|
|
33
|
-
|
|
33
|
+
ignoredColumns: {
|
|
34
34
|
push: {},
|
|
35
35
|
pull: {},
|
|
36
36
|
},
|
|
@@ -118,9 +118,9 @@ export function configureSupastash(config) {
|
|
|
118
118
|
..._config.fieldEnforcement,
|
|
119
119
|
...config.fieldEnforcement,
|
|
120
120
|
},
|
|
121
|
-
|
|
122
|
-
push: config.
|
|
123
|
-
pull: config.
|
|
121
|
+
ignoredColumns: {
|
|
122
|
+
push: config.ignoredColumns?.push ?? _config.ignoredColumns?.push ?? {},
|
|
123
|
+
pull: config.ignoredColumns?.pull ?? _config.ignoredColumns?.pull ?? {},
|
|
124
124
|
},
|
|
125
125
|
};
|
|
126
126
|
_configured = true;
|
|
@@ -154,12 +154,12 @@ export type SupastashConfig<T extends SupastashSQLiteClientTypes> = {
|
|
|
154
154
|
* - `pull`: columns removed from each row before it is written to the local SQLite DB.
|
|
155
155
|
*
|
|
156
156
|
* @example
|
|
157
|
-
*
|
|
157
|
+
* ignoredColumns: {
|
|
158
158
|
* push: { orders: ["internal_notes", "cost_price"] },
|
|
159
159
|
* pull: { users: ["password_hash", "secret_token"] },
|
|
160
160
|
* }
|
|
161
161
|
*/
|
|
162
|
-
|
|
162
|
+
ignoredColumns?: {
|
|
163
163
|
push?: Record<string, string[]>;
|
|
164
164
|
pull?: Record<string, string[]>;
|
|
165
165
|
};
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import { PayloadData } from "../../../types/query.types";
|
|
2
|
+
import { SupastashFilter } from "../../../types/realtimeData.types";
|
|
3
|
+
/**
|
|
4
|
+
* Builds a combined " AND (...)" SQL string + params array from a list of filters.
|
|
5
|
+
* Never throws — malformed filters are skipped and logged.
|
|
6
|
+
*/
|
|
7
|
+
export declare function buildFilterSql(filters: SupastashFilter[]): {
|
|
8
|
+
sql: string;
|
|
9
|
+
params: unknown[];
|
|
10
|
+
};
|
|
2
11
|
/**
|
|
3
12
|
* Gets all unsynced data from a table
|
|
4
13
|
* @param table - The table to get the data from
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getAllUnsyncedData.d.ts","sourceRoot":"","sources":["../../../../../src/shared/utils/sync/pushLocal/getAllUnsyncedData.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"getAllUnsyncedData.d.ts","sourceRoot":"","sources":["../../../../../src/shared/utils/sync/pushLocal/getAllUnsyncedData.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAgGpE;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG;IAC1D,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,OAAO,EAAE,CAAC;CACnB,CAYA;AAmCD;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,CAuB/B;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,CAS/B"}
|
|
@@ -1,8 +1,99 @@
|
|
|
1
1
|
import { getSupastashDb } from "../../../db/dbInitializer";
|
|
2
|
+
import { tableFilters } from "../../../store/tableFilters";
|
|
2
3
|
import { getTableSchema } from "../../getTableSchema";
|
|
3
4
|
import log from "../../logs";
|
|
4
5
|
import { getRemoteTableSchema } from "../status/remoteSchema";
|
|
5
6
|
const sharedKeysCache = new Map();
|
|
7
|
+
const VALID_OPERATORS = new Set([
|
|
8
|
+
"eq",
|
|
9
|
+
"neq",
|
|
10
|
+
"gt",
|
|
11
|
+
"gte",
|
|
12
|
+
"lt",
|
|
13
|
+
"lte",
|
|
14
|
+
"is",
|
|
15
|
+
"in",
|
|
16
|
+
]);
|
|
17
|
+
function escapeColumn(column) {
|
|
18
|
+
// Basic guard against injection via column names (should only ever be internal identifiers)
|
|
19
|
+
return `"${column.replace(/"/g, '""')}"`;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Builds a single SQL clause + params for one filter condition.
|
|
23
|
+
* Returns null (instead of throwing) if the filter is malformed.
|
|
24
|
+
*/
|
|
25
|
+
function buildSingleClause(f) {
|
|
26
|
+
try {
|
|
27
|
+
// Composite OR filter: { or: SupastashFilter[] }
|
|
28
|
+
if ("or" in f && Array.isArray(f.or)) {
|
|
29
|
+
const orFilters = f.or;
|
|
30
|
+
const built = orFilters
|
|
31
|
+
.map((sub) => buildSingleClause(sub))
|
|
32
|
+
.filter((c) => !!c);
|
|
33
|
+
if (!built.length)
|
|
34
|
+
return null;
|
|
35
|
+
const sql = "(" + built.map((c) => c.sql).join(" OR ") + ")";
|
|
36
|
+
const params = built.flatMap((c) => c.params);
|
|
37
|
+
return { sql, params };
|
|
38
|
+
}
|
|
39
|
+
// Flat filter: { column, operator, value }
|
|
40
|
+
const { column, operator, value } = f;
|
|
41
|
+
if (!column || !operator || !VALID_OPERATORS.has(operator)) {
|
|
42
|
+
log(`[Supastash] Skipping invalid filter: ${JSON.stringify(f)}`);
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
const col = escapeColumn(column);
|
|
46
|
+
switch (operator) {
|
|
47
|
+
case "is":
|
|
48
|
+
// sqlite uses IS for null comparisons; also support IS TRUE/FALSE-ish via 0/1
|
|
49
|
+
if (value === null) {
|
|
50
|
+
return { sql: `${col} IS NULL`, params: [] };
|
|
51
|
+
}
|
|
52
|
+
return { sql: `${col} IS ?`, params: [value] };
|
|
53
|
+
case "in": {
|
|
54
|
+
const values = Array.isArray(value) ? value : [value];
|
|
55
|
+
if (!values.length)
|
|
56
|
+
return null;
|
|
57
|
+
const placeholders = values.map(() => "?").join(", ");
|
|
58
|
+
return { sql: `${col} IN (${placeholders})`, params: values };
|
|
59
|
+
}
|
|
60
|
+
case "eq":
|
|
61
|
+
return { sql: `${col} = ?`, params: [value] };
|
|
62
|
+
case "neq":
|
|
63
|
+
return { sql: `${col} != ?`, params: [value] };
|
|
64
|
+
case "gt":
|
|
65
|
+
return { sql: `${col} > ?`, params: [value] };
|
|
66
|
+
case "gte":
|
|
67
|
+
return { sql: `${col} >= ?`, params: [value] };
|
|
68
|
+
case "lt":
|
|
69
|
+
return { sql: `${col} < ?`, params: [value] };
|
|
70
|
+
case "lte":
|
|
71
|
+
return { sql: `${col} <= ?`, params: [value] };
|
|
72
|
+
default:
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
log(`[Supastash] Failed to build filter clause: ${String(err)}`);
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Builds a combined " AND (...)" SQL string + params array from a list of filters.
|
|
83
|
+
* Never throws — malformed filters are skipped and logged.
|
|
84
|
+
*/
|
|
85
|
+
export function buildFilterSql(filters) {
|
|
86
|
+
if (!filters?.length)
|
|
87
|
+
return { sql: "", params: [] };
|
|
88
|
+
const clauses = filters
|
|
89
|
+
.map((f) => buildSingleClause(f))
|
|
90
|
+
.filter((c) => !!c);
|
|
91
|
+
if (!clauses.length)
|
|
92
|
+
return { sql: "", params: [] };
|
|
93
|
+
const sql = " AND " + clauses.map((c) => c.sql).join(" AND ");
|
|
94
|
+
const params = clauses.flatMap((c) => c.params);
|
|
95
|
+
return { sql, params };
|
|
96
|
+
}
|
|
6
97
|
async function getRemoteKeys(table) {
|
|
7
98
|
if (sharedKeysCache.has(table)) {
|
|
8
99
|
return sharedKeysCache.get(table);
|
|
@@ -30,15 +121,17 @@ async function getRemoteKeys(table) {
|
|
|
30
121
|
*/
|
|
31
122
|
export async function getAllUnsyncedData(table) {
|
|
32
123
|
const db = await getSupastashDb();
|
|
124
|
+
const filters = tableFilters.get(table) ?? [];
|
|
125
|
+
const filterSqlDetails = buildFilterSql(filters);
|
|
33
126
|
const remoteKeys = await getRemoteKeys(table);
|
|
34
127
|
if (!remoteKeys) {
|
|
35
|
-
const query = `SELECT * FROM ${table} WHERE synced_at IS NULL AND deleted_at IS NULL`;
|
|
36
|
-
const data = await db.getAllAsync(query);
|
|
128
|
+
const query = `SELECT * FROM ${table} WHERE synced_at IS NULL AND deleted_at IS NULL${filterSqlDetails.sql}`;
|
|
129
|
+
const data = await db.getAllAsync(query, filterSqlDetails.params);
|
|
37
130
|
return data ?? null;
|
|
38
131
|
}
|
|
39
132
|
const columns = remoteKeys.join(", ");
|
|
40
|
-
const query = `SELECT ${columns} FROM ${table} WHERE synced_at IS NULL AND deleted_at IS NULL`;
|
|
41
|
-
const data = await db.getAllAsync(query);
|
|
133
|
+
const query = `SELECT ${columns} FROM ${table} WHERE synced_at IS NULL AND deleted_at IS NULL${filterSqlDetails.sql}`;
|
|
134
|
+
const data = await db.getAllAsync(query, filterSqlDetails.params);
|
|
42
135
|
return data ?? null;
|
|
43
136
|
}
|
|
44
137
|
/**
|
|
@@ -48,6 +141,8 @@ export async function getAllUnsyncedData(table) {
|
|
|
48
141
|
*/
|
|
49
142
|
export async function getAllDeletedData(table) {
|
|
50
143
|
const db = await getSupastashDb();
|
|
51
|
-
const
|
|
144
|
+
const filters = tableFilters.get(table) ?? [];
|
|
145
|
+
const filterSqlDetails = buildFilterSql(filters);
|
|
146
|
+
const data = await db.getAllAsync(`SELECT deleted_at, id FROM ${table} WHERE deleted_at IS NOT NULL${filterSqlDetails.sql}`, filterSqlDetails.params);
|
|
52
147
|
return data ?? null;
|
|
53
148
|
}
|