@anfenn/dync 1.0.13 → 1.0.15

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/README.md CHANGED
@@ -8,7 +8,8 @@ Start with a Website or PWA using IndexedDB, sync with your existing REST API, a
8
8
 
9
9
  ## Why Dync?
10
10
 
11
- 1. Frictionless upgrade path from an offline-first PWA targeting IndexedDB, to when:
11
+ 1. Target IndexedDB as a PWA, and SQLite in the AppStores, with no code changes and native storage performance
12
+ 2. Frictionless upgrade path from an offline-first PWA targeting IndexedDB, to when:
12
13
 
13
14
  **A) Substring search is required on many records** (IndexedDB doesn't support this so will do a full table scan in the JS VM, which is both slow and will spike memory)
14
15
 
@@ -18,7 +19,6 @@ Start with a Website or PWA using IndexedDB, sync with your existing REST API, a
18
19
 
19
20
  ... you can simply add CapacitorJs or move to React Native which have sqlite & secure enclave storage, and only change the adapter Dync uses
20
21
 
21
- 2. Write once, run anywhere (Web, CapacitorJs, React Native, Electron & Node) with no code changes and native storage performance
22
22
  3. Completely free and open source
23
23
 
24
24
  <br>See first-hand in this fully working example: [examples/react-capacitor](examples/react-capacitor)
@@ -21,6 +21,47 @@ function newLogger(base, min) {
21
21
  var SERVER_PK = "id";
22
22
  var LOCAL_PK = "_localId";
23
23
  var UPDATED_AT = "updated_at";
24
+ var ApiError = class extends Error {
25
+ isNetworkError;
26
+ cause;
27
+ constructor(message, isNetworkError2, cause) {
28
+ super(message);
29
+ this.name = "ApiError";
30
+ this.isNetworkError = isNetworkError2;
31
+ this.cause = cause;
32
+ }
33
+ };
34
+ function isNetworkError(error) {
35
+ const message = error.message.toLowerCase();
36
+ const name = error.name;
37
+ if (name === "TypeError" && (message.includes("failed to fetch") || message.includes("network request failed"))) {
38
+ return true;
39
+ }
40
+ const code = error.code;
41
+ if (code === "ERR_NETWORK" || code === "ECONNABORTED" || code === "ENOTFOUND" || code === "ECONNREFUSED") {
42
+ return true;
43
+ }
44
+ if (error.isAxiosError && error.response === void 0) {
45
+ return true;
46
+ }
47
+ if (name === "ApolloError" && error.networkError) {
48
+ return true;
49
+ }
50
+ if (message.includes("network error") || message.includes("networkerror")) {
51
+ return true;
52
+ }
53
+ return false;
54
+ }
55
+ function parseApiError(error) {
56
+ if (error instanceof ApiError) {
57
+ return error;
58
+ }
59
+ if (error instanceof Error) {
60
+ return new ApiError(error.message, isNetworkError(error), error);
61
+ }
62
+ const message = String(error);
63
+ return new ApiError(message, false);
64
+ }
24
65
  var SyncAction = /* @__PURE__ */ ((SyncAction2) => {
25
66
  SyncAction2["Create"] = "create";
26
67
  SyncAction2["Update"] = "update";
@@ -102,6 +143,7 @@ var DEFAULT_STATE = {
102
143
  var StateManager = class {
103
144
  persistedState;
104
145
  syncStatus;
146
+ apiError;
105
147
  listeners = /* @__PURE__ */ new Set();
106
148
  storageAdapter;
107
149
  hydrated = false;
@@ -142,12 +184,8 @@ var StateManager = class {
142
184
  this.persistedState = resolveNextState(this.persistedState, setterOrState);
143
185
  return this.persist();
144
186
  }
145
- /**
146
- * Set error in memory only without persisting to database.
147
- * Used when the database itself failed to open.
148
- */
149
- setErrorInMemory(error) {
150
- this.persistedState = { ...this.persistedState, error };
187
+ setApiError(error) {
188
+ this.apiError = error ? parseApiError(error) : void 0;
151
189
  this.emit();
152
190
  }
153
191
  addPendingChange(change) {
@@ -227,7 +265,7 @@ var StateManager = class {
227
265
  this.emit();
228
266
  }
229
267
  getSyncState() {
230
- return buildSyncState(this.persistedState, this.syncStatus, this.hydrated);
268
+ return buildSyncState(this.persistedState, this.syncStatus, this.hydrated, this.apiError);
231
269
  }
232
270
  subscribe(listener) {
233
271
  this.listeners.add(listener);
@@ -250,12 +288,13 @@ function resolveNextState(current, setterOrState) {
250
288
  }
251
289
  return { ...current, ...setterOrState };
252
290
  }
253
- function buildSyncState(state, status, hydrated) {
291
+ function buildSyncState(state, status, hydrated, apiError) {
254
292
  const persisted = clonePersistedState(state);
255
293
  const syncState = {
256
294
  ...persisted,
257
295
  status,
258
- hydrated
296
+ hydrated,
297
+ apiError
259
298
  };
260
299
  deleteKeyIfEmptyObject(syncState, "conflicts");
261
300
  return syncState;
@@ -1463,10 +1502,7 @@ var DyncBase = class {
1463
1502
  this.emitMutation({ type: "pull", tableName });
1464
1503
  }
1465
1504
  this.syncStatus = "idle";
1466
- await this.state.setState((syncState) => ({
1467
- ...syncState,
1468
- error: pullResult.error ?? firstPushSyncError
1469
- }));
1505
+ this.state.setApiError(pullResult.error ?? firstPushSyncError);
1470
1506
  if (this.mutationsDuringSync) {
1471
1507
  this.mutationsDuringSync = false;
1472
1508
  this.syncOnce().catch(() => {
@@ -3771,4 +3807,4 @@ export {
3771
3807
  SqliteQueryContext,
3772
3808
  SQLiteAdapter2 as SQLiteAdapter
3773
3809
  };
3774
- //# sourceMappingURL=chunk-6B5N26W3.js.map
3810
+ //# sourceMappingURL=chunk-I4L3W4PX.js.map