11import { type Atom , atom } from 'jotai'
2+ import {
3+ INTERNAL_Mounted ,
4+ INTERNAL_buildStoreRev1 as INTERNAL_buildStore ,
5+ INTERNAL_getBuildingBlocksRev1 as INTERNAL_getBuildingBlocks ,
6+ INTERNAL_isSelfAtom ,
7+ type INTERNAL_Store as Store ,
8+ } from 'jotai/vanilla/internals'
29import { __DEV__ } from '../env'
310import type {
411 AnyAtom ,
512 AnyAtomFamily ,
613 AnyWritableAtom ,
14+ BuildingBlocks ,
15+ CloneAtom ,
716 Scope ,
817 ScopedStore ,
9- Store ,
10- WithOriginal ,
1118} from '../types'
12- import { SCOPE } from '../types'
19+ import { CONSUMER , EXPLICIT , SCOPE } from '../types'
20+ import { isCloneAtom , isEqualSet } from '../utils'
1321
1422const globalScopeKey : { name ?: string } = { }
1523if ( __DEV__ ) {
@@ -73,7 +81,10 @@ export function createScope({
7381
7482 // populate explicitly scoped atoms
7583 for ( const anAtom of atomSet ) {
76- explicit . set ( anAtom , [ cloneAtom ( anAtom , currentScope ) , currentScope ] )
84+ explicit . set ( anAtom , [
85+ cloneAtom ( anAtom , currentScope , EXPLICIT ) ,
86+ currentScope ,
87+ ] )
7788 }
7889
7990 const cleanupFamiliesSet = new Set < ( ) => void > ( )
@@ -183,13 +194,18 @@ export function createScope({
183194 /**
184195 * @returns a scoped copy of the atom
185196 */
186- function cloneAtom < T > ( originalAtom : Atom < T > , implicitScope ?: Scope ) {
187- // avoid reading `init` to preserve lazy initialization
188- const scopedAtom : WithOriginal < Atom < T > > = Object . create (
197+ function cloneAtom < T > (
198+ originalAtom : Atom < T > ,
199+ implicitScope ?: Scope ,
200+ cloneType ?: EXPLICIT | CONSUMER
201+ ) {
202+ const scopedAtom : CloneAtom < Atom < T > > = Object . create (
203+ // avoid reading `init` to preserve lazy initialization
189204 Object . getPrototypeOf ( originalAtom ) ,
190205 Object . getOwnPropertyDescriptors ( originalAtom )
191206 )
192- scopedAtom . originalAtom = originalAtom
207+ scopedAtom . o = originalAtom
208+ scopedAtom . x = cloneType
193209
194210 if ( scopedAtom . read !== defaultRead ) {
195211 scopedAtom . read = createScopedRead < typeof scopedAtom > (
@@ -263,6 +279,125 @@ export function createScope({
263279
264280 const scopedStore = createPatchedStore ( parentStore , currentScope )
265281 return scopedStore
282+
283+ /**
284+ * @returns a patched store that intercepts get and set calls to apply the scope
285+ */
286+ function createPatchedStore ( baseStore : Store , scope : Scope ) : ScopedStore {
287+ const baseBuildingBlocks = INTERNAL_getBuildingBlocks ( baseStore )
288+ const [ atomStateMap , mountedMap , invalidatedAtoms , changedAtoms ] =
289+ baseBuildingBlocks
290+ const ensureAtomState = baseBuildingBlocks [ 11 ]
291+ const readAtomState = baseBuildingBlocks [ 14 ]
292+ const buildingBlocks : BuildingBlocks = [
293+ atomStateMap ,
294+ undefined ,
295+ invalidatedAtoms ,
296+ changedAtoms ,
297+ ]
298+ const internalMountedMap = new WeakMap < AnyAtom , INTERNAL_Mounted > ( )
299+ buildingBlocks [ 1 ] = {
300+ get : ( atom ) => {
301+ if ( ! isCloneAtom ( atom ) ) return mountedMap . get ( atom )
302+ if ( ! checkConsumer ( atom ) ) return mountedMap . get ( atom . o )
303+ return internalMountedMap . get ( atom )
304+ } ,
305+ set : ( atom , mounted ) => {
306+ if ( ! isCloneAtom ( atom ) ) return mountedMap . set ( atom , mounted )
307+ if ( ! checkConsumer ( atom ) ) return mountedMap . set ( atom . o , mounted )
308+ return internalMountedMap . set ( atom , mounted )
309+ } ,
310+ has : ( atom ) => {
311+ if ( ! isCloneAtom ( atom ) ) return mountedMap . has ( atom )
312+ if ( ! checkConsumer ( atom ) ) return mountedMap . has ( atom . o )
313+ return internalMountedMap . has ( atom )
314+ } ,
315+ delete : ( atom ) => {
316+ if ( ! isCloneAtom ( atom ) ) return mountedMap . delete ( atom )
317+ if ( ! checkConsumer ( atom ) ) return mountedMap . delete ( atom . o )
318+ return internalMountedMap . delete ( atom )
319+ } ,
320+ }
321+ buildingBlocks [ 14 ] = ( atom ) => {
322+ checkConsumer ( atom )
323+ const deps = new Set ( ensureAtomState ( atom ) . d . keys ( ) )
324+ if ( isCloneAtom ( atom ) && atom . x === undefined ) {
325+ const newAtomState = readAtomState ( atom . o )
326+ // deps changed?
327+ const newDeps = new Set ( newAtomState . d . keys ( ) )
328+ if ( ! isEqualSet ( deps , newDeps ) ) {
329+ checkConsumer ( atom )
330+ }
331+ return newAtomState
332+ }
333+ return readAtomState ( atom )
334+ }
335+ const wrappedBaseStore = INTERNAL_buildStore ( ...buildingBlocks )
336+ const storeShim : ScopedStore = {
337+ get ( anAtom , ...args ) {
338+ const [ scopedAtom ] = scope . getAtom ( anAtom )
339+ return wrappedBaseStore . get ( scopedAtom , ...args )
340+ } ,
341+ set ( anAtom , ...args ) {
342+ const [ scopedAtom , implicitScope ] = scope . getAtom ( anAtom )
343+ const restore = scope . prepareWriteAtom (
344+ scopedAtom ,
345+ anAtom ,
346+ implicitScope ,
347+ scope
348+ )
349+ try {
350+ return wrappedBaseStore . set ( scopedAtom , ...args )
351+ } finally {
352+ restore ?.( )
353+ }
354+ } ,
355+ sub ( anAtom , ...args ) {
356+ const [ scopedAtom ] = scope . getAtom ( anAtom )
357+ return wrappedBaseStore . sub ( scopedAtom , ...args )
358+ } ,
359+ [ SCOPE ] : scope ,
360+ }
361+ return Object . assign ( wrappedBaseStore , storeShim ) as ScopedStore
362+
363+ /**
364+ * Check if the atom is a consumer.
365+ * Looks at the atom's dependencies to determine if it is a consumer.
366+ * Updates the atom's clone type with the new value if it changed.
367+ * Recursively checks the dependents if mounted.
368+ * @param atom
369+ * @returns true if the atom is a consumer
370+ */
371+ function checkConsumer ( atom : AnyAtom ) : boolean {
372+ let atomState = ensureAtomState ( atom )
373+ const mountedState = mountedMap . get ( atom )
374+ if ( ! isCloneAtom ( atom ) || atom . x === EXPLICIT ) {
375+ return false
376+ }
377+
378+ if ( ! mountedState && mountedMap . has ( atom . o ) ) {
379+ atomState = ensureAtomState ( atom . o )
380+ }
381+
382+ const dependencies = Array . from ( atomState . d . keys ( ) ) . filter (
383+ ( a ) => ! INTERNAL_isSelfAtom ( atom , a )
384+ )
385+
386+ const isConsumer = dependencies . some (
387+ ( atom ) =>
388+ ( isCloneAtom ( atom ) && ( atom . x === CONSUMER || atom . x === EXPLICIT ) ) ||
389+ explicit . has ( atom ) // TODO: a consumer can also read consumers and inherited too.
390+ )
391+ if ( atom . x === CONSUMER || atom . x === undefined ) {
392+ const newValue = isConsumer ? CONSUMER : undefined
393+ if ( atom . x !== newValue ) {
394+ atom . x = newValue
395+ mountedState ?. t . forEach ( checkConsumer )
396+ }
397+ }
398+ return isConsumer
399+ }
400+ }
266401}
267402
268403function isWritableAtom ( anAtom : AnyAtom ) : anAtom is AnyWritableAtom {
@@ -282,46 +417,3 @@ function combineVoidFunctions(...fns: (() => void)[]) {
282417 }
283418 }
284419}
285-
286- function PatchedStore ( ) { }
287-
288- /**
289- * @returns a patched store that intercepts get and set calls to apply the scope
290- */
291- function createPatchedStore ( baseStore : Store , scope : Scope ) : ScopedStore {
292- const store : ScopedStore = {
293- ...baseStore ,
294- get ( anAtom , ...args ) {
295- const [ scopedAtom ] = scope . getAtom ( anAtom )
296- return baseStore . get ( scopedAtom , ...args )
297- } ,
298- set ( anAtom , ...args ) {
299- const [ scopedAtom , implicitScope ] = scope . getAtom ( anAtom )
300- const restore = scope . prepareWriteAtom (
301- scopedAtom ,
302- anAtom ,
303- implicitScope ,
304- scope
305- )
306- try {
307- return baseStore . set ( scopedAtom , ...args )
308- } finally {
309- restore ?.( )
310- }
311- } ,
312- sub ( anAtom , ...args ) {
313- const [ scopedAtom ] = scope . getAtom ( anAtom )
314- return baseStore . sub ( scopedAtom , ...args )
315- } ,
316- [ SCOPE ] : scope ,
317- // TODO: update this patch to support devtools
318- }
319- return Object . assign ( Object . create ( PatchedStore . prototype ) , store )
320- }
321-
322- /**
323- * @returns true if the current scope is the first descendant scope under Provider
324- */
325- export function isTopLevelScope ( parentStore : Store ) {
326- return ! ( parentStore instanceof PatchedStore )
327- }
0 commit comments