11import { type Atom , atom } from 'jotai'
22import { __DEV__ } from '../env'
3- import type { AnyAtom , AnyAtomFamily , AnyWritableAtom , Scope } from '../types'
3+ import {
4+ type AnyAtom ,
5+ type AnyAtomFamily ,
6+ type AnyWritableAtom ,
7+ SCOPE ,
8+ type Scope ,
9+ type ScopedStore ,
10+ type Store ,
11+ } from '../types'
412
513const globalScopeKey : { name ?: string } = { }
614if ( __DEV__ ) {
715 globalScopeKey . name = 'unscoped'
8- globalScopeKey . toString = toString
16+ globalScopeKey . toString = toNameString
917}
1018
1119type GlobalScopeKey = typeof globalScopeKey
1220
13- export function createScope (
14- atoms : Set < AnyAtom > = new Set ( ) ,
15- atomFamilies : Set < AnyAtomFamily > = new Set ( ) ,
16- parentScope : Scope | undefined ,
17- scopeName ?: string | undefined
18- ) : Scope {
21+ export function createScope ( {
22+ atomSet = new Set ( ) ,
23+ atomFamilySet = new Set ( ) ,
24+ parentStore,
25+ scopeName,
26+ } : {
27+ atomSet ?: Set < AnyAtom >
28+ atomFamilySet ?: Set < AnyAtomFamily >
29+ parentStore : Store | ScopedStore
30+ scopeName ?: string
31+ } ) : ScopedStore {
32+ const parentScope = SCOPE in parentStore ? parentStore [ SCOPE ] : undefined
1933 const explicit = new WeakMap < AnyAtom , [ AnyAtom , Scope ?] > ( )
2034 const implicit = new WeakMap < AnyAtom , [ AnyAtom , Scope ?] > ( )
2135 type ScopeMap = WeakMap < AnyAtom , [ AnyAtom , Scope ?] >
@@ -53,16 +67,16 @@ export function createScope(
5367
5468 if ( scopeName && __DEV__ ) {
5569 currentScope . name = scopeName
56- currentScope . toString = toString
70+ currentScope . toString = toNameString
5771 }
5872
5973 // populate explicitly scoped atoms
60- for ( const anAtom of atoms ) {
74+ for ( const anAtom of atomSet ) {
6175 explicit . set ( anAtom , [ cloneAtom ( anAtom , currentScope ) , currentScope ] )
6276 }
6377
6478 const cleanupFamiliesSet = new Set < ( ) => void > ( )
65- for ( const atomFamily of atomFamilies ) {
79+ for ( const atomFamily of atomFamilySet ) {
6680 for ( const param of atomFamily . getParams ( ) ) {
6781 const anAtom = atomFamily ( param )
6882 if ( ! explicit . has ( anAtom ) ) {
@@ -72,7 +86,7 @@ export function createScope(
7286 const cleanupFamily = atomFamily . unstable_listen ( ( e ) => {
7387 if ( e . type === 'CREATE' && ! explicit . has ( e . atom ) ) {
7488 explicit . set ( e . atom , [ cloneAtom ( e . atom , currentScope ) , currentScope ] )
75- } else if ( ! atoms . has ( e . atom ) ) {
89+ } else if ( ! atomSet . has ( e . atom ) ) {
7690 explicit . delete ( e . atom )
7791 }
7892 } )
@@ -245,7 +259,8 @@ export function createScope(
245259 }
246260 }
247261
248- return currentScope
262+ const scopedStore = createPatchedStore ( parentStore , currentScope )
263+ return scopedStore
249264}
250265
251266function isWritableAtom ( anAtom : AnyAtom ) : anAtom is AnyWritableAtom {
@@ -254,7 +269,7 @@ function isWritableAtom(anAtom: AnyAtom): anAtom is AnyWritableAtom {
254269
255270const { read : defaultRead , write : defaultWrite } = atom < unknown > ( null )
256271
257- function toString ( this : { name : string } ) {
272+ function toNameString ( this : { name : string } ) {
258273 return this . name
259274}
260275
@@ -265,3 +280,46 @@ function combineVoidFunctions(...fns: (() => void)[]) {
265280 }
266281 }
267282}
283+
284+ function PatchedStore ( ) { }
285+
286+ /**
287+ * @returns a patched store that intercepts get and set calls to apply the scope
288+ */
289+ function createPatchedStore ( baseStore : Store , scope : Scope ) : ScopedStore {
290+ const store : ScopedStore = {
291+ ...baseStore ,
292+ get ( anAtom , ...args ) {
293+ const [ scopedAtom ] = scope . getAtom ( anAtom )
294+ return baseStore . get ( scopedAtom , ...args )
295+ } ,
296+ set ( anAtom , ...args ) {
297+ const [ scopedAtom , implicitScope ] = scope . getAtom ( anAtom )
298+ const restore = scope . prepareWriteAtom (
299+ scopedAtom ,
300+ anAtom ,
301+ implicitScope ,
302+ scope
303+ )
304+ try {
305+ return baseStore . set ( scopedAtom , ...args )
306+ } finally {
307+ restore ?.( )
308+ }
309+ } ,
310+ sub ( anAtom , ...args ) {
311+ const [ scopedAtom ] = scope . getAtom ( anAtom )
312+ return baseStore . sub ( scopedAtom , ...args )
313+ } ,
314+ [ SCOPE ] : scope ,
315+ // TODO: update this patch to support devtools
316+ }
317+ return Object . assign ( Object . create ( PatchedStore . prototype ) , store )
318+ }
319+
320+ /**
321+ * @returns true if the current scope is the first descendant scope under Provider
322+ */
323+ export function isTopLevelScope ( parentStore : Store ) {
324+ return ! ( parentStore instanceof PatchedStore )
325+ }
0 commit comments