Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: dev store with unstable_derive #2852

Merged
merged 14 commits into from
Dec 23, 2024
134 changes: 85 additions & 49 deletions src/vanilla/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ type DevStoreRev4 = {
dev4_restore_atoms: (values: Iterable<readonly [AnyAtom, AnyValue]>) => void
}

type PrdStore = {
type Store = {
get: <Value>(atom: Atom<Value>) => Value
set: <Value, Args extends unknown[], Result>(
atom: WritableAtom<Value, Args, Result>,
Expand All @@ -283,21 +283,12 @@ type PrdStore = {
unstable_derive: (fn: (...args: StoreArgs) => StoreArgs) => Store
}

type Store = PrdStore | (PrdStore & DevStoreRev4)

export type INTERNAL_DevStoreRev4 = DevStoreRev4
export type INTERNAL_PrdStore = PrdStore
export type INTERNAL_PrdStore = Store

const buildStore = (
...[getAtomState, atomRead, atomWrite, atomOnMount]: StoreArgs
): Store => {
// for debugging purpose only
let debugMountedAtoms: Set<AnyAtom>

if (import.meta.env?.MODE !== 'production') {
debugMountedAtoms = new Set()
}

const setAtomStateValueOrPromise = (
atom: AnyAtom,
atomState: AtomState,
Expand Down Expand Up @@ -636,9 +627,6 @@ const buildStore = (
d: new Set(atomState.d.keys()),
t: new Set(),
}
if (import.meta.env?.MODE !== 'production') {
debugMountedAtoms.add(atom)
}
if (isActuallyWritableAtom(atom)) {
const mounted = atomState.m
let setAtom: (...args: unknown[]) => unknown
Expand Down Expand Up @@ -688,9 +676,6 @@ const buildStore = (
addBatchFuncLow(batch, () => onUnmount(batch))
}
delete atomState.m
if (import.meta.env?.MODE !== 'production') {
debugMountedAtoms.delete(atom)
}
// unmount dependencies
for (const a of atomState.d.keys()) {
const aMounted = unmountAtom(batch, a, getAtomState(a))
Expand Down Expand Up @@ -725,43 +710,90 @@ const buildStore = (
sub: subscribeAtom,
unstable_derive,
}
if (import.meta.env?.MODE !== 'production') {
const devStore: DevStoreRev4 = {
// store dev methods (these are tentative and subject to change without notice)
dev4_get_internal_weak_map: () => ({
get: (atom) => {
const atomState = getAtomState(atom)
if (atomState.n === 0) {
// for backward compatibility
return undefined
return store
}

const deriveDevStore = (store: Store): Store & DevStoreRev4 => {
const proxyAtomStateMap = new WeakMap()
const debugMountedAtoms = new Set<AnyAtom>()
let savedGetAtomState: StoreArgs[0]
let inRestoreAtom = false
const derivedStore = store.unstable_derive(
(getAtomState, atomRead, atomWrite, atomOnMount) => {
dmaskasky marked this conversation as resolved.
Show resolved Hide resolved
savedGetAtomState = getAtomState
return [
(atom) => {
let proxyAtomState = proxyAtomStateMap.get(atom)
if (!proxyAtomState) {
const atomState = getAtomState(atom)
proxyAtomState = new Proxy(atomState, {
set(target, prop, value) {
if (prop === 'm') {
debugMountedAtoms.add(atom)
}
return Reflect.set(target, prop, value)
},
deleteProperty(target, prop) {
if (prop === 'm') {
debugMountedAtoms.delete(atom)
}
return Reflect.deleteProperty(target, prop)
},
})
proxyAtomStateMap.set(atom, proxyAtomState)
}
return atomState
return proxyAtomState
},
}),
dev4_get_mounted_atoms: () => debugMountedAtoms,
dev4_restore_atoms: (values) => {
const batch = createBatch()
for (const [atom, value] of values) {
if (hasInitialValue(atom)) {
const atomState = getAtomState(atom)
const prevEpochNumber = atomState.n
setAtomStateValueOrPromise(atom, atomState, value)
mountDependencies(batch, atom, atomState)
if (prevEpochNumber !== atomState.n) {
registerBatchAtom(batch, atom, atomState)
recomputeDependents(batch, atom, atomState)
}
atomRead,
(atom, getter, setter, ...args) => {
if (inRestoreAtom) {
return setter(atom, ...args)
}
return atomWrite(atom, getter, setter, ...args)
},
atomOnMount,
]
},
)
const savedStoreSet = derivedStore.set
const devStore: DevStoreRev4 = {
// store dev methods (these are tentative and subject to change without notice)
dev4_get_internal_weak_map: () => ({
get: (atom) => {
const atomState = savedGetAtomState(atom)
if (atomState.n === 0) {
// for backward compatibility
return undefined
}
flushBatch(batch)
return atomState
},
}
Object.assign(store, devStore)
}),
dev4_get_mounted_atoms: () => debugMountedAtoms,
dev4_restore_atoms: (values) => {
const restoreAtom: WritableAtom<null, [], void> = {
read: () => null,
write: (_get, set) => {
if (inRestoreAtom) {
throw new Error('[Bug] restoreAtoms cannot be called recursively')
}
inRestoreAtom = true
for (const [atom, value] of values) {
if (hasInitialValue(atom)) {
set(atom as never, value)
}
}
inRestoreAtom = false
},
}
savedStoreSet(restoreAtom)
},
}
return store
return Object.assign(derivedStore, devStore)
}

export const createStore = (): Store => {
type PrdOrDevStore = Store | (Store & DevStoreRev4)
dmaskasky marked this conversation as resolved.
Show resolved Hide resolved

export const createStore = (): PrdOrDevStore => {
const atomStateMap = new WeakMap()
const getAtomState = <Value>(atom: Atom<Value>) => {
if (import.meta.env?.MODE !== 'production' && !atom) {
Expand All @@ -774,17 +806,21 @@ export const createStore = (): Store => {
}
return atomState
}
return buildStore(
const store = buildStore(
getAtomState,
(atom, ...params) => atom.read(...params),
(atom, ...params) => atom.write(...params),
(atom, ...params) => atom.onMount?.(...params),
)
if (import.meta.env?.MODE !== 'production') {
return deriveDevStore(store)
}
return store
}

let defaultStore: Store | undefined
let defaultStore: PrdOrDevStore | undefined

export const getDefaultStore = (): Store => {
export const getDefaultStore = (): PrdOrDevStore => {
if (!defaultStore) {
defaultStore = createStore()
if (import.meta.env?.MODE !== 'production') {
Expand Down
Loading