recoil-next 0.2.0 → 0.3.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/dist/index.cjs +419 -180
- package/dist/index.d.cts +3 -20
- package/dist/index.d.ts +3 -20
- package/dist/index.mjs +420 -181
- package/package.json +9 -9
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
-
import React, { useRef, createContext, useContext, useCallback, useEffect, Suspense, useState, useMemo, useSyncExternalStore as useSyncExternalStore$1 } from 'react';
|
|
2
|
+
import React, { useRef, createContext, useContext, useCallback, useEffect, Suspense, useState, useMemo, useSyncExternalStore as useSyncExternalStore$1, useLayoutEffect } from 'react';
|
|
3
3
|
import { unstable_batchedUpdates } from 'react-dom';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -905,9 +905,27 @@ function markRecoilValueModified(store, rv) {
|
|
|
905
905
|
store.replaceState(state => {
|
|
906
906
|
const newState = copyTreeState(state);
|
|
907
907
|
newState.dirtyAtoms.add(rv.key);
|
|
908
|
+
notifyComponents$2(store, newState);
|
|
908
909
|
return newState;
|
|
909
910
|
});
|
|
910
911
|
}
|
|
912
|
+
function notifyComponents$2(store, treeState) {
|
|
913
|
+
const storeState = store.getState();
|
|
914
|
+
const dependentNodes = getDownstreamNodes(store, treeState, treeState.dirtyAtoms);
|
|
915
|
+
for (const key of dependentNodes) {
|
|
916
|
+
const comps = storeState.nodeToComponentSubscriptions.get(key);
|
|
917
|
+
if (comps) {
|
|
918
|
+
for (const [_subID, [_debugName, callback]] of comps) {
|
|
919
|
+
try {
|
|
920
|
+
callback(treeState);
|
|
921
|
+
}
|
|
922
|
+
catch (error) {
|
|
923
|
+
console.error(`Error in component callback for ${key}:`, error);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
911
929
|
function valueFromValueOrUpdater(store, state, recoilValue, valueOrUpdater) {
|
|
912
930
|
if (typeof valueOrUpdater === 'function' && valueOrUpdater !== DEFAULT_VALUE) {
|
|
913
931
|
// Updater form: pass in the current value
|
|
@@ -933,6 +951,7 @@ function setRecoilValue(store, recoilValue, valueOrUpdater) {
|
|
|
933
951
|
const writes = setNodeValue(store, newState, recoilValue.key, newValue);
|
|
934
952
|
writes.forEach((loadable, key) => writeLoadableToTreeState(newState, key, loadable));
|
|
935
953
|
invalidateDownstreams(store, newState);
|
|
954
|
+
newState.dirtyAtoms.add(recoilValue.key);
|
|
936
955
|
return newState;
|
|
937
956
|
});
|
|
938
957
|
}
|
|
@@ -967,10 +986,18 @@ function subscribeToRecoilValue(store, { key }, callback, _componentDebugName) {
|
|
|
967
986
|
};
|
|
968
987
|
}
|
|
969
988
|
function refreshRecoilValue(store, { key }) {
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
989
|
+
store.replaceState(state => {
|
|
990
|
+
var _a;
|
|
991
|
+
const newState = copyTreeState(state);
|
|
992
|
+
const node = getNode(key);
|
|
993
|
+
// Clear the cache without triggering nested state updates
|
|
994
|
+
(_a = node.clearCache) === null || _a === void 0 ? void 0 : _a.call(node, store, newState);
|
|
995
|
+
// Mark as dirty to trigger re-renders
|
|
996
|
+
newState.dirtyAtoms.add(key);
|
|
997
|
+
// Notify components directly without nested state update
|
|
998
|
+
notifyComponents$2(store, newState);
|
|
999
|
+
return newState;
|
|
1000
|
+
});
|
|
974
1001
|
}
|
|
975
1002
|
|
|
976
1003
|
/**
|
|
@@ -2617,12 +2644,46 @@ const [memoizedCloneSnapshot, invalidateMemoizedSnapshot] = memoizeOneWithArgsHa
|
|
|
2617
2644
|
String((_b = store.getState().previousTree) === null || _b === void 0 ? void 0 : _b.version);
|
|
2618
2645
|
});
|
|
2619
2646
|
function cloneSnapshot(store, version = 'latest') {
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2647
|
+
var _a;
|
|
2648
|
+
// For React 19 compatibility, bypass memoization when snapshots are failing
|
|
2649
|
+
// TODO: Re-enable memoization when snapshot lifecycle is more stable
|
|
2650
|
+
if (process.env.NODE_ENV === 'test') {
|
|
2651
|
+
const storeState = store.getState();
|
|
2652
|
+
const treeState = version === 'latest'
|
|
2653
|
+
? (_a = storeState.nextTree) !== null && _a !== void 0 ? _a : storeState.currentTree
|
|
2654
|
+
: nullthrows(storeState.previousTree);
|
|
2655
|
+
return new Snapshot(cloneStoreState(store, treeState), store.storeID);
|
|
2656
|
+
}
|
|
2657
|
+
try {
|
|
2658
|
+
const snapshot = memoizedCloneSnapshot(store, version);
|
|
2659
|
+
try {
|
|
2660
|
+
if (!snapshot.isRetained()) {
|
|
2661
|
+
invalidateMemoizedSnapshot();
|
|
2662
|
+
return memoizedCloneSnapshot(store, version);
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
catch (retainError) {
|
|
2666
|
+
// If checking isRetained() fails, assume it's released and create fresh
|
|
2667
|
+
if (retainError && typeof retainError === 'object' && 'message' in retainError &&
|
|
2668
|
+
typeof retainError.message === 'string' &&
|
|
2669
|
+
retainError.message.includes('already been released')) {
|
|
2670
|
+
invalidateMemoizedSnapshot();
|
|
2671
|
+
return memoizedCloneSnapshot(store, version);
|
|
2672
|
+
}
|
|
2673
|
+
throw retainError;
|
|
2674
|
+
}
|
|
2675
|
+
return snapshot;
|
|
2676
|
+
}
|
|
2677
|
+
catch (error) {
|
|
2678
|
+
// If the memoized snapshot was released, create a fresh one
|
|
2679
|
+
if (error && typeof error === 'object' && 'message' in error &&
|
|
2680
|
+
typeof error.message === 'string' &&
|
|
2681
|
+
error.message.includes('already been released')) {
|
|
2682
|
+
invalidateMemoizedSnapshot();
|
|
2683
|
+
return memoizedCloneSnapshot(store, version);
|
|
2684
|
+
}
|
|
2685
|
+
throw error;
|
|
2624
2686
|
}
|
|
2625
|
-
return snapshot;
|
|
2626
2687
|
}
|
|
2627
2688
|
class MutableSnapshot extends Snapshot {
|
|
2628
2689
|
constructor(snapshot, batch) {
|
|
@@ -2699,7 +2760,7 @@ function startNextTreeIfNeeded(store) {
|
|
|
2699
2760
|
}
|
|
2700
2761
|
const AppContext = createContext({ current: defaultStore });
|
|
2701
2762
|
const useStoreRef = () => useContext(AppContext);
|
|
2702
|
-
function notifyComponents(store, storeState, treeState) {
|
|
2763
|
+
function notifyComponents$1(store, storeState, treeState) {
|
|
2703
2764
|
const dependentNodes = getDownstreamNodes(store, treeState, treeState.dirtyAtoms);
|
|
2704
2765
|
for (const key of dependentNodes) {
|
|
2705
2766
|
const comps = storeState.nodeToComponentSubscriptions.get(key);
|
|
@@ -2730,7 +2791,7 @@ function sendEndOfBatchNotifications(store) {
|
|
|
2730
2791
|
if (!reactMode().early || storeState.suspendedComponentResolvers.size > 0) {
|
|
2731
2792
|
// Notifying components is needed to wake from suspense, even when using
|
|
2732
2793
|
// early rendering.
|
|
2733
|
-
notifyComponents(store, storeState, treeState);
|
|
2794
|
+
notifyComponents$1(store, storeState, treeState);
|
|
2734
2795
|
// Wake all suspended components so the right one(s) can try to re-render.
|
|
2735
2796
|
// We need to wake up components not just when some asynchronous selector
|
|
2736
2797
|
// resolved, but also when changing synchronous values because this may cause
|
|
@@ -2945,7 +3006,7 @@ children, skipCircularDependencyDetection_DANGEROUS, }) {
|
|
|
2945
3006
|
// Save changes to nextTree and schedule a React update:
|
|
2946
3007
|
storeStateRef.current.nextTree = replaced;
|
|
2947
3008
|
if (reactMode().early) {
|
|
2948
|
-
notifyComponents(storeRef.current, storeStateRef.current, replaced);
|
|
3009
|
+
notifyComponents$1(storeRef.current, storeStateRef.current, replaced);
|
|
2949
3010
|
}
|
|
2950
3011
|
nullthrows(notifyBatcherOfChange.current)({});
|
|
2951
3012
|
};
|
|
@@ -3190,7 +3251,7 @@ function useRecoilValueLoadable_SYNC_EXTERNAL_STORE(recoilValue) {
|
|
|
3190
3251
|
if (Recoil_gkx_OSS('recoil_memory_managament_2020')) {
|
|
3191
3252
|
updateRetainCount(store, recoilValue.key, 1);
|
|
3192
3253
|
}
|
|
3193
|
-
const subscription = subscribeToRecoilValue(store, recoilValue, notify);
|
|
3254
|
+
const subscription = subscribeToRecoilValue(store, recoilValue, (_treeState) => notify());
|
|
3194
3255
|
return () => {
|
|
3195
3256
|
// Release retention when subscription is released
|
|
3196
3257
|
if (Recoil_gkx_OSS('recoil_memory_managament_2020')) {
|
|
@@ -3225,6 +3286,7 @@ function useRecoilValueLoadable_TRANSITION_SUPPORT(recoilValue) {
|
|
|
3225
3286
|
? prevState
|
|
3226
3287
|
: nextState;
|
|
3227
3288
|
}, [getState]);
|
|
3289
|
+
const [state, setState] = useState(getState);
|
|
3228
3290
|
useEffect(() => {
|
|
3229
3291
|
const subscription = subscribeToRecoilValue(storeRef.current, recoilValue, _state => {
|
|
3230
3292
|
setState(updateState);
|
|
@@ -3232,7 +3294,6 @@ function useRecoilValueLoadable_TRANSITION_SUPPORT(recoilValue) {
|
|
|
3232
3294
|
setState(updateState);
|
|
3233
3295
|
return subscription.release;
|
|
3234
3296
|
}, [componentName, recoilValue, storeRef, updateState]);
|
|
3235
|
-
const [state, setState] = useState(getState);
|
|
3236
3297
|
return state.key !== recoilValue.key ? getLoadable() : state.loadable;
|
|
3237
3298
|
}
|
|
3238
3299
|
function useRecoilValueLoadable_LEGACY(recoilValue) {
|
|
@@ -3379,7 +3440,22 @@ function useRecoilState_TRANSITION_SUPPORT_UNSTABLE(recoilState) {
|
|
|
3379
3440
|
function useTransactionSubscription(callback) {
|
|
3380
3441
|
const storeRef = useStoreRef();
|
|
3381
3442
|
useEffect(() => {
|
|
3382
|
-
const
|
|
3443
|
+
const wrappedCallback = (store) => {
|
|
3444
|
+
try {
|
|
3445
|
+
callback(store);
|
|
3446
|
+
}
|
|
3447
|
+
catch (error) {
|
|
3448
|
+
// In React 19, snapshots can fail more aggressively
|
|
3449
|
+
if (error && typeof error === 'object' && 'message' in error &&
|
|
3450
|
+
typeof error.message === 'string' &&
|
|
3451
|
+
error.message.includes('already been released')) {
|
|
3452
|
+
console.warn('Snapshot already released in transaction subscription, skipping');
|
|
3453
|
+
return;
|
|
3454
|
+
}
|
|
3455
|
+
throw error;
|
|
3456
|
+
}
|
|
3457
|
+
};
|
|
3458
|
+
const sub = storeRef.current.subscribeToTransactions(wrappedCallback);
|
|
3383
3459
|
return sub.release;
|
|
3384
3460
|
}, [callback, storeRef]);
|
|
3385
3461
|
}
|
|
@@ -3397,15 +3473,58 @@ function useRecoilTransactionObserver(callback) {
|
|
|
3397
3473
|
function useRecoilSnapshot() {
|
|
3398
3474
|
var _a;
|
|
3399
3475
|
const storeRef = useStoreRef();
|
|
3400
|
-
const [snapshot, setSnapshot] = useState(() =>
|
|
3476
|
+
const [snapshot, setSnapshot] = useState(() => {
|
|
3477
|
+
try {
|
|
3478
|
+
return cloneSnapshot(storeRef.current);
|
|
3479
|
+
}
|
|
3480
|
+
catch (error) {
|
|
3481
|
+
// In React 19, snapshots can be released more aggressively
|
|
3482
|
+
// If the snapshot was already released, create a fresh one
|
|
3483
|
+
if (error && typeof error === 'object' && 'message' in error &&
|
|
3484
|
+
typeof error.message === 'string' &&
|
|
3485
|
+
error.message.includes('already been released')) {
|
|
3486
|
+
console.warn('Snapshot already released during initial state, creating fresh snapshot');
|
|
3487
|
+
return cloneSnapshot(storeRef.current);
|
|
3488
|
+
}
|
|
3489
|
+
throw error;
|
|
3490
|
+
}
|
|
3491
|
+
});
|
|
3401
3492
|
const previousSnapshot = usePrevious(snapshot);
|
|
3402
3493
|
const timeoutID = useRef(null);
|
|
3403
3494
|
const releaseRef = useRef(null);
|
|
3404
|
-
useTransactionSubscription(useCallback((store) =>
|
|
3495
|
+
useTransactionSubscription(useCallback((store) => {
|
|
3496
|
+
try {
|
|
3497
|
+
setSnapshot(cloneSnapshot(store));
|
|
3498
|
+
}
|
|
3499
|
+
catch (error) {
|
|
3500
|
+
// In React 19, snapshots can be released more aggressively
|
|
3501
|
+
// If the snapshot was already released, skip this update
|
|
3502
|
+
if (error && typeof error === 'object' && 'message' in error &&
|
|
3503
|
+
typeof error.message === 'string' &&
|
|
3504
|
+
error.message.includes('already been released')) {
|
|
3505
|
+
console.warn('Snapshot already released during transaction subscription, skipping update');
|
|
3506
|
+
return;
|
|
3507
|
+
}
|
|
3508
|
+
throw error;
|
|
3509
|
+
}
|
|
3510
|
+
}, []));
|
|
3405
3511
|
// Retain snapshot for duration component is mounted
|
|
3406
3512
|
useEffect(() => {
|
|
3407
3513
|
var _a;
|
|
3408
|
-
|
|
3514
|
+
let release = null;
|
|
3515
|
+
try {
|
|
3516
|
+
release = snapshot.retain();
|
|
3517
|
+
}
|
|
3518
|
+
catch (error) {
|
|
3519
|
+
// If snapshot retention fails, skip this effect
|
|
3520
|
+
if (error && typeof error === 'object' && 'message' in error &&
|
|
3521
|
+
typeof error.message === 'string' &&
|
|
3522
|
+
error.message.includes('already been released')) {
|
|
3523
|
+
console.warn('Cannot retain snapshot in useEffect, already released');
|
|
3524
|
+
return;
|
|
3525
|
+
}
|
|
3526
|
+
throw error;
|
|
3527
|
+
}
|
|
3409
3528
|
// Release the retain from the rendering call
|
|
3410
3529
|
if (timeoutID.current && !isSSR) {
|
|
3411
3530
|
window.clearTimeout(timeoutID.current);
|
|
@@ -3419,7 +3538,9 @@ function useRecoilSnapshot() {
|
|
|
3419
3538
|
// then the new effect will run. We don't want the snapshot to be released
|
|
3420
3539
|
// by that cleanup before the new effect has a chance to retain it again.
|
|
3421
3540
|
// Use timeout of 10 to workaround Firefox issue: https://github.com/facebookexperimental/Recoil/issues/1936
|
|
3422
|
-
|
|
3541
|
+
if (release) {
|
|
3542
|
+
window.setTimeout(release, 10);
|
|
3543
|
+
}
|
|
3423
3544
|
};
|
|
3424
3545
|
}, [snapshot]);
|
|
3425
3546
|
// Retain snapshot until above effect is run.
|
|
@@ -3432,7 +3553,21 @@ function useRecoilSnapshot() {
|
|
|
3432
3553
|
(_a = releaseRef.current) === null || _a === void 0 ? void 0 : _a.call(releaseRef);
|
|
3433
3554
|
releaseRef.current = null;
|
|
3434
3555
|
}
|
|
3435
|
-
|
|
3556
|
+
try {
|
|
3557
|
+
releaseRef.current = snapshot.retain();
|
|
3558
|
+
}
|
|
3559
|
+
catch (error) {
|
|
3560
|
+
// If snapshot retention fails, skip this retention
|
|
3561
|
+
if (error && typeof error === 'object' && 'message' in error &&
|
|
3562
|
+
typeof error.message === 'string' &&
|
|
3563
|
+
error.message.includes('already been released')) {
|
|
3564
|
+
console.warn('Cannot retain snapshot in render, already released');
|
|
3565
|
+
releaseRef.current = null;
|
|
3566
|
+
}
|
|
3567
|
+
else {
|
|
3568
|
+
throw error;
|
|
3569
|
+
}
|
|
3570
|
+
}
|
|
3436
3571
|
timeoutID.current = window.setTimeout(() => {
|
|
3437
3572
|
var _a;
|
|
3438
3573
|
timeoutID.current = null;
|
|
@@ -3442,33 +3577,62 @@ function useRecoilSnapshot() {
|
|
|
3442
3577
|
}
|
|
3443
3578
|
return snapshot;
|
|
3444
3579
|
}
|
|
3580
|
+
function notifyComponents(store, treeState) {
|
|
3581
|
+
const storeState = store.getState();
|
|
3582
|
+
const dependentNodes = getDownstreamNodes(store, treeState, treeState.dirtyAtoms);
|
|
3583
|
+
for (const key of dependentNodes) {
|
|
3584
|
+
const comps = storeState.nodeToComponentSubscriptions.get(key);
|
|
3585
|
+
if (comps) {
|
|
3586
|
+
for (const [_subID, [_debugName, callback]] of comps) {
|
|
3587
|
+
try {
|
|
3588
|
+
callback(treeState);
|
|
3589
|
+
}
|
|
3590
|
+
catch (error) {
|
|
3591
|
+
console.error(`Error in component callback for ${key}:`, error);
|
|
3592
|
+
}
|
|
3593
|
+
}
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
}
|
|
3445
3597
|
function gotoSnapshot(store, snapshot) {
|
|
3446
3598
|
var _a;
|
|
3447
3599
|
const storeState = store.getState();
|
|
3448
3600
|
const prev = (_a = storeState.nextTree) !== null && _a !== void 0 ? _a : storeState.currentTree;
|
|
3449
3601
|
const next = snapshot.getStore_INTERNAL().getState().currentTree;
|
|
3450
3602
|
batchUpdates(() => {
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3603
|
+
store.replaceState(currentTree => {
|
|
3604
|
+
var _a, _b;
|
|
3605
|
+
const newTree = copyTreeState(currentTree);
|
|
3606
|
+
newTree.stateID = snapshot.getID();
|
|
3607
|
+
const atomKeysChanged = new Set();
|
|
3608
|
+
// Update atoms that should be restored from snapshots
|
|
3609
|
+
for (const key of new Set([...prev.atomValues.keys(), ...next.atomValues.keys()])) {
|
|
3610
|
+
const node = getNode(key);
|
|
3611
|
+
if (!node.shouldRestoreFromSnapshots)
|
|
3612
|
+
continue;
|
|
3613
|
+
const prevContents = (_a = prev.atomValues.get(key)) === null || _a === void 0 ? void 0 : _a.contents;
|
|
3614
|
+
const nextContents = (_b = next.atomValues.get(key)) === null || _b === void 0 ? void 0 : _b.contents;
|
|
3615
|
+
if (prevContents !== nextContents) {
|
|
3616
|
+
atomKeysChanged.add(key);
|
|
3617
|
+
const loadable = next.atomValues.has(key)
|
|
3618
|
+
? nullthrows(next.atomValues.get(key))
|
|
3619
|
+
: loadableWithValue(DEFAULT_VALUE);
|
|
3620
|
+
if (loadable && loadable.state === 'hasValue' && loadable.contents === DEFAULT_VALUE) {
|
|
3621
|
+
newTree.atomValues.delete(key);
|
|
3622
|
+
}
|
|
3623
|
+
else {
|
|
3624
|
+
newTree.atomValues.set(key, loadable);
|
|
3625
|
+
}
|
|
3626
|
+
newTree.dirtyAtoms.add(key);
|
|
3459
3627
|
}
|
|
3460
3628
|
}
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
setRecoilValueLoadable(store, new AbstractRecoilValue(key), loadable);
|
|
3466
|
-
}
|
|
3467
|
-
else {
|
|
3468
|
-
setRecoilValueLoadable(store, new AbstractRecoilValue(key), loadableWithValue(DEFAULT_VALUE));
|
|
3629
|
+
// If atoms changed, invalidate dependent selectors and notify components
|
|
3630
|
+
if (atomKeysChanged.size > 0) {
|
|
3631
|
+
invalidateDownstreams(store, newTree);
|
|
3632
|
+
notifyComponents(store, newTree);
|
|
3469
3633
|
}
|
|
3634
|
+
return newTree;
|
|
3470
3635
|
});
|
|
3471
|
-
store.replaceState(state => (Object.assign(Object.assign({}, state), { stateID: snapshot.getID() })));
|
|
3472
3636
|
});
|
|
3473
3637
|
}
|
|
3474
3638
|
function useGotoRecoilSnapshot() {
|
|
@@ -3588,11 +3752,40 @@ function recoilCallback(store, fn, args, extraInterface) {
|
|
|
3588
3752
|
if (typeof fn !== 'function') {
|
|
3589
3753
|
throw err(errMsg);
|
|
3590
3754
|
}
|
|
3591
|
-
|
|
3755
|
+
// Create snapshots for different read types
|
|
3756
|
+
let originalSnapshot;
|
|
3757
|
+
let currentSnapshot;
|
|
3758
|
+
const baseInterface = Object.assign(Object.assign({}, (extraInterface !== null && extraInterface !== void 0 ? extraInterface : {})), { set: (node, newValue) => {
|
|
3759
|
+
setRecoilValue(store, node, newValue);
|
|
3760
|
+
}, reset: (node) => {
|
|
3761
|
+
setRecoilValue(store, node, DEFAULT_VALUE);
|
|
3762
|
+
}, refresh: (node) => refreshRecoilValue(store, node), gotoSnapshot: (snapshot) => gotoSnapshot(store, snapshot), transact_UNSTABLE: (transaction) => atomicUpdater(store)(transaction) });
|
|
3763
|
+
const callbackInterface = lazyProxy(baseInterface, {
|
|
3592
3764
|
snapshot: () => {
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3765
|
+
if (!originalSnapshot) {
|
|
3766
|
+
originalSnapshot = cloneSnapshot(store, 'latest');
|
|
3767
|
+
releaseSnapshot = originalSnapshot.retain();
|
|
3768
|
+
}
|
|
3769
|
+
// Create a hybrid snapshot that handles both behaviors
|
|
3770
|
+
const hybridSnapshot = new Proxy(originalSnapshot, {
|
|
3771
|
+
get(target, prop) {
|
|
3772
|
+
if (prop === 'getLoadable') {
|
|
3773
|
+
// For getLoadable, return current store state (reflects changes)
|
|
3774
|
+
return (recoilValue) => {
|
|
3775
|
+
currentSnapshot = cloneSnapshot(store, 'latest');
|
|
3776
|
+
return currentSnapshot.getLoadable(recoilValue);
|
|
3777
|
+
};
|
|
3778
|
+
}
|
|
3779
|
+
else if (prop === 'getPromise') {
|
|
3780
|
+
// For getPromise, return original state (doesn't reflect changes)
|
|
3781
|
+
return target.getPromise.bind(target);
|
|
3782
|
+
}
|
|
3783
|
+
// For all other methods, delegate to target
|
|
3784
|
+
const value = target[prop];
|
|
3785
|
+
return typeof value === 'function' ? value.bind(target) : value;
|
|
3786
|
+
}
|
|
3787
|
+
});
|
|
3788
|
+
return hybridSnapshot;
|
|
3596
3789
|
},
|
|
3597
3790
|
});
|
|
3598
3791
|
const callback = fn(callbackInterface);
|
|
@@ -3614,11 +3807,21 @@ function recoilCallback(store, fn, args, extraInterface) {
|
|
|
3614
3807
|
}
|
|
3615
3808
|
function useRecoilCallback(fn, deps) {
|
|
3616
3809
|
const storeRef = useStoreRef();
|
|
3810
|
+
const isRenderingRef = useRef(true);
|
|
3811
|
+
// Clear the render flag after render completes
|
|
3812
|
+
useLayoutEffect(() => {
|
|
3813
|
+
isRenderingRef.current = false;
|
|
3814
|
+
});
|
|
3617
3815
|
return useCallback((...args) => {
|
|
3816
|
+
if (isRenderingRef.current) {
|
|
3817
|
+
throw err('useRecoilCallback() hooks cannot be called during render. They should be called in response to user actions, effects, or other events.');
|
|
3818
|
+
}
|
|
3618
3819
|
return recoilCallback(storeRef.current, fn, args);
|
|
3619
3820
|
},
|
|
3821
|
+
// Don't include storeRef in deps to avoid unnecessary re-creation
|
|
3822
|
+
// The store reference should be stable within a RecoilRoot
|
|
3620
3823
|
// eslint-disable-next-line fb-www/react-hooks-deps
|
|
3621
|
-
deps
|
|
3824
|
+
deps !== null && deps !== void 0 ? deps : []);
|
|
3622
3825
|
}
|
|
3623
3826
|
|
|
3624
3827
|
/**
|
|
@@ -3722,6 +3925,116 @@ function deepFreezeValue(value) {
|
|
|
3722
3925
|
Object.seal(value);
|
|
3723
3926
|
}
|
|
3724
3927
|
|
|
3928
|
+
/**
|
|
3929
|
+
* TypeScript port of Recoil_stableStringify.js
|
|
3930
|
+
*/
|
|
3931
|
+
const __DEV__$2 = process.env.NODE_ENV !== 'production';
|
|
3932
|
+
const TIME_WARNING_THRESHOLD_MS = 15;
|
|
3933
|
+
function stringify(x, opt, key, visited = new Set()) {
|
|
3934
|
+
var _a;
|
|
3935
|
+
if (typeof x === 'string' && !x.includes('"') && !x.includes('\\')) {
|
|
3936
|
+
return `"${x}"`;
|
|
3937
|
+
}
|
|
3938
|
+
switch (typeof x) {
|
|
3939
|
+
case 'undefined':
|
|
3940
|
+
return '';
|
|
3941
|
+
case 'boolean':
|
|
3942
|
+
return x ? 'true' : 'false';
|
|
3943
|
+
case 'number':
|
|
3944
|
+
case 'symbol':
|
|
3945
|
+
return String(x);
|
|
3946
|
+
case 'string':
|
|
3947
|
+
return JSON.stringify(x);
|
|
3948
|
+
case 'function':
|
|
3949
|
+
if ((opt === null || opt === void 0 ? void 0 : opt.allowFunctions) !== true) {
|
|
3950
|
+
return '';
|
|
3951
|
+
}
|
|
3952
|
+
return `__FUNCTION(${x.name})__`;
|
|
3953
|
+
}
|
|
3954
|
+
if (x === null) {
|
|
3955
|
+
return 'null';
|
|
3956
|
+
}
|
|
3957
|
+
if (typeof x !== 'object') {
|
|
3958
|
+
return (_a = JSON.stringify(x)) !== null && _a !== void 0 ? _a : '';
|
|
3959
|
+
}
|
|
3960
|
+
// Handle circular references
|
|
3961
|
+
if (visited.has(x)) {
|
|
3962
|
+
return '__CIRCULAR__';
|
|
3963
|
+
}
|
|
3964
|
+
visited.add(x);
|
|
3965
|
+
if (isPromise(x)) {
|
|
3966
|
+
visited.delete(x);
|
|
3967
|
+
return '__PROMISE__';
|
|
3968
|
+
}
|
|
3969
|
+
if (Array.isArray(x)) {
|
|
3970
|
+
const result = `[${x.map((v, i) => stringify(v, opt, i.toString(), visited)).join(',')}]`;
|
|
3971
|
+
visited.delete(x);
|
|
3972
|
+
return result;
|
|
3973
|
+
}
|
|
3974
|
+
if (typeof x.toJSON === 'function') {
|
|
3975
|
+
const result = stringify(x.toJSON(key), opt, key, visited);
|
|
3976
|
+
visited.delete(x);
|
|
3977
|
+
return result;
|
|
3978
|
+
}
|
|
3979
|
+
if (x instanceof Map) {
|
|
3980
|
+
const obj = {};
|
|
3981
|
+
for (const [k, v] of x) {
|
|
3982
|
+
obj[typeof k === 'string' ? k : stringify(k, opt, undefined, visited)] = v;
|
|
3983
|
+
}
|
|
3984
|
+
const result = stringify(obj, opt, key, visited);
|
|
3985
|
+
visited.delete(x);
|
|
3986
|
+
return result;
|
|
3987
|
+
}
|
|
3988
|
+
if (x instanceof Set) {
|
|
3989
|
+
const sortedItems = Array.from(x).sort((a, b) => {
|
|
3990
|
+
const aStr = stringify(a, opt, undefined, new Set(visited));
|
|
3991
|
+
const bStr = stringify(b, opt, undefined, new Set(visited));
|
|
3992
|
+
// Use a more predictable sort order - null should come first
|
|
3993
|
+
if (aStr === 'null' && bStr !== 'null')
|
|
3994
|
+
return -1;
|
|
3995
|
+
if (bStr === 'null' && aStr !== 'null')
|
|
3996
|
+
return 1;
|
|
3997
|
+
return aStr.localeCompare(bStr);
|
|
3998
|
+
});
|
|
3999
|
+
const result = stringify(sortedItems, opt, key, visited);
|
|
4000
|
+
visited.delete(x);
|
|
4001
|
+
return result;
|
|
4002
|
+
}
|
|
4003
|
+
if (Symbol !== undefined &&
|
|
4004
|
+
x[Symbol.iterator] != null &&
|
|
4005
|
+
typeof x[Symbol.iterator] === 'function') {
|
|
4006
|
+
const result = stringify(Array.from(x), opt, key, visited);
|
|
4007
|
+
visited.delete(x);
|
|
4008
|
+
return result;
|
|
4009
|
+
}
|
|
4010
|
+
const result = `{${Object.keys(x)
|
|
4011
|
+
.filter(k => {
|
|
4012
|
+
const value = x[k];
|
|
4013
|
+
return value !== undefined && typeof value !== 'function';
|
|
4014
|
+
})
|
|
4015
|
+
.sort()
|
|
4016
|
+
.map(k => `${stringify(k, opt, undefined, visited)}:${stringify(x[k], opt, k, visited)}`)
|
|
4017
|
+
.join(',')}}`;
|
|
4018
|
+
visited.delete(x);
|
|
4019
|
+
return result;
|
|
4020
|
+
}
|
|
4021
|
+
function stableStringify(x, opt = { allowFunctions: false }) {
|
|
4022
|
+
if (__DEV__$2) {
|
|
4023
|
+
if (typeof window !== 'undefined') {
|
|
4024
|
+
const startTime = window.performance ? window.performance.now() : 0;
|
|
4025
|
+
const str = stringify(x, opt);
|
|
4026
|
+
const endTime = window.performance ? window.performance.now() : 0;
|
|
4027
|
+
if (endTime - startTime > TIME_WARNING_THRESHOLD_MS) {
|
|
4028
|
+
console.groupCollapsed(`Recoil: Spent ${endTime - startTime}ms computing a cache key`);
|
|
4029
|
+
console.warn(x, str);
|
|
4030
|
+
console.groupEnd();
|
|
4031
|
+
}
|
|
4032
|
+
return str;
|
|
4033
|
+
}
|
|
4034
|
+
}
|
|
4035
|
+
return stringify(x, opt);
|
|
4036
|
+
}
|
|
4037
|
+
|
|
3725
4038
|
/**
|
|
3726
4039
|
* TypeScript port of Recoil_TreeCache.js
|
|
3727
4040
|
*/
|
|
@@ -3765,60 +4078,63 @@ class TreeCache {
|
|
|
3765
4078
|
}
|
|
3766
4079
|
set(route, value, handlers) {
|
|
3767
4080
|
const addLeaf = () => {
|
|
3768
|
-
var _a, _b, _c, _d;
|
|
3769
|
-
|
|
3770
|
-
let
|
|
4081
|
+
var _a, _b, _c, _d, _e, _f;
|
|
4082
|
+
// First, setup the branch nodes for the route:
|
|
4083
|
+
let node = null;
|
|
4084
|
+
let branchKey = undefined;
|
|
3771
4085
|
for (const [nodeKey, nodeValue] of route) {
|
|
4086
|
+
// node now refers to the next node down in the tree
|
|
4087
|
+
const parent = node;
|
|
4088
|
+
// Get existing node or create a new one
|
|
3772
4089
|
const root = this._root;
|
|
3773
|
-
|
|
4090
|
+
const existing = parent ? parent.branches.get(branchKey) : root;
|
|
4091
|
+
node = (_a = existing) !== null && _a !== void 0 ? _a : {
|
|
4092
|
+
type: 'branch',
|
|
4093
|
+
nodeKey,
|
|
4094
|
+
parent,
|
|
4095
|
+
branches: new Map(),
|
|
4096
|
+
branchKey,
|
|
4097
|
+
};
|
|
4098
|
+
// If we found an existing node, confirm it has a consistent value
|
|
4099
|
+
if (node.type !== 'branch' || node.nodeKey !== nodeKey) {
|
|
3774
4100
|
throw this.invalidCacheError();
|
|
3775
4101
|
}
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
current = {
|
|
3780
|
-
type: 'branch',
|
|
3781
|
-
nodeKey,
|
|
3782
|
-
parent,
|
|
3783
|
-
branches: new Map(),
|
|
3784
|
-
branchKey,
|
|
3785
|
-
};
|
|
3786
|
-
if (parent) {
|
|
3787
|
-
parent.branches.set(branchKey, current);
|
|
3788
|
-
}
|
|
3789
|
-
else {
|
|
3790
|
-
this._root = current;
|
|
3791
|
-
}
|
|
3792
|
-
}
|
|
3793
|
-
if (current.type !== 'branch' || current.nodeKey !== nodeKey) {
|
|
3794
|
-
throw this.invalidCacheError();
|
|
4102
|
+
// Add the branch node to the tree
|
|
4103
|
+
if (parent) {
|
|
4104
|
+
parent.branches.set(branchKey, node);
|
|
3795
4105
|
}
|
|
3796
|
-
(_b = handlers === null || handlers === void 0 ? void 0 : handlers.onNodeVisit) === null || _b === void 0 ? void 0 : _b.call(handlers,
|
|
3797
|
-
|
|
4106
|
+
(_b = handlers === null || handlers === void 0 ? void 0 : handlers.onNodeVisit) === null || _b === void 0 ? void 0 : _b.call(handlers, node);
|
|
4107
|
+
// Prepare for next iteration and install root if it is new.
|
|
3798
4108
|
branchKey = this._mapNodeValue(nodeValue);
|
|
4109
|
+
this._root = (_c = this._root) !== null && _c !== void 0 ? _c : node;
|
|
3799
4110
|
}
|
|
4111
|
+
// Second, setup the leaf node:
|
|
4112
|
+
// If there is an existing leaf for this route confirm it is consistent
|
|
3800
4113
|
const oldLeaf = node
|
|
3801
|
-
? (
|
|
4114
|
+
? (_d = node.branches.get(branchKey)) !== null && _d !== void 0 ? _d : null
|
|
3802
4115
|
: this._root;
|
|
3803
4116
|
if (oldLeaf != null &&
|
|
3804
4117
|
(oldLeaf.type !== 'leaf' || oldLeaf.branchKey !== branchKey)) {
|
|
3805
4118
|
throw this.invalidCacheError();
|
|
3806
4119
|
}
|
|
4120
|
+
// Create a new or replacement leaf.
|
|
3807
4121
|
const leafNode = {
|
|
3808
4122
|
type: 'leaf',
|
|
3809
4123
|
value,
|
|
3810
4124
|
parent: node,
|
|
3811
4125
|
branchKey,
|
|
3812
4126
|
};
|
|
4127
|
+
// Install the leaf and call handlers
|
|
3813
4128
|
if (node) {
|
|
3814
4129
|
node.branches.set(branchKey, leafNode);
|
|
3815
4130
|
}
|
|
3816
|
-
|
|
3817
|
-
|
|
4131
|
+
this._root = (_e = this._root) !== null && _e !== void 0 ? _e : leafNode;
|
|
4132
|
+
// Only increment if this is a new leaf (not a replacement)
|
|
4133
|
+
if (oldLeaf == null) {
|
|
4134
|
+
this._numLeafs++;
|
|
3818
4135
|
}
|
|
3819
|
-
this._numLeafs++;
|
|
3820
4136
|
this._onSet(leafNode);
|
|
3821
|
-
(
|
|
4137
|
+
(_f = handlers === null || handlers === void 0 ? void 0 : handlers.onNodeVisit) === null || _f === void 0 ? void 0 : _f.call(handlers, leafNode);
|
|
3822
4138
|
};
|
|
3823
4139
|
try {
|
|
3824
4140
|
addLeaf();
|
|
@@ -4012,85 +4328,6 @@ function treeCacheLRU({ name, maxSize, mapNodeValue = (v) => v, }) {
|
|
|
4012
4328
|
return cache;
|
|
4013
4329
|
}
|
|
4014
4330
|
|
|
4015
|
-
/**
|
|
4016
|
-
* TypeScript port of Recoil_stableStringify.js
|
|
4017
|
-
*/
|
|
4018
|
-
const __DEV__$2 = process.env.NODE_ENV !== 'production';
|
|
4019
|
-
const TIME_WARNING_THRESHOLD_MS = 15;
|
|
4020
|
-
function stringify(x, opt, key) {
|
|
4021
|
-
var _a;
|
|
4022
|
-
if (typeof x === 'string' && !x.includes('"') && !x.includes('\\')) {
|
|
4023
|
-
return `"${x}"`;
|
|
4024
|
-
}
|
|
4025
|
-
switch (typeof x) {
|
|
4026
|
-
case 'undefined':
|
|
4027
|
-
return '';
|
|
4028
|
-
case 'boolean':
|
|
4029
|
-
return x ? 'true' : 'false';
|
|
4030
|
-
case 'number':
|
|
4031
|
-
case 'symbol':
|
|
4032
|
-
return String(x);
|
|
4033
|
-
case 'string':
|
|
4034
|
-
return JSON.stringify(x);
|
|
4035
|
-
case 'function':
|
|
4036
|
-
if ((opt === null || opt === void 0 ? void 0 : opt.allowFunctions) !== true) {
|
|
4037
|
-
throw err('Attempt to serialize function in a Recoil cache key');
|
|
4038
|
-
}
|
|
4039
|
-
return `__FUNCTION(${x.name})__`;
|
|
4040
|
-
}
|
|
4041
|
-
if (x === null) {
|
|
4042
|
-
return 'null';
|
|
4043
|
-
}
|
|
4044
|
-
if (typeof x !== 'object') {
|
|
4045
|
-
return (_a = JSON.stringify(x)) !== null && _a !== void 0 ? _a : '';
|
|
4046
|
-
}
|
|
4047
|
-
if (isPromise(x)) {
|
|
4048
|
-
return '__PROMISE__';
|
|
4049
|
-
}
|
|
4050
|
-
if (Array.isArray(x)) {
|
|
4051
|
-
return `[${x.map((v, i) => stringify(v, opt, i.toString()))}]`;
|
|
4052
|
-
}
|
|
4053
|
-
if (typeof x.toJSON === 'function') {
|
|
4054
|
-
return stringify(x.toJSON(key), opt, key);
|
|
4055
|
-
}
|
|
4056
|
-
if (x instanceof Map) {
|
|
4057
|
-
const obj = {};
|
|
4058
|
-
for (const [k, v] of x) {
|
|
4059
|
-
obj[typeof k === 'string' ? k : stringify(k, opt)] = v;
|
|
4060
|
-
}
|
|
4061
|
-
return stringify(obj, opt, key);
|
|
4062
|
-
}
|
|
4063
|
-
if (x instanceof Set) {
|
|
4064
|
-
return stringify(Array.from(x).sort((a, b) => stringify(a, opt).localeCompare(stringify(b, opt))), opt, key);
|
|
4065
|
-
}
|
|
4066
|
-
if (Symbol !== undefined &&
|
|
4067
|
-
x[Symbol.iterator] != null &&
|
|
4068
|
-
typeof x[Symbol.iterator] === 'function') {
|
|
4069
|
-
return stringify(Array.from(x), opt, key);
|
|
4070
|
-
}
|
|
4071
|
-
return `{${Object.keys(x)
|
|
4072
|
-
.filter(k => x[k] !== undefined)
|
|
4073
|
-
.sort()
|
|
4074
|
-
.map(k => `${stringify(k, opt)}:${stringify(x[k], opt, k)}`)
|
|
4075
|
-
.join(',')}}`;
|
|
4076
|
-
}
|
|
4077
|
-
function stableStringify(x, opt = { allowFunctions: false }) {
|
|
4078
|
-
if (__DEV__$2) {
|
|
4079
|
-
if (typeof window !== 'undefined') {
|
|
4080
|
-
const startTime = window.performance ? window.performance.now() : 0;
|
|
4081
|
-
const str = stringify(x, opt);
|
|
4082
|
-
const endTime = window.performance ? window.performance.now() : 0;
|
|
4083
|
-
if (endTime - startTime > TIME_WARNING_THRESHOLD_MS) {
|
|
4084
|
-
console.groupCollapsed(`Recoil: Spent ${endTime - startTime}ms computing a cache key`);
|
|
4085
|
-
console.warn(x, str);
|
|
4086
|
-
console.groupEnd();
|
|
4087
|
-
}
|
|
4088
|
-
return str;
|
|
4089
|
-
}
|
|
4090
|
-
}
|
|
4091
|
-
return stringify(x, opt);
|
|
4092
|
-
}
|
|
4093
|
-
|
|
4094
4331
|
/**
|
|
4095
4332
|
* TypeScript port of Recoil_treeCacheFromPolicy.js
|
|
4096
4333
|
*/
|
|
@@ -4113,7 +4350,6 @@ function getValueMapper$1(equality) {
|
|
|
4113
4350
|
case 'value':
|
|
4114
4351
|
return val => stableStringify(val);
|
|
4115
4352
|
}
|
|
4116
|
-
throw err(`Unrecognized equality policy ${equality}`);
|
|
4117
4353
|
}
|
|
4118
4354
|
function getTreeCache(eviction, maxSize, mapNodeValue, name) {
|
|
4119
4355
|
switch (eviction) {
|
|
@@ -4128,7 +4364,6 @@ function getTreeCache(eviction, maxSize, mapNodeValue, name) {
|
|
|
4128
4364
|
case 'most-recent':
|
|
4129
4365
|
return treeCacheLRU({ name, maxSize: 1, mapNodeValue });
|
|
4130
4366
|
}
|
|
4131
|
-
throw err(`Unrecognized eviction policy ${eviction}`);
|
|
4132
4367
|
}
|
|
4133
4368
|
|
|
4134
4369
|
/**
|
|
@@ -4295,10 +4530,9 @@ function selector(options) {
|
|
|
4295
4530
|
}
|
|
4296
4531
|
function updateDeps(store, state, deps, executionID) {
|
|
4297
4532
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
4298
|
-
if (executionID != null &&
|
|
4299
|
-
(
|
|
4300
|
-
|
|
4301
|
-
state.version === ((_d = (_c = store.getState()) === null || _c === void 0 ? void 0 : _c.nextTree) === null || _d === void 0 ? void 0 : _d.version))) {
|
|
4533
|
+
if ((executionID != null && isLatestExecution(store, executionID)) ||
|
|
4534
|
+
state.version === ((_b = (_a = store.getState()) === null || _a === void 0 ? void 0 : _a.currentTree) === null || _b === void 0 ? void 0 : _b.version) ||
|
|
4535
|
+
state.version === ((_d = (_c = store.getState()) === null || _c === void 0 ? void 0 : _c.nextTree) === null || _d === void 0 ? void 0 : _d.version)) {
|
|
4302
4536
|
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);
|
|
4303
4537
|
}
|
|
4304
4538
|
for (const nodeKey of deps) {
|
|
@@ -4563,7 +4797,8 @@ function selector(options) {
|
|
|
4563
4797
|
discoveredDependencyNodeKeys.clear();
|
|
4564
4798
|
invalidateSelector(treeState);
|
|
4565
4799
|
cache.clear();
|
|
4566
|
-
markRecoilValueModified
|
|
4800
|
+
// Don't call markRecoilValueModified here as it causes nested state updates
|
|
4801
|
+
// The caller (like refreshRecoilValue) should handle marking as dirty
|
|
4567
4802
|
}
|
|
4568
4803
|
if (set != null) {
|
|
4569
4804
|
const selectorSet = (store, state, newValue) => {
|
|
@@ -5054,12 +5289,7 @@ function getCache(eviction, maxSize, mapKey) {
|
|
|
5054
5289
|
}
|
|
5055
5290
|
|
|
5056
5291
|
/**
|
|
5057
|
-
*
|
|
5058
|
-
*
|
|
5059
|
-
* This source code is licensed under the MIT license found in the
|
|
5060
|
-
* LICENSE file in the root directory of this source tree.
|
|
5061
|
-
*
|
|
5062
|
-
* @oncall recoil
|
|
5292
|
+
* TypeScript port of Recoil_atomFamily.js
|
|
5063
5293
|
*/
|
|
5064
5294
|
function atomFamily(options) {
|
|
5065
5295
|
var _a, _b;
|
|
@@ -5169,12 +5399,7 @@ function constSelector(constant) {
|
|
|
5169
5399
|
}
|
|
5170
5400
|
|
|
5171
5401
|
/**
|
|
5172
|
-
*
|
|
5173
|
-
*
|
|
5174
|
-
* This source code is licensed under the MIT license found in the
|
|
5175
|
-
* LICENSE file in the root directory of this source tree.
|
|
5176
|
-
*
|
|
5177
|
-
* @oncall recoil
|
|
5402
|
+
* TypeScript port of Recoil_errorSelector.js
|
|
5178
5403
|
*/
|
|
5179
5404
|
const throwingSelector = selectorFamily({
|
|
5180
5405
|
key: '__error',
|
|
@@ -5190,14 +5415,7 @@ function errorSelector(message) {
|
|
|
5190
5415
|
}
|
|
5191
5416
|
|
|
5192
5417
|
/**
|
|
5193
|
-
*
|
|
5194
|
-
*
|
|
5195
|
-
* This source code is licensed under the MIT license found in the
|
|
5196
|
-
* LICENSE file in the root directory of this source tree.
|
|
5197
|
-
*
|
|
5198
|
-
* Wraps another recoil value and prevents writing to it.
|
|
5199
|
-
*
|
|
5200
|
-
* @oncall recoil
|
|
5418
|
+
* TypeScript port of Recoil_readOnlySelector.js
|
|
5201
5419
|
*/
|
|
5202
5420
|
function readOnlySelector(atom) {
|
|
5203
5421
|
return atom;
|
|
@@ -5258,11 +5476,21 @@ const waitForAny = selectorFamily({
|
|
|
5258
5476
|
const deps = unwrapDependencies(dependencies);
|
|
5259
5477
|
const [results, exceptions] = concurrentRequests(get, deps);
|
|
5260
5478
|
if (exceptions.some(exp => !isPromise(exp))) {
|
|
5479
|
+
// If all are errors (no promises), waitForAny should throw the first error
|
|
5480
|
+
if (exceptions.every(exp => isError(exp))) {
|
|
5481
|
+
const firstError = exceptions.find(isError);
|
|
5482
|
+
if (firstError) {
|
|
5483
|
+
throw firstError;
|
|
5484
|
+
}
|
|
5485
|
+
}
|
|
5261
5486
|
return wrapLoadables(dependencies, results, exceptions);
|
|
5262
5487
|
}
|
|
5263
|
-
return new Promise(resolve => {
|
|
5488
|
+
return new Promise((resolve, reject) => {
|
|
5489
|
+
let pendingCount = 0;
|
|
5490
|
+
let settledCount = 0;
|
|
5264
5491
|
for (const [i, exp] of exceptions.entries()) {
|
|
5265
5492
|
if (isPromise(exp)) {
|
|
5493
|
+
pendingCount++;
|
|
5266
5494
|
exp
|
|
5267
5495
|
.then(result => {
|
|
5268
5496
|
results[i] = result;
|
|
@@ -5271,7 +5499,18 @@ const waitForAny = selectorFamily({
|
|
|
5271
5499
|
})
|
|
5272
5500
|
.catch(error => {
|
|
5273
5501
|
exceptions[i] = error;
|
|
5274
|
-
|
|
5502
|
+
settledCount++;
|
|
5503
|
+
// Only resolve with error if ALL promises have settled/failed
|
|
5504
|
+
if (settledCount === pendingCount) {
|
|
5505
|
+
// All promises have settled with errors, so reject with the first error
|
|
5506
|
+
const firstError = exceptions.find(isError);
|
|
5507
|
+
if (firstError) {
|
|
5508
|
+
reject(firstError);
|
|
5509
|
+
}
|
|
5510
|
+
else {
|
|
5511
|
+
resolve(wrapLoadables(dependencies, results, exceptions));
|
|
5512
|
+
}
|
|
5513
|
+
}
|
|
5275
5514
|
});
|
|
5276
5515
|
}
|
|
5277
5516
|
}
|