@@ -1446,6 +1446,136 @@ describe("Atom", () => {
14461446
14471447 unmount2 ( )
14481448 } )
1449+
1450+ it ( "subscriptionRef with runtime recomputes when dependencies change" , async ( ) => {
1451+ vitest . useRealTimers ( )
1452+
1453+ // Test that subscriptionRef atoms properly recompute when their dependencies change
1454+ // This test was added based on a user report that subscriptionRef atoms don't recompute
1455+ // when dependencies accessed via get.some() change.
1456+ const chatIdAtom = Atom . make < Option . Option < string > > ( Option . none ( ) ) . pipe ( Atom . keepAlive )
1457+
1458+ // A regular derived atom using Effect.fnUntraced
1459+ let derivedRecomputes = 0
1460+ const derivedStateAtom = counterRuntime . atom (
1461+ Effect . fnUntraced ( function * ( get : Atom . Context ) {
1462+ const chatId = yield * get . some ( chatIdAtom )
1463+ derivedRecomputes ++
1464+ return chatId
1465+ } )
1466+ )
1467+
1468+ // A subscriptionRef atom that also depends on chatIdAtom
1469+ let subRefRecomputes = 0
1470+ const stateAtom = counterRuntime . subscriptionRef ( ( get ) =>
1471+ Effect . gen ( function * ( ) {
1472+ const chatId = yield * get . some ( chatIdAtom )
1473+ subRefRecomputes ++
1474+ return yield * SubscriptionRef . make ( chatId )
1475+ } )
1476+ )
1477+
1478+ const r = Registry . make ( )
1479+ const unmountDerived = r . mount ( derivedStateAtom )
1480+ const unmountSubRef = r . mount ( stateAtom )
1481+
1482+ // Initially, chatIdAtom is None, so both should fail with NoSuchElementException
1483+ let derivedResult = r . get ( derivedStateAtom )
1484+ let subRefResult = r . get ( stateAtom )
1485+ expect ( derivedRecomputes ) . toEqual ( 0 )
1486+ expect ( subRefRecomputes ) . toEqual ( 0 )
1487+
1488+ // Set chatIdAtom to Some("chat-1")
1489+ r . set ( chatIdAtom , Option . some ( "chat-1" ) )
1490+ await new Promise ( ( resolve ) => resolve ( null ) )
1491+
1492+ derivedResult = r . get ( derivedStateAtom )
1493+ subRefResult = r . get ( stateAtom )
1494+
1495+ assert ( Result . isSuccess ( derivedResult ) )
1496+ expect ( derivedResult . value ) . toEqual ( "chat-1" )
1497+ expect ( derivedRecomputes ) . toEqual ( 1 )
1498+
1499+ assert ( Result . isSuccess ( subRefResult ) )
1500+ expect ( subRefResult . value ) . toEqual ( "chat-1" )
1501+ expect ( subRefRecomputes ) . toEqual ( 1 )
1502+
1503+ // Change chatIdAtom to Some("chat-2") - both atoms should recompute
1504+ r . set ( chatIdAtom , Option . some ( "chat-2" ) )
1505+ await new Promise ( ( resolve ) => resolve ( null ) )
1506+
1507+ derivedResult = r . get ( derivedStateAtom )
1508+ subRefResult = r . get ( stateAtom )
1509+
1510+ // The derived atom should have recomputed
1511+ assert ( Result . isSuccess ( derivedResult ) )
1512+ expect ( derivedResult . value ) . toEqual ( "chat-2" )
1513+ expect ( derivedRecomputes ) . toEqual ( 2 )
1514+
1515+ // The subscriptionRef atom should ALSO have recomputed
1516+ assert ( Result . isSuccess ( subRefResult ) )
1517+ expect ( subRefResult . value ) . toEqual ( "chat-2" )
1518+ expect ( subRefRecomputes ) . toEqual ( 2 )
1519+
1520+ unmountDerived ( )
1521+ unmountSubRef ( )
1522+ } )
1523+
1524+ it ( "subscriptionRef fails with multiple registries (no shared context)" , async ( ) => {
1525+ vitest . useRealTimers ( )
1526+
1527+ // This test simulates what happens when there's no RegistryProvider wrapping the app.
1528+ // Different parts of the app might end up using different registries, which could
1529+ // cause atoms to not see each other's updates.
1530+ const chatIdAtom = Atom . make < Option . Option < string > > ( Option . none ( ) ) . pipe ( Atom . keepAlive )
1531+
1532+ // A regular derived atom using Effect.fnUntraced
1533+ let derivedRecomputes = 0
1534+ const derivedStateAtom = counterRuntime . atom (
1535+ Effect . fnUntraced ( function * ( get : Atom . Context ) {
1536+ const chatId = yield * get . some ( chatIdAtom )
1537+ derivedRecomputes ++
1538+ return chatId
1539+ } )
1540+ )
1541+
1542+ // A subscriptionRef atom that also depends on chatIdAtom
1543+ let subRefRecomputes = 0
1544+ const stateAtom = counterRuntime . subscriptionRef ( ( get ) =>
1545+ Effect . gen ( function * ( ) {
1546+ const chatId = yield * get . some ( chatIdAtom )
1547+ subRefRecomputes ++
1548+ return yield * SubscriptionRef . make ( chatId )
1549+ } )
1550+ )
1551+
1552+ // Simulate the problematic scenario: using different registries
1553+ // This is what happens when there's no RegistryProvider - each useAtom call
1554+ // might get a different registry instance
1555+ const r1 = Registry . make ( ) // Registry for chatIdAtom updates
1556+ const r2 = Registry . make ( ) // Registry for derivedStateAtom
1557+ const r3 = Registry . make ( ) // Registry for stateAtom
1558+
1559+ // Mount atoms in their respective registries
1560+ const unmountDerived = r2 . mount ( derivedStateAtom )
1561+ const unmountSubRef = r3 . mount ( stateAtom )
1562+
1563+ // Set chatIdAtom in r1 - this update won't propagate to r2 and r3!
1564+ r1 . set ( chatIdAtom , Option . some ( "chat-1" ) )
1565+ await new Promise ( ( resolve ) => resolve ( null ) )
1566+
1567+ // The atoms in r2 and r3 won't see the update because they're using different registries
1568+ // This demonstrates why RegistryProvider is important
1569+ r2 . get ( derivedStateAtom )
1570+ r3 . get ( stateAtom )
1571+
1572+ // Both atoms should still be in their initial state because they didn't see the update
1573+ expect ( derivedRecomputes ) . toEqual ( 0 )
1574+ expect ( subRefRecomputes ) . toEqual ( 0 )
1575+
1576+ unmountDerived ( )
1577+ unmountSubRef ( )
1578+ } )
14491579} )
14501580
14511581interface BuildCounter {
0 commit comments