supastash 0.1.27 → 0.1.28

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.
@@ -0,0 +1,18 @@
1
+ /**
2
+ * React hook that returns the current global sync status across all tracked Supastash tables.
3
+ *
4
+ * - Listens for the "updateSyncStatus" event from the event bus.
5
+ * - Recomputes sync status whenever the event is emitted (e.g. after CRUD operations).
6
+ * - Status can be:
7
+ * - "pending" → at least one table has pending sync rows
8
+ * - "error" → at least one table has failed sync rows
9
+ * - "synced" → all tracked tables are fully synced
10
+ *
11
+ * @returns {"pending" | "error" | "synced"} The current sync status
12
+ *
13
+ * @example
14
+ * const syncStatus = useSupastashSyncStatus();
15
+ * if (syncStatus === "pending") showSyncingIndicator();
16
+ */
17
+ export declare function useSupastashSyncStatus(): "error" | "pending" | "synced";
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/syncStatus/index.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,mCAiBrC"}
@@ -0,0 +1,34 @@
1
+ import { useEffect, useState } from "react";
2
+ import { supastashEventBus } from "../../utils/events/eventBus";
3
+ import { getSupastashStatus } from "../../utils/sync/queryStatus";
4
+ /**
5
+ * React hook that returns the current global sync status across all tracked Supastash tables.
6
+ *
7
+ * - Listens for the "updateSyncStatus" event from the event bus.
8
+ * - Recomputes sync status whenever the event is emitted (e.g. after CRUD operations).
9
+ * - Status can be:
10
+ * - "pending" → at least one table has pending sync rows
11
+ * - "error" → at least one table has failed sync rows
12
+ * - "synced" → all tracked tables are fully synced
13
+ *
14
+ * @returns {"pending" | "error" | "synced"} The current sync status
15
+ *
16
+ * @example
17
+ * const syncStatus = useSupastashSyncStatus();
18
+ * if (syncStatus === "pending") showSyncingIndicator();
19
+ */
20
+ export function useSupastashSyncStatus() {
21
+ const [syncStatus, setSyncStatus] = useState("synced");
22
+ useEffect(() => {
23
+ const refreshSyncStatus = () => {
24
+ const status = getSupastashStatus();
25
+ setSyncStatus(status);
26
+ };
27
+ refreshSyncStatus();
28
+ supastashEventBus.on("updateSyncStatus", refreshSyncStatus);
29
+ return () => {
30
+ supastashEventBus.off("updateSyncStatus", refreshSyncStatus);
31
+ };
32
+ }, []);
33
+ return syncStatus;
34
+ }
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ export { getSupastashDb } from "./db/dbInitializer";
4
4
  export { useSupastashData } from "./hooks/supastashData";
5
5
  export { useSupastash } from "./hooks/supastashLogic";
6
6
  export { syncAllTables, syncTable } from "./hooks/syncEngine";
7
+ export { useSupastashSyncStatus } from "./hooks/syncStatus";
7
8
  export { supastashEventBus } from "./utils/events/eventBus";
8
9
  export { supastash } from "./utils/query/builder";
9
10
  export { dropAllTables, dropTable, wipeAllTables, wipeOldDataForAllTables, wipeOldDataForATable, wipeTable, } from "./utils/schema/wipeTables";
@@ -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,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,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"}
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ export { useSupastashData } from "./hooks/supastashData";
5
5
  //export { useSupastashLiteQuery } from "./hooks/supastashLiteQuery";
6
6
  export { useSupastash } from "./hooks/supastashLogic";
7
7
  export { syncAllTables, syncTable } from "./hooks/syncEngine";
8
+ export { useSupastashSyncStatus } from "./hooks/syncStatus";
8
9
  export { supastashEventBus } from "./utils/events/eventBus";
9
10
  export { supastash } from "./utils/query/builder";
10
11
  export { dropAllTables, dropTable, wipeAllTables, wipeOldDataForAllTables, wipeOldDataForATable, wipeTable, } from "./utils/schema/wipeTables";
@@ -0,0 +1,24 @@
1
+ /**
2
+ * A map tracking sync status for each row in each table.
3
+ *
4
+ * @example
5
+ * {
6
+ * "table1": {
7
+ * "1": "pending",
8
+ * "2": "success",
9
+ * "3": "error"
10
+ * },
11
+ * "table2": {
12
+ * "a": "success",
13
+ * "b": "pending"
14
+ * }
15
+ * }
16
+ *
17
+ * This structure means:
18
+ * - `syncStatusMap.get("table1")?.get("1")` would return "pending"
19
+ * - Outer key = table name
20
+ * - Inner key = row ID (as string)
21
+ * - Value = sync status of that row
22
+ */
23
+ export declare const syncStatusMap: Map<string, Map<string, "error" | "pending" | "success">>;
24
+ //# sourceMappingURL=syncStatus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"syncStatus.d.ts","sourceRoot":"","sources":["../../src/store/syncStatus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,aAAa,2DAGvB,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * A map tracking sync status for each row in each table.
3
+ *
4
+ * @example
5
+ * {
6
+ * "table1": {
7
+ * "1": "pending",
8
+ * "2": "success",
9
+ * "3": "error"
10
+ * },
11
+ * "table2": {
12
+ * "a": "success",
13
+ * "b": "pending"
14
+ * }
15
+ * }
16
+ *
17
+ * This structure means:
18
+ * - `syncStatusMap.get("table1")?.get("1")` would return "pending"
19
+ * - Outer key = table name
20
+ * - Inner key = row ID (as string)
21
+ * - Value = sync status of that row
22
+ */
23
+ export const syncStatusMap = new Map();
@@ -1 +1 @@
1
- {"version":3,"file":"mainQueryHelpers.d.ts","sourceRoot":"","sources":["../../../../src/utils/query/helpers/mainQueryHelpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAEL,WAAW,EACX,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACf,MAAM,4BAA4B,CAAC;AAOpC,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,OAAO,EACjB,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,MAAM,GACZ,IAAI,CAUN;AAED,wBAAgB,eAAe,CAAC,CAAC,EAC/B,OAAO,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,GACtB,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,GAAG,SAAS,CAc5B;AAED,wBAAgB,cAAc,CAAC,CAAC,SAAS,OAAO,EAAE,CAAC,SAAS,WAAW,EAAE,CAAC,EAAE,CAAC,EAC3E,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,WAAW,EACnB,WAAW,EAAE,mBAAmB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,EAChD,YAAY,EAAE,mBAAmB,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,GAC7C,CAAC,KAAK,GAAG;IAAE,aAAa,CAAC,EAAE,cAAc,CAAA;CAAE,CAAC,GAAG,IAAI,CAwBrD;AAyBD,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;AA+ED,wBAAsB,eAAe,CACnC,CAAC,SAAS,WAAW,EACrB,CAAC,SAAS,OAAO,EACjB,CAAC,EACD,CAAC,EAED,KAAK,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAC7B,OAAO,CAAC;IACT,WAAW,EAAE,mBAAmB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IACjD,YAAY,EAAE,mBAAmB,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC;CAChD,CAAC,CA2CD"}
1
+ {"version":3,"file":"mainQueryHelpers.d.ts","sourceRoot":"","sources":["../../../../src/utils/query/helpers/mainQueryHelpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAEL,WAAW,EACX,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACf,MAAM,4BAA4B,CAAC;AASpC,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,OAAO,EACjB,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,MAAM,GACZ,IAAI,CAUN;AAED,wBAAgB,eAAe,CAAC,CAAC,EAC/B,OAAO,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,GACtB,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,GAAG,SAAS,CAc5B;AAED,wBAAgB,cAAc,CAAC,CAAC,SAAS,OAAO,EAAE,CAAC,SAAS,WAAW,EAAE,CAAC,EAAE,CAAC,EAC3E,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,WAAW,EACnB,WAAW,EAAE,mBAAmB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,EAChD,YAAY,EAAE,mBAAmB,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,GAC7C,CAAC,KAAK,GAAG;IAAE,aAAa,CAAC,EAAE,cAAc,CAAA;CAAE,CAAC,GAAG,IAAI,CAwBrD;AAyBD,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;AAmFD,wBAAsB,eAAe,CACnC,CAAC,SAAS,WAAW,EACrB,CAAC,SAAS,OAAO,EACjB,CAAC,EACD,CAAC,EAED,KAAK,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAC7B,OAAO,CAAC;IACT,WAAW,EAAE,mBAAmB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IACjD,YAAY,EAAE,mBAAmB,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC;CAChD,CAAC,CA2CD"}
@@ -1,5 +1,7 @@
1
1
  import { isOnline } from "../../../utils/connection";
2
2
  import { log, logWarn } from "../../../utils/logs";
3
+ import { getQueryStatusFromDb } from "../../../utils/sync/queryStatus";
4
+ import { supastashEventBus } from "../../events/eventBus";
3
5
  import { generateUUIDv4 } from "../../genUUID";
4
6
  import { queryLocalDb } from "../localDbQuery";
5
7
  import { querySupabase } from "../remoteQuery/supabaseQuery";
@@ -98,6 +100,8 @@ async function processBatch() {
98
100
  if (!isConnected) {
99
101
  const offlineRetries = (calledOfflineRetries.get(opKey) || 0) + 1;
100
102
  if (offlineRetries > MAX_OFFLINE_RETRIES) {
103
+ await getQueryStatusFromDb(state.table);
104
+ supastashEventBus.emit("updateSyncStatus");
101
105
  logWarn(`[Supastash] Gave up on ${opKey} after ${MAX_OFFLINE_RETRIES} offline retries`);
102
106
  reject(new Error(`Offline retry limit exceeded for ${opKey}`));
103
107
  break;
@@ -1 +1 @@
1
- {"version":3,"file":"supabaseQuery.d.ts","sourceRoot":"","sources":["../../../../src/utils/query/remoteQuery/supabaseQuery.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,cAAc,EACf,MAAM,4BAA4B,CAAC;AAQpC;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,CAAC,SAAS,OAAO,EAAE,CAAC,EAAE,CAAC,EACzD,KAAK,EAAE,cAAc,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC,CAAC,EACxC,SAAS,UAAQ,GAChB,OAAO,CAAC,mBAAmB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAkNpC"}
1
+ {"version":3,"file":"supabaseQuery.d.ts","sourceRoot":"","sources":["../../../../src/utils/query/remoteQuery/supabaseQuery.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,cAAc,EACf,MAAM,4BAA4B,CAAC;AAUpC;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,CAAC,SAAS,OAAO,EAAE,CAAC,EAAE,CAAC,EACzD,KAAK,EAAE,cAAc,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC,CAAC,EACxC,SAAS,UAAQ,GAChB,OAAO,CAAC,mBAAmB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAuNpC"}
@@ -1,5 +1,7 @@
1
1
  import { getSupastashConfig } from "../../../core/config";
2
2
  import { getSupastashDb } from "../../../db/dbInitializer";
3
+ import { getQueryStatusFromDb } from "../../../utils/sync/queryStatus";
4
+ import { supastashEventBus } from "../../events/eventBus";
3
5
  import { refreshScreen } from "../../refreshScreenCalls";
4
6
  import { getSafeValue } from "../../serializer";
5
7
  import { updateLocalSyncedAt } from "../../syncUpdate";
@@ -125,6 +127,10 @@ export async function querySupabase(state, isBatched = false) {
125
127
  }
126
128
  const result = await filterQuery;
127
129
  const db = await getSupastashDb();
130
+ if (result.error) {
131
+ await getQueryStatusFromDb(table);
132
+ supastashEventBus.emit("updateSyncStatus");
133
+ }
128
134
  if (!result.error &&
129
135
  method !== "select" &&
130
136
  type !== "remoteOnly" &&
@@ -1 +1 @@
1
- {"version":3,"file":"refreshScreenCalls.d.ts","sourceRoot":"","sources":["../../src/utils/refreshScreenCalls.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAcjD"}
1
+ {"version":3,"file":"refreshScreenCalls.d.ts","sourceRoot":"","sources":["../../src/utils/refreshScreenCalls.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAejD"}
@@ -11,6 +11,7 @@ export function refreshScreen(table) {
11
11
  const timeout = setTimeout(() => {
12
12
  supastashEventBus.emit(`refresh:${table}`);
13
13
  supastashEventBus.emit(`supastash:refreshZustand:${table}`);
14
+ supastashEventBus.emit("updateSyncStatus");
14
15
  timesFetched.delete(table);
15
16
  debounceMap.delete(table);
16
17
  }, timeoutMs);
@@ -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,iBAU/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;AAyEzD;;;;GAIG;AACH,wBAAsB,UAAU,CAC9B,KAAK,EAAE,MAAM,EACb,eAAe,EAAE,WAAW,EAAE,iBAW/B"}
@@ -1,6 +1,8 @@
1
1
  import { getSupastashConfig } from "../../../core/config";
2
2
  import { getSupastashDb } from "../../../db/dbInitializer";
3
+ import { supastashEventBus } from "../../events/eventBus";
3
4
  import log from "../../logs";
5
+ import { setQueryStatus } from "../queryStatus";
4
6
  import { parseStringifiedFields } from "./parseFields";
5
7
  const CHUNK_SIZE = 500;
6
8
  /**
@@ -14,7 +16,10 @@ async function permanentlyDeleteChunkLocally(table, chunk) {
14
16
  await db.runAsync(`DELETE FROM ${table} WHERE id = ?`, [row.id]);
15
17
  }
16
18
  }
17
- function errorHandler(error, table, attempts) {
19
+ function errorHandler(error, table, attempts, toDelete) {
20
+ for (const row of toDelete) {
21
+ setQueryStatus(row.id, table, "error");
22
+ }
18
23
  log(`Delete attempt ${attempts + 1} failed for a delete operation on table ${table}`, error);
19
24
  }
20
25
  /**
@@ -33,10 +38,14 @@ async function deleteChunk(table, chunk) {
33
38
  while (attempts < 3) {
34
39
  const { error } = await supabase.from(table).upsert(toDelete);
35
40
  if (!error) {
41
+ for (const row of toDelete) {
42
+ setQueryStatus(row.id, table, "success");
43
+ }
44
+ supastashEventBus.emit("updateSyncStatus");
36
45
  await permanentlyDeleteChunkLocally(table, toDelete);
37
46
  break;
38
47
  }
39
- errorHandler(error, table, attempts);
48
+ errorHandler(error, table, attempts, toDelete);
40
49
  attempts++;
41
50
  await new Promise((res) => setTimeout(res, 1000 * Math.pow(2, attempts)));
42
51
  }
@@ -47,7 +56,10 @@ async function deleteChunk(table, chunk) {
47
56
  * @param unsyncedRecords - The unsynced records to delete
48
57
  */
49
58
  export async function deleteData(table, unsyncedRecords) {
50
- const cleanRecords = unsyncedRecords.map(({ synced_at, ...rest }) => parseStringifiedFields(rest));
59
+ const cleanRecords = unsyncedRecords.map(({ synced_at, ...rest }) => {
60
+ setQueryStatus(rest.id, table, "pending");
61
+ return parseStringifiedFields(rest);
62
+ });
51
63
  for (let i = 0; i < cleanRecords.length; i += CHUNK_SIZE) {
52
64
  const chunk = cleanRecords.slice(i, i + CHUNK_SIZE);
53
65
  await deleteChunk(table, chunk);
@@ -1 +1 @@
1
- {"version":3,"file":"sendUnsyncedToSupabase.d.ts","sourceRoot":"","sources":["../../../../src/utils/sync/pushLocal/sendUnsyncedToSupabase.ts"],"names":[],"mappings":"AASA;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,MAAM,EACb,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,EACrD,MAAM,CAAC,EAAE,MAAM,EAAE,iBAsClB"}
1
+ {"version":3,"file":"sendUnsyncedToSupabase.d.ts","sourceRoot":"","sources":["../../../../src/utils/sync/pushLocal/sendUnsyncedToSupabase.ts"],"names":[],"mappings":"AASA;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,MAAM,EACb,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,EACrD,MAAM,CAAC,EAAE,MAAM,EAAE,iBAiClB"}
@@ -27,12 +27,8 @@ export async function pushLocalDataToRemote(table, onPushToRemote, noSync) {
27
27
  await uploadData(table, data, onPushToRemote);
28
28
  refreshScreen(table);
29
29
  }
30
- const payloadForDeletedData = deletedData?.map((item) => ({
31
- id: item.id,
32
- deleted_at: item.deleted_at,
33
- }));
34
- if (payloadForDeletedData && payloadForDeletedData.length > 0) {
35
- await deleteData(table, payloadForDeletedData);
30
+ if (deletedData && deletedData.length > 0) {
31
+ await deleteData(table, deletedData);
36
32
  }
37
33
  }
38
34
  catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"uploadChunk.d.ts","sourceRoot":"","sources":["../../../../src/utils/sync/pushLocal/uploadChunk.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAiKzD;;;;GAIG;AACH,wBAAsB,UAAU,CAC9B,KAAK,EAAE,MAAM,EACb,eAAe,EAAE,WAAW,EAAE,EAC9B,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,iBAUtD"}
1
+ {"version":3,"file":"uploadChunk.d.ts","sourceRoot":"","sources":["../../../../src/utils/sync/pushLocal/uploadChunk.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AA8KzD;;;;GAIG;AACH,wBAAsB,UAAU,CAC9B,KAAK,EAAE,MAAM,EACb,eAAe,EAAE,WAAW,EAAE,EAC9B,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,iBAYtD"}
@@ -1,7 +1,9 @@
1
1
  import { getSupastashConfig } from "../../../core/config";
2
+ import { supastashEventBus } from "../../events/eventBus";
2
3
  import log, { logError } from "../../logs";
3
4
  import { supabaseClientErr } from "../../supabaseClientErr";
4
5
  import { updateLocalSyncedAt } from "../../syncUpdate";
6
+ import { setQueryStatus } from "../queryStatus";
5
7
  import { parseStringifiedFields } from "./parseFields";
6
8
  const RANDOM_OLD_DATE = new Date("2000-01-01").toISOString();
7
9
  const CHUNK_SIZE = 500;
@@ -12,6 +14,9 @@ async function updateSyncStatus(table, rows) {
12
14
  }
13
15
  }
14
16
  function errorHandler(error, table, toUpsert, attempts) {
17
+ for (const row of toUpsert) {
18
+ setQueryStatus(row.id, table, "error");
19
+ }
15
20
  if (attempts === 5) {
16
21
  log(`[Supastash] Upsert attempt ${attempts} failed for table ${table} \n
17
22
  Error: ${error.message} \n
@@ -67,6 +72,7 @@ async function uploadChunk(table, chunk, onPushToRemote) {
67
72
  }
68
73
  else {
69
74
  const { synced_at, ...rest } = row;
75
+ setQueryStatus(rest.id, table, "pending");
70
76
  toUpsert.push(rest);
71
77
  }
72
78
  }
@@ -85,6 +91,9 @@ async function uploadChunk(table, chunk, onPushToRemote) {
85
91
  break;
86
92
  }
87
93
  if (result) {
94
+ for (const row of toUpsert) {
95
+ setQueryStatus(row.id, table, "success");
96
+ }
88
97
  success = true;
89
98
  }
90
99
  else {
@@ -95,6 +104,10 @@ async function uploadChunk(table, chunk, onPushToRemote) {
95
104
  else {
96
105
  const { error } = await supabase.from(table).upsert(toUpsert);
97
106
  if (!error) {
107
+ for (const row of toUpsert) {
108
+ setQueryStatus(row.id, table, "success");
109
+ }
110
+ supastashEventBus.emit("updateSyncStatus");
98
111
  success = true;
99
112
  }
100
113
  else {
@@ -122,7 +135,9 @@ async function uploadChunk(table, chunk, onPushToRemote) {
122
135
  * @param unsyncedRecords - The unsynced records to upload
123
136
  */
124
137
  export async function uploadData(table, unsyncedRecords, onPushToRemote) {
125
- const cleanRecords = unsyncedRecords.map(({ synced_at, deleted_at, ...rest }) => parseStringifiedFields(rest));
138
+ const cleanRecords = unsyncedRecords.map(({ synced_at, deleted_at, ...rest }) => {
139
+ return parseStringifiedFields(rest);
140
+ });
126
141
  for (let i = 0; i < cleanRecords.length; i += CHUNK_SIZE) {
127
142
  const chunk = cleanRecords.slice(i, i + CHUNK_SIZE);
128
143
  await uploadChunk(table, chunk, onPushToRemote);
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Sets the sync status of a query (row) in a specific table.
3
+ * Automatically removes the entry if the status is 'success'.
4
+ *
5
+ * @param rowId - The unique ID of the row
6
+ * @param table - The name of the table
7
+ * @param status - One of: "pending", "success", or "error"
8
+ */
9
+ export declare function setQueryStatus(rowId: string, table: string, status: "pending" | "success" | "error"): void;
10
+ /**
11
+ * Gets the sync status of a table from the database.
12
+ *
13
+ * @param table - Table name
14
+ */
15
+ export declare function getQueryStatusFromDb(table: string): Promise<void>;
16
+ /**
17
+ * Gets the aggregate sync status of a specific table.
18
+ *
19
+ * @param table - Table name
20
+ * @returns An object with:
21
+ * - size: total tracked rows
22
+ * - errors: number of rows in "error" status
23
+ * - pending: number of rows in "pending" status
24
+ * - status: overall table status: "success" | "pending" | "error"
25
+ */
26
+ export declare function getTableStatus(table: string): {
27
+ size: number | undefined;
28
+ errors: number;
29
+ pending: number;
30
+ status: string;
31
+ };
32
+ /**
33
+ * Gets the global sync status across all tracked tables.
34
+ *
35
+ * @returns "error" if any table has errors,
36
+ * "pending" if any table is syncing,
37
+ * "synced" if all tables are fully synced
38
+ */
39
+ export declare function getSupastashStatus(): "error" | "pending" | "synced";
40
+ //# sourceMappingURL=queryStatus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queryStatus.d.ts","sourceRoot":"","sources":["../../../src/utils/sync/queryStatus.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,QAYxC;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,KAAK,EAAE,MAAM,iBAgBvD;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM;;;;;EAwB3C;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,mCAcjC"}
@@ -0,0 +1,93 @@
1
+ import { getSupastashDb } from "../../db/dbInitializer";
2
+ import { syncStatusMap } from "../../store/syncStatus";
3
+ /**
4
+ * Sets the sync status of a query (row) in a specific table.
5
+ * Automatically removes the entry if the status is 'success'.
6
+ *
7
+ * @param rowId - The unique ID of the row
8
+ * @param table - The name of the table
9
+ * @param status - One of: "pending", "success", or "error"
10
+ */
11
+ export function setQueryStatus(rowId, table, status) {
12
+ if (status === "success") {
13
+ syncStatusMap.get(table)?.delete(rowId);
14
+ return;
15
+ }
16
+ if (syncStatusMap.has(table)) {
17
+ syncStatusMap.get(table)?.set(rowId, status);
18
+ }
19
+ else {
20
+ syncStatusMap.set(table, new Map());
21
+ syncStatusMap.get(table)?.set(rowId, status);
22
+ }
23
+ }
24
+ /**
25
+ * Gets the sync status of a table from the database.
26
+ *
27
+ * @param table - Table name
28
+ */
29
+ export async function getQueryStatusFromDb(table) {
30
+ const db = await getSupastashDb();
31
+ const tableData = await db.getAllAsync(`SELECT id FROM ${table} WHERE synced_at IS NULL`);
32
+ let tableMap = syncStatusMap.get(table);
33
+ if (!tableMap) {
34
+ tableMap = new Map();
35
+ syncStatusMap.set(table, tableMap);
36
+ }
37
+ for (const row of tableData) {
38
+ tableMap.set(row.id, "pending");
39
+ }
40
+ }
41
+ /**
42
+ * Gets the aggregate sync status of a specific table.
43
+ *
44
+ * @param table - Table name
45
+ * @returns An object with:
46
+ * - size: total tracked rows
47
+ * - errors: number of rows in "error" status
48
+ * - pending: number of rows in "pending" status
49
+ * - status: overall table status: "success" | "pending" | "error"
50
+ */
51
+ export function getTableStatus(table) {
52
+ const tableStatus = syncStatusMap.get(table);
53
+ const size = tableStatus?.size;
54
+ const statusArray = Array.from(tableStatus?.values() || []);
55
+ let errorCount = 0;
56
+ let pendingCount = 0;
57
+ statusArray.forEach((status) => {
58
+ if (status === "error") {
59
+ errorCount++;
60
+ }
61
+ else if (status === "pending") {
62
+ pendingCount++;
63
+ }
64
+ });
65
+ let status = "success";
66
+ if (pendingCount > 0) {
67
+ status = "pending";
68
+ }
69
+ else if (errorCount > 0) {
70
+ status = "error";
71
+ }
72
+ return { size, errors: errorCount, pending: pendingCount, status };
73
+ }
74
+ /**
75
+ * Gets the global sync status across all tracked tables.
76
+ *
77
+ * @returns "error" if any table has errors,
78
+ * "pending" if any table is syncing,
79
+ * "synced" if all tables are fully synced
80
+ */
81
+ export function getSupastashStatus() {
82
+ const tables = Array.from(syncStatusMap.keys());
83
+ for (const table of tables) {
84
+ const tableStatus = getTableStatus(table);
85
+ if (tableStatus.status === "error") {
86
+ return "error";
87
+ }
88
+ if (tableStatus.status === "pending") {
89
+ return "pending";
90
+ }
91
+ }
92
+ return "synced";
93
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supastash",
3
- "version": "0.1.27",
3
+ "version": "0.1.28",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",