recoil-next 0.2.0 → 0.4.0
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 +5 -5
- package/dist/index.cjs +486 -217
- package/dist/index.d.cts +9 -26
- package/dist/index.d.ts +9 -26
- package/dist/index.mjs +487 -218
- package/package.json +12 -12
package/dist/index.cjs
CHANGED
|
@@ -13,7 +13,9 @@ function err(message) {
|
|
|
13
13
|
try {
|
|
14
14
|
throw error;
|
|
15
15
|
}
|
|
16
|
-
catch (_) {
|
|
16
|
+
catch (_) {
|
|
17
|
+
/* T-ignore */
|
|
18
|
+
}
|
|
17
19
|
}
|
|
18
20
|
return error;
|
|
19
21
|
}
|
|
@@ -114,7 +116,8 @@ class BaseLoadable {
|
|
|
114
116
|
throw err(`Loadable expected error, but in "${this.state}" state`);
|
|
115
117
|
}
|
|
116
118
|
is(other) {
|
|
117
|
-
return other.state === this.state &&
|
|
119
|
+
return (other.state === this.state &&
|
|
120
|
+
Object.is(other.contents, this.contents));
|
|
118
121
|
}
|
|
119
122
|
}
|
|
120
123
|
class ValueLoadable extends BaseLoadable {
|
|
@@ -824,7 +827,6 @@ const getNextComponentID = () => nextComponentID++;
|
|
|
824
827
|
/** React mode and feature detection helpers */
|
|
825
828
|
var _a;
|
|
826
829
|
// Access either useSyncExternalStore or unstable_useSyncExternalStore depending on React version.
|
|
827
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
828
830
|
const useSyncExternalStore =
|
|
829
831
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
830
832
|
(_a = React.useSyncExternalStore) !== null && _a !== void 0 ? _a :
|
|
@@ -835,14 +837,17 @@ let ReactRendererVersionMismatchWarnOnce = false;
|
|
|
835
837
|
function currentRendererSupportsUseSyncExternalStore() {
|
|
836
838
|
var _a;
|
|
837
839
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
838
|
-
const internals = React
|
|
840
|
+
const internals = React
|
|
841
|
+
.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
|
|
839
842
|
if (!internals)
|
|
840
843
|
return false;
|
|
841
844
|
const { ReactCurrentDispatcher, ReactCurrentOwner } = internals;
|
|
842
845
|
// The dispatcher can be on ReactCurrentDispatcher.current (newer) or ReactCurrentOwner.currentDispatcher (older)
|
|
843
846
|
const dispatcher = (_a = ReactCurrentDispatcher === null || ReactCurrentDispatcher === void 0 ? void 0 : ReactCurrentDispatcher.current) !== null && _a !== void 0 ? _a : ReactCurrentOwner === null || ReactCurrentOwner === void 0 ? void 0 : ReactCurrentOwner.currentDispatcher;
|
|
844
847
|
const isSupported = (dispatcher === null || dispatcher === void 0 ? void 0 : dispatcher.useSyncExternalStore) != null;
|
|
845
|
-
if (useSyncExternalStore !== undefined &&
|
|
848
|
+
if (useSyncExternalStore !== undefined &&
|
|
849
|
+
!isSupported &&
|
|
850
|
+
!ReactRendererVersionMismatchWarnOnce) {
|
|
846
851
|
ReactRendererVersionMismatchWarnOnce = true;
|
|
847
852
|
recoverableViolation('A React renderer without React 18+ API support is being used with React 18+.');
|
|
848
853
|
}
|
|
@@ -893,7 +898,8 @@ writes) {
|
|
|
893
898
|
return result;
|
|
894
899
|
}
|
|
895
900
|
function writeLoadableToTreeState(state, key, loadable) {
|
|
896
|
-
if (loadable.state === 'hasValue' &&
|
|
901
|
+
if (loadable.state === 'hasValue' &&
|
|
902
|
+
loadable.contents instanceof DefaultValue) {
|
|
897
903
|
state.atomValues.delete(key);
|
|
898
904
|
}
|
|
899
905
|
else {
|
|
@@ -907,11 +913,30 @@ function markRecoilValueModified(store, rv) {
|
|
|
907
913
|
store.replaceState(state => {
|
|
908
914
|
const newState = copyTreeState(state);
|
|
909
915
|
newState.dirtyAtoms.add(rv.key);
|
|
916
|
+
notifyComponents$2(store, newState);
|
|
910
917
|
return newState;
|
|
911
918
|
});
|
|
912
919
|
}
|
|
920
|
+
function notifyComponents$2(store, treeState) {
|
|
921
|
+
const storeState = store.getState();
|
|
922
|
+
const dependentNodes = getDownstreamNodes(store, treeState, treeState.dirtyAtoms);
|
|
923
|
+
for (const key of dependentNodes) {
|
|
924
|
+
const comps = storeState.nodeToComponentSubscriptions.get(key);
|
|
925
|
+
if (comps) {
|
|
926
|
+
for (const [_subID, [_debugName, callback]] of comps) {
|
|
927
|
+
try {
|
|
928
|
+
callback(treeState);
|
|
929
|
+
}
|
|
930
|
+
catch (error) {
|
|
931
|
+
console.error(`Error in component callback for ${key}:`, error);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
}
|
|
913
937
|
function valueFromValueOrUpdater(store, state, recoilValue, valueOrUpdater) {
|
|
914
|
-
if (typeof valueOrUpdater === 'function' &&
|
|
938
|
+
if (typeof valueOrUpdater === 'function' &&
|
|
939
|
+
valueOrUpdater !== DEFAULT_VALUE) {
|
|
915
940
|
// Updater form: pass in the current value
|
|
916
941
|
const current = getRecoilValueAsLoadable(store, recoilValue, state);
|
|
917
942
|
if (current.state === 'loading') {
|
|
@@ -935,6 +960,7 @@ function setRecoilValue(store, recoilValue, valueOrUpdater) {
|
|
|
935
960
|
const writes = setNodeValue(store, newState, recoilValue.key, newValue);
|
|
936
961
|
writes.forEach((loadable, key) => writeLoadableToTreeState(newState, key, loadable));
|
|
937
962
|
invalidateDownstreams(store, newState);
|
|
963
|
+
newState.dirtyAtoms.add(recoilValue.key);
|
|
938
964
|
return newState;
|
|
939
965
|
});
|
|
940
966
|
}
|
|
@@ -969,10 +995,18 @@ function subscribeToRecoilValue(store, { key }, callback, _componentDebugName) {
|
|
|
969
995
|
};
|
|
970
996
|
}
|
|
971
997
|
function refreshRecoilValue(store, { key }) {
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
998
|
+
store.replaceState(state => {
|
|
999
|
+
var _a;
|
|
1000
|
+
const newState = copyTreeState(state);
|
|
1001
|
+
const node = getNode(key);
|
|
1002
|
+
// Clear the cache without triggering nested state updates
|
|
1003
|
+
(_a = node.clearCache) === null || _a === void 0 ? void 0 : _a.call(node, store, newState);
|
|
1004
|
+
// Mark as dirty to trigger re-renders
|
|
1005
|
+
newState.dirtyAtoms.add(key);
|
|
1006
|
+
// Notify components directly without nested state update
|
|
1007
|
+
notifyComponents$2(store, newState);
|
|
1008
|
+
return newState;
|
|
1009
|
+
});
|
|
976
1010
|
}
|
|
977
1011
|
|
|
978
1012
|
/**
|
|
@@ -2355,8 +2389,7 @@ function* concatIterables(iters) {
|
|
|
2355
2389
|
* TypeScript port of Recoil_Environment.js
|
|
2356
2390
|
*/
|
|
2357
2391
|
const isSSR = typeof window === 'undefined';
|
|
2358
|
-
const isWindow = (value) => !isSSR &&
|
|
2359
|
-
(value === window || value instanceof Window);
|
|
2392
|
+
const isWindow = (value) => !isSSR && (value === window || value instanceof Window);
|
|
2360
2393
|
const isReactNative = typeof navigator !== 'undefined' && navigator.product === 'ReactNative';
|
|
2361
2394
|
|
|
2362
2395
|
/**
|
|
@@ -2426,7 +2459,8 @@ class Snapshot {
|
|
|
2426
2459
|
? recoilValues.values()
|
|
2427
2460
|
: opt.isInitialized === true
|
|
2428
2461
|
? recoilValuesForKeys(concatIterables([knownAtoms, knownSelectors]))
|
|
2429
|
-
: filterIterable(recoilValues.values(),
|
|
2462
|
+
: filterIterable(recoilValues.values(), recoilValue => !knownAtoms.has(recoilValue.key) &&
|
|
2463
|
+
!knownSelectors.has(recoilValue.key));
|
|
2430
2464
|
};
|
|
2431
2465
|
// Report the current status of a node.
|
|
2432
2466
|
// This peeks the current state and does not affect the snapshot state at all
|
|
@@ -2591,10 +2625,7 @@ function cloneStoreState(store, treeState, bumpVersion = false) {
|
|
|
2591
2625
|
// FIXME here's a copy
|
|
2592
2626
|
// Create blank cleanup handlers for atoms so snapshots don't re-run
|
|
2593
2627
|
// atom effects.
|
|
2594
|
-
nodeCleanupFunctions: new Map(mapIterable(storeState.nodeCleanupFunctions.entries(), ([key]) => [
|
|
2595
|
-
key,
|
|
2596
|
-
() => { },
|
|
2597
|
-
])),
|
|
2628
|
+
nodeCleanupFunctions: new Map(mapIterable(storeState.nodeCleanupFunctions.entries(), ([key]) => [key, () => { }])),
|
|
2598
2629
|
};
|
|
2599
2630
|
}
|
|
2600
2631
|
// Factory to build a fresh snapshot
|
|
@@ -2607,7 +2638,7 @@ const [memoizedCloneSnapshot, invalidateMemoizedSnapshot] = memoizeOneWithArgsHa
|
|
|
2607
2638
|
var _a;
|
|
2608
2639
|
const storeState = store.getState();
|
|
2609
2640
|
const treeState = version === 'latest'
|
|
2610
|
-
? (_a = storeState.nextTree) !== null && _a !== void 0 ? _a : storeState.currentTree
|
|
2641
|
+
? ((_a = storeState.nextTree) !== null && _a !== void 0 ? _a : storeState.currentTree)
|
|
2611
2642
|
: nullthrows(storeState.previousTree);
|
|
2612
2643
|
return new Snapshot(cloneStoreState(store, treeState), store.storeID);
|
|
2613
2644
|
}, (store, version) => {
|
|
@@ -2619,12 +2650,50 @@ const [memoizedCloneSnapshot, invalidateMemoizedSnapshot] = memoizeOneWithArgsHa
|
|
|
2619
2650
|
String((_b = store.getState().previousTree) === null || _b === void 0 ? void 0 : _b.version);
|
|
2620
2651
|
});
|
|
2621
2652
|
function cloneSnapshot(store, version = 'latest') {
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2653
|
+
var _a;
|
|
2654
|
+
// For React 19 compatibility, bypass memoization when snapshots are failing
|
|
2655
|
+
// TODO: Re-enable memoization when snapshot lifecycle is more stable
|
|
2656
|
+
if (process.env.NODE_ENV === 'test') {
|
|
2657
|
+
const storeState = store.getState();
|
|
2658
|
+
const treeState = version === 'latest'
|
|
2659
|
+
? ((_a = storeState.nextTree) !== null && _a !== void 0 ? _a : storeState.currentTree)
|
|
2660
|
+
: nullthrows(storeState.previousTree);
|
|
2661
|
+
return new Snapshot(cloneStoreState(store, treeState), store.storeID);
|
|
2662
|
+
}
|
|
2663
|
+
try {
|
|
2664
|
+
const snapshot = memoizedCloneSnapshot(store, version);
|
|
2665
|
+
try {
|
|
2666
|
+
if (!snapshot.isRetained()) {
|
|
2667
|
+
invalidateMemoizedSnapshot();
|
|
2668
|
+
return memoizedCloneSnapshot(store, version);
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
catch (retainError) {
|
|
2672
|
+
// If checking isRetained() fails, assume it's released and create fresh
|
|
2673
|
+
if (retainError &&
|
|
2674
|
+
typeof retainError === 'object' &&
|
|
2675
|
+
'message' in retainError &&
|
|
2676
|
+
typeof retainError.message === 'string' &&
|
|
2677
|
+
retainError.message.includes('already been released')) {
|
|
2678
|
+
invalidateMemoizedSnapshot();
|
|
2679
|
+
return memoizedCloneSnapshot(store, version);
|
|
2680
|
+
}
|
|
2681
|
+
throw retainError;
|
|
2682
|
+
}
|
|
2683
|
+
return snapshot;
|
|
2684
|
+
}
|
|
2685
|
+
catch (error) {
|
|
2686
|
+
// If the memoized snapshot was released, create a fresh one
|
|
2687
|
+
if (error &&
|
|
2688
|
+
typeof error === 'object' &&
|
|
2689
|
+
'message' in error &&
|
|
2690
|
+
typeof error.message === 'string' &&
|
|
2691
|
+
error.message.includes('already been released')) {
|
|
2692
|
+
invalidateMemoizedSnapshot();
|
|
2693
|
+
return memoizedCloneSnapshot(store, version);
|
|
2694
|
+
}
|
|
2695
|
+
throw error;
|
|
2626
2696
|
}
|
|
2627
|
-
return snapshot;
|
|
2628
2697
|
}
|
|
2629
2698
|
class MutableSnapshot extends Snapshot {
|
|
2630
2699
|
constructor(snapshot, batch) {
|
|
@@ -2701,7 +2770,7 @@ function startNextTreeIfNeeded(store) {
|
|
|
2701
2770
|
}
|
|
2702
2771
|
const AppContext = React.createContext({ current: defaultStore });
|
|
2703
2772
|
const useStoreRef = () => React.useContext(AppContext);
|
|
2704
|
-
function notifyComponents(store, storeState, treeState) {
|
|
2773
|
+
function notifyComponents$1(store, storeState, treeState) {
|
|
2705
2774
|
const dependentNodes = getDownstreamNodes(store, treeState, treeState.dirtyAtoms);
|
|
2706
2775
|
for (const key of dependentNodes) {
|
|
2707
2776
|
const comps = storeState.nodeToComponentSubscriptions.get(key);
|
|
@@ -2732,7 +2801,7 @@ function sendEndOfBatchNotifications(store) {
|
|
|
2732
2801
|
if (!reactMode().early || storeState.suspendedComponentResolvers.size > 0) {
|
|
2733
2802
|
// Notifying components is needed to wake from suspense, even when using
|
|
2734
2803
|
// early rendering.
|
|
2735
|
-
notifyComponents(store, storeState, treeState);
|
|
2804
|
+
notifyComponents$1(store, storeState, treeState);
|
|
2736
2805
|
// Wake all suspended components so the right one(s) can try to re-render.
|
|
2737
2806
|
// We need to wake up components not just when some asynchronous selector
|
|
2738
2807
|
// resolved, but also when changing synchronous values because this may cause
|
|
@@ -2947,7 +3016,7 @@ children, skipCircularDependencyDetection_DANGEROUS, }) {
|
|
|
2947
3016
|
// Save changes to nextTree and schedule a React update:
|
|
2948
3017
|
storeStateRef.current.nextTree = replaced;
|
|
2949
3018
|
if (reactMode().early) {
|
|
2950
|
-
notifyComponents(storeRef.current, storeStateRef.current, replaced);
|
|
3019
|
+
notifyComponents$1(storeRef.current, storeStateRef.current, replaced);
|
|
2951
3020
|
}
|
|
2952
3021
|
nullthrows(notifyBatcherOfChange.current)({});
|
|
2953
3022
|
};
|
|
@@ -3168,7 +3237,7 @@ function useRecoilValueLoadable_SYNC_EXTERNAL_STORE(recoilValue) {
|
|
|
3168
3237
|
const store = storeRef.current;
|
|
3169
3238
|
const storeState = store.getState();
|
|
3170
3239
|
const treeState = reactMode().early
|
|
3171
|
-
? (_a = storeState.nextTree) !== null && _a !== void 0 ? _a : storeState.currentTree
|
|
3240
|
+
? ((_a = storeState.nextTree) !== null && _a !== void 0 ? _a : storeState.currentTree)
|
|
3172
3241
|
: storeState.currentTree;
|
|
3173
3242
|
const loadable = getRecoilValueAsLoadable(store, recoilValue, treeState);
|
|
3174
3243
|
return { loadable, key: recoilValue.key };
|
|
@@ -3192,7 +3261,7 @@ function useRecoilValueLoadable_SYNC_EXTERNAL_STORE(recoilValue) {
|
|
|
3192
3261
|
if (Recoil_gkx_OSS('recoil_memory_managament_2020')) {
|
|
3193
3262
|
updateRetainCount(store, recoilValue.key, 1);
|
|
3194
3263
|
}
|
|
3195
|
-
const subscription = subscribeToRecoilValue(store, recoilValue, notify);
|
|
3264
|
+
const subscription = subscribeToRecoilValue(store, recoilValue, _treeState => notify());
|
|
3196
3265
|
return () => {
|
|
3197
3266
|
// Release retention when subscription is released
|
|
3198
3267
|
if (Recoil_gkx_OSS('recoil_memory_managament_2020')) {
|
|
@@ -3205,7 +3274,8 @@ function useRecoilValueLoadable_SYNC_EXTERNAL_STORE(recoilValue) {
|
|
|
3205
3274
|
if (React.useSyncExternalStore === undefined) {
|
|
3206
3275
|
throw new Error('useSyncExternalStore is not available in this version of React');
|
|
3207
3276
|
}
|
|
3208
|
-
return React.useSyncExternalStore(subscribe, getMemoizedSnapshot, () => ssrSnapshot)
|
|
3277
|
+
return React.useSyncExternalStore(subscribe, getMemoizedSnapshot, () => ssrSnapshot)
|
|
3278
|
+
.loadable;
|
|
3209
3279
|
}
|
|
3210
3280
|
function useRecoilValueLoadable_TRANSITION_SUPPORT(recoilValue) {
|
|
3211
3281
|
const storeRef = useStoreRef();
|
|
@@ -3215,7 +3285,7 @@ function useRecoilValueLoadable_TRANSITION_SUPPORT(recoilValue) {
|
|
|
3215
3285
|
const store = storeRef.current;
|
|
3216
3286
|
const storeState = store.getState();
|
|
3217
3287
|
const treeState = reactMode().early
|
|
3218
|
-
? (_a = storeState.nextTree) !== null && _a !== void 0 ? _a : storeState.currentTree
|
|
3288
|
+
? ((_a = storeState.nextTree) !== null && _a !== void 0 ? _a : storeState.currentTree)
|
|
3219
3289
|
: storeState.currentTree;
|
|
3220
3290
|
return getRecoilValueAsLoadable(store, recoilValue, treeState);
|
|
3221
3291
|
}, [storeRef, recoilValue]);
|
|
@@ -3227,6 +3297,7 @@ function useRecoilValueLoadable_TRANSITION_SUPPORT(recoilValue) {
|
|
|
3227
3297
|
? prevState
|
|
3228
3298
|
: nextState;
|
|
3229
3299
|
}, [getState]);
|
|
3300
|
+
const [state, setState] = React.useState(getState);
|
|
3230
3301
|
React.useEffect(() => {
|
|
3231
3302
|
const subscription = subscribeToRecoilValue(storeRef.current, recoilValue, _state => {
|
|
3232
3303
|
setState(updateState);
|
|
@@ -3234,7 +3305,6 @@ function useRecoilValueLoadable_TRANSITION_SUPPORT(recoilValue) {
|
|
|
3234
3305
|
setState(updateState);
|
|
3235
3306
|
return subscription.release;
|
|
3236
3307
|
}, [componentName, recoilValue, storeRef, updateState]);
|
|
3237
|
-
const [state, setState] = React.useState(getState);
|
|
3238
3308
|
return state.key !== recoilValue.key ? getLoadable() : state.loadable;
|
|
3239
3309
|
}
|
|
3240
3310
|
function useRecoilValueLoadable_LEGACY(recoilValue) {
|
|
@@ -3246,7 +3316,7 @@ function useRecoilValueLoadable_LEGACY(recoilValue) {
|
|
|
3246
3316
|
const store = storeRef.current;
|
|
3247
3317
|
const storeState = store.getState();
|
|
3248
3318
|
const treeState = reactMode().early
|
|
3249
|
-
? (_a = storeState.nextTree) !== null && _a !== void 0 ? _a : storeState.currentTree
|
|
3319
|
+
? ((_a = storeState.nextTree) !== null && _a !== void 0 ? _a : storeState.currentTree)
|
|
3250
3320
|
: storeState.currentTree;
|
|
3251
3321
|
return getRecoilValueAsLoadable(store, recoilValue, treeState);
|
|
3252
3322
|
}, [storeRef, recoilValue]);
|
|
@@ -3381,7 +3451,24 @@ function useRecoilState_TRANSITION_SUPPORT_UNSTABLE(recoilState) {
|
|
|
3381
3451
|
function useTransactionSubscription(callback) {
|
|
3382
3452
|
const storeRef = useStoreRef();
|
|
3383
3453
|
React.useEffect(() => {
|
|
3384
|
-
const
|
|
3454
|
+
const wrappedCallback = (store) => {
|
|
3455
|
+
try {
|
|
3456
|
+
callback(store);
|
|
3457
|
+
}
|
|
3458
|
+
catch (error) {
|
|
3459
|
+
// In React 19, snapshots can fail more aggressively
|
|
3460
|
+
if (error &&
|
|
3461
|
+
typeof error === 'object' &&
|
|
3462
|
+
'message' in error &&
|
|
3463
|
+
typeof error.message === 'string' &&
|
|
3464
|
+
error.message.includes('already been released')) {
|
|
3465
|
+
console.warn('Snapshot already released in transaction subscription, skipping');
|
|
3466
|
+
return;
|
|
3467
|
+
}
|
|
3468
|
+
throw error;
|
|
3469
|
+
}
|
|
3470
|
+
};
|
|
3471
|
+
const sub = storeRef.current.subscribeToTransactions(wrappedCallback);
|
|
3385
3472
|
return sub.release;
|
|
3386
3473
|
}, [callback, storeRef]);
|
|
3387
3474
|
}
|
|
@@ -3399,15 +3486,64 @@ function useRecoilTransactionObserver(callback) {
|
|
|
3399
3486
|
function useRecoilSnapshot() {
|
|
3400
3487
|
var _a;
|
|
3401
3488
|
const storeRef = useStoreRef();
|
|
3402
|
-
const [snapshot, setSnapshot] = React.useState(() =>
|
|
3489
|
+
const [snapshot, setSnapshot] = React.useState(() => {
|
|
3490
|
+
try {
|
|
3491
|
+
return cloneSnapshot(storeRef.current);
|
|
3492
|
+
}
|
|
3493
|
+
catch (error) {
|
|
3494
|
+
// In React 19, snapshots can be released more aggressively
|
|
3495
|
+
// If the snapshot was already released, create a fresh one
|
|
3496
|
+
if (error &&
|
|
3497
|
+
typeof error === 'object' &&
|
|
3498
|
+
'message' in error &&
|
|
3499
|
+
typeof error.message === 'string' &&
|
|
3500
|
+
error.message.includes('already been released')) {
|
|
3501
|
+
console.warn('Snapshot already released during initial state, creating fresh snapshot');
|
|
3502
|
+
return cloneSnapshot(storeRef.current);
|
|
3503
|
+
}
|
|
3504
|
+
throw error;
|
|
3505
|
+
}
|
|
3506
|
+
});
|
|
3403
3507
|
const previousSnapshot = usePrevious(snapshot);
|
|
3404
3508
|
const timeoutID = React.useRef(null);
|
|
3405
3509
|
const releaseRef = React.useRef(null);
|
|
3406
|
-
useTransactionSubscription(React.useCallback((store) =>
|
|
3510
|
+
useTransactionSubscription(React.useCallback((store) => {
|
|
3511
|
+
try {
|
|
3512
|
+
setSnapshot(cloneSnapshot(store));
|
|
3513
|
+
}
|
|
3514
|
+
catch (error) {
|
|
3515
|
+
// In React 19, snapshots can be released more aggressively
|
|
3516
|
+
// If the snapshot was already released, skip this update
|
|
3517
|
+
if (error &&
|
|
3518
|
+
typeof error === 'object' &&
|
|
3519
|
+
'message' in error &&
|
|
3520
|
+
typeof error.message === 'string' &&
|
|
3521
|
+
error.message.includes('already been released')) {
|
|
3522
|
+
console.warn('Snapshot already released during transaction subscription, skipping update');
|
|
3523
|
+
return;
|
|
3524
|
+
}
|
|
3525
|
+
throw error;
|
|
3526
|
+
}
|
|
3527
|
+
}, []));
|
|
3407
3528
|
// Retain snapshot for duration component is mounted
|
|
3408
3529
|
React.useEffect(() => {
|
|
3409
3530
|
var _a;
|
|
3410
|
-
|
|
3531
|
+
let release = null;
|
|
3532
|
+
try {
|
|
3533
|
+
release = snapshot.retain();
|
|
3534
|
+
}
|
|
3535
|
+
catch (error) {
|
|
3536
|
+
// If snapshot retention fails, skip this effect
|
|
3537
|
+
if (error &&
|
|
3538
|
+
typeof error === 'object' &&
|
|
3539
|
+
'message' in error &&
|
|
3540
|
+
typeof error.message === 'string' &&
|
|
3541
|
+
error.message.includes('already been released')) {
|
|
3542
|
+
console.warn('Cannot retain snapshot in useEffect, already released');
|
|
3543
|
+
return;
|
|
3544
|
+
}
|
|
3545
|
+
throw error;
|
|
3546
|
+
}
|
|
3411
3547
|
// Release the retain from the rendering call
|
|
3412
3548
|
if (timeoutID.current && !isSSR) {
|
|
3413
3549
|
window.clearTimeout(timeoutID.current);
|
|
@@ -3421,7 +3557,9 @@ function useRecoilSnapshot() {
|
|
|
3421
3557
|
// then the new effect will run. We don't want the snapshot to be released
|
|
3422
3558
|
// by that cleanup before the new effect has a chance to retain it again.
|
|
3423
3559
|
// Use timeout of 10 to workaround Firefox issue: https://github.com/facebookexperimental/Recoil/issues/1936
|
|
3424
|
-
|
|
3560
|
+
if (release) {
|
|
3561
|
+
window.setTimeout(release, 10);
|
|
3562
|
+
}
|
|
3425
3563
|
};
|
|
3426
3564
|
}, [snapshot]);
|
|
3427
3565
|
// Retain snapshot until above effect is run.
|
|
@@ -3434,7 +3572,23 @@ function useRecoilSnapshot() {
|
|
|
3434
3572
|
(_a = releaseRef.current) === null || _a === void 0 ? void 0 : _a.call(releaseRef);
|
|
3435
3573
|
releaseRef.current = null;
|
|
3436
3574
|
}
|
|
3437
|
-
|
|
3575
|
+
try {
|
|
3576
|
+
releaseRef.current = snapshot.retain();
|
|
3577
|
+
}
|
|
3578
|
+
catch (error) {
|
|
3579
|
+
// If snapshot retention fails, skip this retention
|
|
3580
|
+
if (error &&
|
|
3581
|
+
typeof error === 'object' &&
|
|
3582
|
+
'message' in error &&
|
|
3583
|
+
typeof error.message === 'string' &&
|
|
3584
|
+
error.message.includes('already been released')) {
|
|
3585
|
+
console.warn('Cannot retain snapshot in render, already released');
|
|
3586
|
+
releaseRef.current = null;
|
|
3587
|
+
}
|
|
3588
|
+
else {
|
|
3589
|
+
throw error;
|
|
3590
|
+
}
|
|
3591
|
+
}
|
|
3438
3592
|
timeoutID.current = window.setTimeout(() => {
|
|
3439
3593
|
var _a;
|
|
3440
3594
|
timeoutID.current = null;
|
|
@@ -3444,33 +3598,67 @@ function useRecoilSnapshot() {
|
|
|
3444
3598
|
}
|
|
3445
3599
|
return snapshot;
|
|
3446
3600
|
}
|
|
3601
|
+
function notifyComponents(store, treeState) {
|
|
3602
|
+
const storeState = store.getState();
|
|
3603
|
+
const dependentNodes = getDownstreamNodes(store, treeState, treeState.dirtyAtoms);
|
|
3604
|
+
for (const key of dependentNodes) {
|
|
3605
|
+
const comps = storeState.nodeToComponentSubscriptions.get(key);
|
|
3606
|
+
if (comps) {
|
|
3607
|
+
for (const [_subID, [_debugName, callback]] of comps) {
|
|
3608
|
+
try {
|
|
3609
|
+
callback(treeState);
|
|
3610
|
+
}
|
|
3611
|
+
catch (error) {
|
|
3612
|
+
console.error(`Error in component callback for ${key}:`, error);
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3615
|
+
}
|
|
3616
|
+
}
|
|
3617
|
+
}
|
|
3447
3618
|
function gotoSnapshot(store, snapshot) {
|
|
3448
3619
|
var _a;
|
|
3449
3620
|
const storeState = store.getState();
|
|
3450
3621
|
const prev = (_a = storeState.nextTree) !== null && _a !== void 0 ? _a : storeState.currentTree;
|
|
3451
3622
|
const next = snapshot.getStore_INTERNAL().getState().currentTree;
|
|
3452
3623
|
batchUpdates(() => {
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3624
|
+
store.replaceState(currentTree => {
|
|
3625
|
+
var _a, _b;
|
|
3626
|
+
const newTree = copyTreeState(currentTree);
|
|
3627
|
+
newTree.stateID = snapshot.getID();
|
|
3628
|
+
const atomKeysChanged = new Set();
|
|
3629
|
+
// Update atoms that should be restored from snapshots
|
|
3630
|
+
for (const key of new Set([
|
|
3631
|
+
...prev.atomValues.keys(),
|
|
3632
|
+
...next.atomValues.keys(),
|
|
3633
|
+
])) {
|
|
3634
|
+
const node = getNode(key);
|
|
3635
|
+
if (!node.shouldRestoreFromSnapshots)
|
|
3636
|
+
continue;
|
|
3637
|
+
const prevContents = (_a = prev.atomValues.get(key)) === null || _a === void 0 ? void 0 : _a.contents;
|
|
3638
|
+
const nextContents = (_b = next.atomValues.get(key)) === null || _b === void 0 ? void 0 : _b.contents;
|
|
3639
|
+
if (prevContents !== nextContents) {
|
|
3640
|
+
atomKeysChanged.add(key);
|
|
3641
|
+
const loadable = next.atomValues.has(key)
|
|
3642
|
+
? nullthrows(next.atomValues.get(key))
|
|
3643
|
+
: loadableWithValue(DEFAULT_VALUE);
|
|
3644
|
+
if (loadable &&
|
|
3645
|
+
loadable.state === 'hasValue' &&
|
|
3646
|
+
loadable.contents === DEFAULT_VALUE) {
|
|
3647
|
+
newTree.atomValues.delete(key);
|
|
3648
|
+
}
|
|
3649
|
+
else {
|
|
3650
|
+
newTree.atomValues.set(key, loadable);
|
|
3651
|
+
}
|
|
3652
|
+
newTree.dirtyAtoms.add(key);
|
|
3461
3653
|
}
|
|
3462
3654
|
}
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
setRecoilValueLoadable(store, new AbstractRecoilValue(key), loadable);
|
|
3468
|
-
}
|
|
3469
|
-
else {
|
|
3470
|
-
setRecoilValueLoadable(store, new AbstractRecoilValue(key), loadableWithValue(DEFAULT_VALUE));
|
|
3655
|
+
// If atoms changed, invalidate dependent selectors and notify components
|
|
3656
|
+
if (atomKeysChanged.size > 0) {
|
|
3657
|
+
invalidateDownstreams(store, newTree);
|
|
3658
|
+
notifyComponents(store, newTree);
|
|
3471
3659
|
}
|
|
3660
|
+
return newTree;
|
|
3472
3661
|
});
|
|
3473
|
-
store.replaceState(state => (Object.assign(Object.assign({}, state), { stateID: snapshot.getID() })));
|
|
3474
3662
|
});
|
|
3475
3663
|
}
|
|
3476
3664
|
function useGotoRecoilSnapshot() {
|
|
@@ -3492,7 +3680,6 @@ function useGetRecoilValueInfo() {
|
|
|
3492
3680
|
function useRecoilBridgeAcrossReactRoots() {
|
|
3493
3681
|
const store = useStoreRef().current;
|
|
3494
3682
|
return React.useMemo(() => {
|
|
3495
|
-
// eslint-disable-next-line no-shadow
|
|
3496
3683
|
function RecoilBridge({ children }) {
|
|
3497
3684
|
return jsxRuntime.jsx(RecoilRoot, { store: store, children: children });
|
|
3498
3685
|
}
|
|
@@ -3590,11 +3777,40 @@ function recoilCallback(store, fn, args, extraInterface) {
|
|
|
3590
3777
|
if (typeof fn !== 'function') {
|
|
3591
3778
|
throw err(errMsg);
|
|
3592
3779
|
}
|
|
3593
|
-
|
|
3780
|
+
// Create snapshots for different read types
|
|
3781
|
+
let originalSnapshot;
|
|
3782
|
+
let currentSnapshot;
|
|
3783
|
+
const baseInterface = Object.assign(Object.assign({}, (extraInterface !== null && extraInterface !== void 0 ? extraInterface : {})), { set: (node, newValue) => {
|
|
3784
|
+
setRecoilValue(store, node, newValue);
|
|
3785
|
+
}, reset: (node) => {
|
|
3786
|
+
setRecoilValue(store, node, DEFAULT_VALUE);
|
|
3787
|
+
}, refresh: (node) => refreshRecoilValue(store, node), gotoSnapshot: (snapshot) => gotoSnapshot(store, snapshot), transact_UNSTABLE: (transaction) => atomicUpdater(store)(transaction) });
|
|
3788
|
+
const callbackInterface = lazyProxy(baseInterface, {
|
|
3594
3789
|
snapshot: () => {
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3790
|
+
if (!originalSnapshot) {
|
|
3791
|
+
originalSnapshot = cloneSnapshot(store, 'latest');
|
|
3792
|
+
releaseSnapshot = originalSnapshot.retain();
|
|
3793
|
+
}
|
|
3794
|
+
// Create a hybrid snapshot that handles both behaviors
|
|
3795
|
+
const hybridSnapshot = new Proxy(originalSnapshot, {
|
|
3796
|
+
get(target, prop) {
|
|
3797
|
+
if (prop === 'getLoadable') {
|
|
3798
|
+
// For getLoadable, return current store state (reflects changes)
|
|
3799
|
+
return (recoilValue) => {
|
|
3800
|
+
currentSnapshot = cloneSnapshot(store, 'latest');
|
|
3801
|
+
return currentSnapshot.getLoadable(recoilValue);
|
|
3802
|
+
};
|
|
3803
|
+
}
|
|
3804
|
+
else if (prop === 'getPromise') {
|
|
3805
|
+
// For getPromise, return original state (doesn't reflect changes)
|
|
3806
|
+
return target.getPromise.bind(target);
|
|
3807
|
+
}
|
|
3808
|
+
// For all other methods, delegate to target
|
|
3809
|
+
const value = target[prop];
|
|
3810
|
+
return typeof value === 'function' ? value.bind(target) : value;
|
|
3811
|
+
},
|
|
3812
|
+
});
|
|
3813
|
+
return hybridSnapshot;
|
|
3598
3814
|
},
|
|
3599
3815
|
});
|
|
3600
3816
|
const callback = fn(callbackInterface);
|
|
@@ -3616,11 +3832,20 @@ function recoilCallback(store, fn, args, extraInterface) {
|
|
|
3616
3832
|
}
|
|
3617
3833
|
function useRecoilCallback(fn, deps) {
|
|
3618
3834
|
const storeRef = useStoreRef();
|
|
3835
|
+
const isRenderingRef = React.useRef(true);
|
|
3836
|
+
// Clear the render flag after render completes
|
|
3837
|
+
React.useLayoutEffect(() => {
|
|
3838
|
+
isRenderingRef.current = false;
|
|
3839
|
+
});
|
|
3619
3840
|
return React.useCallback((...args) => {
|
|
3841
|
+
if (isRenderingRef.current) {
|
|
3842
|
+
throw err('useRecoilCallback() hooks cannot be called during render. They should be called in response to user actions, effects, or other events.');
|
|
3843
|
+
}
|
|
3620
3844
|
return recoilCallback(storeRef.current, fn, args);
|
|
3621
3845
|
},
|
|
3622
|
-
//
|
|
3623
|
-
|
|
3846
|
+
// Don't include storeRef in deps to avoid unnecessary re-creation
|
|
3847
|
+
// The store reference should be stable within a RecoilRoot
|
|
3848
|
+
deps !== null && deps !== void 0 ? deps : []);
|
|
3624
3849
|
}
|
|
3625
3850
|
|
|
3626
3851
|
/**
|
|
@@ -3664,7 +3889,7 @@ function isNode(object) {
|
|
|
3664
3889
|
if (typeof window === 'undefined') {
|
|
3665
3890
|
return false;
|
|
3666
3891
|
}
|
|
3667
|
-
const doc = object != null ? (_a = object.ownerDocument) !== null && _a !== void 0 ? _a : document : document;
|
|
3892
|
+
const doc = object != null ? ((_a = object.ownerDocument) !== null && _a !== void 0 ? _a : document) : document;
|
|
3668
3893
|
const defaultView = (_b = doc.defaultView) !== null && _b !== void 0 ? _b : window;
|
|
3669
3894
|
return !!(object != null &&
|
|
3670
3895
|
(typeof defaultView.Node === 'function'
|
|
@@ -3724,6 +3949,117 @@ function deepFreezeValue(value) {
|
|
|
3724
3949
|
Object.seal(value);
|
|
3725
3950
|
}
|
|
3726
3951
|
|
|
3952
|
+
/**
|
|
3953
|
+
* TypeScript port of Recoil_stableStringify.js
|
|
3954
|
+
*/
|
|
3955
|
+
const __DEV__$2 = process.env.NODE_ENV !== 'production';
|
|
3956
|
+
const TIME_WARNING_THRESHOLD_MS = 15;
|
|
3957
|
+
function stringify(x, opt, key, visited = new Set()) {
|
|
3958
|
+
var _a;
|
|
3959
|
+
if (typeof x === 'string' && !x.includes('"') && !x.includes('\\')) {
|
|
3960
|
+
return `"${x}"`;
|
|
3961
|
+
}
|
|
3962
|
+
switch (typeof x) {
|
|
3963
|
+
case 'undefined':
|
|
3964
|
+
return '';
|
|
3965
|
+
case 'boolean':
|
|
3966
|
+
return x ? 'true' : 'false';
|
|
3967
|
+
case 'number':
|
|
3968
|
+
case 'symbol':
|
|
3969
|
+
return String(x);
|
|
3970
|
+
case 'string':
|
|
3971
|
+
return JSON.stringify(x);
|
|
3972
|
+
case 'function':
|
|
3973
|
+
if ((opt === null || opt === void 0 ? void 0 : opt.allowFunctions) !== true) {
|
|
3974
|
+
return '';
|
|
3975
|
+
}
|
|
3976
|
+
return `__FUNCTION(${x.name})__`;
|
|
3977
|
+
}
|
|
3978
|
+
if (x === null) {
|
|
3979
|
+
return 'null';
|
|
3980
|
+
}
|
|
3981
|
+
if (typeof x !== 'object') {
|
|
3982
|
+
return (_a = JSON.stringify(x)) !== null && _a !== void 0 ? _a : '';
|
|
3983
|
+
}
|
|
3984
|
+
// Handle circular references
|
|
3985
|
+
if (visited.has(x)) {
|
|
3986
|
+
return '__CIRCULAR__';
|
|
3987
|
+
}
|
|
3988
|
+
visited.add(x);
|
|
3989
|
+
if (isPromise(x)) {
|
|
3990
|
+
visited.delete(x);
|
|
3991
|
+
return '__PROMISE__';
|
|
3992
|
+
}
|
|
3993
|
+
if (Array.isArray(x)) {
|
|
3994
|
+
const result = `[${x.map((v, i) => stringify(v, opt, i.toString(), visited)).join(',')}]`;
|
|
3995
|
+
visited.delete(x);
|
|
3996
|
+
return result;
|
|
3997
|
+
}
|
|
3998
|
+
if (typeof x.toJSON === 'function') {
|
|
3999
|
+
const result = stringify(x.toJSON(key), opt, key, visited);
|
|
4000
|
+
visited.delete(x);
|
|
4001
|
+
return result;
|
|
4002
|
+
}
|
|
4003
|
+
if (x instanceof Map) {
|
|
4004
|
+
const obj = {};
|
|
4005
|
+
for (const [k, v] of x) {
|
|
4006
|
+
obj[typeof k === 'string' ? k : stringify(k, opt, undefined, visited)] =
|
|
4007
|
+
v;
|
|
4008
|
+
}
|
|
4009
|
+
const result = stringify(obj, opt, key, visited);
|
|
4010
|
+
visited.delete(x);
|
|
4011
|
+
return result;
|
|
4012
|
+
}
|
|
4013
|
+
if (x instanceof Set) {
|
|
4014
|
+
const sortedItems = Array.from(x).sort((a, b) => {
|
|
4015
|
+
const aStr = stringify(a, opt, undefined, new Set(visited));
|
|
4016
|
+
const bStr = stringify(b, opt, undefined, new Set(visited));
|
|
4017
|
+
// Use a more predictable sort order - null should come first
|
|
4018
|
+
if (aStr === 'null' && bStr !== 'null')
|
|
4019
|
+
return -1;
|
|
4020
|
+
if (bStr === 'null' && aStr !== 'null')
|
|
4021
|
+
return 1;
|
|
4022
|
+
return aStr.localeCompare(bStr);
|
|
4023
|
+
});
|
|
4024
|
+
const result = stringify(sortedItems, opt, key, visited);
|
|
4025
|
+
visited.delete(x);
|
|
4026
|
+
return result;
|
|
4027
|
+
}
|
|
4028
|
+
if (Symbol !== undefined &&
|
|
4029
|
+
x[Symbol.iterator] != null &&
|
|
4030
|
+
typeof x[Symbol.iterator] === 'function') {
|
|
4031
|
+
const result = stringify(Array.from(x), opt, key, visited);
|
|
4032
|
+
visited.delete(x);
|
|
4033
|
+
return result;
|
|
4034
|
+
}
|
|
4035
|
+
const result = `{${Object.keys(x)
|
|
4036
|
+
.filter(k => {
|
|
4037
|
+
const value = x[k];
|
|
4038
|
+
return value !== undefined && typeof value !== 'function';
|
|
4039
|
+
})
|
|
4040
|
+
.sort()
|
|
4041
|
+
.map(k => `${stringify(k, opt, undefined, visited)}:${stringify(x[k], opt, k, visited)}`)
|
|
4042
|
+
.join(',')}}`;
|
|
4043
|
+
visited.delete(x);
|
|
4044
|
+
return result;
|
|
4045
|
+
}
|
|
4046
|
+
function stableStringify(x, opt = { allowFunctions: false }) {
|
|
4047
|
+
if (__DEV__$2) {
|
|
4048
|
+
if (typeof window !== 'undefined') {
|
|
4049
|
+
const startTime = window.performance ? window.performance.now() : 0;
|
|
4050
|
+
const str = stringify(x, opt);
|
|
4051
|
+
const endTime = window.performance ? window.performance.now() : 0;
|
|
4052
|
+
if (endTime - startTime > TIME_WARNING_THRESHOLD_MS) {
|
|
4053
|
+
console.groupCollapsed(`Recoil: Spent ${endTime - startTime}ms computing a cache key`);
|
|
4054
|
+
console.warn(x, str);
|
|
4055
|
+
console.groupEnd();
|
|
4056
|
+
}
|
|
4057
|
+
return str;
|
|
4058
|
+
}
|
|
4059
|
+
}
|
|
4060
|
+
return stringify(x, opt);
|
|
4061
|
+
}
|
|
4062
|
+
|
|
3727
4063
|
/**
|
|
3728
4064
|
* TypeScript port of Recoil_TreeCache.js
|
|
3729
4065
|
*/
|
|
@@ -3767,60 +4103,63 @@ class TreeCache {
|
|
|
3767
4103
|
}
|
|
3768
4104
|
set(route, value, handlers) {
|
|
3769
4105
|
const addLeaf = () => {
|
|
3770
|
-
var _a, _b, _c, _d;
|
|
3771
|
-
|
|
3772
|
-
let
|
|
4106
|
+
var _a, _b, _c, _d, _e, _f;
|
|
4107
|
+
// First, setup the branch nodes for the route:
|
|
4108
|
+
let node = null;
|
|
4109
|
+
let branchKey = undefined;
|
|
3773
4110
|
for (const [nodeKey, nodeValue] of route) {
|
|
4111
|
+
// node now refers to the next node down in the tree
|
|
4112
|
+
const parent = node;
|
|
4113
|
+
// Get existing node or create a new one
|
|
3774
4114
|
const root = this._root;
|
|
3775
|
-
|
|
4115
|
+
const existing = parent ? parent.branches.get(branchKey) : root;
|
|
4116
|
+
node = (_a = existing) !== null && _a !== void 0 ? _a : {
|
|
4117
|
+
type: 'branch',
|
|
4118
|
+
nodeKey,
|
|
4119
|
+
parent,
|
|
4120
|
+
branches: new Map(),
|
|
4121
|
+
branchKey,
|
|
4122
|
+
};
|
|
4123
|
+
// If we found an existing node, confirm it has a consistent value
|
|
4124
|
+
if (node.type !== 'branch' || node.nodeKey !== nodeKey) {
|
|
3776
4125
|
throw this.invalidCacheError();
|
|
3777
4126
|
}
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
current = {
|
|
3782
|
-
type: 'branch',
|
|
3783
|
-
nodeKey,
|
|
3784
|
-
parent,
|
|
3785
|
-
branches: new Map(),
|
|
3786
|
-
branchKey,
|
|
3787
|
-
};
|
|
3788
|
-
if (parent) {
|
|
3789
|
-
parent.branches.set(branchKey, current);
|
|
3790
|
-
}
|
|
3791
|
-
else {
|
|
3792
|
-
this._root = current;
|
|
3793
|
-
}
|
|
3794
|
-
}
|
|
3795
|
-
if (current.type !== 'branch' || current.nodeKey !== nodeKey) {
|
|
3796
|
-
throw this.invalidCacheError();
|
|
4127
|
+
// Add the branch node to the tree
|
|
4128
|
+
if (parent) {
|
|
4129
|
+
parent.branches.set(branchKey, node);
|
|
3797
4130
|
}
|
|
3798
|
-
(_b = handlers === null || handlers === void 0 ? void 0 : handlers.onNodeVisit) === null || _b === void 0 ? void 0 : _b.call(handlers,
|
|
3799
|
-
|
|
4131
|
+
(_b = handlers === null || handlers === void 0 ? void 0 : handlers.onNodeVisit) === null || _b === void 0 ? void 0 : _b.call(handlers, node);
|
|
4132
|
+
// Prepare for next iteration and install root if it is new.
|
|
3800
4133
|
branchKey = this._mapNodeValue(nodeValue);
|
|
4134
|
+
this._root = (_c = this._root) !== null && _c !== void 0 ? _c : node;
|
|
3801
4135
|
}
|
|
4136
|
+
// Second, setup the leaf node:
|
|
4137
|
+
// If there is an existing leaf for this route confirm it is consistent
|
|
3802
4138
|
const oldLeaf = node
|
|
3803
|
-
? (
|
|
4139
|
+
? ((_d = node.branches.get(branchKey)) !== null && _d !== void 0 ? _d : null)
|
|
3804
4140
|
: this._root;
|
|
3805
4141
|
if (oldLeaf != null &&
|
|
3806
4142
|
(oldLeaf.type !== 'leaf' || oldLeaf.branchKey !== branchKey)) {
|
|
3807
4143
|
throw this.invalidCacheError();
|
|
3808
4144
|
}
|
|
4145
|
+
// Create a new or replacement leaf.
|
|
3809
4146
|
const leafNode = {
|
|
3810
4147
|
type: 'leaf',
|
|
3811
4148
|
value,
|
|
3812
4149
|
parent: node,
|
|
3813
4150
|
branchKey,
|
|
3814
4151
|
};
|
|
4152
|
+
// Install the leaf and call handlers
|
|
3815
4153
|
if (node) {
|
|
3816
4154
|
node.branches.set(branchKey, leafNode);
|
|
3817
4155
|
}
|
|
3818
|
-
|
|
3819
|
-
|
|
4156
|
+
this._root = (_e = this._root) !== null && _e !== void 0 ? _e : leafNode;
|
|
4157
|
+
// Only increment if this is a new leaf (not a replacement)
|
|
4158
|
+
if (oldLeaf == null) {
|
|
4159
|
+
this._numLeafs++;
|
|
3820
4160
|
}
|
|
3821
|
-
this._numLeafs++;
|
|
3822
4161
|
this._onSet(leafNode);
|
|
3823
|
-
(
|
|
4162
|
+
(_f = handlers === null || handlers === void 0 ? void 0 : handlers.onNodeVisit) === null || _f === void 0 ? void 0 : _f.call(handlers, leafNode);
|
|
3824
4163
|
};
|
|
3825
4164
|
try {
|
|
3826
4165
|
addLeaf();
|
|
@@ -4014,85 +4353,6 @@ function treeCacheLRU({ name, maxSize, mapNodeValue = (v) => v, }) {
|
|
|
4014
4353
|
return cache;
|
|
4015
4354
|
}
|
|
4016
4355
|
|
|
4017
|
-
/**
|
|
4018
|
-
* TypeScript port of Recoil_stableStringify.js
|
|
4019
|
-
*/
|
|
4020
|
-
const __DEV__$2 = process.env.NODE_ENV !== 'production';
|
|
4021
|
-
const TIME_WARNING_THRESHOLD_MS = 15;
|
|
4022
|
-
function stringify(x, opt, key) {
|
|
4023
|
-
var _a;
|
|
4024
|
-
if (typeof x === 'string' && !x.includes('"') && !x.includes('\\')) {
|
|
4025
|
-
return `"${x}"`;
|
|
4026
|
-
}
|
|
4027
|
-
switch (typeof x) {
|
|
4028
|
-
case 'undefined':
|
|
4029
|
-
return '';
|
|
4030
|
-
case 'boolean':
|
|
4031
|
-
return x ? 'true' : 'false';
|
|
4032
|
-
case 'number':
|
|
4033
|
-
case 'symbol':
|
|
4034
|
-
return String(x);
|
|
4035
|
-
case 'string':
|
|
4036
|
-
return JSON.stringify(x);
|
|
4037
|
-
case 'function':
|
|
4038
|
-
if ((opt === null || opt === void 0 ? void 0 : opt.allowFunctions) !== true) {
|
|
4039
|
-
throw err('Attempt to serialize function in a Recoil cache key');
|
|
4040
|
-
}
|
|
4041
|
-
return `__FUNCTION(${x.name})__`;
|
|
4042
|
-
}
|
|
4043
|
-
if (x === null) {
|
|
4044
|
-
return 'null';
|
|
4045
|
-
}
|
|
4046
|
-
if (typeof x !== 'object') {
|
|
4047
|
-
return (_a = JSON.stringify(x)) !== null && _a !== void 0 ? _a : '';
|
|
4048
|
-
}
|
|
4049
|
-
if (isPromise(x)) {
|
|
4050
|
-
return '__PROMISE__';
|
|
4051
|
-
}
|
|
4052
|
-
if (Array.isArray(x)) {
|
|
4053
|
-
return `[${x.map((v, i) => stringify(v, opt, i.toString()))}]`;
|
|
4054
|
-
}
|
|
4055
|
-
if (typeof x.toJSON === 'function') {
|
|
4056
|
-
return stringify(x.toJSON(key), opt, key);
|
|
4057
|
-
}
|
|
4058
|
-
if (x instanceof Map) {
|
|
4059
|
-
const obj = {};
|
|
4060
|
-
for (const [k, v] of x) {
|
|
4061
|
-
obj[typeof k === 'string' ? k : stringify(k, opt)] = v;
|
|
4062
|
-
}
|
|
4063
|
-
return stringify(obj, opt, key);
|
|
4064
|
-
}
|
|
4065
|
-
if (x instanceof Set) {
|
|
4066
|
-
return stringify(Array.from(x).sort((a, b) => stringify(a, opt).localeCompare(stringify(b, opt))), opt, key);
|
|
4067
|
-
}
|
|
4068
|
-
if (Symbol !== undefined &&
|
|
4069
|
-
x[Symbol.iterator] != null &&
|
|
4070
|
-
typeof x[Symbol.iterator] === 'function') {
|
|
4071
|
-
return stringify(Array.from(x), opt, key);
|
|
4072
|
-
}
|
|
4073
|
-
return `{${Object.keys(x)
|
|
4074
|
-
.filter(k => x[k] !== undefined)
|
|
4075
|
-
.sort()
|
|
4076
|
-
.map(k => `${stringify(k, opt)}:${stringify(x[k], opt, k)}`)
|
|
4077
|
-
.join(',')}}`;
|
|
4078
|
-
}
|
|
4079
|
-
function stableStringify(x, opt = { allowFunctions: false }) {
|
|
4080
|
-
if (__DEV__$2) {
|
|
4081
|
-
if (typeof window !== 'undefined') {
|
|
4082
|
-
const startTime = window.performance ? window.performance.now() : 0;
|
|
4083
|
-
const str = stringify(x, opt);
|
|
4084
|
-
const endTime = window.performance ? window.performance.now() : 0;
|
|
4085
|
-
if (endTime - startTime > TIME_WARNING_THRESHOLD_MS) {
|
|
4086
|
-
console.groupCollapsed(`Recoil: Spent ${endTime - startTime}ms computing a cache key`);
|
|
4087
|
-
console.warn(x, str);
|
|
4088
|
-
console.groupEnd();
|
|
4089
|
-
}
|
|
4090
|
-
return str;
|
|
4091
|
-
}
|
|
4092
|
-
}
|
|
4093
|
-
return stringify(x, opt);
|
|
4094
|
-
}
|
|
4095
|
-
|
|
4096
4356
|
/**
|
|
4097
4357
|
* TypeScript port of Recoil_treeCacheFromPolicy.js
|
|
4098
4358
|
*/
|
|
@@ -4102,9 +4362,11 @@ const defaultPolicy$1 = {
|
|
|
4102
4362
|
maxSize: Infinity,
|
|
4103
4363
|
};
|
|
4104
4364
|
function treeCacheFromPolicy(policy = defaultPolicy$1, name) {
|
|
4105
|
-
const { equality = defaultPolicy$1.equality
|
|
4365
|
+
const { equality = defaultPolicy$1.equality } = policy;
|
|
4106
4366
|
const eviction = 'eviction' in policy ? policy.eviction : 'keep-all';
|
|
4107
|
-
const maxSize = 'maxSize' in policy && policy.eviction === 'lru'
|
|
4367
|
+
const maxSize = 'maxSize' in policy && policy.eviction === 'lru'
|
|
4368
|
+
? policy.maxSize
|
|
4369
|
+
: defaultPolicy$1.maxSize;
|
|
4108
4370
|
const valueMapper = getValueMapper$1(equality);
|
|
4109
4371
|
return getTreeCache(eviction, maxSize, valueMapper, name);
|
|
4110
4372
|
}
|
|
@@ -4115,7 +4377,6 @@ function getValueMapper$1(equality) {
|
|
|
4115
4377
|
case 'value':
|
|
4116
4378
|
return val => stableStringify(val);
|
|
4117
4379
|
}
|
|
4118
|
-
throw err(`Unrecognized equality policy ${equality}`);
|
|
4119
4380
|
}
|
|
4120
4381
|
function getTreeCache(eviction, maxSize, mapNodeValue, name) {
|
|
4121
4382
|
switch (eviction) {
|
|
@@ -4130,7 +4391,6 @@ function getTreeCache(eviction, maxSize, mapNodeValue, name) {
|
|
|
4130
4391
|
case 'most-recent':
|
|
4131
4392
|
return treeCacheLRU({ name, maxSize: 1, mapNodeValue });
|
|
4132
4393
|
}
|
|
4133
|
-
throw err(`Unrecognized eviction policy ${eviction}`);
|
|
4134
4394
|
}
|
|
4135
4395
|
|
|
4136
4396
|
/**
|
|
@@ -4297,10 +4557,9 @@ function selector(options) {
|
|
|
4297
4557
|
}
|
|
4298
4558
|
function updateDeps(store, state, deps, executionID) {
|
|
4299
4559
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
4300
|
-
if (executionID != null &&
|
|
4301
|
-
(
|
|
4302
|
-
|
|
4303
|
-
state.version === ((_d = (_c = store.getState()) === null || _c === void 0 ? void 0 : _c.nextTree) === null || _d === void 0 ? void 0 : _d.version))) {
|
|
4560
|
+
if ((executionID != null && isLatestExecution(store, executionID)) ||
|
|
4561
|
+
state.version === ((_b = (_a = store.getState()) === null || _a === void 0 ? void 0 : _a.currentTree) === null || _b === void 0 ? void 0 : _b.version) ||
|
|
4562
|
+
state.version === ((_d = (_c = store.getState()) === null || _c === void 0 ? void 0 : _c.nextTree) === null || _d === void 0 ? void 0 : _d.version)) {
|
|
4304
4563
|
saveDepsToStore(key, deps, store, (_g = (_f = (_e = store.getState()) === null || _e === void 0 ? void 0 : _e.nextTree) === null || _f === void 0 ? void 0 : _f.version) !== null && _g !== void 0 ? _g : store.getState().currentTree.version);
|
|
4305
4564
|
}
|
|
4306
4565
|
for (const nodeKey of deps) {
|
|
@@ -4410,8 +4669,7 @@ function selector(options) {
|
|
|
4410
4669
|
});
|
|
4411
4670
|
}
|
|
4412
4671
|
catch (error) {
|
|
4413
|
-
throw err(`Problem with cache lookup for selector "${key}": ${error
|
|
4414
|
-
.message}`);
|
|
4672
|
+
throw err(`Problem with cache lookup for selector "${key}": ${error.message}`);
|
|
4415
4673
|
}
|
|
4416
4674
|
if (cachedLoadable) {
|
|
4417
4675
|
state.atomValues.set(key, cachedLoadable);
|
|
@@ -4516,8 +4774,7 @@ function selector(options) {
|
|
|
4516
4774
|
cache.set(depValuesToDepRoute(depValues), loadable);
|
|
4517
4775
|
}
|
|
4518
4776
|
catch (error) {
|
|
4519
|
-
throw err(`Problem with setting cache for selector "${key}": ${error
|
|
4520
|
-
.message}`);
|
|
4777
|
+
throw err(`Problem with setting cache for selector "${key}": ${error.message}`);
|
|
4521
4778
|
}
|
|
4522
4779
|
}
|
|
4523
4780
|
function detectCircularDependencies(fn) {
|
|
@@ -4565,7 +4822,8 @@ function selector(options) {
|
|
|
4565
4822
|
discoveredDependencyNodeKeys.clear();
|
|
4566
4823
|
invalidateSelector(treeState);
|
|
4567
4824
|
cache.clear();
|
|
4568
|
-
markRecoilValueModified
|
|
4825
|
+
// Don't call markRecoilValueModified here as it causes nested state updates
|
|
4826
|
+
// The caller (like refreshRecoilValue) should handle marking as dirty
|
|
4569
4827
|
}
|
|
4570
4828
|
if (set != null) {
|
|
4571
4829
|
const selectorSet = (store, state, newValue) => {
|
|
@@ -4812,7 +5070,8 @@ function baseAtom(options) {
|
|
|
4812
5070
|
var _a;
|
|
4813
5071
|
const { release } = store.subscribeToTransactions(currentStore => {
|
|
4814
5072
|
var _a, _b;
|
|
4815
|
-
let {
|
|
5073
|
+
let { previousTree } = currentStore.getState();
|
|
5074
|
+
const { currentTree } = currentStore.getState();
|
|
4816
5075
|
if (!previousTree) {
|
|
4817
5076
|
recoverableViolation('Transaction subscribers notified without a next tree being present -- this is a bug in Recoil');
|
|
4818
5077
|
previousTree = currentTree;
|
|
@@ -4912,7 +5171,9 @@ function baseAtom(options) {
|
|
|
4912
5171
|
function setAtom(_store, state, newValue) {
|
|
4913
5172
|
if (state.atomValues.has(key)) {
|
|
4914
5173
|
const existing = state.atomValues.get(key);
|
|
4915
|
-
if (existing &&
|
|
5174
|
+
if (existing &&
|
|
5175
|
+
existing.state === 'hasValue' &&
|
|
5176
|
+
newValue === existing.contents) {
|
|
4916
5177
|
return new Map();
|
|
4917
5178
|
}
|
|
4918
5179
|
}
|
|
@@ -5027,9 +5288,11 @@ const defaultPolicy = {
|
|
|
5027
5288
|
maxSize: Infinity,
|
|
5028
5289
|
};
|
|
5029
5290
|
function cacheFromPolicy(policy = defaultPolicy) {
|
|
5030
|
-
const { equality = defaultPolicy.equality
|
|
5291
|
+
const { equality = defaultPolicy.equality } = policy;
|
|
5031
5292
|
const eviction = 'eviction' in policy ? policy.eviction : 'keep-all';
|
|
5032
|
-
const maxSize = 'maxSize' in policy && policy.eviction === 'lru'
|
|
5293
|
+
const maxSize = 'maxSize' in policy && policy.eviction === 'lru'
|
|
5294
|
+
? policy.maxSize
|
|
5295
|
+
: defaultPolicy.maxSize;
|
|
5033
5296
|
const valueMapper = getValueMapper(equality);
|
|
5034
5297
|
const cache = getCache(eviction, maxSize, valueMapper);
|
|
5035
5298
|
return cache;
|
|
@@ -5048,7 +5311,10 @@ function getCache(eviction, maxSize, mapKey) {
|
|
|
5048
5311
|
case 'keep-all':
|
|
5049
5312
|
return new MapCache({ mapKey: mapKey });
|
|
5050
5313
|
case 'lru':
|
|
5051
|
-
return new LRUCache({
|
|
5314
|
+
return new LRUCache({
|
|
5315
|
+
mapKey: mapKey,
|
|
5316
|
+
maxSize: nullthrows(maxSize),
|
|
5317
|
+
});
|
|
5052
5318
|
case 'most-recent':
|
|
5053
5319
|
return new LRUCache({ mapKey: mapKey, maxSize: 1 });
|
|
5054
5320
|
}
|
|
@@ -5056,12 +5322,7 @@ function getCache(eviction, maxSize, mapKey) {
|
|
|
5056
5322
|
}
|
|
5057
5323
|
|
|
5058
5324
|
/**
|
|
5059
|
-
*
|
|
5060
|
-
*
|
|
5061
|
-
* This source code is licensed under the MIT license found in the
|
|
5062
|
-
* LICENSE file in the root directory of this source tree.
|
|
5063
|
-
*
|
|
5064
|
-
* @oncall recoil
|
|
5325
|
+
* TypeScript port of Recoil_atomFamily.js
|
|
5065
5326
|
*/
|
|
5066
5327
|
function atomFamily(options) {
|
|
5067
5328
|
var _a, _b;
|
|
@@ -5087,7 +5348,7 @@ function atomFamily(options) {
|
|
|
5087
5348
|
? options.effects(params)
|
|
5088
5349
|
: typeof options.effects_UNSTABLE === 'function'
|
|
5089
5350
|
? options.effects_UNSTABLE(params)
|
|
5090
|
-
: (_b = options.effects) !== null && _b !== void 0 ? _b : options.effects_UNSTABLE }));
|
|
5351
|
+
: ((_b = options.effects) !== null && _b !== void 0 ? _b : options.effects_UNSTABLE) }));
|
|
5091
5352
|
atomCache.set(params, newAtom);
|
|
5092
5353
|
setConfigDeletionHandler(newAtom.key, () => {
|
|
5093
5354
|
atomCache.delete(params);
|
|
@@ -5171,12 +5432,7 @@ function constSelector(constant) {
|
|
|
5171
5432
|
}
|
|
5172
5433
|
|
|
5173
5434
|
/**
|
|
5174
|
-
*
|
|
5175
|
-
*
|
|
5176
|
-
* This source code is licensed under the MIT license found in the
|
|
5177
|
-
* LICENSE file in the root directory of this source tree.
|
|
5178
|
-
*
|
|
5179
|
-
* @oncall recoil
|
|
5435
|
+
* TypeScript port of Recoil_errorSelector.js
|
|
5180
5436
|
*/
|
|
5181
5437
|
const throwingSelector = selectorFamily({
|
|
5182
5438
|
key: '__error',
|
|
@@ -5192,14 +5448,7 @@ function errorSelector(message) {
|
|
|
5192
5448
|
}
|
|
5193
5449
|
|
|
5194
5450
|
/**
|
|
5195
|
-
*
|
|
5196
|
-
*
|
|
5197
|
-
* This source code is licensed under the MIT license found in the
|
|
5198
|
-
* LICENSE file in the root directory of this source tree.
|
|
5199
|
-
*
|
|
5200
|
-
* Wraps another recoil value and prevents writing to it.
|
|
5201
|
-
*
|
|
5202
|
-
* @oncall recoil
|
|
5451
|
+
* TypeScript port of Recoil_readOnlySelector.js
|
|
5203
5452
|
*/
|
|
5204
5453
|
function readOnlySelector(atom) {
|
|
5205
5454
|
return atom;
|
|
@@ -5260,11 +5509,21 @@ const waitForAny = selectorFamily({
|
|
|
5260
5509
|
const deps = unwrapDependencies(dependencies);
|
|
5261
5510
|
const [results, exceptions] = concurrentRequests(get, deps);
|
|
5262
5511
|
if (exceptions.some(exp => !isPromise(exp))) {
|
|
5512
|
+
// If all are errors (no promises), waitForAny should throw the first error
|
|
5513
|
+
if (exceptions.every(exp => isError(exp))) {
|
|
5514
|
+
const firstError = exceptions.find(isError);
|
|
5515
|
+
if (firstError) {
|
|
5516
|
+
throw firstError;
|
|
5517
|
+
}
|
|
5518
|
+
}
|
|
5263
5519
|
return wrapLoadables(dependencies, results, exceptions);
|
|
5264
5520
|
}
|
|
5265
|
-
return new Promise(resolve => {
|
|
5521
|
+
return new Promise((resolve, reject) => {
|
|
5522
|
+
let pendingCount = 0;
|
|
5523
|
+
let settledCount = 0;
|
|
5266
5524
|
for (const [i, exp] of exceptions.entries()) {
|
|
5267
5525
|
if (isPromise(exp)) {
|
|
5526
|
+
pendingCount++;
|
|
5268
5527
|
exp
|
|
5269
5528
|
.then(result => {
|
|
5270
5529
|
results[i] = result;
|
|
@@ -5273,7 +5532,18 @@ const waitForAny = selectorFamily({
|
|
|
5273
5532
|
})
|
|
5274
5533
|
.catch(error => {
|
|
5275
5534
|
exceptions[i] = error;
|
|
5276
|
-
|
|
5535
|
+
settledCount++;
|
|
5536
|
+
// Only resolve with error if ALL promises have settled/failed
|
|
5537
|
+
if (settledCount === pendingCount) {
|
|
5538
|
+
// All promises have settled with errors, so reject with the first error
|
|
5539
|
+
const firstError = exceptions.find(isError);
|
|
5540
|
+
if (firstError) {
|
|
5541
|
+
reject(firstError);
|
|
5542
|
+
}
|
|
5543
|
+
else {
|
|
5544
|
+
resolve(wrapLoadables(dependencies, results, exceptions));
|
|
5545
|
+
}
|
|
5546
|
+
}
|
|
5277
5547
|
});
|
|
5278
5548
|
}
|
|
5279
5549
|
}
|
|
@@ -5305,7 +5575,7 @@ const waitForAllSettled = selectorFamily({
|
|
|
5305
5575
|
if (exceptions.every(exp => !isPromise(exp))) {
|
|
5306
5576
|
return wrapLoadables(dependencies, results, exceptions);
|
|
5307
5577
|
}
|
|
5308
|
-
return
|
|
5578
|
+
return Promise.all(exceptions.map((exp, i) => isPromise(exp)
|
|
5309
5579
|
? exp
|
|
5310
5580
|
.then(result => {
|
|
5311
5581
|
results[i] = result;
|
|
@@ -5315,8 +5585,7 @@ const waitForAllSettled = selectorFamily({
|
|
|
5315
5585
|
results[i] = undefined;
|
|
5316
5586
|
exceptions[i] = error;
|
|
5317
5587
|
})
|
|
5318
|
-
: null))
|
|
5319
|
-
.then(() => wrapLoadables(dependencies, results, exceptions)));
|
|
5588
|
+
: null)).then(() => wrapLoadables(dependencies, results, exceptions));
|
|
5320
5589
|
},
|
|
5321
5590
|
dangerouslyAllowMutability: true,
|
|
5322
5591
|
});
|