UNPKG

@zeix/cause-effect

Version:

Cause & Effect - reactive state management with signals.

61 lines (56 loc) 1.69 kB
import { type Signal, match } from './signal' import { CircularDependencyError, isFunction, toError } from './util' import { watch, type Watcher } from './scheduler' /* === Types === */ export type TapMatcher<T extends {}> = { ok: (value: T) => void | (() => void) err?: (error: Error) => void | (() => void) nil?: () => void | (() => void) } export type EffectMatcher<S extends Signal<{}>[]> = { signals: S ok: (...values: { [K in keyof S]: S[K] extends Signal<infer T> ? T : never }) => void | (() => void) err?: (...errors: Error[]) => void | (() => void) nil?: () => void | (() => void) } /* === Exported Functions === */ /** * Define what happens when a reactive state changes * * @since 0.1.0 * @param {EffectMatcher<S> | (() => void | (() => void))} matcher - effect matcher or callback * @returns {() => void} - cleanup function for the effect */ export function effect<S extends Signal<{}>[]>( matcher: EffectMatcher<S> | (() => void | (() => void)) ): () => void { const { signals, ok, err = console.error, nil = () => {} } = isFunction(matcher) ? { signals: [] as unknown as S, ok: matcher } : matcher let running = false const run = (() => watch(() => { if (running) throw new CircularDependencyError('effect') running = true let cleanup: void | (() => void) = undefined try { cleanup = match<S, void | (() => void)>({ signals, ok, err, nil }) as void | (() => void) } catch (e) { err(toError(e)) } if (isFunction(cleanup)) run.cleanups.add(cleanup) running = false }, run)) as Watcher run.cleanups = new Set() run() return () => { run.cleanups.forEach((fn: () => void) => fn()) run.cleanups.clear() } }