@anfenn/dync 1.0.14 → 1.0.16
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/{chunk-6B5N26W3.js → chunk-NJF2KCLA.js} +49 -14
- package/dist/chunk-NJF2KCLA.js.map +1 -0
- package/dist/index.cjs +48 -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 +48 -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 +9 -11
- package/src/helpers.ts +50 -1
- package/src/index.shared.ts +1 -4
- package/src/types.ts +13 -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,5 @@
|
|
|
1
|
-
import { deleteKeyIfEmptyObject, omitFields } from '../helpers';
|
|
2
|
-
import { LOCAL_PK, UPDATED_AT, SyncAction, type PendingChange, type PersistedSyncState, type SyncState, type SyncStatus } from '../types';
|
|
1
|
+
import { deleteKeyIfEmptyObject, omitFields, parseApiError } from '../helpers';
|
|
2
|
+
import { LOCAL_PK, UPDATED_AT, SyncAction, ApiError, type PendingChange, type PersistedSyncState, type SyncState, type SyncStatus } from '../types';
|
|
3
3
|
import type { StorageAdapter } from '../storage/types';
|
|
4
4
|
|
|
5
5
|
const LOCAL_ONLY_SYNC_FIELDS = [LOCAL_PK, UPDATED_AT];
|
|
@@ -28,7 +28,7 @@ export interface StateHelpers {
|
|
|
28
28
|
hydrate(): Promise<void>;
|
|
29
29
|
getState(): PersistedSyncState;
|
|
30
30
|
setState(setterOrState: PersistedSyncState | ((state: PersistedSyncState) => Partial<PersistedSyncState>)): Promise<void>;
|
|
31
|
-
|
|
31
|
+
setApiError(error: Error | undefined): void;
|
|
32
32
|
addPendingChange(change: Omit<PendingChange, 'version'>): Promise<void>;
|
|
33
33
|
samePendingVersion(tableName: string, localId: string, version: number): boolean;
|
|
34
34
|
removePendingChange(localId: string, tableName: string): Promise<void>;
|
|
@@ -44,6 +44,7 @@ export interface StateHelpers {
|
|
|
44
44
|
export class StateManager implements StateHelpers {
|
|
45
45
|
private persistedState: PersistedSyncState;
|
|
46
46
|
private syncStatus: SyncStatus;
|
|
47
|
+
private apiError?: ApiError;
|
|
47
48
|
private readonly listeners = new Set<(state: SyncState) => void>();
|
|
48
49
|
private readonly storageAdapter?: StorageAdapter;
|
|
49
50
|
private hydrated = false;
|
|
@@ -100,12 +101,8 @@ export class StateManager implements StateHelpers {
|
|
|
100
101
|
return this.persist();
|
|
101
102
|
}
|
|
102
103
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
* Used when the database itself failed to open.
|
|
106
|
-
*/
|
|
107
|
-
setErrorInMemory(error: Error): void {
|
|
108
|
-
this.persistedState = { ...this.persistedState, error };
|
|
104
|
+
setApiError(error: Error | undefined): void {
|
|
105
|
+
this.apiError = error ? parseApiError(error) : undefined;
|
|
109
106
|
this.emit();
|
|
110
107
|
}
|
|
111
108
|
|
|
@@ -199,7 +196,7 @@ export class StateManager implements StateHelpers {
|
|
|
199
196
|
}
|
|
200
197
|
|
|
201
198
|
getSyncState(): SyncState {
|
|
202
|
-
return buildSyncState(this.persistedState, this.syncStatus, this.hydrated);
|
|
199
|
+
return buildSyncState(this.persistedState, this.syncStatus, this.hydrated, this.apiError);
|
|
203
200
|
}
|
|
204
201
|
|
|
205
202
|
subscribe(listener: (state: SyncState) => void): () => void {
|
|
@@ -229,12 +226,13 @@ function resolveNextState(
|
|
|
229
226
|
return { ...current, ...setterOrState };
|
|
230
227
|
}
|
|
231
228
|
|
|
232
|
-
function buildSyncState(state: PersistedSyncState, status: SyncStatus, hydrated: boolean): SyncState {
|
|
229
|
+
function buildSyncState(state: PersistedSyncState, status: SyncStatus, hydrated: boolean, apiError?: ApiError): SyncState {
|
|
233
230
|
const persisted = clonePersistedState(state);
|
|
234
231
|
const syncState: SyncState = {
|
|
235
232
|
...persisted,
|
|
236
233
|
status,
|
|
237
234
|
hydrated,
|
|
235
|
+
apiError,
|
|
238
236
|
};
|
|
239
237
|
deleteKeyIfEmptyObject(syncState, 'conflicts');
|
|
240
238
|
return syncState;
|
package/src/helpers.ts
CHANGED
|
@@ -1,8 +1,57 @@
|
|
|
1
|
-
import { SyncAction } from './types';
|
|
1
|
+
import { ApiError, SyncAction } from './types';
|
|
2
2
|
import { createLocalId } from './createLocalId';
|
|
3
3
|
|
|
4
4
|
export { createLocalId };
|
|
5
5
|
|
|
6
|
+
export function parseApiError(error: any): ApiError {
|
|
7
|
+
if (error instanceof ApiError) {
|
|
8
|
+
return error;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (typeof error === 'string') {
|
|
12
|
+
return new ApiError(error, false);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return new ApiError(error.message, isNetworkError(error), error);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Detects if an error is a network-level failure from common HTTP libraries.
|
|
20
|
+
* Supports: fetch, axios, Apollo GraphQL, and generic network errors.
|
|
21
|
+
*/
|
|
22
|
+
function isNetworkError(error: any): boolean {
|
|
23
|
+
const message = error.message?.toLowerCase() ?? '';
|
|
24
|
+
const name = error.name;
|
|
25
|
+
|
|
26
|
+
// fetch: throws TypeError on network failure
|
|
27
|
+
if (name === 'TypeError' && (message.includes('failed to fetch') || message.includes('network request failed'))) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// axios: sets error.code for network issues
|
|
32
|
+
const code = error.code;
|
|
33
|
+
if (code === 'ERR_NETWORK' || code === 'ECONNABORTED' || code === 'ENOTFOUND' || code === 'ECONNREFUSED') {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// axios: no response means request never reached server
|
|
38
|
+
if (error.isAxiosError && error.response === undefined) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Apollo GraphQL: network error wrapper
|
|
43
|
+
if (name === 'ApolloError' && error.networkError) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Generic network error messages
|
|
48
|
+
if (message.includes('network error') || message.includes('networkerror')) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
6
55
|
export function sleep(ms: number, signal?: AbortSignal): Promise<void> {
|
|
7
56
|
return new Promise((resolve) => {
|
|
8
57
|
if (signal?.aborted) {
|
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,18 @@ 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
|
+
|
|
8
20
|
export interface SyncedRecord {
|
|
9
21
|
_localId: string;
|
|
10
22
|
id?: any;
|
|
@@ -138,7 +150,6 @@ export interface PersistedSyncState {
|
|
|
138
150
|
firstLoadDone: boolean;
|
|
139
151
|
pendingChanges: PendingChange[];
|
|
140
152
|
lastPulled: Record<string, string>;
|
|
141
|
-
error?: Error;
|
|
142
153
|
conflicts?: Record<string, Conflict>;
|
|
143
154
|
}
|
|
144
155
|
|
|
@@ -147,6 +158,7 @@ export type SyncStatus = 'disabled' | 'disabling' | 'idle' | 'syncing' | 'error'
|
|
|
147
158
|
export interface SyncState extends PersistedSyncState {
|
|
148
159
|
status: SyncStatus;
|
|
149
160
|
hydrated: boolean;
|
|
161
|
+
apiError?: ApiError;
|
|
150
162
|
}
|
|
151
163
|
|
|
152
164
|
export enum SyncAction {
|