import type { Flags } from "./flags.ts" import { flags } from "./flags.ts" import { createReactiveSystem } from "./reactive-system.ts" import type { Link, Node, ReactiveSystem } from "./reactive-system.ts" export interface BaseReactorOptions { name?: string | undefined onDepChanged?: (() => void) | undefined onSubChanged?: (() => void) | undefined onHasDep?: ((dep: BaseReactor) => void) | undefined onNoDep?: ((dep: BaseReactor) => void) | undefined onHasSub?: ((sub: BaseReactor) => void) | undefined onNoSub?: ((sub: BaseReactor) => void) | undefined onDispose?: (() => void) | undefined } export abstract class BaseReactor implements Node { headDepLink?: Link | undefined tailDepLink?: Link | undefined headSubLink?: Link | undefined tailSubLink?: Link | undefined flags: Flags protected value: V protected _name: string | undefined protected _onDepChanged?: (() => void) | undefined protected _onSubChanged?: (() => void) | undefined protected _onHasDep?: ((dep: BaseReactor) => void) | undefined protected _onNoDep?: ((dep: BaseReactor) => void) | undefined protected _onHasSub?: ((sub: BaseReactor) => void) | undefined protected _onNoSub?: ((sub: BaseReactor) => void) | undefined protected _onDispose?: (() => void) | undefined constructor(options: BaseReactorOptions) { this.flags = flags() // undefined 可能是使用者期待的合法值,因此这里使用 VOID 占位 this.value = VOID as V this._name = options.name this._onDepChanged = options.onDepChanged this._onSubChanged = options.onSubChanged this._onHasDep = options.onHasDep this._onNoDep = options.onNoDep this._onHasSub = options.onHasSub this._onNoSub = options.onNoSub this._onDispose = options.onDispose } getName(): string | undefined { return this._name } setName(name: string | undefined): void { this._name = name } abstract trackThis(): void /** * @description Reactor 在运行过程中状态可能会发生变化,此方法用于将状态重置。比如: * 在错误发生时调用。 */ abstract reset(): void /** * @description 删除当前 Reactor 与其它 Reactor 之间的所有连接,将状态重置,删除有必要删除的资源, * 目标是使当前 Reactor 可以被安全地丢弃而不会引起内存泄漏。 */ abstract dispose(): void triggerOnDepChanged(): void { this._onDepChanged?.() } triggerOnSubChanged(): void { this._onSubChanged?.() } triggerOnHasDep(dep: BaseReactor): void { this._onHasDep?.(dep) } triggerOnNoDep(dep: BaseReactor): void { this._onNoDep?.(dep) } triggerOnHasSub(sub: BaseReactor): void { this._onHasSub?.(sub) } triggerOnNoSub(sub: BaseReactor): void { this._onNoSub?.(sub) this.dispose() } triggerOnDispose(): void { this._onDispose?.() } } const VOID = Symbol("void") as unknown const isVoid = (value: unknown): boolean => { return value === VOID } let queue: Effect[] = [] const flush = (): void => { const tempFlushQueue = queue queue = [] try { while (tempFlushQueue.length !== 0) { const effect = tempFlushQueue.shift()! effect.run() } } catch { while (tempFlushQueue.length !== 0) { const effect = tempFlushQueue.shift()! effect.reset() } } } let batchDepth = 0 export const flushBatch = (): void => { if (batchDepth === 0) { flush() } } export const startBatch = (): void => { batchDepth = batchDepth + 1 } export const endBatch = (): void => { batchDepth = batchDepth - 1 if (batchDepth < 0) { throw new Error("endBatch called without matching startBatch") } flushBatch() } export const batch = (fn: () => void): void => { startBatch() try { fn() } finally { endBatch() } } export const reactiveSystem: ReactiveSystem = createReactiveSystem({ onDepChanged: (sub: Node) => { if (sub instanceof BaseReactor) { return sub.triggerOnDepChanged() } }, onSubChanged: (dep: Node) => { if (dep instanceof BaseReactor) { return dep.triggerOnSubChanged() } }, onHasDep: (sub: Node, dep: Node) => { if (sub instanceof BaseReactor && dep instanceof BaseReactor) { return sub.triggerOnHasDep(dep) } }, onNoDep: (sub: Node, dep: Node) => { if (sub instanceof BaseReactor && dep instanceof BaseReactor) { return sub.triggerOnNoDep(dep) } }, onHasSub: (dep: Node, sub: Node) => { if (dep instanceof BaseReactor && sub instanceof BaseReactor) { return dep.triggerOnHasSub(sub) } }, onNoSub: (dep: Node, sub: Node) => { if (dep instanceof BaseReactor && sub instanceof BaseReactor) { return dep.triggerOnNoSub(sub) } }, update(node: Signal | Derived | Computed) { return node.update() }, notify(node: Effect) { const nodes = [] let currentNode: Effect | undefined = node while (currentNode !== undefined) { if (currentNode.flags.hasWatching() === false) { break } currentNode.notify() nodes.push(currentNode) const nextNode: Node | undefined = currentNode?.headSubLink?.sub currentNode = nextNode instanceof Effect ? nextNode : undefined } queue.push(...nodes.toReversed()) }, }) const { removeAllDepLinksOfNode, removeAllSubLinksOfNode, withTracking, track, trackNearestMutableOrWatching, shallowPropagate, deeeeepPropagate, resolvePending, } = reactiveSystem const defaultIsEqual = (oldValue: V, newValue: V): boolean => { return Object.is(oldValue, newValue) } export interface BaseSignalValueGetterContext {} export interface InitializingSignalValueGetterContext extends BaseSignalValueGetterContext { isInitializingRun: true } export interface UpdatingSignalValueGetterContext extends BaseSignalValueGetterContext { isInitializingRun: false previousValue: V } export type SignalValueGetterContext = | InitializingSignalValueGetterContext | UpdatingSignalValueGetterContext export type SignalValueInitializer = (context: InitializingSignalValueGetterContext) => V export type SignalValueUpdater = (context: UpdatingSignalValueGetterContext) => V export type SignalValueGetter = (context: SignalValueGetterContext) => V export interface SignalOptions extends BaseReactorOptions { isEqual?: ((oldValue: V, newValue: V) => boolean) | undefined } /** * @description Signal 的值只有在被获取的时候才会更新。 */ export class Signal extends BaseReactor { private readonly isEqual: (oldValue: V, newValue: V) => boolean private valueGetter: SignalValueGetter constructor(valueInitializer: SignalValueInitializer, options: SignalOptions) { super(options) this.headSubLink = undefined this.tailSubLink = undefined this.flags = this.getInitialFlags() this.isEqual = options.isEqual ?? defaultIsEqual this.valueGetter = valueInitializer as SignalValueGetter } private getInitialFlags(): Flags { // 值在创建时尚未计算,因此初始时添加 Dirty 标记 return flags().setMutable().setDirty() } trackThis(): void { trackNearestMutableOrWatching(this) } reset(): void { this.flags = this.getInitialFlags() } dispose(): void { removeAllDepLinksOfNode(this) removeAllSubLinksOfNode(this) this.reset() this.triggerOnDispose() } private internalGet(options: { shouldTrack: boolean }): V { const { shouldTrack } = options if (shouldTrack === true) { this.trackThis() } let valueChanged = false // 如果包含 Dirty 标记,则进行更新 const shouldUpdate = this.flags.hasDirty() if (shouldUpdate === true) { valueChanged = this.update() } // 如果值变化,则通知下游(仅通知直接下游) if (valueChanged === true) { shallowPropagate(this) } return this.value } get(): V { return this.internalGet({ shouldTrack: true }) } getWithoutTrack(): V { return this.internalGet({ shouldTrack: false }) } private internalSet(options: { valueUpdater: SignalValueUpdater }): void { const { valueUpdater } = options // 将新值暂存起来,等用到的时候再执行更新 this.valueGetter = valueUpdater as SignalValueGetter // 为当前节点添加 Dirty 标记 this.flags.setDirty() // 更新所有下游节点的标记 deeeeepPropagate(this) // 执行更新队列 flushBatch() } set(value: V): void { return this.internalSet({ valueUpdater: () => value }) } setWithoutCalculate(valueUpdater: SignalValueUpdater): void { return this.internalSet({ valueUpdater }) } /** * @description 返回值表示更新前后值是否变化。 */ update(): boolean { this.flags.unsetDirty() const isInitializingRun = isVoid(this.value) === true if (isInitializingRun === true) { const newValue = this.valueGetter({ isInitializingRun: true }) this.value = newValue return true } else { const oldValue = this.value const newValue = this.valueGetter({ isInitializingRun: false, previousValue: oldValue }) this.value = newValue const valueChanged = this.isEqual(oldValue, newValue) === false return valueChanged } } } export interface BaseDerivedValueGetterContext {} export interface InitializingSignalValueGetterContext extends BaseDerivedValueGetterContext { isInitializingRun: true } export interface UpdatingDerivedValueGetterContext extends BaseDerivedValueGetterContext { isInitializingRun: false previousValue: V } export type DerivedValueGetterContext = | InitializingSignalValueGetterContext | UpdatingDerivedValueGetterContext export type DerivedValueUpdater = (context: UpdatingDerivedValueGetterContext) => V export type DerivedValueGetter = (context: DerivedValueGetterContext) => V export interface DerivedOptions extends BaseReactorOptions { isEqual?: ((oldValue: V, newValue: V) => boolean) | undefined } /** * @description Derived 的值只有在被获取的时候才会更新。 * Derived 的值只有在上游节点的值发生变化时才会更新。 * Derived 的下游节点全部解除连接时会自动与所有上游节点解除连接,并回到初始状态。 */ export class Derived extends BaseReactor { private readonly isEqual: (oldValue: V, newValue: V) => boolean private readonly valueGetter: DerivedValueGetter private manualValueGetter: DerivedValueGetter | undefined constructor(valueGetter: DerivedValueGetter, options: DerivedOptions) { super(options) this.headDepLink = undefined this.tailDepLink = undefined this.headSubLink = undefined this.tailSubLink = undefined this.flags = this.getInitialFlags() this.isEqual = options.isEqual ?? defaultIsEqual this.valueGetter = valueGetter this.manualValueGetter = undefined } private getInitialFlags(): Flags { // 值在创建时尚未计算,因此初始时添加 Dirty 标记 return flags().setMutable().setDirty() } trackThis(): void { trackNearestMutableOrWatching(this) } reset(): void { this.flags = this.getInitialFlags() } dispose(): void { removeAllDepLinksOfNode(this) removeAllSubLinksOfNode(this) this.reset() this.triggerOnDispose() } private internalGet(options: { shouldTrack: boolean }): V { const { shouldTrack } = options if (shouldTrack === true) { this.trackThis() } let valueChanged = false // 如果包含 Pending 标记,则进行解决 const shouldResolve = this.flags.hasPending() if (shouldResolve === true) { resolvePending(this) } // 如果包含 Dirty 标记,则进行更新 const shouldUpdate = this.flags.hasDirty() if (shouldUpdate === true) { valueChanged = this.update() } // 如果值变化,则通知下游(仅通知直接下游) if (valueChanged === true) { shallowPropagate(this) } return this.value } get(): V { return this.internalGet({ shouldTrack: true }) } getWithoutTrack(): V { return this.internalGet({ shouldTrack: false }) } private internalSet(options: { valueUpdater: DerivedValueUpdater }): void { const { valueUpdater } = options // 将新值暂存起来,等用到的时候再执行更新 this.manualValueGetter = valueUpdater as DerivedValueGetter // 为当前节点添加 Dirty 标记 this.flags.setDirty() // 更新所有下游节点的标记 deeeeepPropagate(this) // 执行更新队列 flushBatch() } set(value: V): void { return this.internalSet({ valueUpdater: () => value }) } setWithoutCalculate(valueUpdater: DerivedValueUpdater): void { return this.internalSet({ valueUpdater }) } /** * @description 返回值表示更新前后值是否变化。 */ private manualUpdate(): boolean { const manualValueGetter = this.manualValueGetter if (manualValueGetter === undefined) { throw new Error("Derived: manualValueGetter is undefined.") } this.manualValueGetter = undefined this.flags.unsetDirty() const isInitializingRun = isVoid(this.value) === true if (isInitializingRun === true) { const newValue = manualValueGetter({ isInitializingRun: true }) this.value = newValue return true } else { const oldValue = this.value const newValue = manualValueGetter({ isInitializingRun: false, previousValue: oldValue }) this.value = newValue const valueChanged = this.isEqual(oldValue, newValue) === false return valueChanged } } /** * @description 返回值表示更新前后值是否变化。 */ update(): boolean { if (this.manualValueGetter !== undefined) { return this.manualUpdate() } const result = withTracking(this, () => { this.flags.unsetDirty() const isInitializingRun = isVoid(this.value) === true if (isInitializingRun === true) { const newValue = this.valueGetter({ isInitializingRun: true }) this.value = newValue return true } else { const oldValue = this.value const newValue = this.valueGetter({ isInitializingRun: false, previousValue: oldValue }) this.value = newValue const valueChanged = this.isEqual(oldValue, newValue) === false return valueChanged } }) return result } } export interface BaseComputedValueGetterContext {} export interface InitializingComputedValueGetterContext extends BaseComputedValueGetterContext { isInitializingRun: true } export interface UpdatingComputedValueGetterContext extends BaseComputedValueGetterContext { isInitializingRun: false previousValue: V } export type ComputedValueGetterContext = | InitializingComputedValueGetterContext | UpdatingComputedValueGetterContext export type ComputedValueGetter = (context: ComputedValueGetterContext) => V export interface ComputedOptions extends BaseReactorOptions { isEqual?: ((oldValue: V, newValue: V) => boolean) | undefined } /** * @description Computed 的值只有在被获取的时候才会更新。 * Computed 的值只有在上游节点的值发生变化时才会更新。 * Computed 的下游节点全部解除连接时会自动与所有上游节点解除连接,并回到初始状态。 */ export class Computed extends BaseReactor { private readonly isEqual: (oldValue: V, newValue: V) => boolean private readonly valueGetter: ComputedValueGetter constructor(valueGetter: ComputedValueGetter, options: ComputedOptions) { super(options) this.headDepLink = undefined this.tailDepLink = undefined this.headSubLink = undefined this.tailSubLink = undefined this.flags = this.getInitialFlags() this.isEqual = options.isEqual ?? defaultIsEqual this.valueGetter = valueGetter } private getInitialFlags(): Flags { // 值在创建时尚未计算,因此初始时添加 Dirty 标记 return flags().setMutable().setDirty() } trackThis(): void { trackNearestMutableOrWatching(this) } reset(): void { this.flags = this.getInitialFlags() } dispose(): void { removeAllDepLinksOfNode(this) removeAllSubLinksOfNode(this) this.reset() this.triggerOnDispose() } private internalGet(options: { shouldTrack: boolean }): V { const { shouldTrack } = options if (shouldTrack === true) { this.trackThis() } let valueChanged = false // 如果包含 Pending 标记,则进行解决 const shouldResolve = this.flags.hasPending() if (shouldResolve === true) { resolvePending(this) } // 如果包含 Dirty 标记,则进行更新 const shouldUpdate = this.flags.hasDirty() if (shouldUpdate === true) { valueChanged = this.update() } // 如果值变化,则通知下游(仅通知直接下游) if (valueChanged === true) { shallowPropagate(this) } return this.value } get(): V { return this.internalGet({ shouldTrack: true }) } getWithoutTrack(): V { return this.internalGet({ shouldTrack: false }) } /** * @description 返回值表示更新前后值是否变化。 */ update(): boolean { const result = withTracking(this, () => { this.flags.unsetDirty() const isInitializingRun = isVoid(this.value) === true if (isInitializingRun === true) { const newValue = this.valueGetter({ isInitializingRun: true }) this.value = newValue return true } else { const oldValue = this.value const newValue = this.valueGetter({ isInitializingRun: false, previousValue: oldValue }) this.value = newValue const valueChanged = this.isEqual(oldValue, newValue) === false return valueChanged } }) return result } } export type CleanupFn = () => void export interface BaseEffectValueGetterContext { setCleanup: (cleanup: CleanupFn) => void } export interface InitializingEffectValueGetterContext extends BaseEffectValueGetterContext { isInitializingRun: true } export interface UpdatingEffectValueGetterContext extends BaseEffectValueGetterContext { isInitializingRun: false previousValue: V } export type EffectValueGetterContext = | InitializingEffectValueGetterContext | UpdatingEffectValueGetterContext export type EffectValueGetter = (context: EffectValueGetterContext) => V export interface EffectOptions extends BaseReactorOptions {} /** * @description Effect 会在创建时立即执行一次传入的函数。 * Effect 会在每次上游节点的值发生变化时重新执行传入的函数。 * Effect 在任意时刻最多只能一个待运行。 * Effect 可以与 Effect 建立连接。 * 作为上游节点的 Effect 在没有下游节点时会自动与所有上游节点解除连接,并将标记清空。 */ export class Effect extends BaseReactor { private readonly valueGetter: EffectValueGetter private cleanupFn: CleanupFn | undefined constructor(valueGetter: EffectValueGetter, options: EffectOptions) { super(options) this.headDepLink = undefined this.tailDepLink = undefined this.headSubLink = undefined this.tailSubLink = undefined this.flags = this.getInitialFlags() this.valueGetter = valueGetter this.cleanupFn = undefined this.trackThis() this.runWithTracking() } private getInitialFlags(): Flags { // 在创建完成后即进入观察状态,因此初始时添加 Watching 标记 return flags().setWatching() } trackThis(): void { track(this) } private cleanup(): void { this.cleanupFn?.() this.cleanupFn = undefined } private runWithTracking(): V { const result = withTracking(this, () => { this.cleanup() const isInitializingRun = isVoid(this.value) === true if (isInitializingRun === true) { const context: EffectValueGetterContext = { setCleanup: (cleanup) => { this.cleanupFn = cleanup }, isInitializingRun, } const value = this.valueGetter(context) this.value = value return value } else { const context: EffectValueGetterContext = { setCleanup: (cleanup) => { this.cleanupFn = cleanup }, isInitializingRun, previousValue: this.value, } const value = this.valueGetter(context) this.value = value return value } }) return result } reset(): void { this.flags = this.getInitialFlags() } dispose(): void { this.cleanup() removeAllDepLinksOfNode(this) removeAllSubLinksOfNode(this) this.reset() this.triggerOnDispose() } notify(): void { this.flags.unsetWatching() } run(): void { // 如果包含 Pending 标记,则进行解决 const shouldResolve = this.flags.hasPending() if (shouldResolve === true) { resolvePending(this) } // order matters: // - 需要在 resolvePending 之后,因为:resolvePending 时可能触发 shallowPropagate, // 进而触发 notify,notify 只会作用于包含 Watching 标记的节点。 // - 需要在 runWithTracking 之前,因为:Signal 只会 track 包含 Watching 标记的 Effect。 this.flags.setWatching() // 如果包含 Dirty 标记,则进行更新 const shouldRun = this.flags.hasDirty() if (shouldRun === true) { this.runWithTracking() } } } export interface BaseEffectScopeValueGetterContext extends BaseEffectValueGetterContext {} export interface InitializingEffectScopeValueGetterContext extends BaseEffectScopeValueGetterContext, InitializingEffectValueGetterContext {} export interface UpdatingEffectScopeValueGetterContext extends BaseEffectScopeValueGetterContext, UpdatingEffectValueGetterContext {} export type EffectScopeValueGetterContext = | InitializingEffectScopeValueGetterContext | UpdatingEffectScopeValueGetterContext export type EffectScopeValueGetter = (context: EffectScopeValueGetterContext) => V export interface EffectScopeOptions extends BaseReactorOptions {} export class EffectScope extends BaseReactor { private readonly valueGetter: EffectScopeValueGetter private cleanupFn: CleanupFn | void constructor(valueGetter: EffectScopeValueGetter, options: EffectScopeOptions) { super(options) this.headDepLink = undefined this.tailDepLink = undefined this.headSubLink = undefined this.tailSubLink = undefined this.flags = this.getInitialFlags() this.valueGetter = valueGetter this.cleanupFn = undefined this.trackThis() this.runWithTracking() } private getInitialFlags(): Flags { // 只负责管理作用域,不需要添加任何标记 return flags() } private cleanup(): void { this.cleanupFn?.() this.cleanupFn = undefined } private runWithTracking(): V { const result = withTracking(this, () => { this.cleanup() const isInitializingRun = isVoid(this.value) === true if (isInitializingRun === true) { const context: EffectScopeValueGetterContext = { setCleanup: (cleanup) => { this.cleanupFn = cleanup }, isInitializingRun, } const value = this.valueGetter(context) this.value = value return value } else { const context: EffectScopeValueGetterContext = { setCleanup: (cleanup) => { this.cleanupFn = cleanup }, isInitializingRun, previousValue: this.value, } const value = this.valueGetter(context) this.value = value return value } }) return result } trackThis(): void { track(this) } reset(): void { this.flags = this.getInitialFlags() } dispose(): void { this.cleanup() removeAllDepLinksOfNode(this) removeAllSubLinksOfNode(this) this.reset() this.triggerOnDispose() } } export interface BaseTriggerValueGetterContext { setCleanup: (cleanup: CleanupFn) => void } export interface InitializingTriggerValueGetterContext extends BaseTriggerValueGetterContext { isInitializingRun: true } export interface UpdatingTriggerValueGetterContext extends BaseTriggerValueGetterContext { isInitializingRun: false previousValue: V } export type TriggerValueGetterContext = | InitializingTriggerValueGetterContext | UpdatingTriggerValueGetterContext export type TriggerValueGetter = (context: TriggerValueGetterContext) => V export interface TriggerOptions extends BaseReactorOptions {} export class Trigger extends BaseReactor { private readonly valueGetter: TriggerValueGetter private cleanupFn: CleanupFn | void constructor(valueGetter: TriggerValueGetter, options: TriggerOptions) { super(options) this.headDepLink = undefined this.tailDepLink = undefined this.headSubLink = undefined this.tailSubLink = undefined this.flags = this.getInitialFlags() this.valueGetter = valueGetter this.cleanupFn = undefined this.runWithTracking() } private getInitialFlags(): Flags { return flags().setWatching() } private cleanup(): void { this.cleanupFn?.() this.cleanupFn = undefined } private runWithTracking(): V { // 以依赖收集的方式获取目标 Signals const result = withTracking(this, () => { this.cleanup() const isInitializingRun = isVoid(this.value) === true if (isInitializingRun === true) { const context: TriggerValueGetterContext = { isInitializingRun, setCleanup: (cleanup) => { this.cleanupFn = cleanup }, } const value = this.valueGetter(context) this.value = value return value } else { const context: TriggerValueGetterContext = { isInitializingRun, setCleanup: (cleanup) => { this.cleanupFn = cleanup }, previousValue: this.value, } const value = this.valueGetter(context) this.value = value return value } }) // 移除目标 Singals 与当前 Signal 之间的连接,并触发更新机制 const targetDeps = removeAllDepLinksOfNode(this) targetDeps.forEach((dep) => { deeeeepPropagate(dep) shallowPropagate(dep) }) // 执行更新队列 flushBatch() this.dispose() return result } trackThis(): void { track(this) } reset(): void { this.flags = this.getInitialFlags() } dispose(): void { this.cleanup() removeAllDepLinksOfNode(this) removeAllSubLinksOfNode(this) this.reset() this.triggerOnDispose() } } export const isReactor = (target: unknown): target is BaseReactor => { return target instanceof BaseReactor } export const isSignal = (target: unknown): target is Signal => { return target instanceof Signal } export const signal = ( valueInitializer: SignalValueInitializer, options?: SignalOptions, ): Signal => { return new Signal(valueInitializer, options ?? {}) } export const isDerived = (target: unknown): target is Derived => { return target instanceof Derived } export const derived = ( valueGetter: DerivedValueGetter, options?: DerivedOptions, ): Derived => { return new Derived(valueGetter, options ?? {}) } export const isComputed = (target: unknown): target is Computed => { return target instanceof Computed } export const computed = ( valueGetter: ComputedValueGetter, options?: ComputedOptions, ): Computed => { return new Computed(valueGetter, options ?? {}) } export const isEffect = (target: unknown): target is Effect => { return target instanceof Effect } export const effect = ( valueGetter: EffectValueGetter, options?: EffectOptions, ): Effect => { return new Effect(valueGetter, options ?? {}) } export const isEffectScope = (target: unknown): target is EffectScope => { return target instanceof EffectScope } export const effectScope = ( valueGetter: EffectScopeValueGetter, options?: EffectScopeOptions, ): EffectScope => { return new EffectScope(valueGetter, options ?? {}) } export const isTrigger = (target: unknown): target is Trigger => { return target instanceof Trigger } export const trigger = ( valueGetter: TriggerValueGetter, options?: TriggerOptions, ): Trigger => { return new Trigger(valueGetter, options ?? {}) } export type ValueReactor = Signal | Derived | Computed export type ActionReactor = Effect | EffectScope | Trigger export const isValueReactor = (target: unknown): target is ValueReactor => { return isSignal(target) || isDerived(target) || isComputed(target) } export const isActionReactor = (target: unknown): target is ActionReactor => { return isEffect(target) || isEffectScope(target) || isTrigger(target) } // oxlint-disable-next-line no-explicit-any export type AnyReactor = BaseReactor // oxlint-disable-next-line no-explicit-any export type AnyValueReactor = ValueReactor // oxlint-disable-next-line no-explicit-any export type AnyActionReactor = ActionReactor export type ValueOfReactor = R extends BaseReactor ? V : never export type ValueOfValueReactor = R extends ValueReactor ? V : never export type ValueOfActionReactor = R extends ActionReactor ? V : never