supastash 0.1.47 → 0.1.49

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.
@@ -97,7 +97,7 @@ export function useSupastashData(table, options = {}) {
97
97
  unsub.current = AppState.addEventListener("change", (state) => {
98
98
  if (state === "active") {
99
99
  if (!isAnyNullish) {
100
- initialFetchAndSync();
100
+ void initialFetchAndSync();
101
101
  }
102
102
  }
103
103
  });
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/hooks/syncEngine/pullFromRemote/index.ts"],"names":[],"mappings":"AASA;;GAEG;AACH,wBAAsB,cAAc,kBAoEnC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/hooks/syncEngine/pullFromRemote/index.ts"],"names":[],"mappings":"AASA;;GAEG;AACH,wBAAsB,cAAc,kBAqEnC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/hooks/syncEngine/pushLocal/index.ts"],"names":[],"mappings":"AAcA;;GAEG;AACH,wBAAsB,aAAa,kBAiGlC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/hooks/syncEngine/pushLocal/index.ts"],"names":[],"mappings":"AAcA;;GAEG;AACH,wBAAsB,aAAa,kBAkGlC"}
@@ -1,2 +1,3 @@
1
1
  export declare function isOnline(): Promise<boolean>;
2
+ export declare function isNetworkError(err: any): boolean;
2
3
  //# sourceMappingURL=connection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/utils/connection.ts"],"names":[],"mappings":"AAEA,wBAAsB,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC,CAKjD"}
1
+ {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/utils/connection.ts"],"names":[],"mappings":"AAEA,wBAAsB,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC,CAKjD;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAehD"}
@@ -7,3 +7,21 @@ export async function isOnline() {
7
7
  return false;
8
8
  return true;
9
9
  }
10
+ export function isNetworkError(err) {
11
+ if (!err)
12
+ return false;
13
+ if (err.message === "Failed to fetch")
14
+ return true;
15
+ if (err.message?.includes("Network request failed"))
16
+ return true;
17
+ if (err.message?.includes("NetworkError"))
18
+ return true;
19
+ if (err.message?.includes("timeout"))
20
+ return true;
21
+ if (err.__isOffline === true)
22
+ return true;
23
+ const hasPgCode = typeof err.code === "string" && err.code.length > 0;
24
+ if (!hasPgCode)
25
+ return true;
26
+ return false;
27
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"createTable.d.ts","sourceRoot":"","sources":["../../../src/utils/fetchData/createTable.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AA8CtD;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,iBAsCrE"}
1
+ {"version":3,"file":"createTable.d.ts","sourceRoot":"","sources":["../../../src/utils/fetchData/createTable.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAqDtD;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,iBAsCrE"}
@@ -1,6 +1,7 @@
1
1
  import { getSupastashConfig } from "../../core/config";
2
2
  import { getSupastashDb } from "../../db/dbInitializer";
3
3
  import { tableSchemaData } from "../../store/tableSchemaData";
4
+ import { isNetworkError, isOnline } from "../connection";
4
5
  import log from "../logs";
5
6
  import { supabaseClientErr } from "../supabaseClientErr";
6
7
  import { checkIfTableExist } from "../tableValidator";
@@ -19,11 +20,17 @@ async function getTableSchema(table) {
19
20
  if (errorCount.get(table) && (errorCount.get(table) || 0) > 3) {
20
21
  return null;
21
22
  }
23
+ const isConnected = await isOnline();
24
+ if (!isConnected) {
25
+ return null;
26
+ }
22
27
  const { data, error } = await supabase.rpc("get_table_schema", {
23
28
  table_name: table,
24
29
  });
25
30
  if (error) {
26
- log(`[Supastash] Error getting table schema for table ${table}: ${error.message}`);
31
+ if (!isNetworkError(error)) {
32
+ log(`[Supastash] Error getting table schema for table ${table}: ${error.message}`);
33
+ }
27
34
  errorCount.set(table, (errorCount.get(table) || 0) + 1);
28
35
  return null;
29
36
  }
@@ -1,4 +1,4 @@
1
- import { logError } from "../../logs";
1
+ import { logWarn } from "../../logs";
2
2
  import { refreshScreen } from "../../refreshScreenCalls";
3
3
  import { assignInsertIds, getCommonError, runSyncStrategy, validatePayloadForSingleInsert, } from "../helpers/mainQueryHelpers";
4
4
  import { validateQuery } from "../helpers/queryValidator";
@@ -42,7 +42,7 @@ export async function queryDb(state) {
42
42
  });
43
43
  }
44
44
  catch (error) {
45
- logError(`[Supastash] ${error instanceof Error ? error.message : String(error)}`);
45
+ logWarn(`[Supastash] ${error instanceof Error ? error.message : String(error)}`);
46
46
  if (state.viewRemoteResult) {
47
47
  return Promise.resolve({
48
48
  remote: null,
@@ -1 +1 @@
1
- {"version":3,"file":"queueRemote.d.ts","sourceRoot":"","sources":["../../../../src/utils/query/helpers/queueRemote.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,WAAW,EACX,cAAc,EACf,MAAM,4BAA4B,CAAC;AA8BpC,wBAAgB,eAAe,CAAC,CAAC,SAAS,WAAW,EAAE,CAAC,SAAS,OAAO,EAAE,CAAC,EACzE,KAAK,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAC7B,OAAO,CAAC,OAAO,CAAC,CAYlB"}
1
+ {"version":3,"file":"queueRemote.d.ts","sourceRoot":"","sources":["../../../../src/utils/query/helpers/queueRemote.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,WAAW,EACX,cAAc,EACf,MAAM,4BAA4B,CAAC;AA0CpC,wBAAgB,eAAe,CAAC,CAAC,SAAS,WAAW,EAAE,CAAC,SAAS,OAAO,EAAE,CAAC,EACzE,KAAK,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAC7B,OAAO,CAAC,OAAO,CAAC,CAYlB"}
@@ -7,11 +7,18 @@ let batchQueue = [];
7
7
  let isProcessing = false;
8
8
  let batchTimer = null;
9
9
  const BATCH_DELAY = 10;
10
+ const MAX_RETRIES = 3;
11
+ const MAX_OFFLINE_RETRIES = 5;
10
12
  const retryCount = new Map();
11
13
  const calledOfflineRetries = new Map();
12
14
  const successfulCalls = new Set();
13
- const MAX_RETRIES = 3;
14
- const MAX_OFFLINE_RETRIES = 5;
15
+ const seenFailureLog = new Set();
16
+ function isDuplicateKeyError(error) {
17
+ return (!!error &&
18
+ ((error.code && error.code === "23505") ||
19
+ (typeof error.message === "string" &&
20
+ error.message.toLowerCase().includes("duplicate key"))));
21
+ }
15
22
  function delay(ms) {
16
23
  return new Promise((resolve) => setTimeout(resolve, ms));
17
24
  }
@@ -54,14 +61,17 @@ async function processBatch() {
54
61
  const isConnected = await isOnline();
55
62
  if (!isConnected) {
56
63
  const offlineRetries = (calledOfflineRetries.get(opKey) || 0) + 1;
64
+ calledOfflineRetries.set(opKey, offlineRetries);
57
65
  if (offlineRetries > MAX_OFFLINE_RETRIES) {
66
+ if (!seenFailureLog.has(opKey)) {
67
+ seenFailureLog.add(opKey);
68
+ log(`[Supastash] Offline — persisted locally, will retry later: ${opKey}`);
69
+ }
58
70
  await getQueryStatusFromDb(state.table);
59
71
  supastashEventBus.emit("updateSyncStatus");
60
- logWarn(`[Supastash] Gave up on ${opKey} after ${MAX_OFFLINE_RETRIES} offline retries`);
61
72
  reject(new Error(`Offline retry limit exceeded for ${opKey}`));
62
73
  break;
63
74
  }
64
- calledOfflineRetries.set(opKey, offlineRetries);
65
75
  await delay(1000);
66
76
  continue;
67
77
  }
@@ -77,9 +87,19 @@ async function processBatch() {
77
87
  else {
78
88
  const currentRetries = retryCount.get(opKey) ?? 0;
79
89
  retryCount.set(opKey, currentRetries + 1);
90
+ if (isDuplicateKeyError(error)) {
91
+ if (!seenFailureLog.has(opKey)) {
92
+ seenFailureLog.add(opKey);
93
+ logWarn(`[Supastash] Duplicate key on ${state.table} (op=${state.method}) — seems already synced; will retry on next full sync: id=${state.id ?? "-"}`);
94
+ }
95
+ reject(new Error(`Duplicate key (23505) for ${opKey}`));
96
+ break;
97
+ }
80
98
  if (currentRetries >= MAX_RETRIES) {
81
- logWarn(`[Supastash] Gave up on ${opKey} after ${MAX_RETRIES} retries ERROR: ${error.message} will retry on next sync`);
82
- logWarn("[Supastash] Full error object:", JSON.stringify(error));
99
+ if (!seenFailureLog.has(opKey)) {
100
+ seenFailureLog.add(opKey);
101
+ logWarn(`[Supastash] Gave up on ${opKey} after ${MAX_RETRIES} retries — will retry on next sync`);
102
+ }
83
103
  reject(new Error(`Max retries exceeded for ${opKey}`));
84
104
  break;
85
105
  }
@@ -87,8 +107,12 @@ async function processBatch() {
87
107
  }
88
108
  }
89
109
  }
90
- catch (error) {
91
- reject(error);
110
+ catch (err) {
111
+ if (!seenFailureLog.has(opKey)) {
112
+ seenFailureLog.add(opKey);
113
+ logWarn(`[Supastash] Unexpected error processing ${opKey} — ${String(err.message ?? err)}`);
114
+ }
115
+ reject(err);
92
116
  }
93
117
  }
94
118
  isProcessing = false;
@@ -90,7 +90,7 @@ export async function updateLocalDb(table, filters, onReceiveData) {
90
90
  refreshScreen(table);
91
91
  }
92
92
  catch (error) {
93
- logError(`[Supastash] Error updating local db for ${table}`, error);
93
+ logWarn(`[Supastash] Error updating local db for ${table}`, error);
94
94
  throw error;
95
95
  }
96
96
  finally {
@@ -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,CAyDrE;AAED,eAAe,aAAa,CAAC;AAE7B,wBAAgB,cAAc,CAAC,CAAC,GAAG,GAAG,EACpC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,QAuC7B"}
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,CAyDrE;AAED,eAAe,aAAa,CAAC;AAM7B,wBAAgB,cAAc,CAAC,CAAC,GAAG,GAAG,EACpC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,QAiD7B"}
@@ -58,31 +58,38 @@ function isValidFilter(filters) {
58
58
  return true;
59
59
  }
60
60
  export default isValidFilter;
61
+ const tablesWarned = new Set();
62
+ const debounceWarnTime = 2000;
63
+ let debounceWarnTimeout = null;
61
64
  export function warnOnMisMatch(table, filters) {
62
65
  const existingFilters = filterTracker.get(table);
66
+ let hasMismatch = false;
63
67
  if (existingFilters) {
64
- const changes = [];
65
68
  const maxLength = Math.max(existingFilters.length, filters.length);
66
69
  for (let i = 0; i < maxLength; i++) {
67
70
  const oldFilter = existingFilters[i];
68
71
  const newFilter = filters[i];
69
72
  if (!oldFilter || !newFilter) {
70
- changes.push(` • Filter ${i + 1} was added or removed entirely.`);
71
- continue;
73
+ hasMismatch = true;
74
+ break;
72
75
  }
73
- const { column: oldCol, operator: oldOp } = oldFilter;
74
- const { column: newCol, operator: newOp } = newFilter;
75
- if (oldCol !== newCol || oldOp !== newOp) {
76
- changes.push(` • Filter ${i + 1} changed:\n` +
77
- ` → Column: '${String(oldCol)}' → '${String(newCol)}'\n` +
78
- ` → Operator: '${oldOp}' → '${newOp}'`);
76
+ if (oldFilter.column !== newFilter.column ||
77
+ oldFilter.operator !== newFilter.operator) {
78
+ hasMismatch = true;
79
+ break;
79
80
  }
80
81
  }
81
- if (changes.length > 0) {
82
- logWarn(`[Supastash] Filter signature mismatch for table '${table}'.`);
83
- logWarn(`[Supastash] The filter structure (column/operator) has changed — this may lead to inconsistent sync behavior.`);
84
- logWarn(`[Supastash] Differences:\n${changes.join("\n")}`);
82
+ }
83
+ if (hasMismatch) {
84
+ tablesWarned.add(table);
85
+ if (debounceWarnTimeout) {
86
+ clearTimeout(debounceWarnTimeout);
85
87
  }
88
+ debounceWarnTimeout = setTimeout(() => {
89
+ logWarn(`[Supastash] Conflicting filters detected for table(s): ${Array.from(tablesWarned).join(", ")}. The same table is being synced with different filters across multiple calls. This can cause incomplete or inconsistent local data. Ensure each table is registered with a single, consistent filter.`);
90
+ tablesWarned.clear();
91
+ debounceWarnTimeout = null;
92
+ }, debounceWarnTime);
86
93
  }
87
94
  filterTracker.set(table, filters);
88
95
  }
@@ -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;AAsEzD;;;;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":"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"}
@@ -3,6 +3,7 @@ import { getSupastashDb } from "../../../db/dbInitializer";
3
3
  import { tableSchemaData } from "../../../store/tableSchemaData";
4
4
  import { getTableSchema } from "../../../utils/getTableSchema";
5
5
  import log from "../../../utils/logs";
6
+ import { isNetworkError, isOnline } from "../../connection";
6
7
  import { supabaseClientErr } from "../../supabaseClientErr";
7
8
  const numberOfErrors = new Map();
8
9
  const sharedKeysCache = new Map();
@@ -19,14 +20,20 @@ async function getRemoteKeys(table) {
19
20
  return null;
20
21
  }
21
22
  if (!tableSchemaData.has(table)) {
23
+ const isConnected = await isOnline();
24
+ if (!isConnected) {
25
+ return null;
26
+ }
22
27
  const { data, error } = await supabase.rpc("get_table_schema", {
23
28
  table_name: table,
24
29
  });
25
30
  if (error) {
26
- log(`[Supastash] Error getting remote keys for table ${table} on public schema: ${error.message}
31
+ if (!isNetworkError(error)) {
32
+ log(`[Supastash] Error getting remote keys for table ${table} on public schema: ${error.message}
27
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`);
28
- numberOfErrors.set(table, (numberOfErrors.get(table) || 0) + 1);
29
- return null;
34
+ numberOfErrors.set(table, (numberOfErrors.get(table) || 0) + 1);
35
+ return null;
36
+ }
30
37
  }
31
38
  tableSchemaData.set(table, data);
32
39
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supastash",
3
- "version": "0.1.47",
3
+ "version": "0.1.49",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",