@mustafaaksoy41/react-native-offline-queue 0.1.2
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/LICENSE +21 -0
- package/README.md +673 -0
- package/lib/commonjs/adapters/index.js +128 -0
- package/lib/commonjs/adapters/index.js.map +1 -0
- package/lib/commonjs/components/OfflineProvider.js +51 -0
- package/lib/commonjs/components/OfflineProvider.js.map +1 -0
- package/lib/commonjs/components/OfflineSyncPrompt.js +37 -0
- package/lib/commonjs/components/OfflineSyncPrompt.js.map +1 -0
- package/lib/commonjs/core/OfflineManager.js +308 -0
- package/lib/commonjs/core/OfflineManager.js.map +1 -0
- package/lib/commonjs/core/StorageAdapter.js +31 -0
- package/lib/commonjs/core/StorageAdapter.js.map +1 -0
- package/lib/commonjs/core/types.js +15 -0
- package/lib/commonjs/core/types.js.map +1 -0
- package/lib/commonjs/global.d.js +2 -0
- package/lib/commonjs/global.d.js.map +1 -0
- package/lib/commonjs/hooks/useOfflineMutation.js +61 -0
- package/lib/commonjs/hooks/useOfflineMutation.js.map +1 -0
- package/lib/commonjs/hooks/useOfflineQueue.js +21 -0
- package/lib/commonjs/hooks/useOfflineQueue.js.map +1 -0
- package/lib/commonjs/hooks/useOfflineSyncInterceptor.js +42 -0
- package/lib/commonjs/hooks/useOfflineSyncInterceptor.js.map +1 -0
- package/lib/commonjs/hooks/useSyncProgress.js +33 -0
- package/lib/commonjs/hooks/useSyncProgress.js.map +1 -0
- package/lib/commonjs/index.js +134 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/module/adapters/index.js +121 -0
- package/lib/module/adapters/index.js.map +1 -0
- package/lib/module/components/OfflineProvider.js +43 -0
- package/lib/module/components/OfflineProvider.js.map +1 -0
- package/lib/module/components/OfflineSyncPrompt.js +31 -0
- package/lib/module/components/OfflineSyncPrompt.js.map +1 -0
- package/lib/module/core/OfflineManager.js +304 -0
- package/lib/module/core/OfflineManager.js.map +1 -0
- package/lib/module/core/StorageAdapter.js +25 -0
- package/lib/module/core/StorageAdapter.js.map +1 -0
- package/lib/module/core/types.js +11 -0
- package/lib/module/core/types.js.map +1 -0
- package/lib/module/global.d.js +2 -0
- package/lib/module/global.d.js.map +1 -0
- package/lib/module/hooks/useOfflineMutation.js +57 -0
- package/lib/module/hooks/useOfflineMutation.js.map +1 -0
- package/lib/module/hooks/useOfflineQueue.js +17 -0
- package/lib/module/hooks/useOfflineQueue.js.map +1 -0
- package/lib/module/hooks/useOfflineSyncInterceptor.js +38 -0
- package/lib/module/hooks/useOfflineSyncInterceptor.js.map +1 -0
- package/lib/module/hooks/useSyncProgress.js +29 -0
- package/lib/module/hooks/useSyncProgress.js.map +1 -0
- package/lib/module/index.js +20 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/adapters/index.d.ts +12 -0
- package/lib/typescript/adapters/index.d.ts.map +1 -0
- package/lib/typescript/components/OfflineProvider.d.ts +13 -0
- package/lib/typescript/components/OfflineProvider.d.ts.map +1 -0
- package/lib/typescript/components/OfflineSyncPrompt.d.ts +11 -0
- package/lib/typescript/components/OfflineSyncPrompt.d.ts.map +1 -0
- package/lib/typescript/core/OfflineManager.d.ts +53 -0
- package/lib/typescript/core/OfflineManager.d.ts.map +1 -0
- package/lib/typescript/core/StorageAdapter.d.ts +21 -0
- package/lib/typescript/core/StorageAdapter.d.ts.map +1 -0
- package/lib/typescript/core/types.d.ts +23 -0
- package/lib/typescript/core/types.d.ts.map +1 -0
- package/lib/typescript/hooks/useOfflineMutation.d.ts +8 -0
- package/lib/typescript/hooks/useOfflineMutation.d.ts.map +1 -0
- package/lib/typescript/hooks/useOfflineQueue.d.ts +8 -0
- package/lib/typescript/hooks/useOfflineQueue.d.ts.map +1 -0
- package/lib/typescript/hooks/useOfflineSyncInterceptor.d.ts +9 -0
- package/lib/typescript/hooks/useOfflineSyncInterceptor.d.ts.map +1 -0
- package/lib/typescript/hooks/useSyncProgress.d.ts +23 -0
- package/lib/typescript/hooks/useSyncProgress.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +11 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/package.json +73 -0
- package/src/adapters/index.ts +141 -0
- package/src/components/OfflineProvider.tsx +52 -0
- package/src/components/OfflineSyncPrompt.tsx +32 -0
- package/src/core/OfflineManager.ts +338 -0
- package/src/core/StorageAdapter.ts +42 -0
- package/src/core/types.ts +33 -0
- package/src/global.d.ts +1 -0
- package/src/hooks/useOfflineMutation.ts +63 -0
- package/src/hooks/useOfflineQueue.ts +17 -0
- package/src/hooks/useOfflineSyncInterceptor.ts +39 -0
- package/src/hooks/useSyncProgress.ts +32 -0
- package/src/index.ts +17 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { StorageAdapter, RecordStorageAdapter } from './StorageAdapter';
|
|
2
|
+
import type { OfflineAction, SyncProgress } from './types';
|
|
3
|
+
import type { RealmAdapterOptions } from '../adapters';
|
|
4
|
+
export interface OfflineManagerConfig {
|
|
5
|
+
storage?: StorageAdapter | RecordStorageAdapter;
|
|
6
|
+
storageType?: 'mmkv' | 'async-storage' | 'memory' | 'realm';
|
|
7
|
+
storageKey?: string;
|
|
8
|
+
syncMode?: 'auto' | 'manual';
|
|
9
|
+
realmOptions?: RealmAdapterOptions;
|
|
10
|
+
onSyncAction?: (action: OfflineAction) => Promise<void>;
|
|
11
|
+
onOnlineRestore?: (params: {
|
|
12
|
+
pendingCount: number;
|
|
13
|
+
syncNow: () => Promise<void>;
|
|
14
|
+
discardQueue: () => Promise<void>;
|
|
15
|
+
}) => void;
|
|
16
|
+
}
|
|
17
|
+
declare class OfflineManagerClass {
|
|
18
|
+
private queue;
|
|
19
|
+
private storage;
|
|
20
|
+
private storageKey;
|
|
21
|
+
private useRecordAdapter;
|
|
22
|
+
private queueListeners;
|
|
23
|
+
private progressListeners;
|
|
24
|
+
private actionHandlers;
|
|
25
|
+
isInitialized: boolean;
|
|
26
|
+
syncMode: 'auto' | 'manual';
|
|
27
|
+
onSyncAction?: (action: OfflineAction) => Promise<void>;
|
|
28
|
+
onOnlineRestore?: OfflineManagerConfig['onOnlineRestore'];
|
|
29
|
+
isSyncing: boolean;
|
|
30
|
+
private _syncProgress;
|
|
31
|
+
get syncProgress(): SyncProgress;
|
|
32
|
+
configure(config: OfflineManagerConfig): Promise<void>;
|
|
33
|
+
private loadQueue;
|
|
34
|
+
private saveQueue;
|
|
35
|
+
subscribeQueue: (listener: () => void) => (() => void);
|
|
36
|
+
subscribe: (listener: () => void) => (() => void);
|
|
37
|
+
private notifyQueueListeners;
|
|
38
|
+
subscribeProgress: (listener: () => void) => (() => void);
|
|
39
|
+
private updateProgress;
|
|
40
|
+
private updateProgressItem;
|
|
41
|
+
push<T>(actionName: string, payload: T): Promise<OfflineAction<T>>;
|
|
42
|
+
remove(id: string): Promise<void>;
|
|
43
|
+
clear(): Promise<void>;
|
|
44
|
+
getQueue(): OfflineAction[];
|
|
45
|
+
registerHandler(actionName: string, handler: (payload: any) => Promise<void>): void;
|
|
46
|
+
unregisterHandler(actionName: string): void;
|
|
47
|
+
getHandler(actionName: string): ((payload: any) => Promise<void>) | undefined;
|
|
48
|
+
flushQueue(): Promise<void>;
|
|
49
|
+
handleOnlineRestore(): void;
|
|
50
|
+
}
|
|
51
|
+
export declare const OfflineManager: OfflineManagerClass;
|
|
52
|
+
export {};
|
|
53
|
+
//# sourceMappingURL=OfflineManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OfflineManager.d.ts","sourceRoot":"","sources":["../../../src/core/OfflineManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAE7E,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAoB,MAAM,SAAS,CAAC;AAG7E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAWvD,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,cAAc,GAAG,oBAAoB,CAAC;IAChD,WAAW,CAAC,EAAE,MAAM,GAAG,eAAe,GAAG,QAAQ,GAAG,OAAO,CAAC;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAG7B,YAAY,CAAC,EAAE,mBAAmB,CAAC;IAGnC,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAGxD,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE;QACzB,YAAY,EAAE,MAAM,CAAC;QACrB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;QAC7B,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;KACnC,KAAK,IAAI,CAAC;CACZ;AAED,cAAM,mBAAmB;IACvB,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,OAAO,CAAqE;IACpF,OAAO,CAAC,UAAU,CAA8C;IAChE,OAAO,CAAC,gBAAgB,CAAkB;IAG1C,OAAO,CAAC,cAAc,CAA8B;IAGpD,OAAO,CAAC,iBAAiB,CAA8B;IAGvD,OAAO,CAAC,cAAc,CAA2D;IAE1E,aAAa,UAAS;IACtB,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAAY;IACvC,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,eAAe,CAAC,EAAE,oBAAoB,CAAC,iBAAiB,CAAC,CAAC;IAC1D,SAAS,UAAS;IAGzB,OAAO,CAAC,aAAa,CAA8C;IAEnE,IAAW,YAAY,IAAI,YAAY,CAEtC;IAGY,SAAS,CAAC,MAAM,EAAE,oBAAoB;YAsCrC,SAAS;YAkBT,SAAS;IAahB,cAAc,GAAI,UAAU,MAAM,IAAI,KAAG,CAAC,MAAM,IAAI,CAAC,CAG1D;IAGK,SAAS,aANmB,MAAM,IAAI,KAAG,CAAC,MAAM,IAAI,CAAC,CAMrB;IAEvC,OAAO,CAAC,oBAAoB;IAKrB,iBAAiB,GAAI,UAAU,MAAM,IAAI,KAAG,CAAC,MAAM,IAAI,CAAC,CAG7D;IAEF,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,kBAAkB;IAWb,IAAI,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAoBlE,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYjC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAa5B,QAAQ,IAAI,aAAa,EAAE;IAK3B,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC;IAI5E,iBAAiB,CAAC,UAAU,EAAE,MAAM;IAIpC,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS;IAKvE,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IA0FjC,mBAAmB;CAc3B;AAED,eAAO,MAAM,cAAc,qBAA4B,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { OfflineAction } from './types';
|
|
2
|
+
export interface StorageAdapter {
|
|
3
|
+
getItem: (key: string) => Promise<string | null> | string | null;
|
|
4
|
+
setItem: (key: string, value: string) => Promise<void> | void;
|
|
5
|
+
removeItem: (key: string) => Promise<void> | void;
|
|
6
|
+
}
|
|
7
|
+
export interface RecordStorageAdapter {
|
|
8
|
+
insert: (action: OfflineAction) => Promise<void> | void;
|
|
9
|
+
remove: (id: string) => Promise<void> | void;
|
|
10
|
+
getAll: () => Promise<OfflineAction[]> | OfflineAction[];
|
|
11
|
+
clear: () => Promise<void> | void;
|
|
12
|
+
update: (id: string, partial: Partial<OfflineAction>) => Promise<void> | void;
|
|
13
|
+
}
|
|
14
|
+
export declare function isRecordAdapter(adapter: StorageAdapter | RecordStorageAdapter): adapter is RecordStorageAdapter;
|
|
15
|
+
export declare class MemoryStorageAdapter implements StorageAdapter {
|
|
16
|
+
private store;
|
|
17
|
+
getItem(key: string): string | null;
|
|
18
|
+
setItem(key: string, value: string): void;
|
|
19
|
+
removeItem(key: string): void;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=StorageAdapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StorageAdapter.d.ts","sourceRoot":"","sources":["../../../src/core/StorageAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAI7C,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC;IACjE,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC9D,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACnD;AAID,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACxD,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC7C,MAAM,EAAE,MAAM,OAAO,CAAC,aAAa,EAAE,CAAC,GAAG,aAAa,EAAE,CAAC;IACzD,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAClC,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC/E;AAGD,wBAAgB,eAAe,CAC7B,OAAO,EAAE,cAAc,GAAG,oBAAoB,GAC7C,OAAO,IAAI,oBAAoB,CAEjC;AAED,qBAAa,oBAAqB,YAAW,cAAc;IACzD,OAAO,CAAC,KAAK,CAA8B;IAE3C,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAInC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAIzC,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;CAG9B"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface OfflineAction<TPayload = any> {
|
|
2
|
+
id: string;
|
|
3
|
+
actionName: string;
|
|
4
|
+
payload: TPayload;
|
|
5
|
+
createdAt: number;
|
|
6
|
+
retryCount: number;
|
|
7
|
+
}
|
|
8
|
+
export type SyncItemStatus = 'pending' | 'syncing' | 'success' | 'failed';
|
|
9
|
+
export interface SyncProgressItem {
|
|
10
|
+
action: OfflineAction;
|
|
11
|
+
status: SyncItemStatus;
|
|
12
|
+
error?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface SyncProgress {
|
|
15
|
+
isActive: boolean;
|
|
16
|
+
totalCount: number;
|
|
17
|
+
completedCount: number;
|
|
18
|
+
failedCount: number;
|
|
19
|
+
currentAction: OfflineAction | null;
|
|
20
|
+
items: SyncProgressItem[];
|
|
21
|
+
}
|
|
22
|
+
export declare const INITIAL_SYNC_PROGRESS: SyncProgress;
|
|
23
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa,CAAC,QAAQ,GAAG,GAAG;IAC3C,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,QAAQ,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE1E,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,aAAa,CAAC;IACtB,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,aAAa,GAAG,IAAI,CAAC;IACpC,KAAK,EAAE,gBAAgB,EAAE,CAAC;CAC3B;AAED,eAAO,MAAM,qBAAqB,EAAE,YAOnC,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function useOfflineMutation<TPayload>(actionName: string, options?: {
|
|
2
|
+
handler?: (payload: TPayload) => Promise<void>;
|
|
3
|
+
onOptimisticSuccess?: (payload: TPayload) => void;
|
|
4
|
+
onError?: (error: Error, payload: TPayload) => void;
|
|
5
|
+
}): {
|
|
6
|
+
mutateOffline: (payload: TPayload) => Promise<void>;
|
|
7
|
+
};
|
|
8
|
+
//# sourceMappingURL=useOfflineMutation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useOfflineMutation.d.ts","sourceRoot":"","sources":["../../../src/hooks/useOfflineMutation.ts"],"names":[],"mappings":"AAIA,wBAAgB,kBAAkB,CAAC,QAAQ,EACzC,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE;IACR,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,mBAAmB,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,IAAI,CAAC;IAClD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,KAAK,IAAI,CAAC;CACrD;6BAeqC,QAAQ;EAqC/C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useOfflineQueue.d.ts","sourceRoot":"","sources":["../../../src/hooks/useOfflineQueue.ts"],"names":[],"mappings":"AAMA,wBAAgB,eAAe;;;;;;EAU9B"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface InterceptorOptions {
|
|
2
|
+
onPromptNeeded: (params: {
|
|
3
|
+
pendingCount: number;
|
|
4
|
+
syncNow: () => Promise<void>;
|
|
5
|
+
discardQueue: () => Promise<void>;
|
|
6
|
+
}) => void;
|
|
7
|
+
}
|
|
8
|
+
export declare function useOfflineSyncInterceptor({ onPromptNeeded }: InterceptorOptions): void;
|
|
9
|
+
//# sourceMappingURL=useOfflineSyncInterceptor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useOfflineSyncInterceptor.d.ts","sourceRoot":"","sources":["../../../src/hooks/useOfflineSyncInterceptor.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,kBAAkB;IACjC,cAAc,EAAE,CAAC,MAAM,EAAE;QACvB,YAAY,EAAE,MAAM,CAAC;QACrB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;QAC7B,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;KACnC,KAAK,IAAI,CAAC;CACZ;AAED,wBAAgB,yBAAyB,CAAC,EAAE,cAAc,EAAE,EAAE,kBAAkB,QA0B/E"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live sync progress tracker.
|
|
3
|
+
* Use this inside a BottomSheet, Modal, or any UI to show per-item sync status.
|
|
4
|
+
*
|
|
5
|
+
* Returns:
|
|
6
|
+
* - isActive: whether a sync session is currently running
|
|
7
|
+
* - totalCount: total items in this sync batch
|
|
8
|
+
* - completedCount: successfully synced so far
|
|
9
|
+
* - failedCount: items that failed
|
|
10
|
+
* - currentAction: the action currently being synced
|
|
11
|
+
* - items: full list with per-item status (pending | syncing | success | failed)
|
|
12
|
+
* - percentage: 0-100 completion percentage
|
|
13
|
+
*/
|
|
14
|
+
export declare function useSyncProgress(): {
|
|
15
|
+
percentage: number;
|
|
16
|
+
isActive: boolean;
|
|
17
|
+
totalCount: number;
|
|
18
|
+
completedCount: number;
|
|
19
|
+
failedCount: number;
|
|
20
|
+
currentAction: import("..").OfflineAction | null;
|
|
21
|
+
items: import("..").SyncProgressItem[];
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=useSyncProgress.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSyncProgress.d.ts","sourceRoot":"","sources":["../../../src/hooks/useSyncProgress.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe;;;;;;;;EAY9B"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from './core/types';
|
|
2
|
+
export * from './core/StorageAdapter';
|
|
3
|
+
export { OfflineManager, type OfflineManagerConfig } from './core/OfflineManager';
|
|
4
|
+
export { getMMKVAdapter, getAsyncStorageAdapter, getRealmAdapter, type RealmAdapterOptions } from './adapters';
|
|
5
|
+
export * from './components/OfflineProvider';
|
|
6
|
+
export * from './components/OfflineSyncPrompt';
|
|
7
|
+
export * from './hooks/useOfflineQueue';
|
|
8
|
+
export * from './hooks/useOfflineMutation';
|
|
9
|
+
export * from './hooks/useOfflineSyncInterceptor';
|
|
10
|
+
export * from './hooks/useSyncProgress';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AACtC,OAAO,EAAE,cAAc,EAAE,KAAK,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAGlF,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,eAAe,EAAE,KAAK,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAG/G,cAAc,8BAA8B,CAAC;AAC7C,cAAc,gCAAgC,CAAC;AAG/C,cAAc,yBAAyB,CAAC;AACxC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,mCAAmC,CAAC;AAClD,cAAc,yBAAyB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mustafaaksoy41/react-native-offline-queue",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "A flexible, high-performance offline queue and synchronizer for React Native.",
|
|
5
|
+
"main": "lib/commonjs/index.js",
|
|
6
|
+
"module": "lib/module/index.js",
|
|
7
|
+
"types": "lib/typescript/index.d.ts",
|
|
8
|
+
"react-native": "src/index.ts",
|
|
9
|
+
"source": "src/index.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"src",
|
|
12
|
+
"lib",
|
|
13
|
+
"!**/__tests__",
|
|
14
|
+
"!**/__fixtures__",
|
|
15
|
+
"!**/__mocks__"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "bob build",
|
|
19
|
+
"postinstall": "echo '\\n📦 @mustafaaksoy41/react-native-offline-queue installed!\\n⚠️ Required peer: npm install @react-native-community/netinfo\\n💡 Optional peers: react-native-mmkv | @react-native-async-storage/async-storage | realm\\n'"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"react-native",
|
|
23
|
+
"offline",
|
|
24
|
+
"queue",
|
|
25
|
+
"sync",
|
|
26
|
+
"hook"
|
|
27
|
+
],
|
|
28
|
+
"author": "Mustafa Aksoy",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"@react-native-async-storage/async-storage": "*",
|
|
32
|
+
"@react-native-community/netinfo": "*",
|
|
33
|
+
"react": "*",
|
|
34
|
+
"react-native": "*",
|
|
35
|
+
"react-native-mmkv": "*",
|
|
36
|
+
"realm": "*"
|
|
37
|
+
},
|
|
38
|
+
"peerDependenciesMeta": {
|
|
39
|
+
"react-native-mmkv": {
|
|
40
|
+
"optional": true
|
|
41
|
+
},
|
|
42
|
+
"@react-native-async-storage/async-storage": {
|
|
43
|
+
"optional": true
|
|
44
|
+
},
|
|
45
|
+
"realm": {
|
|
46
|
+
"optional": true
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@react-native-community/netinfo": "^11.0.0",
|
|
51
|
+
"@types/react": "^18.2.6",
|
|
52
|
+
"react": "18.2.0",
|
|
53
|
+
"react-native": "0.73.4",
|
|
54
|
+
"react-native-builder-bob": "^0.30.0",
|
|
55
|
+
"typescript": "^5.0.2",
|
|
56
|
+
"vitepress": "^1.6.4",
|
|
57
|
+
"vue": "^3.5.29"
|
|
58
|
+
},
|
|
59
|
+
"react-native-builder-bob": {
|
|
60
|
+
"source": "src",
|
|
61
|
+
"output": "lib",
|
|
62
|
+
"targets": [
|
|
63
|
+
"commonjs",
|
|
64
|
+
"module",
|
|
65
|
+
[
|
|
66
|
+
"typescript",
|
|
67
|
+
{
|
|
68
|
+
"project": "tsconfig.build.json"
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { StorageAdapter } from '../core/StorageAdapter';
|
|
2
|
+
import type { RecordStorageAdapter } from '../core/StorageAdapter';
|
|
3
|
+
import type { OfflineAction } from '../core/types';
|
|
4
|
+
|
|
5
|
+
// Dynamic require approach so Metro doesn't crash if the user hasn't installed the optional library
|
|
6
|
+
|
|
7
|
+
export const getMMKVAdapter = (): StorageAdapter => {
|
|
8
|
+
try {
|
|
9
|
+
const { MMKV } = require('react-native-mmkv');
|
|
10
|
+
const storage = new MMKV();
|
|
11
|
+
return {
|
|
12
|
+
getItem: (key) => storage.getString(key) || null,
|
|
13
|
+
setItem: (key, value) => storage.set(key, value),
|
|
14
|
+
removeItem: (key) => storage.delete(key),
|
|
15
|
+
};
|
|
16
|
+
} catch (error) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
"[OfflineQueue] You selected 'mmkv' storage but react-native-mmkv is not installed. Run 'npm install react-native-mmkv' and 'pod install'."
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const getAsyncStorageAdapter = (): StorageAdapter => {
|
|
24
|
+
try {
|
|
25
|
+
const AsyncStorage = require('@react-native-async-storage/async-storage').default;
|
|
26
|
+
return {
|
|
27
|
+
getItem: async (key) => await AsyncStorage.getItem(key),
|
|
28
|
+
setItem: async (key, value) => await AsyncStorage.setItem(key, value),
|
|
29
|
+
removeItem: async (key) => await AsyncStorage.removeItem(key),
|
|
30
|
+
};
|
|
31
|
+
} catch (error) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
"[OfflineQueue] You selected 'async-storage' but @react-native-async-storage/async-storage is not installed. Run 'npm install @react-native-async-storage/async-storage' and 'pod install'."
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Default Realm schema for the offline queue
|
|
39
|
+
const OFFLINE_QUEUE_SCHEMA_NAME = 'OfflineQueueItem';
|
|
40
|
+
|
|
41
|
+
const OfflineQueueItemSchema = {
|
|
42
|
+
name: OFFLINE_QUEUE_SCHEMA_NAME,
|
|
43
|
+
primaryKey: 'id',
|
|
44
|
+
properties: {
|
|
45
|
+
id: 'string',
|
|
46
|
+
actionName: 'string',
|
|
47
|
+
payload: 'string', // JSON-stringified payload
|
|
48
|
+
createdAt: 'int',
|
|
49
|
+
retryCount: { type: 'int', default: 0 },
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export interface RealmAdapterOptions {
|
|
54
|
+
/** Pass your existing Realm instance to share it with your app */
|
|
55
|
+
realmInstance?: any;
|
|
56
|
+
/** Custom schema name (default: 'OfflineQueueItem') */
|
|
57
|
+
schemaName?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const getRealmAdapter = (options?: RealmAdapterOptions): RecordStorageAdapter => {
|
|
61
|
+
try {
|
|
62
|
+
const Realm = require('realm');
|
|
63
|
+
const schemaName = options?.schemaName || OFFLINE_QUEUE_SCHEMA_NAME;
|
|
64
|
+
|
|
65
|
+
let realm: any;
|
|
66
|
+
|
|
67
|
+
if (options?.realmInstance) {
|
|
68
|
+
realm = options.realmInstance;
|
|
69
|
+
} else {
|
|
70
|
+
// Open a dedicated Realm with our default schema
|
|
71
|
+
realm = new Realm({
|
|
72
|
+
schema: [
|
|
73
|
+
schemaName === OFFLINE_QUEUE_SCHEMA_NAME
|
|
74
|
+
? OfflineQueueItemSchema
|
|
75
|
+
: { ...OfflineQueueItemSchema, name: schemaName },
|
|
76
|
+
],
|
|
77
|
+
path: 'offline-queue.realm',
|
|
78
|
+
schemaVersion: 1,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
insert: (action: OfflineAction) => {
|
|
84
|
+
realm.write(() => {
|
|
85
|
+
realm.create(schemaName, {
|
|
86
|
+
id: action.id,
|
|
87
|
+
actionName: action.actionName,
|
|
88
|
+
payload: JSON.stringify(action.payload),
|
|
89
|
+
createdAt: action.createdAt,
|
|
90
|
+
retryCount: action.retryCount,
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
return Promise.resolve();
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
remove: (id: string) => {
|
|
97
|
+
realm.write(() => {
|
|
98
|
+
const record = realm.objectForPrimaryKey(schemaName, id);
|
|
99
|
+
if (record) realm.delete(record);
|
|
100
|
+
});
|
|
101
|
+
return Promise.resolve();
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
getAll: (): OfflineAction[] => {
|
|
105
|
+
const records = realm.objects(schemaName).sorted('createdAt');
|
|
106
|
+
return Array.from(records).map((r: any) => ({
|
|
107
|
+
id: r.id,
|
|
108
|
+
actionName: r.actionName,
|
|
109
|
+
payload: JSON.parse(r.payload),
|
|
110
|
+
createdAt: r.createdAt,
|
|
111
|
+
retryCount: r.retryCount,
|
|
112
|
+
}));
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
clear: () => {
|
|
116
|
+
realm.write(() => {
|
|
117
|
+
const all = realm.objects(schemaName);
|
|
118
|
+
realm.delete(all);
|
|
119
|
+
});
|
|
120
|
+
return Promise.resolve();
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
update: (id: string, partial: Partial<OfflineAction>) => {
|
|
124
|
+
realm.write(() => {
|
|
125
|
+
const record = realm.objectForPrimaryKey(schemaName, id);
|
|
126
|
+
if (record) {
|
|
127
|
+
if (partial.retryCount !== undefined) record.retryCount = partial.retryCount;
|
|
128
|
+
if (partial.payload !== undefined) record.payload = JSON.stringify(partial.payload);
|
|
129
|
+
if (partial.actionName !== undefined) record.actionName = partial.actionName;
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
return Promise.resolve();
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
} catch (error) {
|
|
136
|
+
throw new Error(
|
|
137
|
+
"[OfflineQueue] You selected 'realm' storage but realm is not installed. Run 'npm install realm' and 'pod install'."
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React, { createContext, useContext, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import NetInfo from '@react-native-community/netinfo';
|
|
3
|
+
import { OfflineManager, type OfflineManagerConfig } from '../core/OfflineManager';
|
|
4
|
+
|
|
5
|
+
interface OfflineContextValue {
|
|
6
|
+
isOnline: boolean | null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const OfflineContext = createContext<OfflineContextValue>({ isOnline: true });
|
|
10
|
+
|
|
11
|
+
export const useNetworkStatus = () => useContext(OfflineContext);
|
|
12
|
+
|
|
13
|
+
export interface OfflineProviderProps {
|
|
14
|
+
children: React.ReactNode;
|
|
15
|
+
config: OfflineManagerConfig;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const OfflineProvider: React.FC<OfflineProviderProps> = ({ children, config }) => {
|
|
19
|
+
const [isOnline, setIsOnline] = useState<boolean | null>(null);
|
|
20
|
+
const wasOfflineRef = useRef(false);
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
OfflineManager.configure(config);
|
|
24
|
+
}, [config]);
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
const unsubscribe = NetInfo.addEventListener((state) => {
|
|
28
|
+
const connected = !!state.isConnected;
|
|
29
|
+
if (__DEV__) console.log('[OfflineQueue] Network:', connected ? 'online' : 'offline', '| type:', state.type);
|
|
30
|
+
|
|
31
|
+
setIsOnline(connected);
|
|
32
|
+
|
|
33
|
+
if (!connected) {
|
|
34
|
+
wasOfflineRef.current = true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (connected && wasOfflineRef.current) {
|
|
38
|
+
wasOfflineRef.current = false;
|
|
39
|
+
if (__DEV__) console.log('[OfflineQueue] Network restored, triggering sync handler');
|
|
40
|
+
OfflineManager.handleOnlineRestore();
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return () => unsubscribe();
|
|
45
|
+
}, []);
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<OfflineContext.Provider value={{ isOnline }}>
|
|
49
|
+
{children}
|
|
50
|
+
</OfflineContext.Provider>
|
|
51
|
+
);
|
|
52
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useNetworkStatus } from './OfflineProvider';
|
|
3
|
+
import { useOfflineQueue } from '../hooks/useOfflineQueue';
|
|
4
|
+
|
|
5
|
+
export interface OfflineSyncPromptProps {
|
|
6
|
+
children: (props: {
|
|
7
|
+
pendingCount: number;
|
|
8
|
+
isSyncing: boolean;
|
|
9
|
+
syncNow: () => Promise<void>;
|
|
10
|
+
cancel: () => Promise<void>;
|
|
11
|
+
}) => React.ReactNode;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const OfflineSyncPrompt: React.FC<OfflineSyncPromptProps> = ({ children }) => {
|
|
15
|
+
const { isOnline } = useNetworkStatus();
|
|
16
|
+
const { pendingCount, isSyncing, syncNow, clearQueue } = useOfflineQueue();
|
|
17
|
+
|
|
18
|
+
if (isOnline && pendingCount > 0) {
|
|
19
|
+
return (
|
|
20
|
+
<>
|
|
21
|
+
{children({
|
|
22
|
+
pendingCount,
|
|
23
|
+
isSyncing,
|
|
24
|
+
syncNow,
|
|
25
|
+
cancel: clearQueue,
|
|
26
|
+
})}
|
|
27
|
+
</>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return null;
|
|
32
|
+
};
|