@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 +2 -2
- package/dist/{chunk-6B5N26W3.js → chunk-I4L3W4PX.js} +50 -14
- package/dist/chunk-I4L3W4PX.js.map +1 -0
- package/dist/index.cjs +49 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/{index.shared-DajtjVW7.d.ts → index.shared-D-fB8Odd.d.ts} +6 -1
- package/dist/{index.shared-C8JTfet7.d.cts → index.shared-DHuT0l_t.d.cts} +6 -1
- package/dist/react/index.cjs +49 -13
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +1 -1
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.js +1 -1
- package/package.json +1 -1
- package/src/core/StateManager.ts +18 -10
- package/src/index.shared.ts +1 -4
- package/src/types.ts +64 -1
- package/dist/chunk-6B5N26W3.js.map +0 -1
package/dist/react/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as ApiFunctions, h as SyncOptions, B as BatchSync, D as Dync, i as SyncState } from '../index.shared-
|
|
1
|
+
import { a as ApiFunctions, h as SyncOptions, B as BatchSync, D as Dync, i as SyncState } from '../index.shared-DHuT0l_t.cjs';
|
|
2
2
|
import { c as StorageAdapter } from '../dexie-BqktVP7s.cjs';
|
|
3
3
|
import '../types-CSbIAfu2.cjs';
|
|
4
4
|
import 'dexie';
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as ApiFunctions, h as SyncOptions, B as BatchSync, D as Dync, i as SyncState } from '../index.shared-
|
|
1
|
+
import { a as ApiFunctions, h as SyncOptions, B as BatchSync, D as Dync, i as SyncState } from '../index.shared-D-fB8Odd.js';
|
|
2
2
|
import { c as StorageAdapter } from '../dexie-DRLMKLl5.js';
|
|
3
3
|
import '../types-CSbIAfu2.js';
|
|
4
4
|
import 'dexie';
|
package/dist/react/index.js
CHANGED
package/package.json
CHANGED
package/src/core/StateManager.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import { deleteKeyIfEmptyObject, omitFields } from '../helpers';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
LOCAL_PK,
|
|
4
|
+
UPDATED_AT,
|
|
5
|
+
SyncAction,
|
|
6
|
+
ApiError,
|
|
7
|
+
parseApiError,
|
|
8
|
+
type PendingChange,
|
|
9
|
+
type PersistedSyncState,
|
|
10
|
+
type SyncState,
|
|
11
|
+
type SyncStatus,
|
|
12
|
+
} from '../types';
|
|
3
13
|
import type { StorageAdapter } from '../storage/types';
|
|
4
14
|
|
|
5
15
|
const LOCAL_ONLY_SYNC_FIELDS = [LOCAL_PK, UPDATED_AT];
|
|
@@ -28,7 +38,7 @@ export interface StateHelpers {
|
|
|
28
38
|
hydrate(): Promise<void>;
|
|
29
39
|
getState(): PersistedSyncState;
|
|
30
40
|
setState(setterOrState: PersistedSyncState | ((state: PersistedSyncState) => Partial<PersistedSyncState>)): Promise<void>;
|
|
31
|
-
|
|
41
|
+
setApiError(error: Error | undefined): void;
|
|
32
42
|
addPendingChange(change: Omit<PendingChange, 'version'>): Promise<void>;
|
|
33
43
|
samePendingVersion(tableName: string, localId: string, version: number): boolean;
|
|
34
44
|
removePendingChange(localId: string, tableName: string): Promise<void>;
|
|
@@ -44,6 +54,7 @@ export interface StateHelpers {
|
|
|
44
54
|
export class StateManager implements StateHelpers {
|
|
45
55
|
private persistedState: PersistedSyncState;
|
|
46
56
|
private syncStatus: SyncStatus;
|
|
57
|
+
private apiError?: ApiError;
|
|
47
58
|
private readonly listeners = new Set<(state: SyncState) => void>();
|
|
48
59
|
private readonly storageAdapter?: StorageAdapter;
|
|
49
60
|
private hydrated = false;
|
|
@@ -100,12 +111,8 @@ export class StateManager implements StateHelpers {
|
|
|
100
111
|
return this.persist();
|
|
101
112
|
}
|
|
102
113
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
* Used when the database itself failed to open.
|
|
106
|
-
*/
|
|
107
|
-
setErrorInMemory(error: Error): void {
|
|
108
|
-
this.persistedState = { ...this.persistedState, error };
|
|
114
|
+
setApiError(error: Error | undefined): void {
|
|
115
|
+
this.apiError = error ? parseApiError(error) : undefined;
|
|
109
116
|
this.emit();
|
|
110
117
|
}
|
|
111
118
|
|
|
@@ -199,7 +206,7 @@ export class StateManager implements StateHelpers {
|
|
|
199
206
|
}
|
|
200
207
|
|
|
201
208
|
getSyncState(): SyncState {
|
|
202
|
-
return buildSyncState(this.persistedState, this.syncStatus, this.hydrated);
|
|
209
|
+
return buildSyncState(this.persistedState, this.syncStatus, this.hydrated, this.apiError);
|
|
203
210
|
}
|
|
204
211
|
|
|
205
212
|
subscribe(listener: (state: SyncState) => void): () => void {
|
|
@@ -229,12 +236,13 @@ function resolveNextState(
|
|
|
229
236
|
return { ...current, ...setterOrState };
|
|
230
237
|
}
|
|
231
238
|
|
|
232
|
-
function buildSyncState(state: PersistedSyncState, status: SyncStatus, hydrated: boolean): SyncState {
|
|
239
|
+
function buildSyncState(state: PersistedSyncState, status: SyncStatus, hydrated: boolean, apiError?: ApiError): SyncState {
|
|
233
240
|
const persisted = clonePersistedState(state);
|
|
234
241
|
const syncState: SyncState = {
|
|
235
242
|
...persisted,
|
|
236
243
|
status,
|
|
237
244
|
hydrated,
|
|
245
|
+
apiError,
|
|
238
246
|
};
|
|
239
247
|
deleteKeyIfEmptyObject(syncState, 'conflicts');
|
|
240
248
|
return syncState;
|
package/src/index.shared.ts
CHANGED
|
@@ -358,10 +358,7 @@ class DyncBase<_TStoreMap = Record<string, any>> {
|
|
|
358
358
|
}
|
|
359
359
|
|
|
360
360
|
this.syncStatus = 'idle';
|
|
361
|
-
|
|
362
|
-
...syncState,
|
|
363
|
-
error: pullResult.error ?? firstPushSyncError,
|
|
364
|
-
}));
|
|
361
|
+
this.state.setApiError(pullResult.error ?? firstPushSyncError);
|
|
365
362
|
|
|
366
363
|
if (this.mutationsDuringSync) {
|
|
367
364
|
this.mutationsDuringSync = false;
|
package/src/types.ts
CHANGED
|
@@ -5,6 +5,69 @@ export const SERVER_PK = 'id';
|
|
|
5
5
|
export const LOCAL_PK = '_localId';
|
|
6
6
|
export const UPDATED_AT = 'updated_at';
|
|
7
7
|
|
|
8
|
+
export class ApiError extends Error {
|
|
9
|
+
readonly isNetworkError: boolean;
|
|
10
|
+
override readonly cause?: Error;
|
|
11
|
+
|
|
12
|
+
constructor(message: string, isNetworkError: boolean, cause?: Error) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = 'ApiError';
|
|
15
|
+
this.isNetworkError = isNetworkError;
|
|
16
|
+
this.cause = cause;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Detects if an error is a network-level failure from common HTTP libraries.
|
|
22
|
+
* Supports: fetch, axios, Apollo GraphQL, and generic network errors.
|
|
23
|
+
*/
|
|
24
|
+
function isNetworkError(error: Error): boolean {
|
|
25
|
+
const message = error.message.toLowerCase();
|
|
26
|
+
const name = error.name;
|
|
27
|
+
|
|
28
|
+
// fetch: throws TypeError on network failure
|
|
29
|
+
if (name === 'TypeError' && (message.includes('failed to fetch') || message.includes('network request failed'))) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// axios: sets error.code for network issues
|
|
34
|
+
const code = (error as any).code;
|
|
35
|
+
if (code === 'ERR_NETWORK' || code === 'ECONNABORTED' || code === 'ENOTFOUND' || code === 'ECONNREFUSED') {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// axios: no response means request never reached server
|
|
40
|
+
if ((error as any).isAxiosError && (error as any).response === undefined) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Apollo GraphQL: network error wrapper
|
|
45
|
+
if (name === 'ApolloError' && (error as any).networkError) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Generic network error messages
|
|
50
|
+
if (message.includes('network error') || message.includes('networkerror')) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function parseApiError(error: unknown): ApiError {
|
|
58
|
+
if (error instanceof ApiError) {
|
|
59
|
+
return error;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (error instanceof Error) {
|
|
63
|
+
return new ApiError(error.message, isNetworkError(error), error);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Non-Error thrown (string, object, etc.)
|
|
67
|
+
const message = String(error);
|
|
68
|
+
return new ApiError(message, false);
|
|
69
|
+
}
|
|
70
|
+
|
|
8
71
|
export interface SyncedRecord {
|
|
9
72
|
_localId: string;
|
|
10
73
|
id?: any;
|
|
@@ -138,7 +201,6 @@ export interface PersistedSyncState {
|
|
|
138
201
|
firstLoadDone: boolean;
|
|
139
202
|
pendingChanges: PendingChange[];
|
|
140
203
|
lastPulled: Record<string, string>;
|
|
141
|
-
error?: Error;
|
|
142
204
|
conflicts?: Record<string, Conflict>;
|
|
143
205
|
}
|
|
144
206
|
|
|
@@ -147,6 +209,7 @@ export type SyncStatus = 'disabled' | 'disabling' | 'idle' | 'syncing' | 'error'
|
|
|
147
209
|
export interface SyncState extends PersistedSyncState {
|
|
148
210
|
status: SyncStatus;
|
|
149
211
|
hydrated: boolean;
|
|
212
|
+
apiError?: ApiError;
|
|
150
213
|
}
|
|
151
214
|
|
|
152
215
|
export enum SyncAction {
|