supastash 0.1.34 → 0.1.35
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/constants/syncDefaults.d.ts +5 -0
- package/dist/constants/syncDefaults.d.ts.map +1 -0
- package/dist/constants/syncDefaults.js +19 -0
- package/dist/core/config/index.d.ts.map +1 -1
- package/dist/core/config/index.js +20 -0
- package/dist/hooks/supastashData/fetchCalls.js +2 -2
- package/dist/hooks/supastashFilters/index.d.ts.map +1 -1
- package/dist/hooks/supastashFilters/index.js +33 -21
- package/dist/hooks/syncEngine/index.d.ts +14 -23
- package/dist/hooks/syncEngine/index.d.ts.map +1 -1
- package/dist/hooks/syncEngine/index.js +177 -65
- package/dist/hooks/syncEngine/pullFromRemote/index.d.ts.map +1 -1
- package/dist/hooks/syncEngine/pullFromRemote/index.js +12 -3
- package/dist/hooks/syncEngine/pushLocal/index.d.ts.map +1 -1
- package/dist/hooks/syncEngine/pushLocal/index.js +43 -14
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/store/syncCalls.d.ts +2 -4
- package/dist/store/syncCalls.d.ts.map +1 -1
- package/dist/types/supastashConfig.types.d.ts +161 -0
- package/dist/types/syncEngine.types.d.ts +15 -0
- package/dist/utils/getSafeValues.d.ts +2 -0
- package/dist/utils/getSafeValues.d.ts.map +1 -0
- package/dist/utils/getSafeValues.js +129 -0
- package/dist/utils/query/helpers/localDb/getLocalMethod.d.ts +2 -2
- package/dist/utils/query/helpers/localDb/getLocalMethod.d.ts.map +1 -1
- package/dist/utils/query/helpers/localDb/getLocalMethod.js +2 -2
- package/dist/utils/query/helpers/localDb/localQueryBuilder.d.ts +2 -2
- package/dist/utils/query/helpers/localDb/localQueryBuilder.d.ts.map +1 -1
- package/dist/utils/query/helpers/localDb/localQueryBuilder.js +2 -2
- package/dist/utils/query/helpers/localDb/upsertMany.d.ts +2 -2
- package/dist/utils/query/helpers/localDb/upsertMany.d.ts.map +1 -1
- package/dist/utils/query/helpers/localDb/upsertMany.js +28 -6
- package/dist/utils/query/helpers/mainQueryHelpers.d.ts +0 -1
- package/dist/utils/query/helpers/mainQueryHelpers.d.ts.map +1 -1
- package/dist/utils/query/helpers/mainQueryHelpers.js +27 -96
- package/dist/utils/query/helpers/queueRemote.d.ts +3 -0
- package/dist/utils/query/helpers/queueRemote.d.ts.map +1 -0
- package/dist/utils/query/helpers/queueRemote.js +95 -0
- package/dist/utils/query/localDbQuery/index.d.ts.map +1 -1
- package/dist/utils/query/localDbQuery/index.js +1 -1
- package/dist/utils/query/localDbQuery/upsert.d.ts +2 -2
- package/dist/utils/query/localDbQuery/upsert.d.ts.map +1 -1
- package/dist/utils/query/localDbQuery/upsert.js +2 -2
- package/dist/utils/query/remoteQuery/supabaseQuery.d.ts.map +1 -1
- package/dist/utils/query/remoteQuery/supabaseQuery.js +3 -2
- package/dist/utils/sync/pullFromRemote/runLimitedConcurrency.d.ts +9 -0
- package/dist/utils/sync/pullFromRemote/runLimitedConcurrency.d.ts.map +1 -0
- package/dist/utils/sync/pullFromRemote/runLimitedConcurrency.js +33 -0
- package/dist/utils/sync/pullFromRemote/updateLocalDb.d.ts.map +1 -1
- package/dist/utils/sync/pullFromRemote/updateLocalDb.js +44 -37
- package/dist/utils/sync/pushLocal/deleteChunks.d.ts.map +1 -1
- package/dist/utils/sync/pushLocal/deleteChunks.js +31 -28
- package/dist/utils/sync/pushLocal/normalize.d.ts +3 -0
- package/dist/utils/sync/pushLocal/normalize.d.ts.map +1 -0
- package/dist/utils/sync/pushLocal/normalize.js +27 -0
- package/dist/utils/sync/pushLocal/sendUnsyncedToSupabase.d.ts +3 -3
- package/dist/utils/sync/pushLocal/sendUnsyncedToSupabase.d.ts.map +1 -1
- package/dist/utils/sync/pushLocal/sendUnsyncedToSupabase.js +17 -10
- package/dist/utils/sync/pushLocal/uploadChunk.d.ts.map +1 -1
- package/dist/utils/sync/pushLocal/uploadChunk.js +89 -103
- package/dist/utils/sync/pushLocal/uploadHelpers.d.ts +17 -0
- package/dist/utils/sync/pushLocal/uploadHelpers.d.ts.map +1 -0
- package/dist/utils/sync/pushLocal/uploadHelpers.js +197 -0
- package/dist/utils/sync/registration/syncCalls.d.ts +25 -0
- package/dist/utils/sync/registration/syncCalls.d.ts.map +1 -0
- package/dist/utils/sync/registration/syncCalls.js +37 -0
- package/dist/utils/syncStatus.d.ts +1 -0
- package/dist/utils/syncStatus.d.ts.map +1 -1
- package/dist/utils/syncStatus.js +15 -1
- package/package.json +1 -1
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { FieldEnforcement, SupastashSyncPolicy } from "../types/supastashConfig.types";
|
|
2
|
+
export declare const DEFAULT_POLICY: SupastashSyncPolicy;
|
|
3
|
+
export declare const DEFAULT_FIELDS: FieldEnforcement;
|
|
4
|
+
export declare const DEFAULT_CHUNK_SIZE = 500;
|
|
5
|
+
//# sourceMappingURL=syncDefaults.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"syncDefaults.d.ts","sourceRoot":"","sources":["../../src/constants/syncDefaults.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACpB,MAAM,gCAAgC,CAAC;AAExC,eAAO,MAAM,cAAc,EAAE,mBAU5B,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,gBAO5B,CAAC;AAEF,eAAO,MAAM,kBAAkB,MAAM,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const DEFAULT_POLICY = {
|
|
2
|
+
nonRetryableCodes: new Set(["23505", "23502", "23514", "23P01"]),
|
|
3
|
+
retryableCodes: new Set(["40001", "40P01", "55P03"]),
|
|
4
|
+
fkCode: "23503",
|
|
5
|
+
onNonRetryable: "accept-server",
|
|
6
|
+
maxTransientMs: 20 * 60 * 1000, // 20m
|
|
7
|
+
maxFkBlockMs: 24 * 60 * 60 * 1000, // 24h
|
|
8
|
+
backoffDelaysMs: [10000, 30000, 120000, 300000, 600000],
|
|
9
|
+
maxBatchAttempts: 5,
|
|
10
|
+
};
|
|
11
|
+
export const DEFAULT_FIELDS = {
|
|
12
|
+
requireCreatedAt: true,
|
|
13
|
+
requireUpdatedAt: true,
|
|
14
|
+
createdAtField: "created_at",
|
|
15
|
+
updatedAtField: "updated_at",
|
|
16
|
+
autoFillMissing: true,
|
|
17
|
+
autoFillDefaultISO: "1970-01-01T00:00:00Z",
|
|
18
|
+
};
|
|
19
|
+
export const DEFAULT_CHUNK_SIZE = 500;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/config/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/config/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,eAAe,EACf,0BAA0B,EAC3B,MAAM,mCAAmC,CAAC;AA0B3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,0BAA0B,EACrE,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG;IAAE,gBAAgB,EAAE,CAAC,CAAA;CAAE,QAuCrD;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,3 +1,4 @@
|
|
|
1
|
+
import { DEFAULT_FIELDS, DEFAULT_POLICY } from "../../constants/syncDefaults";
|
|
1
2
|
let _config = {
|
|
2
3
|
dbName: "supastash_db",
|
|
3
4
|
supabaseClient: null,
|
|
@@ -15,6 +16,9 @@ let _config = {
|
|
|
15
16
|
},
|
|
16
17
|
listeners: 250,
|
|
17
18
|
debugMode: true,
|
|
19
|
+
syncPolicy: DEFAULT_POLICY,
|
|
20
|
+
fieldEnforcement: DEFAULT_FIELDS,
|
|
21
|
+
deleteConflictedRows: false,
|
|
18
22
|
};
|
|
19
23
|
let _configured = false;
|
|
20
24
|
/**
|
|
@@ -76,6 +80,22 @@ export function configureSupastash(config) {
|
|
|
76
80
|
pull: config.pollingInterval?.pull ?? _config.pollingInterval?.pull ?? 30000,
|
|
77
81
|
push: config.pollingInterval?.push ?? _config.pollingInterval?.push ?? 30000,
|
|
78
82
|
},
|
|
83
|
+
syncPolicy: {
|
|
84
|
+
...DEFAULT_POLICY,
|
|
85
|
+
..._config.syncPolicy,
|
|
86
|
+
...config.syncPolicy,
|
|
87
|
+
nonRetryableCodes: config.syncPolicy?.nonRetryableCodes ??
|
|
88
|
+
_config.syncPolicy?.nonRetryableCodes ??
|
|
89
|
+
DEFAULT_POLICY.nonRetryableCodes,
|
|
90
|
+
retryableCodes: config.syncPolicy?.retryableCodes ??
|
|
91
|
+
_config.syncPolicy?.retryableCodes ??
|
|
92
|
+
DEFAULT_POLICY.retryableCodes,
|
|
93
|
+
},
|
|
94
|
+
fieldEnforcement: {
|
|
95
|
+
...DEFAULT_FIELDS,
|
|
96
|
+
..._config.fieldEnforcement,
|
|
97
|
+
...config.fieldEnforcement,
|
|
98
|
+
},
|
|
79
99
|
};
|
|
80
100
|
_configured = true;
|
|
81
101
|
}
|
|
@@ -11,13 +11,13 @@ export function fetchCalls(table, options, initialized) {
|
|
|
11
11
|
if (filter && useFilterWhileSyncing && !tableFiltersUsed.has(table)) {
|
|
12
12
|
tableFilters.set(table, [filter]);
|
|
13
13
|
}
|
|
14
|
-
if (onPushToRemote) {
|
|
14
|
+
if (onPushToRemote && !syncCalls.get(table)?.push) {
|
|
15
15
|
syncCalls.set(table, {
|
|
16
16
|
...(syncCalls.get(table) || {}),
|
|
17
17
|
push: onPushToRemote,
|
|
18
18
|
});
|
|
19
19
|
}
|
|
20
|
-
if (onInsertAndUpdate) {
|
|
20
|
+
if (onInsertAndUpdate && !syncCalls.get(table)?.pull) {
|
|
21
21
|
syncCalls.set(table, {
|
|
22
22
|
...(syncCalls.get(table) || {}),
|
|
23
23
|
pull: onInsertAndUpdate,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/supastashFilters/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/supastashFilters/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAarE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,CAAC,OAAO,UAAU,mBAAmB,CAAC,OAAO,EAAE,eAAe,QAmDnE"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect } from "react";
|
|
2
|
-
import { tableFilters, tableFiltersUsed } from "../../store/tableFilters";
|
|
2
|
+
import { filterTracker, tableFilters, tableFiltersUsed, } from "../../store/tableFilters";
|
|
3
3
|
import { logWarn } from "../../utils/logs";
|
|
4
4
|
import isValidFilter, { warnOnMisMatch, } from "../../utils/sync/pullFromRemote/validateFilters";
|
|
5
5
|
import { checkIfTableExist } from "../../utils/tableValidator";
|
|
@@ -41,31 +41,43 @@ function warnInvalidFilter(filter, table) {
|
|
|
41
41
|
*/
|
|
42
42
|
export default function useSupastashFilters(filters) {
|
|
43
43
|
useEffect(() => {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
let cancelled = false;
|
|
45
|
+
async function run() {
|
|
46
|
+
const incoming = Object.keys(filters ?? {});
|
|
47
|
+
// Remove stale tables
|
|
48
|
+
for (const t of Array.from(tableFilters.keys())) {
|
|
49
|
+
if (!incoming.includes(t)) {
|
|
50
|
+
tableFilters.delete(t);
|
|
51
|
+
tableFiltersUsed.delete(t);
|
|
52
|
+
filterTracker.delete(t);
|
|
50
53
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
}
|
|
55
|
+
// Existence check + per-table registration
|
|
56
|
+
const existence = await Promise.all(incoming.map(async (t) => [t, await checkIfTableExist(t)]));
|
|
57
|
+
if (cancelled)
|
|
58
|
+
return;
|
|
59
|
+
for (const [table, exists] of existence) {
|
|
60
|
+
if (!exists) {
|
|
61
|
+
logWarn(`Table '${table}' does not exist; skipping filters`);
|
|
54
62
|
continue;
|
|
55
63
|
}
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
warnOnMisMatch(table, validFilters);
|
|
64
|
-
tableFilters.set(table, validFilters);
|
|
65
|
-
tableFiltersUsed.add(table);
|
|
64
|
+
const raw = filters[table] ?? [];
|
|
65
|
+
const valid = raw.filter((f) => isValidFilter([f]));
|
|
66
|
+
if (!valid.length) {
|
|
67
|
+
tableFilters.delete(table);
|
|
68
|
+
tableFiltersUsed.delete(table);
|
|
69
|
+
filterTracker.delete(table);
|
|
70
|
+
continue;
|
|
66
71
|
}
|
|
72
|
+
// Warn on signature change and store a cloned array
|
|
73
|
+
warnOnMisMatch(table, valid);
|
|
74
|
+
tableFilters.set(table, valid.map((v) => ({ ...v })));
|
|
75
|
+
tableFiltersUsed.add(table);
|
|
67
76
|
}
|
|
77
|
+
}
|
|
78
|
+
void run();
|
|
79
|
+
return () => {
|
|
80
|
+
cancelled = true;
|
|
68
81
|
};
|
|
69
|
-
void addFilters();
|
|
70
82
|
}, [filters]);
|
|
71
83
|
}
|
|
@@ -1,37 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Push then (optionally) pull.
|
|
3
|
+
* - Single flight across entire app via module-scoped flags.
|
|
4
|
+
* - Both directions gated on connectivity.
|
|
5
|
+
* - "force" ignores pull cadence timing.
|
|
3
6
|
*/
|
|
4
7
|
export declare function syncAll(force?: boolean): Promise<void>;
|
|
8
|
+
/**
|
|
9
|
+
* Hook to start/stop the periodic sync engine.
|
|
10
|
+
* - Staggers push & pull timers.
|
|
11
|
+
* - Debounced foreground trigger.
|
|
12
|
+
* - Shares module-level single-flight guards with syncAll().
|
|
13
|
+
*/
|
|
5
14
|
export declare function useSyncEngine(): {
|
|
6
15
|
startSync: () => void;
|
|
7
16
|
stopSync: () => void;
|
|
8
17
|
};
|
|
9
18
|
/**
|
|
10
|
-
* Manually
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* - Pulls remote data into local SQLite (if a pull handler is registered)
|
|
14
|
-
* - Pushes unsynced local data to Supabase
|
|
15
|
-
* - Skips syncing if already in progress for this table
|
|
16
|
-
* - Applies filter if `useFiltersFromStore` is enabled
|
|
17
|
-
*
|
|
18
|
-
* Use this for explicit sync triggers (e.g., pull-to-refresh).
|
|
19
|
-
*
|
|
20
|
-
* @param {string} table - The name of the table to sync.
|
|
21
|
-
* @returns {Promise<void>}
|
|
19
|
+
* Manually sync a single table (pull then push for that table).
|
|
20
|
+
* - Uses table-specific handlers from syncCalls if provided.
|
|
21
|
+
* - Respects configured filters when enabled.
|
|
22
22
|
*/
|
|
23
23
|
export declare function syncTable(table: string): Promise<void>;
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* This function:
|
|
28
|
-
* - Triggers a full sync of all registered tables
|
|
29
|
-
* - Forces a sync outside the normal polling schedule
|
|
30
|
-
* - Skips any table that is already syncing
|
|
31
|
-
*
|
|
32
|
-
* Useful for global refreshes (e.g., on app foreground).
|
|
33
|
-
*
|
|
34
|
-
* @returns {Promise<void>}
|
|
25
|
+
* Force a global sync pass now (push then pull if due).
|
|
35
26
|
*/
|
|
36
27
|
export declare function syncAllTables(): Promise<void>;
|
|
37
28
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/syncEngine/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/syncEngine/index.ts"],"names":[],"mappings":"AA4BA;;;;;GAKG;AACH,wBAAsB,OAAO,CAAC,KAAK,GAAE,OAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAgCnE;AAmDD;;;;;GAKG;AACH,wBAAgB,aAAa;;;EA8D5B;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,4 +1,4 @@
|
|
|
1
|
-
import { useRef } from "react";
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
2
|
import { AppState } from "react-native";
|
|
3
3
|
import { getSupastashConfig } from "../../core/config";
|
|
4
4
|
import { syncCalls } from "../../store/syncCalls";
|
|
@@ -7,103 +7,215 @@ import { isOnline } from "../../utils/connection";
|
|
|
7
7
|
import log from "../../utils/logs";
|
|
8
8
|
import { updateLocalDb } from "../../utils/sync/pullFromRemote/updateLocalDb";
|
|
9
9
|
import { pushLocalDataToRemote } from "../../utils/sync/pushLocal/sendUnsyncedToSupabase";
|
|
10
|
-
import { pullFromRemote } from "./pullFromRemote";
|
|
11
|
-
import { pushLocalData } from "./pushLocal";
|
|
10
|
+
import { pullFromRemote as doPullFromRemote } from "./pullFromRemote";
|
|
11
|
+
import { pushLocalData as doPushLocalData } from "./pushLocal";
|
|
12
|
+
// -----------------------------
|
|
13
|
+
// Module-scoped state & tunables
|
|
14
|
+
// -----------------------------
|
|
12
15
|
let isSyncing = false;
|
|
13
|
-
let
|
|
14
|
-
|
|
16
|
+
let isPushing = false;
|
|
17
|
+
let isPulling = false;
|
|
18
|
+
let lastPullAt = 0;
|
|
19
|
+
let lastPushAt = 0;
|
|
20
|
+
const MIN_FOREGROUND_GAP = 5000; // ms
|
|
21
|
+
// -----------------------------
|
|
22
|
+
// Core orchestration
|
|
23
|
+
// -----------------------------
|
|
15
24
|
/**
|
|
16
|
-
*
|
|
25
|
+
* Push then (optionally) pull.
|
|
26
|
+
* - Single flight across entire app via module-scoped flags.
|
|
27
|
+
* - Both directions gated on connectivity.
|
|
28
|
+
* - "force" ignores pull cadence timing.
|
|
17
29
|
*/
|
|
18
30
|
export async function syncAll(force = false) {
|
|
19
|
-
if (isSyncing)
|
|
31
|
+
if (isSyncing)
|
|
20
32
|
return;
|
|
21
|
-
}
|
|
22
33
|
if (!(await isOnline()))
|
|
23
34
|
return;
|
|
35
|
+
isSyncing = true;
|
|
36
|
+
const started = Date.now();
|
|
24
37
|
try {
|
|
25
|
-
|
|
26
|
-
|
|
38
|
+
const cfg = getSupastashConfig();
|
|
39
|
+
// PUSH
|
|
40
|
+
if (cfg.syncEngine?.push) {
|
|
41
|
+
await pushLocalDataSafe();
|
|
42
|
+
}
|
|
43
|
+
// PULL
|
|
44
|
+
if (cfg.syncEngine?.pull) {
|
|
45
|
+
const pullInterval = cfg.pollingInterval?.pull ?? 30000;
|
|
27
46
|
const now = Date.now();
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
await pullFromRemote();
|
|
33
|
-
lastFullSync = now;
|
|
47
|
+
const due = force || now - lastPullAt >= pullInterval;
|
|
48
|
+
if (due) {
|
|
49
|
+
await pullFromRemoteSafe();
|
|
50
|
+
lastPullAt = now;
|
|
34
51
|
}
|
|
35
52
|
}
|
|
36
|
-
await pushLocalData();
|
|
37
53
|
}
|
|
38
|
-
catch (
|
|
39
|
-
log(
|
|
54
|
+
catch (e) {
|
|
55
|
+
log("[Supastash] Error in syncAll", {
|
|
56
|
+
msg: String(e),
|
|
57
|
+
code: e?.code ?? e?.name ?? "UNKNOWN",
|
|
58
|
+
});
|
|
40
59
|
}
|
|
41
60
|
finally {
|
|
42
61
|
isSyncing = false;
|
|
62
|
+
log(`[Supastash] syncAll finished in ${Date.now() - started}ms`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// -----------------------------
|
|
66
|
+
// Directional helpers
|
|
67
|
+
// -----------------------------
|
|
68
|
+
async function pushLocalDataSafe() {
|
|
69
|
+
if (isPushing)
|
|
70
|
+
return;
|
|
71
|
+
if (!(await isOnline()))
|
|
72
|
+
return;
|
|
73
|
+
const cfg = getSupastashConfig();
|
|
74
|
+
if (!cfg.syncEngine?.push)
|
|
75
|
+
return;
|
|
76
|
+
isPushing = true;
|
|
77
|
+
try {
|
|
78
|
+
await doPushLocalData();
|
|
79
|
+
lastPushAt = Date.now();
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
log("[Supastash] push error", {
|
|
83
|
+
msg: String(e),
|
|
84
|
+
code: e?.code ?? e?.name ?? "UNKNOWN",
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
isPushing = false;
|
|
43
89
|
}
|
|
44
90
|
}
|
|
91
|
+
async function pullFromRemoteSafe() {
|
|
92
|
+
if (isPulling)
|
|
93
|
+
return;
|
|
94
|
+
if (!(await isOnline()))
|
|
95
|
+
return;
|
|
96
|
+
const cfg = getSupastashConfig();
|
|
97
|
+
if (!cfg.syncEngine?.pull)
|
|
98
|
+
return;
|
|
99
|
+
isPulling = true;
|
|
100
|
+
try {
|
|
101
|
+
await doPullFromRemote();
|
|
102
|
+
lastPullAt = Date.now();
|
|
103
|
+
}
|
|
104
|
+
catch (e) {
|
|
105
|
+
log("[Supastash] pull error", {
|
|
106
|
+
msg: String(e),
|
|
107
|
+
code: e?.code ?? e?.name ?? "UNKNOWN",
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
isPulling = false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// -----------------------------
|
|
115
|
+
// React hook: timer & lifecycle management
|
|
116
|
+
// -----------------------------
|
|
117
|
+
/**
|
|
118
|
+
* Hook to start/stop the periodic sync engine.
|
|
119
|
+
* - Staggers push & pull timers.
|
|
120
|
+
* - Debounced foreground trigger.
|
|
121
|
+
* - Shares module-level single-flight guards with syncAll().
|
|
122
|
+
*/
|
|
45
123
|
export function useSyncEngine() {
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
const
|
|
124
|
+
// Timers & lifecycle
|
|
125
|
+
const intervalRefPush = useRef(null);
|
|
126
|
+
const intervalRefPull = useRef(null);
|
|
127
|
+
const appStateSubRef = useRef(null);
|
|
128
|
+
// Debounce foreground re-entry
|
|
129
|
+
const lastForeground = useRef(0);
|
|
49
130
|
function startSync() {
|
|
50
|
-
if (
|
|
131
|
+
if (intervalRefPush.current ||
|
|
132
|
+
intervalRefPull.current ||
|
|
133
|
+
appStateSubRef.current) {
|
|
51
134
|
return;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
135
|
+
}
|
|
136
|
+
// Kick a one-shot global sync on start
|
|
137
|
+
void syncAll(true);
|
|
138
|
+
const cfg = getSupastashConfig();
|
|
139
|
+
const pushEvery = cfg.pollingInterval?.push ?? 30000;
|
|
140
|
+
const pullEvery = cfg.pollingInterval?.pull ?? 30000;
|
|
141
|
+
// Push ticker
|
|
142
|
+
intervalRefPush.current = setInterval(() => {
|
|
143
|
+
void pushLocalDataSafe();
|
|
144
|
+
}, pushEvery);
|
|
145
|
+
// Pull ticker
|
|
146
|
+
intervalRefPull.current = setInterval(() => {
|
|
147
|
+
void pullFromRemoteSafe();
|
|
148
|
+
}, pullEvery + 500);
|
|
149
|
+
// Foreground trigger
|
|
150
|
+
appStateSubRef.current = AppState.addEventListener("change", (state) => {
|
|
151
|
+
if (state !== "active")
|
|
152
|
+
return;
|
|
153
|
+
const now = Date.now();
|
|
154
|
+
if (now - lastForeground.current < MIN_FOREGROUND_GAP)
|
|
155
|
+
return;
|
|
156
|
+
lastForeground.current = now;
|
|
157
|
+
void syncAll(true);
|
|
63
158
|
});
|
|
64
159
|
}
|
|
65
160
|
function stopSync() {
|
|
66
|
-
if (
|
|
67
|
-
clearInterval(
|
|
68
|
-
|
|
69
|
-
|
|
161
|
+
if (intervalRefPush.current) {
|
|
162
|
+
clearInterval(intervalRefPush.current);
|
|
163
|
+
intervalRefPush.current = null;
|
|
164
|
+
}
|
|
165
|
+
if (intervalRefPull.current) {
|
|
166
|
+
clearInterval(intervalRefPull.current);
|
|
167
|
+
intervalRefPull.current = null;
|
|
168
|
+
}
|
|
169
|
+
appStateSubRef.current?.remove?.();
|
|
170
|
+
appStateSubRef.current = null;
|
|
70
171
|
}
|
|
172
|
+
// Auto-cleanup if the hook lives in a component lifecycle
|
|
173
|
+
useEffect(() => stopSync, []);
|
|
71
174
|
return { startSync, stopSync };
|
|
72
175
|
}
|
|
176
|
+
// -----------------------------
|
|
177
|
+
// Manual triggers
|
|
178
|
+
// -----------------------------
|
|
73
179
|
/**
|
|
74
|
-
* Manually
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* - Pulls remote data into local SQLite (if a pull handler is registered)
|
|
78
|
-
* - Pushes unsynced local data to Supabase
|
|
79
|
-
* - Skips syncing if already in progress for this table
|
|
80
|
-
* - Applies filter if `useFiltersFromStore` is enabled
|
|
81
|
-
*
|
|
82
|
-
* Use this for explicit sync triggers (e.g., pull-to-refresh).
|
|
83
|
-
*
|
|
84
|
-
* @param {string} table - The name of the table to sync.
|
|
85
|
-
* @returns {Promise<void>}
|
|
180
|
+
* Manually sync a single table (pull then push for that table).
|
|
181
|
+
* - Uses table-specific handlers from syncCalls if provided.
|
|
182
|
+
* - Respects configured filters when enabled.
|
|
86
183
|
*/
|
|
87
184
|
export async function syncTable(table) {
|
|
88
|
-
|
|
89
|
-
|
|
185
|
+
if (!(await isOnline()))
|
|
186
|
+
return;
|
|
187
|
+
const cfg = getSupastashConfig();
|
|
188
|
+
const { useFiltersFromStore = true } = cfg?.syncEngine || {};
|
|
90
189
|
const filter = useFiltersFromStore ? tableFilters.get(table) : undefined;
|
|
91
|
-
|
|
92
|
-
|
|
190
|
+
// Pull
|
|
191
|
+
if (cfg.syncEngine?.pull) {
|
|
192
|
+
try {
|
|
193
|
+
await updateLocalDb(table, filter, syncCalls.get(table)?.pull);
|
|
194
|
+
}
|
|
195
|
+
catch (e) {
|
|
196
|
+
log("[Supastash] syncTable pull error", {
|
|
197
|
+
table,
|
|
198
|
+
msg: String(e),
|
|
199
|
+
code: e?.code ?? e?.name ?? "UNKNOWN",
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// Push (use table handler if present)
|
|
204
|
+
if (cfg.syncEngine?.push) {
|
|
205
|
+
try {
|
|
206
|
+
await pushLocalDataToRemote(table, syncCalls.get(table)?.push);
|
|
207
|
+
}
|
|
208
|
+
catch (e) {
|
|
209
|
+
log("[Supastash] syncTable push error", {
|
|
210
|
+
table,
|
|
211
|
+
msg: String(e),
|
|
212
|
+
code: e?.code ?? e?.name ?? "UNKNOWN",
|
|
213
|
+
});
|
|
214
|
+
}
|
|
93
215
|
}
|
|
94
|
-
await pushLocalDataToRemote(table, syncCalls.get(table)?.push);
|
|
95
216
|
}
|
|
96
217
|
/**
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* This function:
|
|
100
|
-
* - Triggers a full sync of all registered tables
|
|
101
|
-
* - Forces a sync outside the normal polling schedule
|
|
102
|
-
* - Skips any table that is already syncing
|
|
103
|
-
*
|
|
104
|
-
* Useful for global refreshes (e.g., on app foreground).
|
|
105
|
-
*
|
|
106
|
-
* @returns {Promise<void>}
|
|
218
|
+
* Force a global sync pass now (push then pull if due).
|
|
107
219
|
*/
|
|
108
220
|
export async function syncAllTables() {
|
|
109
221
|
await syncAll(true);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/hooks/syncEngine/pullFromRemote/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/hooks/syncEngine/pullFromRemote/index.ts"],"names":[],"mappings":"AAQA;;GAEG;AACH,wBAAsB,cAAc,kBA8BnC"}
|
|
@@ -3,6 +3,7 @@ import { syncCalls } from "../../../store/syncCalls";
|
|
|
3
3
|
import { tableFilters } from "../../../store/tableFilters";
|
|
4
4
|
import log from "../../../utils/logs";
|
|
5
5
|
import { getAllTables } from "../../../utils/sync/getAllTables";
|
|
6
|
+
import { runLimitedConcurrency } from "../../../utils/sync/pullFromRemote/runLimitedConcurrency";
|
|
6
7
|
import { updateLocalDb } from "../../../utils/sync/pullFromRemote/updateLocalDb";
|
|
7
8
|
/**
|
|
8
9
|
* Pulls the data from the remote database to the local database
|
|
@@ -16,9 +17,17 @@ export async function pullFromRemote() {
|
|
|
16
17
|
}
|
|
17
18
|
const excludeTables = getSupastashConfig()?.excludeTables?.pull || [];
|
|
18
19
|
const tablesToPull = tables.filter((table) => !excludeTables?.includes(table));
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
const toPull = tablesToPull.map((table) => async () => {
|
|
21
|
+
try {
|
|
22
|
+
const filter = tableFilters.get(table);
|
|
23
|
+
const onReceiveRecord = syncCalls.get(table)?.pull;
|
|
24
|
+
await updateLocalDb(table, filter, onReceiveRecord);
|
|
25
|
+
}
|
|
26
|
+
catch (e) {
|
|
27
|
+
log(`[Supastash] pull table failed: ${table} — ${e?.code ?? e?.name ?? e}`);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
await runLimitedConcurrency(toPull, 3);
|
|
22
31
|
}
|
|
23
32
|
catch (error) {
|
|
24
33
|
log(`[Supastash] Error pulling from remote: ${error}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/hooks/syncEngine/pushLocal/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/hooks/syncEngine/pushLocal/index.ts"],"names":[],"mappings":"AAaA;;GAEG;AACH,wBAAsB,aAAa,kBA4DlC"}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { getSupastashConfig } from "../../../core/config";
|
|
2
2
|
import { syncCalls } from "../../../store/syncCalls";
|
|
3
|
+
import { isOnline } from "../../../utils/connection";
|
|
3
4
|
import log from "../../../utils/logs";
|
|
4
5
|
import { getAllTables } from "../../../utils/sync/getAllTables";
|
|
6
|
+
import { runLimitedConcurrency } from "../../../utils/sync/pullFromRemote/runLimitedConcurrency";
|
|
5
7
|
import { pushLocalDataToRemote } from "../../../utils/sync/pushLocal/sendUnsyncedToSupabase";
|
|
6
|
-
let
|
|
7
|
-
let
|
|
8
|
+
let emptyPassCount = 0;
|
|
9
|
+
let lastEmptyPassAt = 0;
|
|
10
|
+
const tablePushLock = new Map();
|
|
8
11
|
/**
|
|
9
12
|
* Pushes the local data to the remote database
|
|
10
13
|
*/
|
|
@@ -12,23 +15,49 @@ export async function pushLocalData() {
|
|
|
12
15
|
try {
|
|
13
16
|
const tables = await getAllTables();
|
|
14
17
|
if (!tables) {
|
|
15
|
-
log("No tables found");
|
|
18
|
+
log("[Supastash] No tables found");
|
|
16
19
|
return;
|
|
17
20
|
}
|
|
21
|
+
if (!(await isOnline()))
|
|
22
|
+
return;
|
|
18
23
|
const excludeTables = getSupastashConfig()?.excludeTables?.push || [];
|
|
19
24
|
const tablesToPush = tables.filter((table) => !excludeTables?.includes(table));
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
const results = [];
|
|
26
|
+
const jobs = tablesToPush.map((table) => async () => {
|
|
27
|
+
if (tablePushLock.get(table)) {
|
|
28
|
+
results.push({ table, hadWork: false });
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
tablePushLock.set(table, true);
|
|
32
|
+
try {
|
|
33
|
+
const onPush = syncCalls.get(table)?.push;
|
|
34
|
+
const hadWork = await pushLocalDataToRemote(table, onPush);
|
|
35
|
+
results.push({ table, hadWork: !!hadWork });
|
|
36
|
+
}
|
|
37
|
+
catch (e) {
|
|
38
|
+
const msg = e?.code ?? e?.name ?? String(e);
|
|
39
|
+
results.push({ table, hadWork: false, error: msg });
|
|
40
|
+
log(`[Supastash] Push table failed: ${table} — ${msg}`);
|
|
31
41
|
}
|
|
42
|
+
finally {
|
|
43
|
+
tablePushLock.set(table, false);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
await runLimitedConcurrency(jobs, 3);
|
|
47
|
+
const hadAnyWork = results.some((r) => r.hadWork);
|
|
48
|
+
if (!hadAnyWork) {
|
|
49
|
+
emptyPassCount += 1;
|
|
50
|
+
if (emptyPassCount % 150 === 0) {
|
|
51
|
+
const now = Date.now();
|
|
52
|
+
const gap = lastEmptyPassAt ? now - lastEmptyPassAt : 0;
|
|
53
|
+
lastEmptyPassAt = now;
|
|
54
|
+
const noSyncTables = results.map((r) => r.table).join(", ");
|
|
55
|
+
log(`[Supastash] No pushable data for: ${noSyncTables} (empty passes: ${emptyPassCount})${gap ? ` in the last ${gap}ms` : ""}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
emptyPassCount = 0;
|
|
60
|
+
lastEmptyPassAt = Date.now();
|
|
32
61
|
}
|
|
33
62
|
}
|
|
34
63
|
catch (error) {
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export { supastash } from "./utils/query/builder";
|
|
|
10
10
|
export { dropAllTables, dropTable, wipeAllTables, wipeOldDataForAllTables, wipeOldDataForATable, wipeTable, } from "./utils/schema/wipeTables";
|
|
11
11
|
export { getAllTables } from "./utils/sync/getAllTables";
|
|
12
12
|
export { refreshAllTables, refreshTable, refreshTableWithPayload, } from "./utils/sync/refreshTables";
|
|
13
|
+
export { clearSyncCalls, getAllSyncTables, getSyncCall, registerSyncCall, unregisterSyncCall, } from "./utils/sync/registration/syncCalls";
|
|
13
14
|
export { clearAllLocalDeleteLog, clearAllLocalSyncLog, clearLocalDeleteLog, clearLocalSyncLog, getLocalDeleteLog, getLocalSyncLog, setLocalDeleteLog, setLocalSyncLog, } from "./utils/syncStatus";
|
|
14
15
|
export type { CrudMethods } from "./types/query.types";
|
|
15
16
|
export type { RealtimeOptions, SupastashDataResult, SupastashFilter, } from "./types/realtimeData.types";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EACL,aAAa,EACb,SAAS,EACT,aAAa,EACb,uBAAuB,EACvB,oBAAoB,EACpB,SAAS,GACV,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,uBAAuB,GACxB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,eAAe,GAChB,MAAM,oBAAoB,CAAC;AAE5B,YAAY,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,YAAY,EACV,eAAe,EACf,mBAAmB,EACnB,eAAe,GAChB,MAAM,4BAA4B,CAAC;AACpC,YAAY,EACV,eAAe,EACf,mBAAmB,EACnB,0BAA0B,EAC1B,uBAAuB,GACxB,MAAM,+BAA+B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EACL,aAAa,EACb,SAAS,EACT,aAAa,EACb,uBAAuB,EACvB,oBAAoB,EACpB,SAAS,GACV,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,uBAAuB,GACxB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,WAAW,EACX,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EACL,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,eAAe,GAChB,MAAM,oBAAoB,CAAC;AAE5B,YAAY,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,YAAY,EACV,eAAe,EACf,mBAAmB,EACnB,eAAe,GAChB,MAAM,4BAA4B,CAAC;AACpC,YAAY,EACV,eAAe,EACf,mBAAmB,EACnB,0BAA0B,EAC1B,uBAAuB,GACxB,MAAM,+BAA+B,CAAC"}
|