UNPKG

@zedux/atoms

Version:

A Molecular State Engine for React

345 lines (293 loc) 9.63 kB
import { ActionChain, Observable, Settable } from '@zedux/core' import { AtomApi } from '../classes/AtomApi' import { Ecosystem } from '../classes/Ecosystem' import { SelectorCache } from '../classes/Selectors' import { AnyAtomInstance, AnyAtomTemplate, AtomGenerics, AtomGenericsToAtomApiGenerics, AtomInstanceType, AtomParamsType, AtomStateType, } from './atoms' export * from './atoms' // eslint-disable-next-line @typescript-eslint/ban-types export type AnyNonNullishValue = {} export interface AtomConfig<State = any> { dehydrate?: (state: State) => any flags?: string[] hydrate?: (dehydratedState: unknown) => State manualHydration?: boolean ttl?: number } /** * The AtomGettersBase interface. You probably won't want to use this directly. * Use AtomGetters instead. */ export interface AtomGettersBase { /** * Registers a dynamic graph edge on the resolved atom instance when called * synchronously during atom or AtomSelector evaluation. When called * asynchronously, is just an alias for `ecosystem.get` */ get<A extends AnyAtomTemplate>( template: A, params: AtomParamsType<A> ): AtomStateType<A> get<A extends AnyAtomTemplate<{ Params: [] }>>(template: A): AtomStateType<A> get<A extends AnyAtomTemplate>( template: ParamlessTemplate<A> ): AtomStateType<A> get<I extends AnyAtomInstance>(instance: I): AtomStateType<I> /** * Registers a static graph edge on the resolved atom instance when called * synchronously during atom or AtomSelector evaluation. When called * asynchronously, is just an alias for `ecosystem.getInstance` */ getInstance<A extends AnyAtomTemplate>( template: A, params: AtomParamsType<A>, edgeInfo?: GraphEdgeInfo ): AtomInstanceType<A> getInstance<A extends AnyAtomTemplate<{ Params: [] }>>( template: A ): AtomInstanceType<A> getInstance<A extends AnyAtomTemplate>( template: ParamlessTemplate<A> ): AtomInstanceType<A> getInstance<I extends AnyAtomInstance>( instance: I, params?: [], edgeInfo?: GraphEdgeInfo ): I /** * Runs an AtomSelector which receives its own AtomGetters object and can use * those to register its own dynamic and/or static graph edges (when called * synchronously during the AtomSelector's evaluation) * * ```ts * const mySelector = ion('mySelector', ({ select }) => { * // registers a dynamic dependency on myAtom: * const dynamicVal = select(({ get }) => get(myAtom)) * * injectEffect(() => { * // doesn't register anything: * const staticVal = select(({ get }) => get(myAtom)) * }, []) // no need to pass select as a dep; it's a stable reference * }) * ``` * * @see AtomSelector */ select<T, Args extends any[]>( selectorOrConfigOrCache: Selectable<T, Args>, ...args: Args ): T } /** * AtomGetters are used all throughout Zedux. When called synchronously during * atom or AtomSelector evaluation, they register graph edges. When called * asynchronously, they're just aliases for the corresponding ecosystem method. * * ```ts * const mySelector = ion('mySelector', ({ ecosystem, get }) => { * const dynamicVal = get(myAtom) // registers graph edge * const staticVal = ecosystem.get(myAtom) // doesn't register anything * * injectEffect(() => { * const staticVal2 = get(myAtom) // doesn't register anything * // const staticVal2 = ecosystem.get(myAtom) // same exact thing * }, []) * }) * ``` */ export interface AtomGetters extends AtomGettersBase { /** * A reference to the ecosystem of the current atom instance or AtomSelector. * * The ecosystem itself has `get`, `getInstance`, and `select` methods which * can be used instead of the other AtomGetters to prevent graph dependencies * from being registered. * * ```ts * // the current component will NOT rerender when myAtom changes: * const staticVal = useAtomSelector(({ ecosystem }) => ecosystem.get(myAtom)) * * // the current component will rerender when myAtom changes: * const dynamicVal = useAtomSelector(({ get }) => get(myAtom)) * ``` */ ecosystem: Ecosystem } export type AtomInstanceTtl = number | Promise<any> | Observable<any> export type AtomSelector<T = any, Args extends any[] = []> = ( getters: AtomGetters, ...args: Args ) => T export interface AtomSelectorConfig<T = any, Args extends any[] = []> { argsComparator?: (newArgs: Args, oldArgs: Args) => boolean name?: string resultsComparator?: (newResult: T, oldResult: T) => boolean selector: AtomSelector<T, Args> } export type AtomSelectorOrConfig<T = any, Args extends any[] = []> = | AtomSelector<T, Args> | AtomSelectorConfig<T, Args> export type AtomStateFactory<G extends AtomGenerics> = ( ...params: G['Params'] ) => AtomApi<AtomGenericsToAtomApiGenerics<G>> | G['Store'] | G['State'] export type AtomTuple<A extends AnyAtomTemplate> = [A, AtomParamsType<A>] export type AtomValueOrFactory<G extends AtomGenerics> = | AtomStateFactory<G> | G['Store'] | G['State'] export type Cleanup = () => void export type DependentCallback = ( signal: GraphEdgeSignal, val?: any, reason?: EvaluationReason ) => any export interface DependentEdge { callback?: DependentCallback createdAt: number dependentKey?: string flags: number // calculated from the EdgeFlags isMaterialized?: boolean operation: string prevEdge?: WeakRef<DependentEdge> task?: () => void // for external edges - so they can unschedule jobs } export interface EcosystemConfig< Context extends Record<string, any> | undefined = any > { atomDefaults?: { ttl?: number } complexParams?: boolean context?: Context destroyOnUnmount?: boolean flags?: string[] id?: string onReady?: ( ecosystem: Ecosystem<Context>, prevContext?: Context ) => MaybeCleanup overrides?: AnyAtomTemplate[] ssr?: boolean } export interface EcosystemGraphNode { dependencies: Map<string, true> dependents: Map<string, DependentEdge> isSelector?: boolean refCount: number weight: number } export type EffectCallback = () => MaybeCleanup | Promise<any> export interface EvaluationReason<State = any> { action?: ActionChain newState?: State oldState?: State operation: string // e.g. a method like "injectValue" sourceType: EvaluationSourceType sourceId?: string // e.g. a fully-qualified atom instance id like "myAtom-[0]" reasons?: EvaluationReason[] type: EvaluationType } export type EvaluationSourceType = | 'Atom' | 'AtomSelector' | 'External' | 'Injector' | 'Store' export type EvaluationType = | 'cache invalidated' | 'node destroyed' | 'promise changed' | 'state changed' export type ExportsInfusedSetter<State, Exports> = Exports & { (settable: Settable<State>, meta?: any): State } export type GraphEdgeInfo = [ // these flags are calculated from EdgeFlags: flags: number, operation: string ] /** * A low-level detail that tells dependents what sort of event is causing the * current update. Promise changes and state updates are lumped together as * 'Update' signals. If you need to distinguish between them, look at the * EvaluationType (the `type` field) in the full reasons list. */ export type GraphEdgeSignal = 'Destroyed' | 'Updated' export interface GraphViewRecursive { [key: string]: GraphViewRecursive } export interface InjectAtomInstanceConfig { operation?: string subscribe?: boolean } export type InjectorDeps = any[] | undefined export type InjectOrUseSelector<State, Params extends any[]> = Params extends [] ? <D = any>(selector: (state: State) => D) => D : <D = any>(params: Params, selector: (state: State) => D) => D export interface InjectPromiseConfig<T = any> { dataOnly?: boolean initialState?: T runOnInvalidate?: boolean } export interface InjectStoreConfig { hydrate?: boolean subscribe?: boolean } export type IonStateFactory<G extends AtomGenerics> = ( getters: AtomGetters, ...params: G['Params'] ) => AtomApi<AtomGenericsToAtomApiGenerics<G>> | G['Store'] | G['State'] export type LifecycleStatus = 'Active' | 'Destroyed' | 'Initializing' | 'Stale' export type MaybeCleanup = Cleanup | void export interface MutableRefObject<T = any> { current: T } /** * Many Zedux APIs make the `params` parameter optional if the atom doesn't take * params or has only optional params. */ export type ParamlessTemplate<A extends AnyAtomTemplate> = AtomParamsType<A> extends [AnyNonNullishValue | undefined | null, ...any[]] ? never : A /** * Part of the atom instance can be accessed during initial evaluation. The only * fields that are inaccessible are those that don't exist yet 'cause the * initial evaluation is supposed to create them. */ export type PartialAtomInstance = Omit< AnyAtomInstance, 'api' | 'exports' | 'promise' | 'store' > // from Matt Pocock https://twitter.com/mattpocockuk/status/1622730173446557697 export type Prettify<T> = { [K in keyof T]: T[K] } & AnyNonNullishValue export interface PromiseState<T> { data?: T error?: Error isError: boolean isLoading: boolean isSuccess: boolean status: PromiseStatus } export type PromiseStatus = 'error' | 'loading' | 'success' export type Ref<T = any> = MutableRefObject<T> export interface RefObject<T = any> { readonly current: T | null } export type Selectable<T = any, Args extends any[] = []> = | AtomSelector<T, Args> | AtomSelectorConfig<T, Args> | SelectorCache<T, Args> export type StateHookTuple<State, Exports> = [ State, ExportsInfusedSetter<State, Exports> ]