Skip to content

Commit

Permalink
feat!: implement untrack and rework injectors (#170)
Browse files Browse the repository at this point in the history
@affects atoms, react, stores
  • Loading branch information
bowheart authored Feb 8, 2025
1 parent 9277efa commit ec49c84
Show file tree
Hide file tree
Showing 29 changed files with 432 additions and 400 deletions.
2 changes: 1 addition & 1 deletion packages/atoms/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ On top of this, `@zedux/atoms` exports the following APIs and many helper types
- [`injectCallback()`](https://omnistac.github.io/zedux/docs/api/injectors/injectCallback)
- [`injectEcosystem()`](https://omnistac.github.io/zedux/docs/api/injectors/injectEcosystem)
- [`injectEffect()`](https://omnistac.github.io/zedux/docs/api/injectors/injectEffect)
- [`injectInvalidate()`](https://omnistac.github.io/zedux/docs/api/injectors/injectInvalidate)
- [`injectMappedSignal()`](https://omnistac.github.io/zedux/docs/api/injectors/injectMappedSignal)
- [`injectMemo()`](https://omnistac.github.io/zedux/docs/api/injectors/injectMemo)
- [`injectPromise()`](https://omnistac.github.io/zedux/docs/api/injectors/injectPromise)
Expand All @@ -90,6 +89,7 @@ On top of this, `@zedux/atoms` exports the following APIs and many helper types
- [`getEcosystem()`](https://omnistac.github.io/zedux/docs/api/utils/internal-utils#getecosystem)
- [`getInternals()`](https://omnistac.github.io/zedux/docs/api/utils/internal-utils#getinternals)
- [`setInternals()`](https://omnistac.github.io/zedux/docs/api/utils/internal-utils#setinternals)
- [`untrack()`](https://omnistac.github.io/zedux/docs/api/utils/internal-utils#untrack)
- [`wipe()`](https://omnistac.github.io/zedux/docs/api/utils/internal-utils#wipe)

## For Authors
Expand Down
95 changes: 64 additions & 31 deletions packages/atoms/src/classes/instances/AtomInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
AtomGenericsToAtomApiGenerics,
Cleanup,
ExportsInfusedSetter,
LifecycleStatus,
PromiseState,
PromiseStatus,
DehydrationFilter,
Expand All @@ -33,7 +32,6 @@ import {
getInitialPromiseState,
getSuccessPromiseState,
} from '@zedux/atoms/utils/promiseUtils'
import { InjectorDescriptor } from '@zedux/atoms/utils/types'
import { Ecosystem } from '../Ecosystem'
import { AtomApi } from '../AtomApi'
import { AtomTemplateBase } from '../templates/AtomTemplateBase'
Expand All @@ -60,6 +58,33 @@ import {
sendImplicitEcosystemEvent,
} from '@zedux/atoms/utils/events'

export type InjectorDescriptor<T = any> = {
/**
* `c`leanup - tracks cleanup functions, e.g. those returned from
* `injectEffect` callbacks.
*/
c: (() => void) | undefined

/**
* `i`nit - a callback that we need to call immediately after evaluation. This
* is how `injectEffect` works (without `synchronous: true`).
*/
i: (() => void) | undefined

/**
* `t`ype - a unique injector name string. This is how we ensure the user
* didn't add, remove, or reorder injector calls in the state factory.
*/
t: string

/**
* `v`alue - can be anything. For `injectRef`, this is the ref object. For
* `injectMemo` and `injectEffect`, this keeps track of the memoized value
* and/or dependency arrays.
*/
v: T
}

/**
* A standard atom's value can be one of:
*
Expand Down Expand Up @@ -178,11 +203,6 @@ export class AtomInstance<
> extends Signal<G> {
public static $$typeof = Symbol.for(`${prefix}/AtomInstance`)

/**
* @see Signal.l
*/
public l: LifecycleStatus = 'Initializing'

public api?: AtomApi<AtomGenericsToAtomApiGenerics<G>>

// @ts-expect-error this is set in `this.i`nit, right after instantiation, so
Expand All @@ -198,20 +218,30 @@ export class AtomInstance<
*/
public a: boolean | undefined = undefined

/**
* @see Signal.c
*/
public c?: Cleanup

/**
* `I`njectors - tracks injector calls from the last time the state factory
* ran. Initialized on-demand
*/
public I: InjectorDescriptor[] | undefined = undefined

/**
* `N`extInjectors - tracks injector calls as they're made during evaluation
*/
public N: InjectorDescriptor[] | undefined = undefined

/**
* `S`ignal - the signal returned from this atom's state factory. If this is
* undefined, no signal was returned, and this atom itself becomes the signal.
* If this is defined, this atom becomes a thin wrapper around this signal.
*/
public S?: Signal<G>

/**
* @see Signal.c
*/
public c?: Cleanup
public _injectors?: InjectorDescriptor[]
public _isEvaluating?: boolean
public _nextInjectors?: InjectorDescriptor[]
public _promiseError?: Error
public _promiseStatus?: PromiseStatus

Expand Down Expand Up @@ -242,20 +272,11 @@ export class AtomInstance<
public destroy(force?: boolean) {
if (!destroyNodeStart(this, force)) return

// Clean up effect injectors first, then everything else
const nonEffectInjectors: InjectorDescriptor[] = []

this._injectors?.forEach(injector => {
if (injector.type !== '@@zedux/effect') {
nonEffectInjectors.push(injector)
return
if (this.I) {
for (const injector of this.I) {
injector.c?.()
}
injector.cleanup?.()
})

nonEffectInjectors.forEach(injector => {
injector.cleanup?.()
})
}

destroyNodeFinish(this)
}
Expand Down Expand Up @@ -386,7 +407,6 @@ export class AtomInstance<
return
}

this._nextInjectors = []
this._isEvaluating = true
const prevNode = startBuffer(this)

Expand Down Expand Up @@ -434,10 +454,13 @@ export class AtomInstance<
}
}
} catch (err) {
this._nextInjectors.forEach(injector => {
injector.cleanup?.()
})
if (this.N) {
for (const injector of this.N) {
injector.c?.()
}
}

this.N = undefined
destroyBuffer(prevNode)

throw err
Expand All @@ -456,7 +479,17 @@ export class AtomInstance<
this.w = []
}

this._injectors = this._nextInjectors
// kick off side effects and store the new injectors
if (this.N) {
if (!this.e.ssr) {
for (const injector of this.N) {
injector.i?.()
}
}

this.I = this.N
this.N = undefined
}

// let this.i flush updates after status is set to Active
this.l === 'Initializing' || flushBuffer(prevNode)
Expand Down
47 changes: 0 additions & 47 deletions packages/atoms/src/factories/createInjector.ts

This file was deleted.

3 changes: 1 addition & 2 deletions packages/atoms/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { createInjector } from './factories/createInjector'
import {
destroyBuffer,
flushBuffer,
Expand All @@ -18,12 +17,12 @@ export * from './factories/index'
export * from './injectors/index'
export { getEcosystem, getInternals, setInternals, wipe } from './store/index'
export * from './types/index'
export { untrack } from './utils/evaluationContext'

// These are very obfuscated on purpose. Don't use! They're for Zedux packages.
export const zi = {
a: scheduleStaticDependents,
b: destroyNodeStart,
c: createInjector,
d: destroyBuffer,
e: destroyNodeFinish,
i: sendImplicitEcosystemEvent,
Expand Down
1 change: 0 additions & 1 deletion packages/atoms/src/injectors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ export * from './injectAtomValue'
export * from './injectCallback'
export * from './injectEcosystem'
export * from './injectEffect'
export * from './injectInvalidate'
export * from './injectMappedSignal'
export * from './injectMemo'
export * from './injectPromise'
Expand Down
5 changes: 2 additions & 3 deletions packages/atoms/src/injectors/injectAtomGetters.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { Ecosystem } from '../classes/Ecosystem'
import { readInstance } from '../utils/evaluationContext'
import { injectEcosystem } from './injectEcosystem'

/**
* injectAtomGetters
Expand Down Expand Up @@ -31,4 +30,4 @@ import { readInstance } from '../utils/evaluationContext'
*
* @see Ecosystem
*/
export const injectAtomGetters = (): Ecosystem => readInstance().e
export const injectAtomGetters = injectEcosystem
4 changes: 2 additions & 2 deletions packages/atoms/src/injectors/injectAtomInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
ParamsOf,
Selectable,
} from '../types/index'
import { readInstance } from '../utils/evaluationContext'
import { injectSelf } from './injectSelf'

const defaultOperation = 'injectAtomInstance'

Expand Down Expand Up @@ -65,7 +65,7 @@ export const injectAtomInstance: {
params?: ParamsOf<A>,
config?: InjectAtomInstanceConfig
) =>
readInstance().e.getNode(template, params, {
injectSelf().e.getNode(template, params, {
f: config?.subscribe ? Eventless : EventlessStatic,
op: config?.operation || defaultOperation,
})
8 changes: 4 additions & 4 deletions packages/atoms/src/injectors/injectAtomSelector.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { ParamsOf, Selectable, StateOf } from '../types/index'
import { readInstance } from '../utils/evaluationContext'
import { injectSelf } from './injectSelf'

/**
* @deprecated use `injectAtomValue` instead
*
* ```ts
* injectAtomSelector(mySelector, arg1, arg2) // before
* injectAtomValue(mySelector, [arg1, arg2]) // after
* ```
*
* @deprecated use `injectAtomValue` instead
*/
export const injectAtomSelector = <S extends Selectable>(
selectable: S,
...args: ParamsOf<S>
): StateOf<S> => readInstance().e.get(selectable, args)
): StateOf<S> => injectSelf().e.get(selectable, args)
4 changes: 2 additions & 2 deletions packages/atoms/src/injectors/injectEcosystem.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Ecosystem } from '../classes/Ecosystem'
import { readInstance } from '../utils/evaluationContext'
import { injectSelf } from './injectSelf'

/**
* injectEcosystem
Expand Down Expand Up @@ -29,4 +29,4 @@ import { readInstance } from '../utils/evaluationContext'
*
* @see Ecosystem
*/
export const injectEcosystem = (): Ecosystem => readInstance().e
export const injectEcosystem = (): Ecosystem => injectSelf().e
Loading

0 comments on commit ec49c84

Please sign in to comment.