From 21aeebcff43d02d8a45f1ca442a362d16adbab43 Mon Sep 17 00:00:00 2001 From: daishi Date: Thu, 19 Sep 2024 09:12:07 +0900 Subject: [PATCH 1/7] fix(unstable_derive): expose atomRead and atomWrite --- src/vanilla/store.ts | 27 ++++- tests/vanilla/unstable_derive.test.tsx | 156 ++++++++++++++----------- 2 files changed, 107 insertions(+), 76 deletions(-) diff --git a/src/vanilla/store.ts b/src/vanilla/store.ts index 7b04acdbae..c2795598b8 100644 --- a/src/vanilla/store.ts +++ b/src/vanilla/store.ts @@ -273,7 +273,14 @@ type GetAtomState = ( // internal & unstable type type StoreArgs = readonly [ getAtomState: GetAtomState, - // possible other arguments in the future + atomRead: ( + atom: Atom, + ...params: Parameters['read']> + ) => Value, + atomWrite: ( + atom: WritableAtom, + ...params: Parameters['write']> + ) => Result, ] // for debugging purpose only @@ -300,7 +307,11 @@ type Store = PrdStore | (PrdStore & DevStoreRev4) export type INTERNAL_DevStoreRev4 = DevStoreRev4 export type INTERNAL_PrdStore = PrdStore -const buildStore = (getAtomState: StoreArgs[0]): Store => { +const buildStore = ( + getAtomState: StoreArgs[0], + atomRead: StoreArgs[1], + atomWrite: StoreArgs[2], +): Store => { // for debugging purpose only let debugMountedAtoms: Set @@ -447,7 +458,7 @@ const buildStore = (getAtomState: StoreArgs[0]): Store => { }, } try { - const valueOrPromise = atom.read(getter, options as never) + const valueOrPromise = atomRead(atom, getter, options as never) setAtomStateValueOrPromise( atom, atomState, @@ -590,7 +601,7 @@ const buildStore = (getAtomState: StoreArgs[0]): Store => { flushPending(pending) return r as R } - const result = atom.write(getter, setter, ...args) + const result = atomWrite(atom, getter, setter, ...args) return result } @@ -718,7 +729,7 @@ const buildStore = (getAtomState: StoreArgs[0]): Store => { } const unstable_derive = (fn: (...args: StoreArgs) => StoreArgs) => - buildStore(...fn(getAtomState)) + buildStore(...fn(getAtomState, atomRead, atomWrite)) const store: Store = { get: readAtom, @@ -773,7 +784,11 @@ export const createStore = (): Store => { } return atomState } - return buildStore(getAtomState) + return buildStore( + getAtomState, + (atom, ...params) => atom.read(...params), + (atom, ...params) => atom.write(...params), + ) } let defaultStore: Store | undefined diff --git a/tests/vanilla/unstable_derive.test.tsx b/tests/vanilla/unstable_derive.test.tsx index e7bfa468e6..cef9b1129b 100644 --- a/tests/vanilla/unstable_derive.test.tsx +++ b/tests/vanilla/unstable_derive.test.tsx @@ -13,22 +13,26 @@ describe('unstable_derive for scoping atoms', () => { const scopedAtoms = new Set>([a]) const store = createStore() - const derivedStore = store.unstable_derive((getAtomState) => { - const scopedAtomStateMap = new WeakMap() - return [ - (atom, originAtomState) => { - if (scopedAtoms.has(atom)) { - let atomState = scopedAtomStateMap.get(atom) - if (!atomState) { - atomState = { d: new Map(), p: new Set(), n: 0 } - scopedAtomStateMap.set(atom, atomState) + const derivedStore = store.unstable_derive( + (getAtomState, atomRead, atomWrite) => { + const scopedAtomStateMap = new WeakMap() + return [ + (atom, originAtomState) => { + if (scopedAtoms.has(atom)) { + let atomState = scopedAtomStateMap.get(atom) + if (!atomState) { + atomState = { d: new Map(), p: new Set(), n: 0 } + scopedAtomStateMap.set(atom, atomState) + } + return atomState } - return atomState - } - return getAtomState(atom, originAtomState) - }, - ] - }) + return getAtomState(atom, originAtomState) + }, + atomRead, + atomWrite, + ] + }, + ) expect(store.get(a)).toBe('a') expect(derivedStore.get(a)).toBe('a') @@ -55,22 +59,26 @@ describe('unstable_derive for scoping atoms', () => { const scopedAtoms = new Set>([a]) const store = createStore() - const derivedStore = store.unstable_derive((getAtomState) => { - const scopedAtomStateMap = new WeakMap() - return [ - (atom, originAtomState) => { - if (scopedAtoms.has(atom)) { - let atomState = scopedAtomStateMap.get(atom) - if (!atomState) { - atomState = { d: new Map(), p: new Set(), n: 0 } - scopedAtomStateMap.set(atom, atomState) + const derivedStore = store.unstable_derive( + (getAtomState, atomRead, atomWrite) => { + const scopedAtomStateMap = new WeakMap() + return [ + (atom, originAtomState) => { + if (scopedAtoms.has(atom)) { + let atomState = scopedAtomStateMap.get(atom) + if (!atomState) { + atomState = { d: new Map(), p: new Set(), n: 0 } + scopedAtomStateMap.set(atom, atomState) + } + return atomState } - return atomState - } - return getAtomState(atom, originAtomState) - }, - ] - }) + return getAtomState(atom, originAtomState) + }, + atomRead, + atomWrite, + ] + }, + ) expect(store.get(c)).toBe('ab') expect(derivedStore.get(c)).toBe('ab') @@ -96,27 +104,31 @@ describe('unstable_derive for scoping atoms', () => { const scopedAtoms = new Set>([b]) const store = createStore() - const derivedStore = store.unstable_derive((getAtomState) => { - const scopedAtomStateMap = new WeakMap() - const scopedAtomStateSet = new WeakSet() - return [ - (atom, originAtomState) => { - if ( - scopedAtomStateSet.has(originAtomState as never) || - scopedAtoms.has(atom) - ) { - let atomState = scopedAtomStateMap.get(atom) - if (!atomState) { - atomState = { d: new Map(), p: new Set(), n: 0 } - scopedAtomStateMap.set(atom, atomState) - scopedAtomStateSet.add(atomState) + const derivedStore = store.unstable_derive( + (getAtomState, atomRead, atomWrite) => { + const scopedAtomStateMap = new WeakMap() + const scopedAtomStateSet = new WeakSet() + return [ + (atom, originAtomState) => { + if ( + scopedAtomStateSet.has(originAtomState as never) || + scopedAtoms.has(atom) + ) { + let atomState = scopedAtomStateMap.get(atom) + if (!atomState) { + atomState = { d: new Map(), p: new Set(), n: 0 } + scopedAtomStateMap.set(atom, atomState) + scopedAtomStateSet.add(atomState) + } + return atomState } - return atomState - } - return getAtomState(atom, originAtomState) - }, - ] - }) + return getAtomState(atom, originAtomState) + }, + atomRead, + atomWrite, + ] + }, + ) expect(store.get(a)).toBe('a') expect(store.get(b)).toBe('a') @@ -178,27 +190,31 @@ describe('unstable_derive for scoping atoms', () => { function makeStores() { const baseStore = createStore() - const deriStore = baseStore.unstable_derive((getAtomState) => { - const scopedAtomStateMap = new WeakMap() - const scopedAtomStateSet = new WeakSet() - return [ - (atom, originAtomState) => { - if ( - scopedAtomStateSet.has(originAtomState as never) || - scopedAtoms.has(atom) - ) { - let atomState = scopedAtomStateMap.get(atom) - if (!atomState) { - atomState = { d: new Map(), p: new Set(), n: 0 } - scopedAtomStateMap.set(atom, atomState) - scopedAtomStateSet.add(atomState) + const deriStore = baseStore.unstable_derive( + (getAtomState, atomRead, atomWrite) => { + const scopedAtomStateMap = new WeakMap() + const scopedAtomStateSet = new WeakSet() + return [ + (atom, originAtomState) => { + if ( + scopedAtomStateSet.has(originAtomState as never) || + scopedAtoms.has(atom) + ) { + let atomState = scopedAtomStateMap.get(atom) + if (!atomState) { + atomState = { d: new Map(), p: new Set(), n: 0 } + scopedAtomStateMap.set(atom, atomState) + scopedAtomStateSet.add(atomState) + } + return atomState } - return atomState - } - return getAtomState(atom, originAtomState) - }, - ] - }) + return getAtomState(atom, originAtomState) + }, + atomRead, + atomWrite, + ] + }, + ) expect(getAtoms(baseStore)).toEqual(['a', 'b', 'a', 'a', 'ab']) expect(getAtoms(deriStore)).toEqual(['a', 'b', 'a', 'a', 'ab']) return { baseStore, deriStore } From b1a5a08d8f279f06366f1d6aa0699211de8a99ea Mon Sep 17 00:00:00 2001 From: David Maskasky Date: Thu, 26 Sep 2024 20:05:27 -0700 Subject: [PATCH 2/7] Fix/unstable derive - add more tests (#2751) * fix(unstable_derive): add tests * test(unstable_derive): add failing test --- tests/vanilla/unstable_derive.test.tsx | 671 ++++++++++++++++++++++++- 1 file changed, 659 insertions(+), 12 deletions(-) diff --git a/tests/vanilla/unstable_derive.test.tsx b/tests/vanilla/unstable_derive.test.tsx index cef9b1129b..eb41bca911 100644 --- a/tests/vanilla/unstable_derive.test.tsx +++ b/tests/vanilla/unstable_derive.test.tsx @@ -7,7 +7,7 @@ describe('unstable_derive for scoping atoms', () => { * a * S1[a]: a1 */ - it('primitive atom', async () => { + it('primitive atom', () => { const a = atom('a') a.onMount = (setSelf) => setSelf((v) => v + ':mounted') const scopedAtoms = new Set>([a]) @@ -38,12 +38,10 @@ describe('unstable_derive for scoping atoms', () => { expect(derivedStore.get(a)).toBe('a') derivedStore.sub(a, vi.fn()) - await new Promise((resolve) => setTimeout(resolve)) expect(store.get(a)).toBe('a') expect(derivedStore.get(a)).toBe('a:mounted') derivedStore.set(a, (v) => v + ':updated') - await new Promise((resolve) => setTimeout(resolve)) expect(store.get(a)).toBe('a') expect(derivedStore.get(a)).toBe('a:mounted:updated') }) @@ -52,7 +50,7 @@ describe('unstable_derive for scoping atoms', () => { * a, b, c(a + b) * S1[a]: a1, b0, c0(a1 + b0) */ - it('derived atom (scoping primitive)', async () => { + it('derived atom (scoping primitive)', () => { const a = atom('a') const b = atom('b') const c = atom((get) => get(a) + get(b)) @@ -84,16 +82,96 @@ describe('unstable_derive for scoping atoms', () => { expect(derivedStore.get(c)).toBe('ab') derivedStore.set(a, 'a2') - await new Promise((resolve) => setTimeout(resolve)) expect(store.get(c)).toBe('ab') expect(derivedStore.get(c)).toBe('a2b') }) + /** + * a, b(a) + * S1[a]: a1, b0(a1) + */ + it('derived atom with subscribe', () => { + const a = atom('a') + const b = atom( + (get) => get(a), + (_get, set, v: string) => set(a, v), + ) + const scopedAtoms = new Set>([a]) + + function makeStores() { + const store = createStore() + const derivedStore = store.unstable_derive( + (getAtomState, atomRead, atomWrite) => { + const scopedAtomStateMap = new WeakMap() + return [ + (atom, originAtomState) => { + if (scopedAtoms.has(atom)) { + let atomState = scopedAtomStateMap.get(atom) + if (!atomState) { + atomState = { d: new Map(), p: new Set(), n: 0 } + scopedAtomStateMap.set(atom, atomState) + } + return atomState + } + return getAtomState(atom, originAtomState) + }, + atomRead, + atomWrite, + ] + }, + ) + expect(store.get(b)).toBe('a') + expect(derivedStore.get(b)).toBe('a') + return { store, derivedStore } + } + + /** + * Ba[ ]: a0, b0(a0) + * S1[a]: a1, b0(a1) + */ + { + const { store, derivedStore } = makeStores() + store.set(b, '*') + expect(store.get(b)).toBe('*') + expect(derivedStore.get(b)).toBe('a') + } + { + const { store, derivedStore } = makeStores() + derivedStore.set(b, '*') + expect(store.get(b)).toBe('a') + expect(derivedStore.get(b)).toBe('*') + } + { + const { store, derivedStore } = makeStores() + const storeCallback = vi.fn() + const derivedCallback = vi.fn() + store.sub(b, storeCallback) + derivedStore.sub(b, derivedCallback) + store.set(b, '*') + expect(store.get(b)).toBe('*') + expect(derivedStore.get(b)).toBe('a') // FIXME: received '*' + expect(storeCallback).toHaveBeenCalledTimes(1) + expect(derivedCallback).toHaveBeenCalledTimes(0) // FIXME: received 1 + } + { + const { store, derivedStore } = makeStores() + const storeCallback = vi.fn() + const derivedCallback = vi.fn() + store.sub(b, storeCallback) + derivedStore.sub(b, derivedCallback) + derivedStore.set(b, '*') + expect(store.get(b)).toBe('a') + expect(derivedStore.get(b)).toBe('*') // FIXME: received 'a' + expect(storeCallback).toHaveBeenCalledTimes(0) + expect(derivedCallback).toHaveBeenCalledTimes(1) // FIXME: received 1 + } + }) + /** * a, b(a) * S1[b]: a0, b1(a1) */ - it('derived atom (scoping derived)', async () => { + it('derived atom (scoping derived)', () => { const a = atom('a') const b = atom( (get) => get(a), @@ -136,28 +214,24 @@ describe('unstable_derive for scoping atoms', () => { expect(derivedStore.get(b)).toBe('a') store.set(a, 'a2') - await new Promise((resolve) => setTimeout(resolve)) expect(store.get(a)).toBe('a2') expect(store.get(b)).toBe('a2') expect(derivedStore.get(a)).toBe('a2') expect(derivedStore.get(b)).toBe('a') store.set(b, 'a3') - await new Promise((resolve) => setTimeout(resolve)) expect(store.get(a)).toBe('a3') expect(store.get(b)).toBe('a3') expect(derivedStore.get(a)).toBe('a3') expect(derivedStore.get(b)).toBe('a') derivedStore.set(a, 'a4') - await new Promise((resolve) => setTimeout(resolve)) expect(store.get(a)).toBe('a4') expect(store.get(b)).toBe('a4') expect(derivedStore.get(a)).toBe('a4') expect(derivedStore.get(b)).toBe('a') derivedStore.set(b, 'a5') - await new Promise((resolve) => setTimeout(resolve)) expect(store.get(a)).toBe('a4') expect(store.get(b)).toBe('a4') expect(derivedStore.get(a)).toBe('a4') @@ -168,7 +242,7 @@ describe('unstable_derive for scoping atoms', () => { * a, b, c(a), d(c), e(d + b) * S1[d]: a0, b0, c0(a0), d1(c1(a1)), e0(d1(c1(a1)) + b0) */ - it('derived atom (scoping derived chain)', async () => { + it('derived atom (scoping derived chain)', () => { const a = atom('a') const b = atom('b') const c = atom( @@ -231,7 +305,7 @@ describe('unstable_derive for scoping atoms', () => { } /** - * base[d]: a0, b0, c0(a0), d0(c0(a0)), e0(d0(c0(a0)) + b0) + * base[ ]: a0, b0, c0(a0), d0(c0(a0)), e0(d0(c0(a0)) + b0) * deri[d]: a0, b0, c0(a0), d1(c1(a1)), e0(d1(c1(a1)) + b0) */ { @@ -317,4 +391,577 @@ describe('unstable_derive for scoping atoms', () => { expect(getAtoms(deriStore)).toEqual(['a', '*', 'a', '*', '**']) } }) + + /** + * a, b(a), c(a), d(a) + * S1[b, c]: a0, b1(a1), c1(a1), d0(a0) + */ + it('derived atom shares same implicit', () => { + const a = atom('a') + const b = atom( + (get) => get(a), + (_get, set, v: string) => set(a, v), + ) + const c = atom( + (get) => get(a), + (_get, set, v: string) => set(a, v), + ) + const d = atom( + (get) => get(a), + (_get, set, v: string) => set(a, v), + ) + const scopedAtoms = new Set>([b, c]) + + function makeStores() { + const baseStore = createStore() + const deriStore = baseStore.unstable_derive( + (getAtomState, atomRead, atomWrite) => { + const scopedAtomStateMap = new WeakMap() + const scopedAtomStateSet = new WeakSet() + return [ + (atom, originAtomState) => { + if ( + scopedAtomStateSet.has(originAtomState as never) || + scopedAtoms.has(atom) + ) { + let atomState = scopedAtomStateMap.get(atom) + if (!atomState) { + atomState = { d: new Map(), p: new Set(), n: 0 } + scopedAtomStateMap.set(atom, atomState) + scopedAtomStateSet.add(atomState) + } + return atomState + } + return getAtomState(atom, originAtomState) + }, + atomRead, + atomWrite, + ] + }, + ) + expect(getAtoms(baseStore)).toEqual(['a', 'a', 'a', 'a']) + expect(getAtoms(deriStore)).toEqual(['a', 'a', 'a', 'a']) + return { baseStore, deriStore } + } + type Store = ReturnType + function getAtoms(store: Store) { + return [store.get(a), store.get(b), store.get(c), store.get(d)] + } + + /** + * base[ ]: a0, b0(a0), c0(a0), d0(a0), '*' + * deri[b, c]: a0, b0(a0), c1(a1), d0(a0), '*' + */ + { + // UPDATE a0 + // NOCHGE a1 + const { baseStore, deriStore } = makeStores() + baseStore.set(a, '*') + expect(getAtoms(baseStore)).toEqual(['*', '*', '*', '*']) + expect(getAtoms(deriStore)).toEqual(['*', 'a', 'a', '*']) + } + { + // UPDATE b0, b0 -> a0 + // NOCHGE a1 + const { baseStore, deriStore } = makeStores() + baseStore.set(b, '*') + expect(getAtoms(baseStore)).toEqual(['*', '*', '*', '*']) + expect(getAtoms(deriStore)).toEqual(['*', 'a', 'a', '*']) + } + { + // UPDATE c0, c0 -> a0 + // NOCHGE a1 + const { baseStore, deriStore } = makeStores() + baseStore.set(b, '*') + expect(getAtoms(baseStore)).toEqual(['*', '*', '*', '*']) + expect(getAtoms(deriStore)).toEqual(['*', 'a', 'a', '*']) + } + { + // UPDATE a0 + // NOCHGE a1 + const { baseStore, deriStore } = makeStores() + deriStore.set(a, '*') + expect(getAtoms(baseStore)).toEqual(['*', '*', '*', '*']) + expect(getAtoms(deriStore)).toEqual(['*', 'a', 'a', '*']) + } + { + // UPDATE b0, b0 -> a0 + // NOCHGE a1 + const { baseStore, deriStore } = makeStores() + deriStore.set(b, '*') + expect(getAtoms(baseStore)).toEqual(['a', 'a', 'a', 'a']) + expect(getAtoms(deriStore)).toEqual(['a', '*', '*', 'a']) + } + { + // UPDATE c1, c1 -> a1 + // NOCHGE a0 + const { baseStore, deriStore } = makeStores() + deriStore.set(c, '*') + expect(getAtoms(baseStore)).toEqual(['a', 'a', 'a', 'a']) + expect(getAtoms(deriStore)).toEqual(['a', '*', '*', 'a']) + } + }) + + /** + * a, b, c(a + b) + * S1[a]: a1, b0, c0(a1 + b0) + * S2[ ]: a1, b0, c0(a1 + b0) + */ + it('inherited atoms', () => { + const a = atom('a') + const b = atom('b') + const c = atom((get) => get(a) + get(b)) + + function makeStores() { + const s1 = new Set>([a]) + const s2 = new Set>([]) + const baStore = createStore() + const s1Store = baStore.unstable_derive( + (getAtomState, atomRead, atomWrite) => { + const scopedAtomStateMap = new WeakMap() + const scopedAtomStateSet = new WeakSet() + return [ + (atom, originAtomState) => { + if ( + scopedAtomStateSet.has(originAtomState as never) || + s1.has(atom) + ) { + let atomState = scopedAtomStateMap.get(atom) + if (!atomState) { + atomState = { d: new Map(), p: new Set(), n: 0 } + scopedAtomStateMap.set(atom, atomState) + scopedAtomStateSet.add(atomState) + } + return atomState + } + return getAtomState(atom, originAtomState) + }, + atomRead, + atomWrite, + ] + }, + ) + const s2Store = s1Store.unstable_derive( + (getAtomState, atomRead, atomWrite) => { + const scopedAtomStateMap = new WeakMap() + const scopedAtomStateSet = new WeakSet() + return [ + (atom, originAtomState) => { + if ( + scopedAtomStateSet.has(originAtomState as never) || + s2.has(atom) + ) { + let atomState = scopedAtomStateMap.get(atom) + if (!atomState) { + atomState = { d: new Map(), p: new Set(), n: 0 } + scopedAtomStateMap.set(atom, atomState) + scopedAtomStateSet.add(atomState) + } + return atomState + } + return getAtomState(atom, originAtomState) + }, + atomRead, + atomWrite, + ] + }, + ) + expect(getAtoms(baStore)).toEqual(['a', 'b', 'ab']) + expect(getAtoms(s1Store)).toEqual(['a', 'b', 'ab']) + expect(getAtoms(s2Store)).toEqual(['a', 'b', 'ab']) + return { baStore, s1Store, s2Store } + } + type Store = ReturnType + function getAtoms(store: Store) { + return [store.get(a), store.get(b), store.get(c)] + } + + /** + * BA[ ]: a0, b0, c0(a0 + b0) + * S1[a]: a1, b0, c0(a1 + b0) + * S2[ ]: a1, b0, c0(a1 + b0) + */ + { + // UPDATE a0 + // NOCHGE a1, b0 + const { baStore, s1Store, s2Store } = makeStores() + baStore.set(a, '*') + expect(getAtoms(baStore)).toEqual(['*', 'b', '*b']) + expect(getAtoms(s1Store)).toEqual(['a', 'b', 'ab']) + expect(getAtoms(s2Store)).toEqual(['a', 'b', 'ab']) + } + { + // UPDATE b0 + // NOCHGE a0, a1 + const { baStore, s1Store, s2Store } = makeStores() + baStore.set(b, '*') + expect(getAtoms(baStore)).toEqual(['a', '*', 'a*']) + expect(getAtoms(s1Store)).toEqual(['a', '*', 'a*']) + expect(getAtoms(s2Store)).toEqual(['a', '*', 'a*']) + } + { + // UPDATE a1 + // NOCHGE a0, b0 + const { baStore, s1Store, s2Store } = makeStores() + s1Store.set(a, '*') + expect(getAtoms(baStore)).toEqual(['a', 'b', 'ab']) + expect(getAtoms(s1Store)).toEqual(['*', 'b', '*b']) + expect(getAtoms(s2Store)).toEqual(['*', 'b', '*b']) + } + { + // UPDATE b0 + // NOCHGE a0, a1 + const { baStore, s1Store, s2Store } = makeStores() + s1Store.set(b, '*') + expect(getAtoms(baStore)).toEqual(['a', '*', 'a*']) + expect(getAtoms(s1Store)).toEqual(['a', '*', 'a*']) + expect(getAtoms(s2Store)).toEqual(['a', '*', 'a*']) + } + { + // UPDATE a1 + // NOCHGE a0, b0 + const { baStore, s1Store, s2Store } = makeStores() + s2Store.set(a, '*') + expect(getAtoms(baStore)).toEqual(['a', 'b', 'ab']) + expect(getAtoms(s1Store)).toEqual(['*', 'b', '*b']) + expect(getAtoms(s2Store)).toEqual(['*', 'b', '*b']) + } + { + // UPDATE b0 + // NOCHGE a0, a1 + const { baStore, s1Store, s2Store } = makeStores() + s2Store.set(b, '*') + expect(getAtoms(baStore)).toEqual(['a', '*', 'a*']) + expect(getAtoms(s1Store)).toEqual(['a', '*', 'a*']) + expect(getAtoms(s2Store)).toEqual(['a', '*', 'a*']) + } + }) + + /** + * a, b, c(a + b) + * S1[c]: a0, b0, c1(a1 + b1) + * S2[a]: a0, b0, c1(a2 + b1) + */ + it('inherited atoms use explicit in current scope', () => { + const a = atom('a') + a.debugLabel = 'a' + const b = atom('b') + b.debugLabel = 'b' + const c = atom((get) => get(a) + get(b)) + c.debugLabel = 'c' + const s1 = new Set>([c]) + const s2 = new Set>([a]) + + function makeStores() { + const baStore = createStore() + const s1Store = baStore.unstable_derive( + (getAtomState, atomRead, atomWrite) => { + const scopedAtomStateMap = new WeakMap() + const scopedAtomStateSet = new WeakSet() + return [ + (atom, originAtomState) => { + if ( + scopedAtomStateSet.has(originAtomState as never) || + s1.has(atom) + ) { + let atomState = scopedAtomStateMap.get(atom) + if (!atomState) { + atomState = { d: new Map(), p: new Set(), n: 0 } + scopedAtomStateMap.set(atom, atomState) + scopedAtomStateSet.add(atomState) + } + return atomState + } + return getAtomState(atom, originAtomState) + }, + atomRead, + atomWrite, + ] + }, + ) + const s2Store = s1Store.unstable_derive( + (getAtomState, atomRead, atomWrite) => { + const scopedAtomStateMap = new WeakMap() + const scopedAtomStateSet = new WeakSet() + return [ + (atom, originAtomState) => { + if ( + scopedAtomStateSet.has(originAtomState as never) || + s2.has(atom) + ) { + let atomState = scopedAtomStateMap.get(atom) + if (!atomState) { + atomState = { d: new Map(), p: new Set(), n: 0 } + scopedAtomStateMap.set(atom, atomState) + scopedAtomStateSet.add(atomState) + } + return atomState + } + return getAtomState(atom, originAtomState) + }, + atomRead, + atomWrite, + ] + }, + ) + expect(getAtoms(baStore)).toEqual(['a', 'b', 'ab']) + expect(getAtoms(s1Store)).toEqual(['a', 'b', 'ab']) + expect(getAtoms(s2Store)).toEqual(['a', 'b', 'ab']) + return { baStore, s1Store, s2Store } + } + + type Store = ReturnType + function getAtoms(store: Store) { + return [ + store.get(a), + store.get(b), + store.get(c), // + ] + } + + /** + * Ba[ ]: a0, b0, c0(a0 + b0) + * S1[c]: a0, b0, c1(a1 + b1) + * S2[a]: a2, b0, c1(a2 + b1) + */ + { + // UPDATE a0 + // NOCHGE b0, a1, b1, a2 + const { baStore, s1Store, s2Store } = makeStores() + baStore.set(a, '*') + expect(getAtoms(baStore)).toEqual(['*', 'b', '*b']) + expect(getAtoms(s1Store)).toEqual(['*', 'b', 'ab']) + expect(getAtoms(s2Store)).toEqual(['a', 'b', 'ab']) + } + { + // UPDATE a0 + // NOCHGE b0, a1, b1, a2 + const { baStore, s1Store, s2Store } = makeStores() + s1Store.set(a, '*') + expect(getAtoms(baStore)).toEqual(['*', 'b', '*b']) + expect(getAtoms(s1Store)).toEqual(['*', 'b', 'ab']) + expect(getAtoms(s2Store)).toEqual(['a', 'b', 'ab']) + } + { + // UPDATE a2 + // NOCHGE a0, b0, a1, b1 + const { baStore, s1Store, s2Store } = makeStores() + s2Store.set(a, '*') + expect(getAtoms(baStore)).toEqual(['a', 'b', 'ab']) + expect(getAtoms(s1Store)).toEqual(['a', 'b', 'ab']) + expect(getAtoms(s2Store)).toEqual(['*', 'b', '*b']) + } + /** + * Ba[ ]: a0, b0, c0(a0 + b0) + * S1[c]: a0, b0, c1(a1 + b1) + * S2[a]: a2, b0, c1(a2 + b1) + */ + { + // UPDATE b0 + // NOCHGE a0, a1, b1, a2 + const { baStore, s1Store, s2Store } = makeStores() + baStore.set(b, '*') + expect(getAtoms(baStore)).toEqual(['a', '*', 'a*']) // ['a', '*', 'a*'] + expect(getAtoms(s1Store)).toEqual(['a', '*', 'ab']) // ['a', '*', 'ab'] + expect(getAtoms(s2Store)).toEqual(['a', '*', 'ab']) // ['a', '*', 'a*'] + } + { + // UPDATE b0 + // NOCHGE a0, a1, b1, a2 + const { baStore, s1Store, s2Store } = makeStores() + s1Store.set(b, '*') + expect(getAtoms(baStore)).toEqual(['a', '*', 'a*']) + expect(getAtoms(s1Store)).toEqual(['a', '*', 'ab']) + expect(getAtoms(s2Store)).toEqual(['a', '*', 'ab']) + } + { + // UPDATE b0 + // NOCHGE a0, a1, b1, a2 + const { baStore, s1Store, s2Store } = makeStores() + s2Store.set(b, '*') + expect(getAtoms(baStore)).toEqual(['a', '*', 'a*']) + expect(getAtoms(s1Store)).toEqual(['a', '*', 'ab']) + expect(getAtoms(s2Store)).toEqual(['a', '*', 'ab']) + } + }) + + /** + * a, b(a), c(b), d(c), e(d) + * S1[a]: a1, b0(a1), c0(b0(a1)), d0(c0(b0(a1))), e0(d0(c0(b0(a1)))) + * S1[b]: a0, b1(a1), c0(b1(a1)), d0(c0(b1(a1))), e0(d0(c0(b1(a1)))) + * S1[c]: a0, b0(a0), c1(b1(a1)), d0(c1(b1(a1))), e0(d0(c1(b1(a1)))) + * S1[d]: a0, b0(a0), c0(b0(a0)), d1(c1(b1(a1))), e0(d1(c1(b1(a1)))) + * S1[e]: a0, b0(a0), c0(b0(a0)), d0(c0(b0(a0))), e1(d1(c1(b1(a1)))) + */ + it('uses implicit at any distance', () => { + const a = atom('a') + const b = atom( + (get) => get(a), + (_get, set, v: string) => set(a, v), + ) + const c = atom( + (get) => get(b), + (_get, set, v: string) => set(b, v), + ) + const d = atom( + (get) => get(c), + (_get, set, v: string) => set(c, v), + ) + const e = atom( + (get) => get(d), + (_get, set, v: string) => set(d, v), + ) + const scopes = [ + new Set>([a]), + new Set>([b]), + new Set>([c]), + new Set>([d]), + new Set>([e]), + ] as const + + function makeStores(scope: Set>) { + const baseStore = createStore() + const deriStore = baseStore.unstable_derive( + (getAtomState, atomRead, atomWrite) => { + const scopedAtomStateMap = new WeakMap() + const scopedAtomStateSet = new WeakSet() + return [ + (atom, originAtomState) => { + if ( + scopedAtomStateSet.has(originAtomState as never) || + scope.has(atom) + ) { + let atomState = scopedAtomStateMap.get(atom) + if (!atomState) { + atomState = { d: new Map(), p: new Set(), n: 0 } + scopedAtomStateMap.set(atom, atomState) + scopedAtomStateSet.add(atomState) + } + return atomState + } + return getAtomState(atom, originAtomState) + }, + atomRead, + atomWrite, + ] + }, + ) + expect(getAtoms(baseStore)).toEqual(['a', 'a', 'a', 'a', 'a']) + expect(getAtoms(deriStore)).toEqual(['a', 'a', 'a', 'a', 'a']) + return { baseStore, deriStore } + } + + type Store = ReturnType + + function getAtoms(store: Store) { + return [ + store.get(a), + store.get(b), + store.get(c), + store.get(d), + store.get(e), + ] + } + + /** + * Ba[ ]: a0, b0(a0), c0(b0(a0)), d0(c0(b0(a0))), e0(d0(c0(b0(a0)))) + * S1[a]: a1, b0(a1), c0(b0(a1)), d0(c0(b0(a1))), e0(d0(c0(b0(a1)))) + */ + { + const { baseStore, deriStore } = makeStores(scopes[0]) + baseStore.set(a, '*') + expect(getAtoms(baseStore)).toEqual(['*', '*', '*', '*', '*']) + expect(getAtoms(deriStore)).toEqual(['a', 'a', 'a', 'a', 'a']) + } + /** + * Ba[ ]: a0, b0(a0), c0(b0(a0)), d0(c0(b0(a0))), e0(d0(c0(b0(a0)))) + * S1[b]: a0, b1(a1), c0(b1(a1)), d0(c0(b1(a1))), e0(d0(c0(b1(a1)))) + */ + { + const { baseStore, deriStore } = makeStores(scopes[1]) + baseStore.set(b, '*') + expect(getAtoms(baseStore)).toEqual(['*', '*', '*', '*', '*']) + expect(getAtoms(deriStore)).toEqual(['*', 'a', 'a', 'a', 'a']) + } + /** + * Ba[ ]: a0, b0(a0), c0(b0(a0)), d0(c0(b0(a0))), e0(d0(c0(b0(a0)))) + * S1[c]: a0, b0(a0), c1(b1(a1)), d0(c1(b1(a1))), e0(d0(c1(b1(a1)))) + */ + { + const { baseStore, deriStore } = makeStores(scopes[2]) + baseStore.set(c, '*') + expect(getAtoms(baseStore)).toEqual(['*', '*', '*', '*', '*']) + expect(getAtoms(deriStore)).toEqual(['*', '*', 'a', 'a', 'a']) + } + /** + * Ba[ ]: a0, b0(a0), c0(b0(a0)), d0(c0(b0(a0))), e0(d0(c0(b0(a0)))) + * S1[d]: a0, b0(a0), c0(b0(a0)), d1(c1(b1(a1))), e0(d1(c1(b1(a1)))) + */ + { + const { baseStore, deriStore } = makeStores(scopes[3]) + baseStore.set(d, '*') + expect(getAtoms(baseStore)).toEqual(['*', '*', '*', '*', '*']) + expect(getAtoms(deriStore)).toEqual(['*', '*', '*', 'a', 'a']) + } + /** + * Ba[ ]: a0, b0(a0), c0(b0(a0)), d0(c0(b0(a0))), e0(d0(c0(b0(a0)))) + * S1[e]: a0, b0(a0), c0(b0(a0)), d0(c0(b0(a0))), e1(d1(c1(b1(a1)))) + */ + { + const { baseStore, deriStore } = makeStores(scopes[4]) + baseStore.set(e, '*') + expect(getAtoms(baseStore)).toEqual(['*', '*', '*', '*', '*']) + expect(getAtoms(deriStore)).toEqual(['*', '*', '*', '*', 'a']) + } + /** + * Ba[ ]: a0, b0(a0), c0(b0(a0)), d0(c0(b0(a0))), e0(d0(c0(b0(a0)))) + * S1[a]: a1, b0(a1), c0(b0(a1)), d0(c0(b0(a1))), e0(d0(c0(b0(a1)))) + */ + { + const { baseStore, deriStore } = makeStores(scopes[0]) + deriStore.set(a, '*') + expect(getAtoms(baseStore)).toEqual(['a', 'a', 'a', 'a', 'a']) + expect(getAtoms(deriStore)).toEqual(['*', '*', '*', '*', '*']) + } + /** + * Ba[ ]: a0, b0(a0), c0(b0(a0)), d0(c0(b0(a0))), e0(d0(c0(b0(a0)))) + * S1[b]: a0, b1(a1), c0(b1(a1)), d0(c0(b1(a1))), e0(d0(c0(b1(a1)))) + */ + { + const { baseStore, deriStore } = makeStores(scopes[1]) + deriStore.set(b, '*') + expect(getAtoms(baseStore)).toEqual(['a', 'a', 'a', 'a', 'a']) + expect(getAtoms(deriStore)).toEqual(['a', '*', '*', '*', '*']) + } + /** + * Ba[ ]: a0, b0(a0), c0(b0(a0)), d0(c0(b0(a0))), e0(d0(c0(b0(a0)))) + * S1[c]: a0, b0(a0), c1(b1(a1)), d0(c1(b1(a1))), e0(d0(c1(b1(a1)))) + */ + { + const { baseStore, deriStore } = makeStores(scopes[2]) + deriStore.set(c, '*') + expect(getAtoms(baseStore)).toEqual(['a', 'a', 'a', 'a', 'a']) + expect(getAtoms(deriStore)).toEqual(['a', 'a', '*', '*', '*']) + } + /** + * Ba[ ]: a0, b0(a0), c0(b0(a0)), d0(c0(b0(a0))), e0(d0(c0(b0(a0)))) + * S1[d]: a0, b0(a0), c0(b0(a0)), d1(c1(b1(a1))), e0(d1(c1(b1(a1)))) + */ + { + const { baseStore, deriStore } = makeStores(scopes[3]) + deriStore.set(d, '*') + expect(getAtoms(baseStore)).toEqual(['a', 'a', 'a', 'a', 'a']) + expect(getAtoms(deriStore)).toEqual(['a', 'a', 'a', '*', '*']) + } + /** + * Ba[ ]: a0, b0(a0), c0(b0(a0)), d0(c0(b0(a0))), e0(d0(c0(b0(a0)))) + * S1[e]: a0, b0(a0), c0(b0(a0)), d0(c0(b0(a0))), e1(d1(c1(b1(a1)))) + */ + { + const { baseStore, deriStore } = makeStores(scopes[4]) + deriStore.set(e, '*') + expect(getAtoms(baseStore)).toEqual(['a', 'a', 'a', 'a', 'a']) + expect(getAtoms(deriStore)).toEqual(['a', 'a', 'a', 'a', '*']) + } + }) }) From 77c5594920742df24bae74403a0c7cccc9b8229a Mon Sep 17 00:00:00 2001 From: daishi Date: Tue, 1 Oct 2024 09:39:11 +0900 Subject: [PATCH 3/7] remove originAtomState --- src/vanilla/store.ts | 62 +- tests/vanilla/unstable_derive.test.tsx | 810 +------------------------ 2 files changed, 28 insertions(+), 844 deletions(-) diff --git a/src/vanilla/store.ts b/src/vanilla/store.ts index a1b54b3ec8..3ccf360190 100644 --- a/src/vanilla/store.ts +++ b/src/vanilla/store.ts @@ -210,14 +210,9 @@ const flushPending = (pending: Pending) => { } } -type GetAtomState = ( - atom: Atom, - originAtomState?: AtomState, -) => AtomState - // internal & unstable type type StoreArgs = readonly [ - getAtomState: GetAtomState, + getAtomState: (atom: Atom) => AtomState, atomRead: ( atom: Atom, ...params: Parameters['read']> @@ -275,11 +270,7 @@ const buildStore = ( if (isPromiseLike(valueOrPromise)) { patchPromiseForCancelability(valueOrPromise) for (const a of atomState.d.keys()) { - addPendingPromiseToDependency( - atom, - valueOrPromise, - getAtomState(a, atomState), - ) + addPendingPromiseToDependency(atom, valueOrPromise, getAtomState(a)) } atomState.v = valueOrPromise delete atomState.e @@ -298,9 +289,9 @@ const buildStore = ( const readAtomState = ( pending: Pending | undefined, atom: Atom, - atomState: AtomState, force?: (a: AnyAtom) => boolean, ): AtomState => { + const atomState = getAtomState(atom) // See if we can skip recomputing this atom. if (!force?.(atom) && isAtomStateInitialized(atomState)) { // If the atom is mounted, we can use the cache. @@ -315,8 +306,7 @@ const buildStore = ( ([a, n]) => // Recursively, read the atom state of the dependency, and // check if the atom epoch number is unchanged - readAtomState(pending, a, getAtomState(a, atomState), force).n === - n, + readAtomState(pending, a, force).n === n, ) ) { return atomState @@ -327,7 +317,7 @@ const buildStore = ( let isSync = true const getter: Getter = (a: Atom) => { if (isSelfAtom(atom, a)) { - const aState = getAtomState(a, atomState) + const aState = getAtomState(a) if (!isAtomStateInitialized(aState)) { if (hasInitialValue(a)) { setAtomStateValueOrPromise(a, aState, a.init) @@ -339,12 +329,7 @@ const buildStore = ( return returnAtomValue(aState) } // a !== atom - const aState = readAtomState( - pending, - a, - getAtomState(a, atomState), - force, - ) + const aState = readAtomState(pending, a, force) if (isSync) { addDependency(pending, atom, atomState, a, aState) } else { @@ -410,7 +395,7 @@ const buildStore = ( } const readAtom = (atom: Atom): Value => - returnAtomValue(readAtomState(undefined, atom, getAtomState(atom))) + returnAtomValue(readAtomState(undefined, atom)) const getDependents = ( pending: Pending, @@ -419,16 +404,16 @@ const buildStore = ( ): Map => { const dependents = new Map() for (const a of atomState.m?.t || []) { - dependents.set(a, getAtomState(a, atomState)) + dependents.set(a, getAtomState(a)) } for (const atomWithPendingPromise of atomState.p) { dependents.set( atomWithPendingPromise, - getAtomState(atomWithPendingPromise, atomState), + getAtomState(atomWithPendingPromise), ) } getPendingDependents(pending, atom)?.forEach((dependent) => { - dependents.set(dependent, getAtomState(dependent, atomState)) + dependents.set(dependent, getAtomState(dependent)) }) return dependents } @@ -482,7 +467,7 @@ const buildStore = ( } } if (hasChangedDeps) { - readAtomState(pending, a, aState, isMarked) + readAtomState(pending, a, isMarked) mountDependencies(pending, a, aState) if (prevEpochNumber !== aState.n) { addPendingAtom(pending, a, aState) @@ -496,16 +481,15 @@ const buildStore = ( const writeAtomState = ( pending: Pending, atom: WritableAtom, - atomState: AtomState, ...args: Args ): Result => { const getter: Getter = (a: Atom) => - returnAtomValue(readAtomState(pending, a, getAtomState(a, atomState))) + returnAtomValue(readAtomState(pending, a)) const setter: Setter = ( a: WritableAtom, ...args: As ) => { - const aState = getAtomState(a, atomState) + const aState = getAtomState(a) let r: R | undefined if (isSelfAtom(atom, a)) { if (!hasInitialValue(a)) { @@ -522,7 +506,7 @@ const buildStore = ( recomputeDependents(pending, a, aState) } } else { - r = writeAtomState(pending, a, aState, ...args) as R + r = writeAtomState(pending, a, ...args) as R } flushPending(pending) return r as R @@ -536,7 +520,7 @@ const buildStore = ( ...args: Args ): Result => { const pending = createPending() - const result = writeAtomState(pending, atom, getAtomState(atom), ...args) + const result = writeAtomState(pending, atom, ...args) flushPending(pending) return result } @@ -549,7 +533,7 @@ const buildStore = ( if (atomState.m && !isPendingPromise(atomState.v)) { for (const a of atomState.d.keys()) { if (!atomState.m.d.has(a)) { - const aMounted = mountAtom(pending, a, getAtomState(a, atomState)) + const aMounted = mountAtom(pending, a, getAtomState(a)) aMounted.t.add(atom) atomState.m.d.add(a) } @@ -557,7 +541,7 @@ const buildStore = ( for (const a of atomState.m.d || []) { if (!atomState.d.has(a)) { atomState.m.d.delete(a) - const aMounted = unmountAtom(pending, a, getAtomState(a, atomState)) + const aMounted = unmountAtom(pending, a, getAtomState(a)) aMounted?.t.delete(atom) } } @@ -571,10 +555,10 @@ const buildStore = ( ): Mounted => { if (!atomState.m) { // recompute atom state - readAtomState(pending, atom, atomState) + readAtomState(pending, atom) // mount dependencies first for (const a of atomState.d.keys()) { - const aMounted = mountAtom(pending, a, getAtomState(a, atomState)) + const aMounted = mountAtom(pending, a, getAtomState(a)) aMounted.t.add(atom) } // mount self @@ -591,7 +575,7 @@ const buildStore = ( const { onMount } = atom addPendingFunction(pending, () => { const onUnmount = onMount((...args) => - writeAtomState(pending, atom, atomState, ...args), + writeAtomState(pending, atom, ...args), ) if (onUnmount) { mounted.u = onUnmount @@ -610,9 +594,7 @@ const buildStore = ( if ( atomState.m && !atomState.m.l.size && - !Array.from(atomState.m.t).some((a) => - getAtomState(a, atomState).m?.d.has(atom), - ) + !Array.from(atomState.m.t).some((a) => getAtomState(a).m?.d.has(atom)) ) { // unmount self const onUnmount = atomState.m.u @@ -625,7 +607,7 @@ const buildStore = ( } // unmount dependencies for (const a of atomState.d.keys()) { - const aMounted = unmountAtom(pending, a, getAtomState(a, atomState)) + const aMounted = unmountAtom(pending, a, getAtomState(a)) aMounted?.t.delete(atom) } return undefined diff --git a/tests/vanilla/unstable_derive.test.tsx b/tests/vanilla/unstable_derive.test.tsx index eb41bca911..f82b61a0f3 100644 --- a/tests/vanilla/unstable_derive.test.tsx +++ b/tests/vanilla/unstable_derive.test.tsx @@ -17,7 +17,7 @@ describe('unstable_derive for scoping atoms', () => { (getAtomState, atomRead, atomWrite) => { const scopedAtomStateMap = new WeakMap() return [ - (atom, originAtomState) => { + (atom) => { if (scopedAtoms.has(atom)) { let atomState = scopedAtomStateMap.get(atom) if (!atomState) { @@ -26,7 +26,7 @@ describe('unstable_derive for scoping atoms', () => { } return atomState } - return getAtomState(atom, originAtomState) + return getAtomState(atom) }, atomRead, atomWrite, @@ -61,7 +61,7 @@ describe('unstable_derive for scoping atoms', () => { (getAtomState, atomRead, atomWrite) => { const scopedAtomStateMap = new WeakMap() return [ - (atom, originAtomState) => { + (atom) => { if (scopedAtoms.has(atom)) { let atomState = scopedAtomStateMap.get(atom) if (!atomState) { @@ -70,7 +70,7 @@ describe('unstable_derive for scoping atoms', () => { } return atomState } - return getAtomState(atom, originAtomState) + return getAtomState(atom) }, atomRead, atomWrite, @@ -104,7 +104,7 @@ describe('unstable_derive for scoping atoms', () => { (getAtomState, atomRead, atomWrite) => { const scopedAtomStateMap = new WeakMap() return [ - (atom, originAtomState) => { + (atom) => { if (scopedAtoms.has(atom)) { let atomState = scopedAtomStateMap.get(atom) if (!atomState) { @@ -113,7 +113,7 @@ describe('unstable_derive for scoping atoms', () => { } return atomState } - return getAtomState(atom, originAtomState) + return getAtomState(atom) }, atomRead, atomWrite, @@ -166,802 +166,4 @@ describe('unstable_derive for scoping atoms', () => { expect(derivedCallback).toHaveBeenCalledTimes(1) // FIXME: received 1 } }) - - /** - * a, b(a) - * S1[b]: a0, b1(a1) - */ - it('derived atom (scoping derived)', () => { - const a = atom('a') - const b = atom( - (get) => get(a), - (_get, set, v: string) => { - set(a, v) - }, - ) - const scopedAtoms = new Set>([b]) - - const store = createStore() - const derivedStore = store.unstable_derive( - (getAtomState, atomRead, atomWrite) => { - const scopedAtomStateMap = new WeakMap() - const scopedAtomStateSet = new WeakSet() - return [ - (atom, originAtomState) => { - if ( - scopedAtomStateSet.has(originAtomState as never) || - scopedAtoms.has(atom) - ) { - let atomState = scopedAtomStateMap.get(atom) - if (!atomState) { - atomState = { d: new Map(), p: new Set(), n: 0 } - scopedAtomStateMap.set(atom, atomState) - scopedAtomStateSet.add(atomState) - } - return atomState - } - return getAtomState(atom, originAtomState) - }, - atomRead, - atomWrite, - ] - }, - ) - - expect(store.get(a)).toBe('a') - expect(store.get(b)).toBe('a') - expect(derivedStore.get(a)).toBe('a') - expect(derivedStore.get(b)).toBe('a') - - store.set(a, 'a2') - expect(store.get(a)).toBe('a2') - expect(store.get(b)).toBe('a2') - expect(derivedStore.get(a)).toBe('a2') - expect(derivedStore.get(b)).toBe('a') - - store.set(b, 'a3') - expect(store.get(a)).toBe('a3') - expect(store.get(b)).toBe('a3') - expect(derivedStore.get(a)).toBe('a3') - expect(derivedStore.get(b)).toBe('a') - - derivedStore.set(a, 'a4') - expect(store.get(a)).toBe('a4') - expect(store.get(b)).toBe('a4') - expect(derivedStore.get(a)).toBe('a4') - expect(derivedStore.get(b)).toBe('a') - - derivedStore.set(b, 'a5') - expect(store.get(a)).toBe('a4') - expect(store.get(b)).toBe('a4') - expect(derivedStore.get(a)).toBe('a4') - expect(derivedStore.get(b)).toBe('a5') - }) - - /** - * a, b, c(a), d(c), e(d + b) - * S1[d]: a0, b0, c0(a0), d1(c1(a1)), e0(d1(c1(a1)) + b0) - */ - it('derived atom (scoping derived chain)', () => { - const a = atom('a') - const b = atom('b') - const c = atom( - (get) => get(a), - (_get, set, v: string) => set(a, v), - ) - const d = atom( - (get) => get(c), - (_get, set, v: string) => set(c, v), - ) - const e = atom( - (get) => get(d) + get(b), - (_get, set, av: string, bv: string) => { - set(d, av) - set(b, bv) - }, - ) - const scopedAtoms = new Set>([d]) - - function makeStores() { - const baseStore = createStore() - const deriStore = baseStore.unstable_derive( - (getAtomState, atomRead, atomWrite) => { - const scopedAtomStateMap = new WeakMap() - const scopedAtomStateSet = new WeakSet() - return [ - (atom, originAtomState) => { - if ( - scopedAtomStateSet.has(originAtomState as never) || - scopedAtoms.has(atom) - ) { - let atomState = scopedAtomStateMap.get(atom) - if (!atomState) { - atomState = { d: new Map(), p: new Set(), n: 0 } - scopedAtomStateMap.set(atom, atomState) - scopedAtomStateSet.add(atomState) - } - return atomState - } - return getAtomState(atom, originAtomState) - }, - atomRead, - atomWrite, - ] - }, - ) - expect(getAtoms(baseStore)).toEqual(['a', 'b', 'a', 'a', 'ab']) - expect(getAtoms(deriStore)).toEqual(['a', 'b', 'a', 'a', 'ab']) - return { baseStore, deriStore } - } - type Store = ReturnType - function getAtoms(store: Store) { - return [ - store.get(a), - store.get(b), - store.get(c), - store.get(d), - store.get(e), - ] - } - - /** - * base[ ]: a0, b0, c0(a0), d0(c0(a0)), e0(d0(c0(a0)) + b0) - * deri[d]: a0, b0, c0(a0), d1(c1(a1)), e0(d1(c1(a1)) + b0) - */ - { - // UPDATE a0 - // NOCHGE b0 and a1 - const { baseStore, deriStore } = makeStores() - baseStore.set(a, '*') - expect(getAtoms(baseStore)).toEqual(['*', 'b', '*', '*', '*b']) - expect(getAtoms(deriStore)).toEqual(['*', 'b', '*', 'a', 'ab']) - } - { - // UPDATE b0 - // NOCHGE a0 and a1 - const { baseStore, deriStore } = makeStores() - baseStore.set(b, '*') - expect(getAtoms(baseStore)).toEqual(['a', '*', 'a', 'a', 'a*']) - expect(getAtoms(deriStore)).toEqual(['a', '*', 'a', 'a', 'a*']) - } - { - // UPDATE c0, c0 -> a0 - // NOCHGE b0 and a1 - const { baseStore, deriStore } = makeStores() - baseStore.set(c, '*') - expect(getAtoms(baseStore)).toEqual(['*', 'b', '*', '*', '*b']) - expect(getAtoms(deriStore)).toEqual(['*', 'b', '*', 'a', 'ab']) - } - { - // UPDATE d0, d0 -> c0 -> a0 - // NOCHGE b0 and a1 - const { baseStore, deriStore } = makeStores() - baseStore.set(d, '*') - expect(getAtoms(baseStore)).toEqual(['*', 'b', '*', '*', '*b']) - expect(getAtoms(deriStore)).toEqual(['*', 'b', '*', 'a', 'ab']) - } - { - // UPDATE e0, e0 -> d0 -> c0 -> a0 - // └--------------> b0 - // NOCHGE a1 - const { baseStore, deriStore } = makeStores() - baseStore.set(e, '*', '*') - expect(getAtoms(baseStore)).toEqual(['*', '*', '*', '*', '**']) - expect(getAtoms(deriStore)).toEqual(['*', '*', '*', 'a', 'a*']) - } - { - // UPDATE a0 - // NOCHGE b0 and a1 - const { baseStore, deriStore } = makeStores() - deriStore.set(a, '*') - expect(getAtoms(baseStore)).toEqual(['*', 'b', '*', '*', '*b']) - expect(getAtoms(deriStore)).toEqual(['*', 'b', '*', 'a', 'ab']) - } - { - // UPDATE b0 - // NOCHGE a0 and a1 - const { baseStore, deriStore } = makeStores() - deriStore.set(b, '*') - expect(getAtoms(baseStore)).toEqual(['a', '*', 'a', 'a', 'a*']) - expect(getAtoms(deriStore)).toEqual(['a', '*', 'a', 'a', 'a*']) - } - { - // UPDATE c0, c0 -> a0 - // NOCHGE b0 and a1 - const { baseStore, deriStore } = makeStores() - deriStore.set(c, '*') - expect(getAtoms(baseStore)).toEqual(['*', 'b', '*', '*', '*b']) - expect(getAtoms(deriStore)).toEqual(['*', 'b', '*', 'a', 'ab']) - } - { - // UPDATE d1, d1 -> c1 -> a1 - // NOCHGE b0 and a0 - const { baseStore, deriStore } = makeStores() - deriStore.set(d, '*') - expect(getAtoms(baseStore)).toEqual(['a', 'b', 'a', 'a', 'ab']) - expect(getAtoms(deriStore)).toEqual(['a', 'b', 'a', '*', '*b']) - } - { - // UPDATE e0, e0 -> d1 -> c1 -> a1 - // └--------------> b0 - // NOCHGE a0 - const { baseStore, deriStore } = makeStores() - deriStore.set(e, '*', '*') - expect(getAtoms(baseStore)).toEqual(['a', '*', 'a', 'a', 'a*']) - expect(getAtoms(deriStore)).toEqual(['a', '*', 'a', '*', '**']) - } - }) - - /** - * a, b(a), c(a), d(a) - * S1[b, c]: a0, b1(a1), c1(a1), d0(a0) - */ - it('derived atom shares same implicit', () => { - const a = atom('a') - const b = atom( - (get) => get(a), - (_get, set, v: string) => set(a, v), - ) - const c = atom( - (get) => get(a), - (_get, set, v: string) => set(a, v), - ) - const d = atom( - (get) => get(a), - (_get, set, v: string) => set(a, v), - ) - const scopedAtoms = new Set>([b, c]) - - function makeStores() { - const baseStore = createStore() - const deriStore = baseStore.unstable_derive( - (getAtomState, atomRead, atomWrite) => { - const scopedAtomStateMap = new WeakMap() - const scopedAtomStateSet = new WeakSet() - return [ - (atom, originAtomState) => { - if ( - scopedAtomStateSet.has(originAtomState as never) || - scopedAtoms.has(atom) - ) { - let atomState = scopedAtomStateMap.get(atom) - if (!atomState) { - atomState = { d: new Map(), p: new Set(), n: 0 } - scopedAtomStateMap.set(atom, atomState) - scopedAtomStateSet.add(atomState) - } - return atomState - } - return getAtomState(atom, originAtomState) - }, - atomRead, - atomWrite, - ] - }, - ) - expect(getAtoms(baseStore)).toEqual(['a', 'a', 'a', 'a']) - expect(getAtoms(deriStore)).toEqual(['a', 'a', 'a', 'a']) - return { baseStore, deriStore } - } - type Store = ReturnType - function getAtoms(store: Store) { - return [store.get(a), store.get(b), store.get(c), store.get(d)] - } - - /** - * base[ ]: a0, b0(a0), c0(a0), d0(a0), '*' - * deri[b, c]: a0, b0(a0), c1(a1), d0(a0), '*' - */ - { - // UPDATE a0 - // NOCHGE a1 - const { baseStore, deriStore } = makeStores() - baseStore.set(a, '*') - expect(getAtoms(baseStore)).toEqual(['*', '*', '*', '*']) - expect(getAtoms(deriStore)).toEqual(['*', 'a', 'a', '*']) - } - { - // UPDATE b0, b0 -> a0 - // NOCHGE a1 - const { baseStore, deriStore } = makeStores() - baseStore.set(b, '*') - expect(getAtoms(baseStore)).toEqual(['*', '*', '*', '*']) - expect(getAtoms(deriStore)).toEqual(['*', 'a', 'a', '*']) - } - { - // UPDATE c0, c0 -> a0 - // NOCHGE a1 - const { baseStore, deriStore } = makeStores() - baseStore.set(b, '*') - expect(getAtoms(baseStore)).toEqual(['*', '*', '*', '*']) - expect(getAtoms(deriStore)).toEqual(['*', 'a', 'a', '*']) - } - { - // UPDATE a0 - // NOCHGE a1 - const { baseStore, deriStore } = makeStores() - deriStore.set(a, '*') - expect(getAtoms(baseStore)).toEqual(['*', '*', '*', '*']) - expect(getAtoms(deriStore)).toEqual(['*', 'a', 'a', '*']) - } - { - // UPDATE b0, b0 -> a0 - // NOCHGE a1 - const { baseStore, deriStore } = makeStores() - deriStore.set(b, '*') - expect(getAtoms(baseStore)).toEqual(['a', 'a', 'a', 'a']) - expect(getAtoms(deriStore)).toEqual(['a', '*', '*', 'a']) - } - { - // UPDATE c1, c1 -> a1 - // NOCHGE a0 - const { baseStore, deriStore } = makeStores() - deriStore.set(c, '*') - expect(getAtoms(baseStore)).toEqual(['a', 'a', 'a', 'a']) - expect(getAtoms(deriStore)).toEqual(['a', '*', '*', 'a']) - } - }) - - /** - * a, b, c(a + b) - * S1[a]: a1, b0, c0(a1 + b0) - * S2[ ]: a1, b0, c0(a1 + b0) - */ - it('inherited atoms', () => { - const a = atom('a') - const b = atom('b') - const c = atom((get) => get(a) + get(b)) - - function makeStores() { - const s1 = new Set>([a]) - const s2 = new Set>([]) - const baStore = createStore() - const s1Store = baStore.unstable_derive( - (getAtomState, atomRead, atomWrite) => { - const scopedAtomStateMap = new WeakMap() - const scopedAtomStateSet = new WeakSet() - return [ - (atom, originAtomState) => { - if ( - scopedAtomStateSet.has(originAtomState as never) || - s1.has(atom) - ) { - let atomState = scopedAtomStateMap.get(atom) - if (!atomState) { - atomState = { d: new Map(), p: new Set(), n: 0 } - scopedAtomStateMap.set(atom, atomState) - scopedAtomStateSet.add(atomState) - } - return atomState - } - return getAtomState(atom, originAtomState) - }, - atomRead, - atomWrite, - ] - }, - ) - const s2Store = s1Store.unstable_derive( - (getAtomState, atomRead, atomWrite) => { - const scopedAtomStateMap = new WeakMap() - const scopedAtomStateSet = new WeakSet() - return [ - (atom, originAtomState) => { - if ( - scopedAtomStateSet.has(originAtomState as never) || - s2.has(atom) - ) { - let atomState = scopedAtomStateMap.get(atom) - if (!atomState) { - atomState = { d: new Map(), p: new Set(), n: 0 } - scopedAtomStateMap.set(atom, atomState) - scopedAtomStateSet.add(atomState) - } - return atomState - } - return getAtomState(atom, originAtomState) - }, - atomRead, - atomWrite, - ] - }, - ) - expect(getAtoms(baStore)).toEqual(['a', 'b', 'ab']) - expect(getAtoms(s1Store)).toEqual(['a', 'b', 'ab']) - expect(getAtoms(s2Store)).toEqual(['a', 'b', 'ab']) - return { baStore, s1Store, s2Store } - } - type Store = ReturnType - function getAtoms(store: Store) { - return [store.get(a), store.get(b), store.get(c)] - } - - /** - * BA[ ]: a0, b0, c0(a0 + b0) - * S1[a]: a1, b0, c0(a1 + b0) - * S2[ ]: a1, b0, c0(a1 + b0) - */ - { - // UPDATE a0 - // NOCHGE a1, b0 - const { baStore, s1Store, s2Store } = makeStores() - baStore.set(a, '*') - expect(getAtoms(baStore)).toEqual(['*', 'b', '*b']) - expect(getAtoms(s1Store)).toEqual(['a', 'b', 'ab']) - expect(getAtoms(s2Store)).toEqual(['a', 'b', 'ab']) - } - { - // UPDATE b0 - // NOCHGE a0, a1 - const { baStore, s1Store, s2Store } = makeStores() - baStore.set(b, '*') - expect(getAtoms(baStore)).toEqual(['a', '*', 'a*']) - expect(getAtoms(s1Store)).toEqual(['a', '*', 'a*']) - expect(getAtoms(s2Store)).toEqual(['a', '*', 'a*']) - } - { - // UPDATE a1 - // NOCHGE a0, b0 - const { baStore, s1Store, s2Store } = makeStores() - s1Store.set(a, '*') - expect(getAtoms(baStore)).toEqual(['a', 'b', 'ab']) - expect(getAtoms(s1Store)).toEqual(['*', 'b', '*b']) - expect(getAtoms(s2Store)).toEqual(['*', 'b', '*b']) - } - { - // UPDATE b0 - // NOCHGE a0, a1 - const { baStore, s1Store, s2Store } = makeStores() - s1Store.set(b, '*') - expect(getAtoms(baStore)).toEqual(['a', '*', 'a*']) - expect(getAtoms(s1Store)).toEqual(['a', '*', 'a*']) - expect(getAtoms(s2Store)).toEqual(['a', '*', 'a*']) - } - { - // UPDATE a1 - // NOCHGE a0, b0 - const { baStore, s1Store, s2Store } = makeStores() - s2Store.set(a, '*') - expect(getAtoms(baStore)).toEqual(['a', 'b', 'ab']) - expect(getAtoms(s1Store)).toEqual(['*', 'b', '*b']) - expect(getAtoms(s2Store)).toEqual(['*', 'b', '*b']) - } - { - // UPDATE b0 - // NOCHGE a0, a1 - const { baStore, s1Store, s2Store } = makeStores() - s2Store.set(b, '*') - expect(getAtoms(baStore)).toEqual(['a', '*', 'a*']) - expect(getAtoms(s1Store)).toEqual(['a', '*', 'a*']) - expect(getAtoms(s2Store)).toEqual(['a', '*', 'a*']) - } - }) - - /** - * a, b, c(a + b) - * S1[c]: a0, b0, c1(a1 + b1) - * S2[a]: a0, b0, c1(a2 + b1) - */ - it('inherited atoms use explicit in current scope', () => { - const a = atom('a') - a.debugLabel = 'a' - const b = atom('b') - b.debugLabel = 'b' - const c = atom((get) => get(a) + get(b)) - c.debugLabel = 'c' - const s1 = new Set>([c]) - const s2 = new Set>([a]) - - function makeStores() { - const baStore = createStore() - const s1Store = baStore.unstable_derive( - (getAtomState, atomRead, atomWrite) => { - const scopedAtomStateMap = new WeakMap() - const scopedAtomStateSet = new WeakSet() - return [ - (atom, originAtomState) => { - if ( - scopedAtomStateSet.has(originAtomState as never) || - s1.has(atom) - ) { - let atomState = scopedAtomStateMap.get(atom) - if (!atomState) { - atomState = { d: new Map(), p: new Set(), n: 0 } - scopedAtomStateMap.set(atom, atomState) - scopedAtomStateSet.add(atomState) - } - return atomState - } - return getAtomState(atom, originAtomState) - }, - atomRead, - atomWrite, - ] - }, - ) - const s2Store = s1Store.unstable_derive( - (getAtomState, atomRead, atomWrite) => { - const scopedAtomStateMap = new WeakMap() - const scopedAtomStateSet = new WeakSet() - return [ - (atom, originAtomState) => { - if ( - scopedAtomStateSet.has(originAtomState as never) || - s2.has(atom) - ) { - let atomState = scopedAtomStateMap.get(atom) - if (!atomState) { - atomState = { d: new Map(), p: new Set(), n: 0 } - scopedAtomStateMap.set(atom, atomState) - scopedAtomStateSet.add(atomState) - } - return atomState - } - return getAtomState(atom, originAtomState) - }, - atomRead, - atomWrite, - ] - }, - ) - expect(getAtoms(baStore)).toEqual(['a', 'b', 'ab']) - expect(getAtoms(s1Store)).toEqual(['a', 'b', 'ab']) - expect(getAtoms(s2Store)).toEqual(['a', 'b', 'ab']) - return { baStore, s1Store, s2Store } - } - - type Store = ReturnType - function getAtoms(store: Store) { - return [ - store.get(a), - store.get(b), - store.get(c), // - ] - } - - /** - * Ba[ ]: a0, b0, c0(a0 + b0) - * S1[c]: a0, b0, c1(a1 + b1) - * S2[a]: a2, b0, c1(a2 + b1) - */ - { - // UPDATE a0 - // NOCHGE b0, a1, b1, a2 - const { baStore, s1Store, s2Store } = makeStores() - baStore.set(a, '*') - expect(getAtoms(baStore)).toEqual(['*', 'b', '*b']) - expect(getAtoms(s1Store)).toEqual(['*', 'b', 'ab']) - expect(getAtoms(s2Store)).toEqual(['a', 'b', 'ab']) - } - { - // UPDATE a0 - // NOCHGE b0, a1, b1, a2 - const { baStore, s1Store, s2Store } = makeStores() - s1Store.set(a, '*') - expect(getAtoms(baStore)).toEqual(['*', 'b', '*b']) - expect(getAtoms(s1Store)).toEqual(['*', 'b', 'ab']) - expect(getAtoms(s2Store)).toEqual(['a', 'b', 'ab']) - } - { - // UPDATE a2 - // NOCHGE a0, b0, a1, b1 - const { baStore, s1Store, s2Store } = makeStores() - s2Store.set(a, '*') - expect(getAtoms(baStore)).toEqual(['a', 'b', 'ab']) - expect(getAtoms(s1Store)).toEqual(['a', 'b', 'ab']) - expect(getAtoms(s2Store)).toEqual(['*', 'b', '*b']) - } - /** - * Ba[ ]: a0, b0, c0(a0 + b0) - * S1[c]: a0, b0, c1(a1 + b1) - * S2[a]: a2, b0, c1(a2 + b1) - */ - { - // UPDATE b0 - // NOCHGE a0, a1, b1, a2 - const { baStore, s1Store, s2Store } = makeStores() - baStore.set(b, '*') - expect(getAtoms(baStore)).toEqual(['a', '*', 'a*']) // ['a', '*', 'a*'] - expect(getAtoms(s1Store)).toEqual(['a', '*', 'ab']) // ['a', '*', 'ab'] - expect(getAtoms(s2Store)).toEqual(['a', '*', 'ab']) // ['a', '*', 'a*'] - } - { - // UPDATE b0 - // NOCHGE a0, a1, b1, a2 - const { baStore, s1Store, s2Store } = makeStores() - s1Store.set(b, '*') - expect(getAtoms(baStore)).toEqual(['a', '*', 'a*']) - expect(getAtoms(s1Store)).toEqual(['a', '*', 'ab']) - expect(getAtoms(s2Store)).toEqual(['a', '*', 'ab']) - } - { - // UPDATE b0 - // NOCHGE a0, a1, b1, a2 - const { baStore, s1Store, s2Store } = makeStores() - s2Store.set(b, '*') - expect(getAtoms(baStore)).toEqual(['a', '*', 'a*']) - expect(getAtoms(s1Store)).toEqual(['a', '*', 'ab']) - expect(getAtoms(s2Store)).toEqual(['a', '*', 'ab']) - } - }) - - /** - * a, b(a), c(b), d(c), e(d) - * S1[a]: a1, b0(a1), c0(b0(a1)), d0(c0(b0(a1))), e0(d0(c0(b0(a1)))) - * S1[b]: a0, b1(a1), c0(b1(a1)), d0(c0(b1(a1))), e0(d0(c0(b1(a1)))) - * S1[c]: a0, b0(a0), c1(b1(a1)), d0(c1(b1(a1))), e0(d0(c1(b1(a1)))) - * S1[d]: a0, b0(a0), c0(b0(a0)), d1(c1(b1(a1))), e0(d1(c1(b1(a1)))) - * S1[e]: a0, b0(a0), c0(b0(a0)), d0(c0(b0(a0))), e1(d1(c1(b1(a1)))) - */ - it('uses implicit at any distance', () => { - const a = atom('a') - const b = atom( - (get) => get(a), - (_get, set, v: string) => set(a, v), - ) - const c = atom( - (get) => get(b), - (_get, set, v: string) => set(b, v), - ) - const d = atom( - (get) => get(c), - (_get, set, v: string) => set(c, v), - ) - const e = atom( - (get) => get(d), - (_get, set, v: string) => set(d, v), - ) - const scopes = [ - new Set>([a]), - new Set>([b]), - new Set>([c]), - new Set>([d]), - new Set>([e]), - ] as const - - function makeStores(scope: Set>) { - const baseStore = createStore() - const deriStore = baseStore.unstable_derive( - (getAtomState, atomRead, atomWrite) => { - const scopedAtomStateMap = new WeakMap() - const scopedAtomStateSet = new WeakSet() - return [ - (atom, originAtomState) => { - if ( - scopedAtomStateSet.has(originAtomState as never) || - scope.has(atom) - ) { - let atomState = scopedAtomStateMap.get(atom) - if (!atomState) { - atomState = { d: new Map(), p: new Set(), n: 0 } - scopedAtomStateMap.set(atom, atomState) - scopedAtomStateSet.add(atomState) - } - return atomState - } - return getAtomState(atom, originAtomState) - }, - atomRead, - atomWrite, - ] - }, - ) - expect(getAtoms(baseStore)).toEqual(['a', 'a', 'a', 'a', 'a']) - expect(getAtoms(deriStore)).toEqual(['a', 'a', 'a', 'a', 'a']) - return { baseStore, deriStore } - } - - type Store = ReturnType - - function getAtoms(store: Store) { - return [ - store.get(a), - store.get(b), - store.get(c), - store.get(d), - store.get(e), - ] - } - - /** - * Ba[ ]: a0, b0(a0), c0(b0(a0)), d0(c0(b0(a0))), e0(d0(c0(b0(a0)))) - * S1[a]: a1, b0(a1), c0(b0(a1)), d0(c0(b0(a1))), e0(d0(c0(b0(a1)))) - */ - { - const { baseStore, deriStore } = makeStores(scopes[0]) - baseStore.set(a, '*') - expect(getAtoms(baseStore)).toEqual(['*', '*', '*', '*', '*']) - expect(getAtoms(deriStore)).toEqual(['a', 'a', 'a', 'a', 'a']) - } - /** - * Ba[ ]: a0, b0(a0), c0(b0(a0)), d0(c0(b0(a0))), e0(d0(c0(b0(a0)))) - * S1[b]: a0, b1(a1), c0(b1(a1)), d0(c0(b1(a1))), e0(d0(c0(b1(a1)))) - */ - { - const { baseStore, deriStore } = makeStores(scopes[1]) - baseStore.set(b, '*') - expect(getAtoms(baseStore)).toEqual(['*', '*', '*', '*', '*']) - expect(getAtoms(deriStore)).toEqual(['*', 'a', 'a', 'a', 'a']) - } - /** - * Ba[ ]: a0, b0(a0), c0(b0(a0)), d0(c0(b0(a0))), e0(d0(c0(b0(a0)))) - * S1[c]: a0, b0(a0), c1(b1(a1)), d0(c1(b1(a1))), e0(d0(c1(b1(a1)))) - */ - { - const { baseStore, deriStore } = makeStores(scopes[2]) - baseStore.set(c, '*') - expect(getAtoms(baseStore)).toEqual(['*', '*', '*', '*', '*']) - expect(getAtoms(deriStore)).toEqual(['*', '*', 'a', 'a', 'a']) - } - /** - * Ba[ ]: a0, b0(a0), c0(b0(a0)), d0(c0(b0(a0))), e0(d0(c0(b0(a0)))) - * S1[d]: a0, b0(a0), c0(b0(a0)), d1(c1(b1(a1))), e0(d1(c1(b1(a1)))) - */ - { - const { baseStore, deriStore } = makeStores(scopes[3]) - baseStore.set(d, '*') - expect(getAtoms(baseStore)).toEqual(['*', '*', '*', '*', '*']) - expect(getAtoms(deriStore)).toEqual(['*', '*', '*', 'a', 'a']) - } - /** - * Ba[ ]: a0, b0(a0), c0(b0(a0)), d0(c0(b0(a0))), e0(d0(c0(b0(a0)))) - * S1[e]: a0, b0(a0), c0(b0(a0)), d0(c0(b0(a0))), e1(d1(c1(b1(a1)))) - */ - { - const { baseStore, deriStore } = makeStores(scopes[4]) - baseStore.set(e, '*') - expect(getAtoms(baseStore)).toEqual(['*', '*', '*', '*', '*']) - expect(getAtoms(deriStore)).toEqual(['*', '*', '*', '*', 'a']) - } - /** - * Ba[ ]: a0, b0(a0), c0(b0(a0)), d0(c0(b0(a0))), e0(d0(c0(b0(a0)))) - * S1[a]: a1, b0(a1), c0(b0(a1)), d0(c0(b0(a1))), e0(d0(c0(b0(a1)))) - */ - { - const { baseStore, deriStore } = makeStores(scopes[0]) - deriStore.set(a, '*') - expect(getAtoms(baseStore)).toEqual(['a', 'a', 'a', 'a', 'a']) - expect(getAtoms(deriStore)).toEqual(['*', '*', '*', '*', '*']) - } - /** - * Ba[ ]: a0, b0(a0), c0(b0(a0)), d0(c0(b0(a0))), e0(d0(c0(b0(a0)))) - * S1[b]: a0, b1(a1), c0(b1(a1)), d0(c0(b1(a1))), e0(d0(c0(b1(a1)))) - */ - { - const { baseStore, deriStore } = makeStores(scopes[1]) - deriStore.set(b, '*') - expect(getAtoms(baseStore)).toEqual(['a', 'a', 'a', 'a', 'a']) - expect(getAtoms(deriStore)).toEqual(['a', '*', '*', '*', '*']) - } - /** - * Ba[ ]: a0, b0(a0), c0(b0(a0)), d0(c0(b0(a0))), e0(d0(c0(b0(a0)))) - * S1[c]: a0, b0(a0), c1(b1(a1)), d0(c1(b1(a1))), e0(d0(c1(b1(a1)))) - */ - { - const { baseStore, deriStore } = makeStores(scopes[2]) - deriStore.set(c, '*') - expect(getAtoms(baseStore)).toEqual(['a', 'a', 'a', 'a', 'a']) - expect(getAtoms(deriStore)).toEqual(['a', 'a', '*', '*', '*']) - } - /** - * Ba[ ]: a0, b0(a0), c0(b0(a0)), d0(c0(b0(a0))), e0(d0(c0(b0(a0)))) - * S1[d]: a0, b0(a0), c0(b0(a0)), d1(c1(b1(a1))), e0(d1(c1(b1(a1)))) - */ - { - const { baseStore, deriStore } = makeStores(scopes[3]) - deriStore.set(d, '*') - expect(getAtoms(baseStore)).toEqual(['a', 'a', 'a', 'a', 'a']) - expect(getAtoms(deriStore)).toEqual(['a', 'a', 'a', '*', '*']) - } - /** - * Ba[ ]: a0, b0(a0), c0(b0(a0)), d0(c0(b0(a0))), e0(d0(c0(b0(a0)))) - * S1[e]: a0, b0(a0), c0(b0(a0)), d0(c0(b0(a0))), e1(d1(c1(b1(a1)))) - */ - { - const { baseStore, deriStore } = makeStores(scopes[4]) - deriStore.set(e, '*') - expect(getAtoms(baseStore)).toEqual(['a', 'a', 'a', 'a', 'a']) - expect(getAtoms(deriStore)).toEqual(['a', 'a', 'a', 'a', '*']) - } - }) }) From eab2a243b3a80a1dc32d20bbc195d7e6caf413d5 Mon Sep 17 00:00:00 2001 From: daishi Date: Tue, 1 Oct 2024 09:51:15 +0900 Subject: [PATCH 4/7] feat: atomOnMount --- src/vanilla/store.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/vanilla/store.ts b/src/vanilla/store.ts index 3ccf360190..4cbf8f19ce 100644 --- a/src/vanilla/store.ts +++ b/src/vanilla/store.ts @@ -221,6 +221,10 @@ type StoreArgs = readonly [ atom: WritableAtom, ...params: Parameters['write']> ) => Result, + atomOnMount: ( + atom: WritableAtom, + setAtom: (...args: Args) => Result, + ) => OnUnmount | void, ] // for debugging purpose only @@ -251,6 +255,7 @@ const buildStore = ( getAtomState: StoreArgs[0], atomRead: StoreArgs[1], atomWrite: StoreArgs[2], + atomOnMount: StoreArgs[3], ): Store => { // for debugging purpose only let debugMountedAtoms: Set @@ -570,11 +575,10 @@ const buildStore = ( if (import.meta.env?.MODE !== 'production') { debugMountedAtoms.add(atom) } - if (isActuallyWritableAtom(atom) && atom.onMount) { + if (isActuallyWritableAtom(atom)) { const mounted = atomState.m - const { onMount } = atom addPendingFunction(pending, () => { - const onUnmount = onMount((...args) => + const onUnmount = atomOnMount(atom, (...args) => writeAtomState(pending, atom, ...args), ) if (onUnmount) { @@ -631,7 +635,7 @@ const buildStore = ( } const unstable_derive = (fn: (...args: StoreArgs) => StoreArgs) => - buildStore(...fn(getAtomState, atomRead, atomWrite)) + buildStore(...fn(getAtomState, atomRead, atomWrite, atomOnMount)) const store: Store = { get: readAtom, @@ -690,6 +694,7 @@ export const createStore = (): Store => { getAtomState, (atom, ...params) => atom.read(...params), (atom, ...params) => atom.write(...params), + (atom, ...params) => atom.onMount?.(...params), ) } From c6df116a27f57d6292dbe99a7f1d0d2396a21973 Mon Sep 17 00:00:00 2001 From: daishi Date: Tue, 1 Oct 2024 10:06:37 +0900 Subject: [PATCH 5/7] adjust test --- tests/vanilla/unstable_derive.test.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/vanilla/unstable_derive.test.tsx b/tests/vanilla/unstable_derive.test.tsx index f82b61a0f3..8f0c450a2f 100644 --- a/tests/vanilla/unstable_derive.test.tsx +++ b/tests/vanilla/unstable_derive.test.tsx @@ -14,7 +14,7 @@ describe('unstable_derive for scoping atoms', () => { const store = createStore() const derivedStore = store.unstable_derive( - (getAtomState, atomRead, atomWrite) => { + (getAtomState, atomRead, atomWrite, atomOnMount) => { const scopedAtomStateMap = new WeakMap() return [ (atom) => { @@ -30,6 +30,7 @@ describe('unstable_derive for scoping atoms', () => { }, atomRead, atomWrite, + atomOnMount, ] }, ) @@ -58,7 +59,7 @@ describe('unstable_derive for scoping atoms', () => { const store = createStore() const derivedStore = store.unstable_derive( - (getAtomState, atomRead, atomWrite) => { + (getAtomState, atomRead, atomWrite, atomOnMount) => { const scopedAtomStateMap = new WeakMap() return [ (atom) => { @@ -74,6 +75,7 @@ describe('unstable_derive for scoping atoms', () => { }, atomRead, atomWrite, + atomOnMount, ] }, ) @@ -101,7 +103,7 @@ describe('unstable_derive for scoping atoms', () => { function makeStores() { const store = createStore() const derivedStore = store.unstable_derive( - (getAtomState, atomRead, atomWrite) => { + (getAtomState, atomRead, atomWrite, atomOnMount) => { const scopedAtomStateMap = new WeakMap() return [ (atom) => { @@ -117,6 +119,7 @@ describe('unstable_derive for scoping atoms', () => { }, atomRead, atomWrite, + atomOnMount, ] }, ) From 1b694dd365a7c5ef59c7bd4d4e0804e5ee5457d5 Mon Sep 17 00:00:00 2001 From: daishi Date: Tue, 1 Oct 2024 10:16:13 +0900 Subject: [PATCH 6/7] is this too naive? --- tests/vanilla/unstable_derive.test.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/vanilla/unstable_derive.test.tsx b/tests/vanilla/unstable_derive.test.tsx index 8f0c450a2f..f207d22f59 100644 --- a/tests/vanilla/unstable_derive.test.tsx +++ b/tests/vanilla/unstable_derive.test.tsx @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from 'vitest' import { atom, createStore } from 'jotai/vanilla' -import type { Atom } from 'jotai/vanilla' +import type { Atom, Getter } from 'jotai/vanilla' describe('unstable_derive for scoping atoms', () => { /** @@ -117,7 +117,15 @@ describe('unstable_derive for scoping atoms', () => { } return getAtomState(atom) }, - atomRead, + (a, get, options) => { + const myGet: Getter = (aa) => { + if (scopedAtoms.has(aa)) { + scopedAtoms.add(a) // Is this too naive? + } + return get(aa) + } + return atomRead(a, myGet, options) + }, atomWrite, atomOnMount, ] From d9698c422c04fb6bbbe1add4a1aee50e5b0eac6e Mon Sep 17 00:00:00 2001 From: Daishi Kato Date: Wed, 2 Oct 2024 07:34:19 +0900 Subject: [PATCH 7/7] Update src/vanilla/store.ts Co-authored-by: David Maskasky --- src/vanilla/store.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/vanilla/store.ts b/src/vanilla/store.ts index 4cbf8f19ce..ace48d9e4e 100644 --- a/src/vanilla/store.ts +++ b/src/vanilla/store.ts @@ -252,10 +252,7 @@ export type INTERNAL_DevStoreRev4 = DevStoreRev4 export type INTERNAL_PrdStore = PrdStore const buildStore = ( - getAtomState: StoreArgs[0], - atomRead: StoreArgs[1], - atomWrite: StoreArgs[2], - atomOnMount: StoreArgs[3], + ...[getAtomState, atomRead, atomWrite, atomOnMount]: StoreArgs ): Store => { // for debugging purpose only let debugMountedAtoms: Set