@legendapp/state 3.0.0-alpha.1 → 3.0.0-alpha.3
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/.DS_Store +0 -0
- package/CHANGELOG.md +1 -831
- package/LICENSE +1 -21
- package/README.md +1 -141
- package/as/arrayAsRecord.d.mts +5 -0
- package/as/arrayAsRecord.d.ts +5 -0
- package/as/arrayAsRecord.js +28 -0
- package/as/arrayAsRecord.mjs +26 -0
- package/as/arrayAsSet.d.mts +5 -0
- package/as/arrayAsSet.d.ts +5 -0
- package/as/arrayAsSet.js +13 -0
- package/as/arrayAsSet.mjs +11 -0
- package/as/arrayAsString.d.mts +5 -0
- package/as/arrayAsString.d.ts +5 -0
- package/as/arrayAsString.js +13 -0
- package/as/arrayAsString.mjs +11 -0
- package/as/numberAsString.d.mts +5 -0
- package/as/numberAsString.d.ts +5 -0
- package/as/numberAsString.js +13 -0
- package/as/numberAsString.mjs +11 -0
- package/as/recordAsArray.d.mts +5 -0
- package/as/recordAsArray.d.ts +5 -0
- package/as/recordAsArray.js +25 -0
- package/as/recordAsArray.mjs +23 -0
- package/as/recordAsString.d.mts +5 -0
- package/as/recordAsString.d.ts +5 -0
- package/as/recordAsString.js +13 -0
- package/as/recordAsString.mjs +11 -0
- package/as/setAsArray.d.mts +5 -0
- package/as/setAsArray.d.ts +5 -0
- package/as/setAsArray.js +13 -0
- package/as/setAsArray.mjs +11 -0
- package/as/setAsString.d.mts +5 -0
- package/as/setAsString.d.ts +5 -0
- package/as/setAsString.js +13 -0
- package/as/setAsString.mjs +11 -0
- package/as/stringAsArray.d.mts +5 -0
- package/as/stringAsArray.d.ts +5 -0
- package/as/stringAsArray.js +13 -0
- package/as/stringAsArray.mjs +11 -0
- package/as/stringAsNumber.d.mts +5 -0
- package/as/stringAsNumber.d.ts +5 -0
- package/as/stringAsNumber.js +16 -0
- package/as/stringAsNumber.mjs +14 -0
- package/as/stringAsRecord.d.mts +5 -0
- package/as/stringAsRecord.d.ts +5 -0
- package/as/stringAsRecord.js +15 -0
- package/as/stringAsRecord.mjs +13 -0
- package/as/stringAsSet.d.mts +5 -0
- package/as/stringAsSet.d.ts +5 -0
- package/as/stringAsSet.js +13 -0
- package/as/stringAsSet.mjs +11 -0
- package/babel.d.mts +21 -0
- package/babel.d.ts +21 -2
- package/babel.js +57 -53
- package/babel.mjs +65 -0
- package/config/enable$GetSet.js +13 -14
- package/config/enable$GetSet.mjs +13 -14
- package/config/enableReactComponents.d.mts +9 -0
- package/config/enableReactComponents.d.ts +4 -2
- package/config/enableReactComponents.js +13 -10
- package/config/enableReactComponents.mjs +13 -10
- package/config/enableReactNativeComponents.d.mts +22 -0
- package/config/enableReactNativeComponents.d.ts +6 -4
- package/config/enableReactNativeComponents.js +43 -47
- package/config/enableReactNativeComponents.mjs +43 -47
- package/config/enableReactTracking.d.mts +7 -0
- package/config/enableReactTracking.d.ts +3 -2
- package/config/enableReactTracking.js +33 -38
- package/config/enableReactTracking.mjs +33 -38
- package/config/enableReactUse.d.mts +10 -0
- package/config/enableReactUse.d.ts +4 -1
- package/config/enableReactUse.js +15 -14
- package/config/enableReactUse.mjs +15 -14
- package/config/{enable$GetSet.d.ts → enable_GetSet.d.mts} +4 -2
- package/config/enable_GetSet.d.ts +10 -0
- package/config/enable_PeekAssign.d.mts +10 -0
- package/config/enable_PeekAssign.d.ts +4 -2
- package/config/enable_PeekAssign.js +13 -14
- package/config/enable_PeekAssign.mjs +13 -14
- package/helpers/pageHash.d.mts +9 -0
- package/helpers/pageHash.d.ts +2 -0
- package/helpers/pageHash.js +25 -30
- package/helpers/pageHash.mjs +25 -30
- package/helpers/pageHashParams.d.mts +9 -0
- package/helpers/pageHashParams.d.ts +2 -0
- package/helpers/pageHashParams.js +34 -37
- package/helpers/pageHashParams.mjs +34 -37
- package/helpers/time.d.mts +6 -0
- package/helpers/time.d.ts +6 -3
- package/helpers/time.js +17 -17
- package/helpers/time.mjs +17 -17
- package/helpers/trackHistory.d.mts +6 -0
- package/helpers/trackHistory.d.ts +4 -2
- package/helpers/trackHistory.js +13 -16
- package/helpers/trackHistory.mjs +13 -16
- package/helpers/undoRedo.d.mts +37 -0
- package/helpers/undoRedo.d.ts +5 -3
- package/helpers/undoRedo.js +59 -94
- package/helpers/undoRedo.mjs +59 -94
- package/index.d.mts +404 -0
- package/index.d.ts +371 -28
- package/index.js +2015 -2166
- package/index.mjs +2015 -2166
- package/package.json +254 -195
- package/persist-plugins/async-storage.d.mts +18 -0
- package/persist-plugins/async-storage.d.ts +6 -3
- package/persist-plugins/async-storage.js +79 -86
- package/persist-plugins/async-storage.mjs +79 -86
- package/persist-plugins/indexeddb.d.mts +29 -0
- package/persist-plugins/indexeddb.d.ts +6 -3
- package/persist-plugins/indexeddb.js +331 -352
- package/persist-plugins/indexeddb.mjs +331 -352
- package/persist-plugins/local-storage.d.mts +23 -0
- package/persist-plugins/local-storage.d.ts +8 -5
- package/persist-plugins/local-storage.js +74 -76
- package/persist-plugins/local-storage.mjs +74 -76
- package/persist-plugins/mmkv.d.mts +18 -0
- package/persist-plugins/mmkv.d.ts +6 -3
- package/persist-plugins/mmkv.js +82 -86
- package/persist-plugins/mmkv.mjs +82 -86
- package/react-hooks/createObservableHook.d.mts +5 -0
- package/react-hooks/createObservableHook.d.ts +4 -1
- package/react-hooks/createObservableHook.js +29 -30
- package/react-hooks/createObservableHook.mjs +25 -30
- package/react-hooks/useHover.d.mts +5 -0
- package/react-hooks/useHover.d.ts +5 -3
- package/react-hooks/useHover.js +29 -29
- package/react-hooks/useHover.mjs +29 -29
- package/react-hooks/useMeasure.d.mts +9 -0
- package/react-hooks/useMeasure.d.ts +5 -2
- package/react-hooks/useMeasure.js +30 -32
- package/react-hooks/useMeasure.mjs +30 -32
- package/react-hooks/useObservableNextRouter.d.mts +35 -0
- package/react-hooks/useObservableNextRouter.d.ts +9 -7
- package/react-hooks/useObservableNextRouter.js +64 -77
- package/react-hooks/useObservableNextRouter.mjs +60 -77
- package/react.d.mts +157 -0
- package/react.d.ts +157 -21
- package/react.js +458 -749
- package/react.mjs +457 -752
- package/sync-plugins/crud.d.mts +54 -0
- package/sync-plugins/crud.d.ts +12 -10
- package/sync-plugins/crud.js +253 -270
- package/sync-plugins/crud.mjs +253 -270
- package/sync-plugins/fetch.d.mts +21 -0
- package/sync-plugins/fetch.d.ts +7 -4
- package/sync-plugins/fetch.js +50 -37
- package/sync-plugins/fetch.mjs +50 -37
- package/sync-plugins/keel.d.mts +108 -0
- package/sync-plugins/keel.d.ts +17 -15
- package/sync-plugins/keel.js +229 -462
- package/sync-plugins/keel.mjs +227 -464
- package/sync-plugins/supabase.d.mts +39 -0
- package/sync-plugins/supabase.d.ts +16 -14
- package/sync-plugins/supabase.js +128 -128
- package/sync-plugins/supabase.mjs +128 -128
- package/sync-plugins/tanstack-query.d.mts +14 -0
- package/sync-plugins/tanstack-query.d.ts +7 -4
- package/sync-plugins/tanstack-query.js +51 -57
- package/sync-plugins/tanstack-query.mjs +51 -57
- package/sync-plugins/tanstack-react-query.d.mts +8 -0
- package/sync-plugins/tanstack-react-query.d.ts +6 -1
- package/sync-plugins/tanstack-react-query.js +2 -2
- package/sync-plugins/tanstack-react-query.mjs +2 -2
- package/sync.d.mts +351 -0
- package/sync.d.ts +349 -9
- package/sync.js +910 -964
- package/sync.mjs +920 -974
- package/trace.d.mts +9 -0
- package/trace.d.ts +9 -4
- package/trace.js +72 -62
- package/trace.mjs +72 -62
- package/types/babel.d.ts +1 -12
- package/babel.js.map +0 -1
- package/config/enable$GetSet.js.map +0 -1
- package/config/enable$GetSet.mjs.map +0 -1
- package/config/enableReactComponents.js.map +0 -1
- package/config/enableReactComponents.mjs.map +0 -1
- package/config/enableReactNativeComponents.js.map +0 -1
- package/config/enableReactNativeComponents.mjs.map +0 -1
- package/config/enableReactTracking.js.map +0 -1
- package/config/enableReactTracking.mjs.map +0 -1
- package/config/enableReactUse.js.map +0 -1
- package/config/enableReactUse.mjs.map +0 -1
- package/config/enable_PeekAssign.js.map +0 -1
- package/config/enable_PeekAssign.mjs.map +0 -1
- package/helpers/pageHash.js.map +0 -1
- package/helpers/pageHash.mjs.map +0 -1
- package/helpers/pageHashParams.js.map +0 -1
- package/helpers/pageHashParams.mjs.map +0 -1
- package/helpers/time.js.map +0 -1
- package/helpers/time.mjs.map +0 -1
- package/helpers/trackHistory.js.map +0 -1
- package/helpers/trackHistory.mjs.map +0 -1
- package/helpers/undoRedo.js.map +0 -1
- package/helpers/undoRedo.mjs.map +0 -1
- package/history.d.ts +0 -1
- package/history.js +0 -24
- package/history.js.map +0 -1
- package/history.mjs +0 -22
- package/history.mjs.map +0 -1
- package/index.js.map +0 -1
- package/index.mjs.map +0 -1
- package/persist-plugins/async-storage.js.map +0 -1
- package/persist-plugins/async-storage.mjs.map +0 -1
- package/persist-plugins/indexeddb.js.map +0 -1
- package/persist-plugins/indexeddb.mjs.map +0 -1
- package/persist-plugins/local-storage.js.map +0 -1
- package/persist-plugins/local-storage.mjs.map +0 -1
- package/persist-plugins/mmkv.js.map +0 -1
- package/persist-plugins/mmkv.mjs.map +0 -1
- package/react-hooks/createObservableHook.js.map +0 -1
- package/react-hooks/createObservableHook.mjs.map +0 -1
- package/react-hooks/useHover.js.map +0 -1
- package/react-hooks/useHover.mjs.map +0 -1
- package/react-hooks/useMeasure.js.map +0 -1
- package/react-hooks/useMeasure.mjs.map +0 -1
- package/react-hooks/useObservableNextRouter.js.map +0 -1
- package/react-hooks/useObservableNextRouter.mjs.map +0 -1
- package/react.js.map +0 -1
- package/react.mjs.map +0 -1
- package/src/ObservableObject.ts +0 -1350
- package/src/ObservablePrimitive.ts +0 -62
- package/src/babel/index.ts +0 -83
- package/src/batching.ts +0 -357
- package/src/computed.ts +0 -18
- package/src/config/enable$GetSet.ts +0 -30
- package/src/config/enableReactComponents.ts +0 -26
- package/src/config/enableReactNativeComponents.ts +0 -102
- package/src/config/enableReactTracking.ts +0 -62
- package/src/config/enableReactUse.ts +0 -32
- package/src/config/enable_PeekAssign.ts +0 -31
- package/src/config.ts +0 -47
- package/src/createObservable.ts +0 -47
- package/src/event.ts +0 -26
- package/src/globals.ts +0 -235
- package/src/helpers/pageHash.ts +0 -41
- package/src/helpers/pageHashParams.ts +0 -55
- package/src/helpers/time.ts +0 -30
- package/src/helpers/trackHistory.ts +0 -29
- package/src/helpers/undoRedo.ts +0 -111
- package/src/helpers.ts +0 -231
- package/src/is.ts +0 -63
- package/src/linked.ts +0 -17
- package/src/observable.ts +0 -32
- package/src/observableInterfaces.ts +0 -151
- package/src/observableTypes.ts +0 -232
- package/src/observe.ts +0 -89
- package/src/old-plugins/firebase.ts +0 -1053
- package/src/onChange.ts +0 -146
- package/src/persist/configureObservablePersistence.ts +0 -7
- package/src/persist/fieldTransformer.ts +0 -149
- package/src/persist/observablePersistRemoteFunctionsAdapter.ts +0 -39
- package/src/persist/persistObservable.ts +0 -1034
- package/src/persist-plugins/async-storage.ts +0 -99
- package/src/persist-plugins/indexeddb.ts +0 -439
- package/src/persist-plugins/local-storage.ts +0 -86
- package/src/persist-plugins/mmkv.ts +0 -91
- package/src/proxy.ts +0 -28
- package/src/react/Computed.tsx +0 -8
- package/src/react/For.tsx +0 -116
- package/src/react/Memo.tsx +0 -4
- package/src/react/Reactive.tsx +0 -53
- package/src/react/Show.tsx +0 -33
- package/src/react/Switch.tsx +0 -43
- package/src/react/react-globals.ts +0 -3
- package/src/react/reactInterfaces.ts +0 -32
- package/src/react/reactive-observer.tsx +0 -210
- package/src/react/useComputed.ts +0 -36
- package/src/react/useEffectOnce.ts +0 -41
- package/src/react/useIsMounted.ts +0 -16
- package/src/react/useMount.ts +0 -15
- package/src/react/useObservable.ts +0 -24
- package/src/react/useObservableReducer.ts +0 -52
- package/src/react/useObservableState.ts +0 -30
- package/src/react/useObserve.ts +0 -54
- package/src/react/useObserveEffect.ts +0 -40
- package/src/react/usePauseProvider.tsx +0 -16
- package/src/react/useSelector.ts +0 -167
- package/src/react/useUnmount.ts +0 -8
- package/src/react/useWhen.ts +0 -9
- package/src/react-hooks/createObservableHook.ts +0 -53
- package/src/react-hooks/useHover.ts +0 -40
- package/src/react-hooks/useMeasure.ts +0 -48
- package/src/react-hooks/useObservableNextRouter.ts +0 -137
- package/src/retry.ts +0 -71
- package/src/setupTracking.ts +0 -26
- package/src/sync/activateSyncedNode.ts +0 -128
- package/src/sync/configureObservableSync.ts +0 -7
- package/src/sync/persistTypes.ts +0 -216
- package/src/sync/syncHelpers.ts +0 -180
- package/src/sync/syncObservable.ts +0 -1056
- package/src/sync/syncObservableAdapter.ts +0 -31
- package/src/sync/syncTypes.ts +0 -189
- package/src/sync/synced.ts +0 -21
- package/src/sync-plugins/crud.ts +0 -412
- package/src/sync-plugins/fetch.ts +0 -80
- package/src/sync-plugins/keel.ts +0 -495
- package/src/sync-plugins/supabase.ts +0 -249
- package/src/sync-plugins/tanstack-query.ts +0 -113
- package/src/sync-plugins/tanstack-react-query.ts +0 -12
- package/src/trace/traceHelpers.ts +0 -11
- package/src/trace/useTraceListeners.ts +0 -34
- package/src/trace/useTraceUpdates.ts +0 -24
- package/src/trace/useVerifyNotTracking.ts +0 -33
- package/src/trace/useVerifyOneRender.ts +0 -10
- package/src/trackSelector.ts +0 -52
- package/src/tracking.ts +0 -43
- package/src/types/babel.d.ts +0 -12
- package/src/when.ts +0 -75
- package/sync-plugins/crud.js.map +0 -1
- package/sync-plugins/crud.mjs.map +0 -1
- package/sync-plugins/fetch.js.map +0 -1
- package/sync-plugins/fetch.mjs.map +0 -1
- package/sync-plugins/keel.js.map +0 -1
- package/sync-plugins/keel.mjs.map +0 -1
- package/sync-plugins/supabase.js.map +0 -1
- package/sync-plugins/supabase.mjs.map +0 -1
- package/sync-plugins/tanstack-query.js.map +0 -1
- package/sync-plugins/tanstack-query.mjs.map +0 -1
- package/sync-plugins/tanstack-react-query.js.map +0 -1
- package/sync-plugins/tanstack-react-query.mjs.map +0 -1
- package/sync.js.map +0 -1
- package/sync.mjs.map +0 -1
- package/trace.js.map +0 -1
- package/trace.mjs.map +0 -1
|
@@ -1,1053 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Observable,
|
|
3
|
-
ObservableParam,
|
|
4
|
-
ObservablePrimitive,
|
|
5
|
-
TypeAtPath,
|
|
6
|
-
batch,
|
|
7
|
-
constructObjectWithPath,
|
|
8
|
-
deconstructObjectWithPath,
|
|
9
|
-
hasOwnProperty,
|
|
10
|
-
internal,
|
|
11
|
-
isArray,
|
|
12
|
-
isFunction,
|
|
13
|
-
isObject,
|
|
14
|
-
mergeIntoObservable,
|
|
15
|
-
observable,
|
|
16
|
-
observablePrimitive,
|
|
17
|
-
setAtPath,
|
|
18
|
-
when,
|
|
19
|
-
whenReady,
|
|
20
|
-
} from '@legendapp/state';
|
|
21
|
-
// @ts-expect-error asdf
|
|
22
|
-
import { internal as internalPersist, transformObject, transformPath } from '@legendapp/state/persist';
|
|
23
|
-
import {
|
|
24
|
-
LegacyPersistOptions,
|
|
25
|
-
ObservablePersistRemoteClass,
|
|
26
|
-
ObservablePersistRemoteGetParams,
|
|
27
|
-
ObservablePersistRemoteSetParams,
|
|
28
|
-
QueryByModified,
|
|
29
|
-
} from '@legendapp/state/sync';
|
|
30
|
-
import { getAuth, type User } from 'firebase/auth';
|
|
31
|
-
import type { DataSnapshot } from 'firebase/database';
|
|
32
|
-
import {
|
|
33
|
-
DatabaseReference,
|
|
34
|
-
Unsubscribe,
|
|
35
|
-
getDatabase,
|
|
36
|
-
onChildAdded,
|
|
37
|
-
onChildChanged,
|
|
38
|
-
onValue,
|
|
39
|
-
orderByChild,
|
|
40
|
-
query,
|
|
41
|
-
ref,
|
|
42
|
-
serverTimestamp,
|
|
43
|
-
startAt,
|
|
44
|
-
update,
|
|
45
|
-
} from 'firebase/database';
|
|
46
|
-
const { symbolDelete, getPathType, clone } = internal;
|
|
47
|
-
const { observablePersistConfiguration } = internalPersist;
|
|
48
|
-
|
|
49
|
-
function getDateModifiedKey(dateModifiedKey: string | undefined) {
|
|
50
|
-
return dateModifiedKey || observablePersistConfiguration.remoteOptions?.dateModifiedKey || '@';
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
interface FirebaseFns {
|
|
54
|
-
isInitialized: () => boolean;
|
|
55
|
-
getCurrentUser: () => string | undefined;
|
|
56
|
-
ref: (path: string) => DatabaseReference;
|
|
57
|
-
orderByChild: (ref: any, child: string, startAt: number) => any;
|
|
58
|
-
once: (query: any, callback: (snapshot: DataSnapshot) => unknown, onError: (error: Error) => void) => () => void;
|
|
59
|
-
onChildAdded: (
|
|
60
|
-
query: any,
|
|
61
|
-
callback: (snapshot: DataSnapshot) => unknown,
|
|
62
|
-
cancelCallback?: (error: Error) => unknown,
|
|
63
|
-
) => () => void;
|
|
64
|
-
onChildChanged: (
|
|
65
|
-
query: any,
|
|
66
|
-
callback: (snapshot: DataSnapshot) => unknown,
|
|
67
|
-
cancelCallback?: (error: Error) => unknown,
|
|
68
|
-
) => () => void;
|
|
69
|
-
serverTimestamp: () => any;
|
|
70
|
-
update: (object: object) => Promise<void>;
|
|
71
|
-
onAuthStateChanged: (cb: (user: User | null) => void) => void;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
type LocalState<T> = { changes: object | T; timeout?: any };
|
|
75
|
-
|
|
76
|
-
const symbolSaveValue = Symbol('___obsSaveValue');
|
|
77
|
-
|
|
78
|
-
interface SaveInfo {
|
|
79
|
-
[symbolSaveValue]: any;
|
|
80
|
-
adjusted?: boolean;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
type SaveInfoDictionary<T = any> = {
|
|
84
|
-
[K in keyof T]: SaveInfo | SaveInfoDictionary<T[K]>;
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
interface PendingSaves {
|
|
88
|
-
options: LegacyPersistOptions;
|
|
89
|
-
saves: SaveInfoDictionary;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
interface SaveState {
|
|
93
|
-
timeout?: any;
|
|
94
|
-
pendingSaves: Map<string, PendingSaves>;
|
|
95
|
-
savingSaves?: Map<string, PendingSaves>;
|
|
96
|
-
eventSaved?: ObservablePrimitive<true | Error | undefined>;
|
|
97
|
-
numSavesPending: Observable<number>;
|
|
98
|
-
pendingSaveResults: Map<
|
|
99
|
-
string,
|
|
100
|
-
{
|
|
101
|
-
saved: {
|
|
102
|
-
value: any;
|
|
103
|
-
path: string[];
|
|
104
|
-
pathTypes: TypeAtPath[];
|
|
105
|
-
dateModified: number | undefined;
|
|
106
|
-
dateModifiedKey: string | undefined;
|
|
107
|
-
dateModifiedKeyOption: string | undefined;
|
|
108
|
-
onChange: ObservablePersistRemoteGetParams<any>['onChange'];
|
|
109
|
-
}[];
|
|
110
|
-
}
|
|
111
|
-
>;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
interface LoadStatus {
|
|
115
|
-
startedLoading: boolean;
|
|
116
|
-
numLoading: number;
|
|
117
|
-
numWaitingCanSave: number;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
class ObservablePersistFirebaseBase implements ObservablePersistRemoteClass {
|
|
121
|
-
protected _batch: Record<string, any> = {};
|
|
122
|
-
protected fns: FirebaseFns;
|
|
123
|
-
private _pathsLoadStatus = observable<Record<string, LoadStatus>>({});
|
|
124
|
-
private debounceSet;
|
|
125
|
-
private user: Observable<string | undefined | null>;
|
|
126
|
-
private listenErrors: Map<
|
|
127
|
-
any,
|
|
128
|
-
{
|
|
129
|
-
params: ObservablePersistRemoteGetParams<any>;
|
|
130
|
-
path: string[];
|
|
131
|
-
pathTypes: TypeAtPath[];
|
|
132
|
-
dateModified: number | undefined;
|
|
133
|
-
queryByModified: QueryByModified<any> | undefined;
|
|
134
|
-
unsubscribes: (() => void)[];
|
|
135
|
-
retry: number;
|
|
136
|
-
saveState: SaveState;
|
|
137
|
-
onLoadParams: { waiting: number; onLoad: () => void };
|
|
138
|
-
status$: Observable<LoadStatus>;
|
|
139
|
-
}
|
|
140
|
-
> = new Map();
|
|
141
|
-
private saveStates = new Map<ObservableParam<any>, SaveState>();
|
|
142
|
-
|
|
143
|
-
constructor(fns: FirebaseFns) {
|
|
144
|
-
this.fns = fns;
|
|
145
|
-
this.user = observablePrimitive();
|
|
146
|
-
this.debounceSet = observablePersistConfiguration?.remoteOptions?.debounceSet ?? 500;
|
|
147
|
-
|
|
148
|
-
if (this.fns.isInitialized()) {
|
|
149
|
-
this.fns.onAuthStateChanged((user) => {
|
|
150
|
-
this.user.set(user?.uid);
|
|
151
|
-
});
|
|
152
|
-
} else if (
|
|
153
|
-
process.env.NODE_ENV === 'development' &&
|
|
154
|
-
(typeof window !== 'undefined' || (typeof navigator !== 'undefined' && navigator.product === 'ReactNative'))
|
|
155
|
-
) {
|
|
156
|
-
// Warn only in web or react-native. If running on a server this shouldn't work.
|
|
157
|
-
console.warn('[legend-state] Firebase is not initialized. Remote persistence will not work.');
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
public async get<T>(params: ObservablePersistRemoteGetParams<T>) {
|
|
161
|
-
const { obs, options } = params;
|
|
162
|
-
const { remote } = options;
|
|
163
|
-
if (!remote || !remote.firebase) {
|
|
164
|
-
// If the plugin is set globally but it has no firebase options this plugin can't do anything
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
const {
|
|
168
|
-
firebase: { refPath },
|
|
169
|
-
waitForGet,
|
|
170
|
-
} = remote;
|
|
171
|
-
|
|
172
|
-
const { requireAuth, queryByModified } = options.remote!.firebase!;
|
|
173
|
-
|
|
174
|
-
// If requireAuth wait for user to be signed in
|
|
175
|
-
if (requireAuth) {
|
|
176
|
-
await whenReady(this.user);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// If waitForGet wait for it
|
|
180
|
-
if (waitForGet) {
|
|
181
|
-
await whenReady(waitForGet);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const saveState: SaveState = {
|
|
185
|
-
pendingSaveResults: new Map(),
|
|
186
|
-
pendingSaves: new Map(),
|
|
187
|
-
numSavesPending: observable(0),
|
|
188
|
-
};
|
|
189
|
-
this.saveStates.set(obs, saveState);
|
|
190
|
-
|
|
191
|
-
const pathFirebase = refPath(this.fns.getCurrentUser());
|
|
192
|
-
|
|
193
|
-
const status$ = this._pathsLoadStatus[pathFirebase];
|
|
194
|
-
status$.set({
|
|
195
|
-
startedLoading: false,
|
|
196
|
-
numLoading: 0,
|
|
197
|
-
numWaitingCanSave: 0,
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
const onLoadParams = {
|
|
201
|
-
waiting: 0,
|
|
202
|
-
onLoad: () => {
|
|
203
|
-
onLoadParams.waiting--;
|
|
204
|
-
if (onLoadParams.waiting === 0) {
|
|
205
|
-
params.onGet();
|
|
206
|
-
}
|
|
207
|
-
},
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
if (isObject(queryByModified)) {
|
|
211
|
-
this.iterateListen(obs, params, saveState, [], [], queryByModified, onLoadParams, status$);
|
|
212
|
-
} else {
|
|
213
|
-
const dateModified = queryByModified === true ? params.dateModified! : undefined;
|
|
214
|
-
|
|
215
|
-
this._listen(obs, params, saveState, [], [], queryByModified, dateModified, onLoadParams, status$);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
private iterateListen<T>(
|
|
219
|
-
obs: ObservableParam<any>,
|
|
220
|
-
params: ObservablePersistRemoteGetParams<T>,
|
|
221
|
-
saveState: SaveState,
|
|
222
|
-
path: string[],
|
|
223
|
-
pathTypes: TypeAtPath[],
|
|
224
|
-
queryByModified: QueryByModified<any>,
|
|
225
|
-
onLoadParams: { waiting: number; onLoad: () => void },
|
|
226
|
-
status$: Observable<LoadStatus>,
|
|
227
|
-
) {
|
|
228
|
-
const { options } = params;
|
|
229
|
-
|
|
230
|
-
const { ignoreKeys } = options.remote!.firebase!;
|
|
231
|
-
Object.keys(obs).forEach((key) => {
|
|
232
|
-
if (!ignoreKeys || !ignoreKeys.includes(key)) {
|
|
233
|
-
const o = (obs as any)[key] as ObservableParam<any>;
|
|
234
|
-
const q =
|
|
235
|
-
queryByModified[key as keyof typeof queryByModified] || (queryByModified as { '*': boolean })['*'];
|
|
236
|
-
const pathChild = path.concat(key);
|
|
237
|
-
const pathTypesChild = pathTypes.concat(getPathType(o.peek()));
|
|
238
|
-
let dateModified: number | undefined = undefined;
|
|
239
|
-
|
|
240
|
-
if (isObject(q)) {
|
|
241
|
-
this.iterateListen(o, params, saveState, pathChild, pathTypesChild, q, onLoadParams, status$);
|
|
242
|
-
} else {
|
|
243
|
-
if (q === true || q === '*') {
|
|
244
|
-
dateModified = params.dateModified!;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
this._listen(
|
|
248
|
-
o,
|
|
249
|
-
params,
|
|
250
|
-
saveState,
|
|
251
|
-
pathChild,
|
|
252
|
-
pathTypesChild,
|
|
253
|
-
queryByModified,
|
|
254
|
-
dateModified,
|
|
255
|
-
onLoadParams,
|
|
256
|
-
status$,
|
|
257
|
-
);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
private retryListens() {
|
|
263
|
-
// If a listen failed but save succeeded, the save should have fixed
|
|
264
|
-
// the permission problem so try again
|
|
265
|
-
this.listenErrors.forEach((listenError) => {
|
|
266
|
-
const {
|
|
267
|
-
params,
|
|
268
|
-
path,
|
|
269
|
-
pathTypes,
|
|
270
|
-
dateModified,
|
|
271
|
-
queryByModified,
|
|
272
|
-
unsubscribes,
|
|
273
|
-
saveState,
|
|
274
|
-
onLoadParams,
|
|
275
|
-
status$,
|
|
276
|
-
} = listenError;
|
|
277
|
-
listenError.retry++;
|
|
278
|
-
if (listenError.retry < 10) {
|
|
279
|
-
unsubscribes.forEach((cb) => cb());
|
|
280
|
-
this._listen(
|
|
281
|
-
params.obs,
|
|
282
|
-
params,
|
|
283
|
-
saveState,
|
|
284
|
-
path,
|
|
285
|
-
pathTypes,
|
|
286
|
-
queryByModified,
|
|
287
|
-
dateModified,
|
|
288
|
-
onLoadParams,
|
|
289
|
-
status$,
|
|
290
|
-
);
|
|
291
|
-
} else {
|
|
292
|
-
this.listenErrors.delete(listenError);
|
|
293
|
-
}
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
private async _listen<T>(
|
|
297
|
-
obs: ObservableParam<any>,
|
|
298
|
-
params: ObservablePersistRemoteGetParams<T>,
|
|
299
|
-
saveState: SaveState,
|
|
300
|
-
path: string[],
|
|
301
|
-
pathTypes: TypeAtPath[],
|
|
302
|
-
queryByModified: QueryByModified<any> | undefined,
|
|
303
|
-
dateModified: number | undefined,
|
|
304
|
-
onLoadParams: { waiting: number; onLoad: () => void },
|
|
305
|
-
status$: Observable<LoadStatus>,
|
|
306
|
-
) {
|
|
307
|
-
const { options, onError } = params;
|
|
308
|
-
const { fieldTransforms, allowSetIfError, firebase, dateModifiedKey: dateModifiedKeyOption } = options.remote!;
|
|
309
|
-
const { refPath, query, mode } = firebase!;
|
|
310
|
-
|
|
311
|
-
let didError = false;
|
|
312
|
-
const dateModifiedKey = getDateModifiedKey(dateModifiedKeyOption);
|
|
313
|
-
|
|
314
|
-
const originalPath = path;
|
|
315
|
-
if (fieldTransforms && path.length) {
|
|
316
|
-
path = transformPath(path, pathTypes, fieldTransforms);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
const pathFirebase = refPath(this.fns.getCurrentUser()) + path.join('/');
|
|
320
|
-
|
|
321
|
-
let ref = this.fns.ref(pathFirebase);
|
|
322
|
-
if (query) {
|
|
323
|
-
ref = query(ref) as DatabaseReference;
|
|
324
|
-
}
|
|
325
|
-
if (dateModified && !isNaN(dateModified)) {
|
|
326
|
-
ref = this.fns.orderByChild(ref, dateModifiedKey, dateModified + 1);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
const unsubscribes: (() => void)[] = [];
|
|
330
|
-
|
|
331
|
-
status$.numLoading.set((v) => v + 1);
|
|
332
|
-
status$.numWaitingCanSave.set((v) => v + 1);
|
|
333
|
-
|
|
334
|
-
const _onError = (err: Error) => {
|
|
335
|
-
if (!didError) {
|
|
336
|
-
didError = true;
|
|
337
|
-
const existing = this.listenErrors.get(obs);
|
|
338
|
-
if (existing) {
|
|
339
|
-
existing.retry++;
|
|
340
|
-
} else {
|
|
341
|
-
this.listenErrors.set(obs, {
|
|
342
|
-
params,
|
|
343
|
-
path: originalPath,
|
|
344
|
-
pathTypes,
|
|
345
|
-
dateModified,
|
|
346
|
-
queryByModified,
|
|
347
|
-
unsubscribes,
|
|
348
|
-
retry: 0,
|
|
349
|
-
saveState,
|
|
350
|
-
onLoadParams,
|
|
351
|
-
status$,
|
|
352
|
-
});
|
|
353
|
-
params.state.error.set(err);
|
|
354
|
-
onError?.(err);
|
|
355
|
-
if (allowSetIfError) {
|
|
356
|
-
status$.numWaitingCanSave.set((v) => v - 1);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
};
|
|
361
|
-
|
|
362
|
-
if (mode !== 'once') {
|
|
363
|
-
const localState: LocalState<T> = { changes: {} as T };
|
|
364
|
-
const cb = this._onChange.bind(
|
|
365
|
-
this,
|
|
366
|
-
path,
|
|
367
|
-
pathTypes,
|
|
368
|
-
pathFirebase,
|
|
369
|
-
dateModifiedKey,
|
|
370
|
-
dateModifiedKeyOption,
|
|
371
|
-
params as ObservablePersistRemoteGetParams<any>,
|
|
372
|
-
localState,
|
|
373
|
-
saveState,
|
|
374
|
-
status$,
|
|
375
|
-
);
|
|
376
|
-
unsubscribes.push(this.fns.onChildAdded(ref, cb));
|
|
377
|
-
unsubscribes.push(this.fns.onChildChanged(ref, cb));
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
onLoadParams.waiting++;
|
|
381
|
-
|
|
382
|
-
unsubscribes.push(
|
|
383
|
-
this.fns.once(
|
|
384
|
-
ref,
|
|
385
|
-
this._onceValue.bind(
|
|
386
|
-
this,
|
|
387
|
-
path,
|
|
388
|
-
pathTypes,
|
|
389
|
-
pathFirebase,
|
|
390
|
-
dateModifiedKey,
|
|
391
|
-
dateModifiedKeyOption,
|
|
392
|
-
queryByModified,
|
|
393
|
-
onLoadParams.onLoad,
|
|
394
|
-
params as ObservablePersistRemoteGetParams<any>,
|
|
395
|
-
status$,
|
|
396
|
-
),
|
|
397
|
-
_onError,
|
|
398
|
-
),
|
|
399
|
-
);
|
|
400
|
-
}
|
|
401
|
-
private _updatePendingSave(path: string[], value: object, pending: SaveInfoDictionary) {
|
|
402
|
-
if (path.length === 0) {
|
|
403
|
-
pending[symbolSaveValue as any] = value as any;
|
|
404
|
-
} else if (pending[symbolSaveValue as any]) {
|
|
405
|
-
pending[symbolSaveValue as any] = mergeIntoObservable(pending[symbolSaveValue as any], value);
|
|
406
|
-
} else {
|
|
407
|
-
const p = path[0];
|
|
408
|
-
const v = (value as any)[p];
|
|
409
|
-
const pendingChild = pending[p] as any;
|
|
410
|
-
|
|
411
|
-
// If already have a save info here then don't need to go deeper on the path. Just overwrite the value.
|
|
412
|
-
if (pendingChild && pendingChild[symbolSaveValue] !== undefined) {
|
|
413
|
-
const pendingSaveValue = pendingChild[symbolSaveValue];
|
|
414
|
-
pendingChild[symbolSaveValue] =
|
|
415
|
-
isArray(pendingSaveValue) || isObject(pendingSaveValue)
|
|
416
|
-
? mergeIntoObservable(pendingSaveValue, v)
|
|
417
|
-
: v;
|
|
418
|
-
} else {
|
|
419
|
-
// 1. If nothing here
|
|
420
|
-
// 2. If other strings here
|
|
421
|
-
if (!pending[p]) {
|
|
422
|
-
pending[p] = {};
|
|
423
|
-
}
|
|
424
|
-
if (path.length > 1) {
|
|
425
|
-
this._updatePendingSave(path.slice(1), v, pending[p] as SaveInfoDictionary);
|
|
426
|
-
} else {
|
|
427
|
-
pending[p] = { [symbolSaveValue]: v };
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
public async set<T>({ options, changes, obs }: ObservablePersistRemoteSetParams<T>): Promise<
|
|
433
|
-
| {
|
|
434
|
-
changes?: object;
|
|
435
|
-
dateModified?: number;
|
|
436
|
-
pathStrs?: string[];
|
|
437
|
-
}
|
|
438
|
-
| undefined
|
|
439
|
-
> {
|
|
440
|
-
const { remote } = options;
|
|
441
|
-
// If the plugin is set globally but it has no firebase options this plugin can't do anything
|
|
442
|
-
if (!remote || !remote.firebase) {
|
|
443
|
-
return;
|
|
444
|
-
}
|
|
445
|
-
const { waitForSet, debounceSet: debounceSet, log, firebase } = remote;
|
|
446
|
-
|
|
447
|
-
const { requireAuth, refPath: refPathFn } = firebase;
|
|
448
|
-
|
|
449
|
-
if (requireAuth) {
|
|
450
|
-
await whenReady(this.user);
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
const refPath = refPathFn(this.fns.getCurrentUser());
|
|
454
|
-
|
|
455
|
-
const status$ = this._pathsLoadStatus[refPath];
|
|
456
|
-
if (status$.numWaitingCanSave.peek() > 0) {
|
|
457
|
-
// Wait for all listened paths to load before we can save
|
|
458
|
-
await when(() => status$.numWaitingCanSave.get() < 1);
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
if (waitForSet) {
|
|
462
|
-
const waitFor = isFunction(waitForSet) ? waitForSet({ changes, value: obs.peek() }) : waitForSet;
|
|
463
|
-
if (waitFor) {
|
|
464
|
-
await when(waitFor);
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
const saveState = this.saveStates.get(obs)!;
|
|
469
|
-
|
|
470
|
-
// Don't start a save until all outstanding saves are finished
|
|
471
|
-
await when(() => saveState.numSavesPending.get() === 0);
|
|
472
|
-
|
|
473
|
-
const { pendingSaveResults, pendingSaves } = saveState;
|
|
474
|
-
|
|
475
|
-
if (!pendingSaves.has(refPath)) {
|
|
476
|
-
pendingSaves.set(refPath, { options, saves: {} });
|
|
477
|
-
pendingSaveResults.set(refPath, { saved: [] });
|
|
478
|
-
}
|
|
479
|
-
const pending = pendingSaves.get(refPath)!.saves;
|
|
480
|
-
|
|
481
|
-
log?.('verbose', 'Saving', changes);
|
|
482
|
-
|
|
483
|
-
for (let i = 0; i < changes.length; i++) {
|
|
484
|
-
let { valueAtPath } = changes[i];
|
|
485
|
-
const { pathTypes, path } = changes[i];
|
|
486
|
-
if (valueAtPath === undefined) {
|
|
487
|
-
valueAtPath = null;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
const value = constructObjectWithPath(path as string[], pathTypes, clone(valueAtPath)) as unknown as T;
|
|
491
|
-
const pathCloned = path.slice() as string[];
|
|
492
|
-
|
|
493
|
-
this._updatePendingSave(pathCloned, value as unknown as object, pending);
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
if (!saveState.eventSaved) {
|
|
497
|
-
saveState.eventSaved = observablePrimitive();
|
|
498
|
-
}
|
|
499
|
-
// Keep the current eventSaved. This will get reassigned once the timeout activates.
|
|
500
|
-
const eventSaved = saveState.eventSaved;
|
|
501
|
-
|
|
502
|
-
const timeout = debounceSet ?? this.debounceSet;
|
|
503
|
-
|
|
504
|
-
if (timeout) {
|
|
505
|
-
if (saveState.timeout) {
|
|
506
|
-
clearTimeout(saveState.timeout);
|
|
507
|
-
}
|
|
508
|
-
saveState.timeout = setTimeout(this._onTimeoutSave.bind(this, saveState), timeout);
|
|
509
|
-
} else {
|
|
510
|
-
this._onTimeoutSave(saveState);
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
const savedOrError = await when(eventSaved);
|
|
514
|
-
|
|
515
|
-
if (savedOrError === true) {
|
|
516
|
-
this.retryListens();
|
|
517
|
-
|
|
518
|
-
const saveResults = pendingSaveResults.get(refPath);
|
|
519
|
-
|
|
520
|
-
if (saveResults) {
|
|
521
|
-
const { saved } = saveResults;
|
|
522
|
-
if (saved?.length) {
|
|
523
|
-
// Only want to return from saved one time
|
|
524
|
-
if (saveState.numSavesPending.get() === 0) {
|
|
525
|
-
pendingSaveResults.delete(refPath);
|
|
526
|
-
} else {
|
|
527
|
-
saveResults.saved = [];
|
|
528
|
-
}
|
|
529
|
-
let maxModified = 0;
|
|
530
|
-
|
|
531
|
-
// Compile a changes object of all the dateModified
|
|
532
|
-
const changesOut = {};
|
|
533
|
-
for (let i = 0; i < saved.length; i++) {
|
|
534
|
-
const { dateModified, path, pathTypes, dateModifiedKeyOption, dateModifiedKey, value } =
|
|
535
|
-
saved[i];
|
|
536
|
-
if (dateModified) {
|
|
537
|
-
maxModified = Math.max(dateModified, maxModified);
|
|
538
|
-
if (dateModifiedKeyOption) {
|
|
539
|
-
const deconstructed = deconstructObjectWithPath(path, pathTypes, value);
|
|
540
|
-
// Don't resurrect deleted items
|
|
541
|
-
if (deconstructed !== (symbolDelete as any)) {
|
|
542
|
-
Object.assign(
|
|
543
|
-
changesOut,
|
|
544
|
-
constructObjectWithPath(path, pathTypes, {
|
|
545
|
-
[dateModifiedKey!]: dateModified,
|
|
546
|
-
}),
|
|
547
|
-
);
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
const ret = {
|
|
554
|
-
changes: changesOut,
|
|
555
|
-
dateModified: maxModified || undefined,
|
|
556
|
-
};
|
|
557
|
-
|
|
558
|
-
log?.('verbose', 'Saved', changes, ret);
|
|
559
|
-
|
|
560
|
-
return ret;
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
} else {
|
|
564
|
-
throw savedOrError;
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
return {};
|
|
568
|
-
}
|
|
569
|
-
private _constructBatch(
|
|
570
|
-
options: LegacyPersistOptions,
|
|
571
|
-
batch: Record<string, string | object>,
|
|
572
|
-
basePath: string,
|
|
573
|
-
saves: SaveInfoDictionary,
|
|
574
|
-
...path: string[]
|
|
575
|
-
) {
|
|
576
|
-
const { fieldTransforms, dateModifiedKey: dateModifiedKeyOption } = options.remote!;
|
|
577
|
-
const dateModifiedKey = getDateModifiedKey(dateModifiedKeyOption);
|
|
578
|
-
|
|
579
|
-
let valSave = saves[symbolSaveValue as any];
|
|
580
|
-
if (valSave !== undefined) {
|
|
581
|
-
let queryByModified = options.remote!.firebase!.queryByModified;
|
|
582
|
-
if (queryByModified) {
|
|
583
|
-
if (queryByModified !== true && fieldTransforms) {
|
|
584
|
-
queryByModified = transformObject(queryByModified, fieldTransforms);
|
|
585
|
-
}
|
|
586
|
-
valSave = this.insertDatesToSave(batch, queryByModified!, dateModifiedKey, basePath, path, valSave);
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
const pathThis = basePath + path.join('/');
|
|
590
|
-
if (pathThis && !batch[pathThis]) {
|
|
591
|
-
batch[pathThis] = valSave;
|
|
592
|
-
}
|
|
593
|
-
} else {
|
|
594
|
-
Object.keys(saves).forEach((key) => {
|
|
595
|
-
this._constructBatch(options, batch, basePath, saves[key] as any, ...path, key);
|
|
596
|
-
});
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
private _constructBatchesForSave(pendingSaves: Map<string, PendingSaves>) {
|
|
600
|
-
const batches: object[] = [];
|
|
601
|
-
pendingSaves.forEach(({ options, saves }) => {
|
|
602
|
-
const basePath = options.remote!.firebase!.refPath(this.fns.getCurrentUser());
|
|
603
|
-
const batch = {};
|
|
604
|
-
this._constructBatch(options, batch, basePath, saves);
|
|
605
|
-
batches.push(batch);
|
|
606
|
-
});
|
|
607
|
-
|
|
608
|
-
return batches;
|
|
609
|
-
}
|
|
610
|
-
private async _onTimeoutSave(saveState: SaveState) {
|
|
611
|
-
const { pendingSaves, eventSaved } = saveState;
|
|
612
|
-
|
|
613
|
-
saveState.timeout = undefined;
|
|
614
|
-
saveState.eventSaved = undefined;
|
|
615
|
-
saveState.numSavesPending.set((v) => v + 1);
|
|
616
|
-
|
|
617
|
-
if (pendingSaves.size > 0) {
|
|
618
|
-
const batches = clone(this._constructBatchesForSave(pendingSaves)) as Record<string, any>[];
|
|
619
|
-
|
|
620
|
-
saveState.savingSaves = pendingSaves;
|
|
621
|
-
|
|
622
|
-
// Clear the pendingSaves so that the next batch starts from scratch
|
|
623
|
-
saveState.pendingSaves = new Map();
|
|
624
|
-
|
|
625
|
-
if (batches.length > 0) {
|
|
626
|
-
const promises: Promise<{ didSave?: boolean; error?: any }>[] = [];
|
|
627
|
-
for (let i = 0; i < batches.length; i++) {
|
|
628
|
-
const batch = batches[i];
|
|
629
|
-
promises.push(this._saveBatch(batch));
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
const results = await Promise.all(promises);
|
|
633
|
-
const errors = results.filter((result) => result.error);
|
|
634
|
-
if (errors.length === 0) {
|
|
635
|
-
saveState.numSavesPending.set((v) => v - 1);
|
|
636
|
-
eventSaved?.set(true);
|
|
637
|
-
} else {
|
|
638
|
-
eventSaved?.set(errors[0].error);
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
private async _saveBatch(batch: Record<string, any>): Promise<any> {
|
|
644
|
-
const length = JSON.stringify(batch).length;
|
|
645
|
-
|
|
646
|
-
let error: Error | undefined = undefined;
|
|
647
|
-
// Firebase has a maximum limit of 16MB per save so we constrain our saves to
|
|
648
|
-
// less than 12 to be safe
|
|
649
|
-
if (length > 12e6) {
|
|
650
|
-
const parts = splitLargeObject(batch, 6e6);
|
|
651
|
-
let didSave = true;
|
|
652
|
-
|
|
653
|
-
// TODO: Option for logging
|
|
654
|
-
for (let i = 0; i < parts.length; i++) {
|
|
655
|
-
const ret = await this._saveBatch(parts[i]);
|
|
656
|
-
if (ret.error) {
|
|
657
|
-
error = ret.error;
|
|
658
|
-
} else {
|
|
659
|
-
didSave = didSave && ret.didSave;
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
return error ? { error } : { didSave };
|
|
663
|
-
} else {
|
|
664
|
-
for (let i = 0; i < 3; i++) {
|
|
665
|
-
try {
|
|
666
|
-
await this.fns.update(batch);
|
|
667
|
-
return { didSave: true };
|
|
668
|
-
} catch (err) {
|
|
669
|
-
error = err as Error;
|
|
670
|
-
|
|
671
|
-
await new Promise<void>((resolve) => setTimeout(resolve, 500));
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
return { error };
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
private _convertFBTimestamps(obj: any, dateModifiedKey: string, dateModifiedKeyOption: string) {
|
|
679
|
-
let value = obj;
|
|
680
|
-
// Database value can be either { @: number, _: object } or { @: number, ...rest }
|
|
681
|
-
// where @ is the dateModifiedKey
|
|
682
|
-
let dateModified = value[dateModifiedKey];
|
|
683
|
-
if (dateModified) {
|
|
684
|
-
// If user doesn't request a dateModifiedKey then delete it
|
|
685
|
-
if (value._ !== undefined) {
|
|
686
|
-
value = value._;
|
|
687
|
-
} else if (dateModified && Object.keys(value).length < 2) {
|
|
688
|
-
value = symbolDelete;
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
if (!dateModifiedKeyOption) {
|
|
692
|
-
delete value[dateModifiedKey];
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
if (isObject(value)) {
|
|
697
|
-
Object.keys(value).forEach((k) => {
|
|
698
|
-
const val = value[k];
|
|
699
|
-
if (val !== undefined) {
|
|
700
|
-
if (isObject(val) || isArray(val)) {
|
|
701
|
-
const { value: valueChild, dateModified: dateModifiedChild } = this._convertFBTimestamps(
|
|
702
|
-
val,
|
|
703
|
-
dateModifiedKey,
|
|
704
|
-
dateModifiedKeyOption,
|
|
705
|
-
);
|
|
706
|
-
if (dateModifiedChild) {
|
|
707
|
-
dateModified = Math.max(dateModified || 0, dateModifiedChild);
|
|
708
|
-
}
|
|
709
|
-
if (valueChild !== val) {
|
|
710
|
-
value[k] = valueChild;
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
});
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
return { value, dateModified };
|
|
718
|
-
}
|
|
719
|
-
private async _onceValue<T>(
|
|
720
|
-
path: string[],
|
|
721
|
-
pathTypes: TypeAtPath[],
|
|
722
|
-
pathFirebase: string,
|
|
723
|
-
dateModifiedKey: string | undefined,
|
|
724
|
-
dateModifiedKeyOption: string | undefined,
|
|
725
|
-
queryByModified: QueryByModified<any> | undefined,
|
|
726
|
-
onLoad: () => void,
|
|
727
|
-
params: ObservablePersistRemoteGetParams<T>,
|
|
728
|
-
status$: Observable<LoadStatus>,
|
|
729
|
-
snapshot: DataSnapshot,
|
|
730
|
-
) {
|
|
731
|
-
const { onChange } = params;
|
|
732
|
-
const outerValue = snapshot.val();
|
|
733
|
-
|
|
734
|
-
// If this path previously errored, clear the error state
|
|
735
|
-
const obs = params.obs;
|
|
736
|
-
params.state.error.delete();
|
|
737
|
-
this.listenErrors.delete(obs);
|
|
738
|
-
|
|
739
|
-
status$.startedLoading.set(true);
|
|
740
|
-
if (outerValue && isObject(outerValue)) {
|
|
741
|
-
let value;
|
|
742
|
-
let dateModified;
|
|
743
|
-
if (queryByModified) {
|
|
744
|
-
const converted = this._convertFBTimestamps(outerValue, dateModifiedKey!, dateModifiedKeyOption!);
|
|
745
|
-
value = converted.value;
|
|
746
|
-
dateModified = converted.dateModified;
|
|
747
|
-
} else {
|
|
748
|
-
value = outerValue;
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
value = constructObjectWithPath(path, pathTypes, value);
|
|
752
|
-
|
|
753
|
-
const onChangePromise = onChange({
|
|
754
|
-
value,
|
|
755
|
-
path,
|
|
756
|
-
pathTypes,
|
|
757
|
-
mode: queryByModified ? 'assign' : 'set',
|
|
758
|
-
dateModified,
|
|
759
|
-
});
|
|
760
|
-
if (onChangePromise) {
|
|
761
|
-
await onChangePromise;
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
onLoad();
|
|
765
|
-
batch(() => {
|
|
766
|
-
status$.numLoading.set((v) => v - 1);
|
|
767
|
-
status$.numWaitingCanSave.set((v) => v - 1);
|
|
768
|
-
});
|
|
769
|
-
}
|
|
770
|
-
private async _onChange<T>(
|
|
771
|
-
path: string[],
|
|
772
|
-
pathTypes: TypeAtPath[],
|
|
773
|
-
pathFirebase: string,
|
|
774
|
-
dateModifiedKey: string | undefined,
|
|
775
|
-
dateModifiedKeyOption: string | undefined,
|
|
776
|
-
params: ObservablePersistRemoteGetParams<T>,
|
|
777
|
-
localState: LocalState<T>,
|
|
778
|
-
saveState: SaveState,
|
|
779
|
-
status$: Observable<LoadStatus>,
|
|
780
|
-
snapshot: DataSnapshot,
|
|
781
|
-
) {
|
|
782
|
-
const { numLoading, startedLoading } = status$.peek();
|
|
783
|
-
|
|
784
|
-
if (numLoading > 0) {
|
|
785
|
-
// If onceValue has not been called yet, then skip onChange because it will come later
|
|
786
|
-
if (!startedLoading) return;
|
|
787
|
-
|
|
788
|
-
// Wait for load
|
|
789
|
-
await when(() => status$.numLoading.get() < 1);
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
const {
|
|
793
|
-
onChange,
|
|
794
|
-
state,
|
|
795
|
-
options: { remote },
|
|
796
|
-
} = params;
|
|
797
|
-
|
|
798
|
-
const { changeTimeout } = remote!;
|
|
799
|
-
|
|
800
|
-
// Skip changes if disabled
|
|
801
|
-
if (state.isEnabledRemote.peek() === false) return;
|
|
802
|
-
|
|
803
|
-
const key = snapshot.key!;
|
|
804
|
-
const val = snapshot.val();
|
|
805
|
-
|
|
806
|
-
if (val) {
|
|
807
|
-
// eslint-disable-next-line prefer-const
|
|
808
|
-
let { value, dateModified } = this._convertFBTimestamps(val, dateModifiedKey!, dateModifiedKeyOption!);
|
|
809
|
-
|
|
810
|
-
const pathChild = path.concat(key);
|
|
811
|
-
const pathTypesChild = pathTypes.concat('object');
|
|
812
|
-
const constructed = constructObjectWithPath(pathChild, pathTypes, value);
|
|
813
|
-
|
|
814
|
-
if (
|
|
815
|
-
!this.addValuesToPendingSaves(
|
|
816
|
-
pathFirebase,
|
|
817
|
-
constructed,
|
|
818
|
-
pathChild,
|
|
819
|
-
pathTypesChild,
|
|
820
|
-
dateModified,
|
|
821
|
-
dateModifiedKey,
|
|
822
|
-
dateModifiedKeyOption,
|
|
823
|
-
saveState,
|
|
824
|
-
onChange,
|
|
825
|
-
)
|
|
826
|
-
) {
|
|
827
|
-
localState.changes = setAtPath(localState.changes as object, pathChild, pathTypes, value);
|
|
828
|
-
|
|
829
|
-
// Debounce many child changes into a single onChange
|
|
830
|
-
clearTimeout(localState.timeout);
|
|
831
|
-
localState.timeout = setTimeout(() => {
|
|
832
|
-
const changes = localState.changes;
|
|
833
|
-
localState.changes = {};
|
|
834
|
-
onChange({ value: changes as T, path, pathTypes, mode: 'assign', dateModified });
|
|
835
|
-
}, changeTimeout || 300);
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
private insertDateToObject(value: any, dateModifiedKey: string) {
|
|
840
|
-
const timestamp = this.fns.serverTimestamp();
|
|
841
|
-
if (isObject(value)) {
|
|
842
|
-
return Object.assign(value, {
|
|
843
|
-
[dateModifiedKey]: timestamp,
|
|
844
|
-
});
|
|
845
|
-
} else {
|
|
846
|
-
return {
|
|
847
|
-
[dateModifiedKey]: timestamp,
|
|
848
|
-
_: value,
|
|
849
|
-
};
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
private insertDatesToSaveObject(
|
|
853
|
-
batch: Record<string, string | object>,
|
|
854
|
-
queryByModified: QueryByModified<any>,
|
|
855
|
-
dateModifiedKey: string,
|
|
856
|
-
path: string,
|
|
857
|
-
value: any,
|
|
858
|
-
): object {
|
|
859
|
-
if (queryByModified === true) {
|
|
860
|
-
value = this.insertDateToObject(value, dateModifiedKey);
|
|
861
|
-
} else if (isObject(value)) {
|
|
862
|
-
Object.keys(value).forEach((key) => {
|
|
863
|
-
value[key] = this.insertDatesToSaveObject(
|
|
864
|
-
batch,
|
|
865
|
-
(queryByModified as any)[key],
|
|
866
|
-
dateModifiedKey,
|
|
867
|
-
path + '/' + key,
|
|
868
|
-
value[key],
|
|
869
|
-
);
|
|
870
|
-
});
|
|
871
|
-
}
|
|
872
|
-
return value;
|
|
873
|
-
}
|
|
874
|
-
private insertDatesToSave(
|
|
875
|
-
batch: Record<string, string | object>,
|
|
876
|
-
queryByModified: QueryByModified<any>,
|
|
877
|
-
dateModifiedKey: string,
|
|
878
|
-
basePath: string,
|
|
879
|
-
path: string[],
|
|
880
|
-
value: any,
|
|
881
|
-
) {
|
|
882
|
-
let o = queryByModified;
|
|
883
|
-
for (let i = 0; i < path.length; i++) {
|
|
884
|
-
if (o === true) {
|
|
885
|
-
const pathThis = basePath + path.slice(0, i + 1).join('/');
|
|
886
|
-
if (i === path.length - 1) {
|
|
887
|
-
if (!isObject(value)) {
|
|
888
|
-
return this.insertDateToObject(value, dateModifiedKey);
|
|
889
|
-
} else {
|
|
890
|
-
if (isObject(value)) {
|
|
891
|
-
value[dateModifiedKey] = this.fns.serverTimestamp();
|
|
892
|
-
} else {
|
|
893
|
-
batch[pathThis + '/' + dateModifiedKey] = this.fns.serverTimestamp();
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
} else {
|
|
897
|
-
batch[pathThis + '/' + dateModifiedKey] = this.fns.serverTimestamp();
|
|
898
|
-
}
|
|
899
|
-
return value;
|
|
900
|
-
} else if (isObject(o)) {
|
|
901
|
-
o = (o as any)[path[i]];
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
if (o === true && isObject(value)) {
|
|
906
|
-
Object.keys(value).forEach((key) => {
|
|
907
|
-
this.insertDatesToSaveObject(
|
|
908
|
-
batch,
|
|
909
|
-
o,
|
|
910
|
-
dateModifiedKey,
|
|
911
|
-
basePath + path.join('/') + '/' + key,
|
|
912
|
-
value[key],
|
|
913
|
-
);
|
|
914
|
-
});
|
|
915
|
-
} else if (o !== undefined) {
|
|
916
|
-
this.insertDatesToSaveObject(batch, o, dateModifiedKey, basePath + path.join('/'), value);
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
return value;
|
|
920
|
-
}
|
|
921
|
-
private addValuesToPendingSaves(
|
|
922
|
-
refPath: string,
|
|
923
|
-
value: object,
|
|
924
|
-
pathChild: string[],
|
|
925
|
-
pathTypesChild: TypeAtPath[],
|
|
926
|
-
dateModified: number | undefined,
|
|
927
|
-
dateModifiedKey: string | undefined,
|
|
928
|
-
dateModifiedKeyOption: string | undefined,
|
|
929
|
-
saveState: SaveState,
|
|
930
|
-
onChange: ObservablePersistRemoteGetParams<any>['onChange'],
|
|
931
|
-
) {
|
|
932
|
-
const { pendingSaveResults, savingSaves } = saveState;
|
|
933
|
-
let found = false;
|
|
934
|
-
const pathArr = refPath.split('/');
|
|
935
|
-
for (let i = pathArr.length - 1; !found && i >= 0; i--) {
|
|
936
|
-
const p = pathArr[i];
|
|
937
|
-
if (p === '') continue;
|
|
938
|
-
|
|
939
|
-
const path = pathArr.slice(0, i + 1).join('/') + '/';
|
|
940
|
-
|
|
941
|
-
// Look for this saved key in the currently saving saves.
|
|
942
|
-
// If it's being saved locally this must be the remote onChange
|
|
943
|
-
// coming in for this save.
|
|
944
|
-
if (pendingSaveResults.has(path) && savingSaves?.has(path)) {
|
|
945
|
-
found = true;
|
|
946
|
-
if (pathChild.length > 0) {
|
|
947
|
-
const savingSave = savingSaves.get(path)!;
|
|
948
|
-
const save = savingSave.saves[pathChild[0]];
|
|
949
|
-
if (!save) {
|
|
950
|
-
found = false;
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
if (found) {
|
|
955
|
-
const pending = pendingSaveResults.get(path)!;
|
|
956
|
-
pending.saved.push({
|
|
957
|
-
value,
|
|
958
|
-
dateModified,
|
|
959
|
-
path: pathChild,
|
|
960
|
-
pathTypes: pathTypesChild,
|
|
961
|
-
dateModifiedKey,
|
|
962
|
-
dateModifiedKeyOption,
|
|
963
|
-
onChange,
|
|
964
|
-
});
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
value = { [p]: value };
|
|
968
|
-
}
|
|
969
|
-
return found;
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
function estimateSize(value: any): number {
|
|
974
|
-
return ('' + value).length + 2; // Convert to string and account for quotes in JSON.
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
function splitLargeObject(obj: Record<string, any>, limit: number): Record<string, any>[] {
|
|
978
|
-
const parts: Record<string, any>[] = [{}];
|
|
979
|
-
let sizeCount = 0;
|
|
980
|
-
|
|
981
|
-
function recursiveSplit(innerObj: Record<string, any>, path: string[] = []) {
|
|
982
|
-
for (const key in innerObj) {
|
|
983
|
-
if (!hasOwnProperty.call(innerObj, key)) {
|
|
984
|
-
continue;
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
const newPath = [...path, key];
|
|
988
|
-
const keySize = key.length + 4; // Account for quotes and colon in JSON.
|
|
989
|
-
const val = innerObj[key];
|
|
990
|
-
|
|
991
|
-
let itemSize = 0;
|
|
992
|
-
if (val && typeof val === 'object') {
|
|
993
|
-
itemSize = JSON.stringify(val).length;
|
|
994
|
-
} else {
|
|
995
|
-
itemSize = estimateSize(val);
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
if (val && typeof val === 'object' && itemSize > limit) {
|
|
999
|
-
recursiveSplit(val, newPath);
|
|
1000
|
-
} else {
|
|
1001
|
-
// Check if the size of the current item exceeds the limit
|
|
1002
|
-
if (sizeCount > 0 && sizeCount + keySize + itemSize > limit) {
|
|
1003
|
-
parts.push({});
|
|
1004
|
-
sizeCount = 0;
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
const pathKey = newPath.join('/');
|
|
1008
|
-
parts[parts.length - 1][pathKey] = val;
|
|
1009
|
-
sizeCount += keySize + itemSize;
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
recursiveSplit(obj);
|
|
1015
|
-
return parts;
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
// This is the web version of all the firebase functions. It passes them in as arguments so that it could
|
|
1019
|
-
// support firebase on other platforms.
|
|
1020
|
-
export class ObservablePersistFirebase extends ObservablePersistFirebaseBase {
|
|
1021
|
-
constructor() {
|
|
1022
|
-
super({
|
|
1023
|
-
isInitialized: () => {
|
|
1024
|
-
try {
|
|
1025
|
-
return !!getAuth().app;
|
|
1026
|
-
} catch {
|
|
1027
|
-
return false;
|
|
1028
|
-
}
|
|
1029
|
-
},
|
|
1030
|
-
getCurrentUser: () => getAuth().currentUser?.uid,
|
|
1031
|
-
ref: (path: string) => ref(getDatabase(), path),
|
|
1032
|
-
orderByChild: (ref: DatabaseReference, child: string, start: number) =>
|
|
1033
|
-
query(ref, orderByChild(child), startAt(start)),
|
|
1034
|
-
update: (object: object) => update(ref(getDatabase()), object),
|
|
1035
|
-
once: (ref: DatabaseReference, callback, callbackError) => {
|
|
1036
|
-
let unsubscribe: Unsubscribe | undefined;
|
|
1037
|
-
const cb = (snap: DataSnapshot) => {
|
|
1038
|
-
if (unsubscribe) {
|
|
1039
|
-
unsubscribe();
|
|
1040
|
-
unsubscribe = undefined;
|
|
1041
|
-
}
|
|
1042
|
-
callback(snap);
|
|
1043
|
-
};
|
|
1044
|
-
unsubscribe = onValue(ref, cb, callbackError);
|
|
1045
|
-
return unsubscribe;
|
|
1046
|
-
},
|
|
1047
|
-
onChildAdded,
|
|
1048
|
-
onChildChanged,
|
|
1049
|
-
serverTimestamp,
|
|
1050
|
-
onAuthStateChanged: (cb) => getAuth().onAuthStateChanged(cb),
|
|
1051
|
-
});
|
|
1052
|
-
}
|
|
1053
|
-
}
|