diff --git a/packages/dataverse/src/Atom.ts b/packages/dataverse/src/Atom.ts index db5e4b9..422c195 100644 --- a/packages/dataverse/src/Atom.ts +++ b/packages/dataverse/src/Atom.ts @@ -1,7 +1,6 @@ import get from 'lodash-es/get' import isPlainObject from 'lodash-es/isPlainObject' import last from 'lodash-es/last' -import DerivationFromSource from './derivations/DerivationFromSource' import type {IDerivation} from './derivations/IDerivation' import {isDerivation} from './derivations/IDerivation' import type {Pointer, PointerType} from './pointer' @@ -10,6 +9,7 @@ import pointer, {getPointerMeta} from './pointer' import type {$FixMe, $IntentionalAny} from './types' import type {PathBasedReducer} from './utils/PathBasedReducer' import updateDeep from './utils/updateDeep' +import prism from './derivations/prism/prism' type Listener = (newVal: unknown) => void @@ -247,10 +247,14 @@ export default class Atom * @param path - The path to create the derivation at. */ getIdentityDerivation(path: Array): IDerivation { - return new DerivationFromSource<$IntentionalAny>( - (listener) => this._onPathValueChange(path, listener), - () => this.getIn(path), - ) + const subscribe = (listener: (val: unknown) => void) => + this._onPathValueChange(path, listener) + + const getValue = () => this.getIn(path) + + return prism(() => { + return prism.source('value', subscribe, getValue) + }) } } diff --git a/packages/dataverse/src/Box.ts b/packages/dataverse/src/Box.ts index 1b15084..c62716e 100644 --- a/packages/dataverse/src/Box.ts +++ b/packages/dataverse/src/Box.ts @@ -1,5 +1,5 @@ -import DerivationFromSource from './derivations/DerivationFromSource' import type {IDerivation} from './derivations/IDerivation' +import prism from './derivations/prism/prism' import Emitter from './utils/Emitter' /** @@ -51,10 +51,12 @@ export default class Box implements IBox { */ protected _value: V, ) { - this._publicDerivation = new DerivationFromSource( - (listener) => this._emitter.tappable.tap(listener), - this.get.bind(this), - ) + const subscribe = (listener: (val: V) => void) => + this._emitter.tappable.tap(listener) + const getValue = () => this._value + this._publicDerivation = prism(() => { + return prism.source('value', subscribe, getValue) + }) } /** diff --git a/packages/dataverse/src/derivations/DerivationFromSource.ts b/packages/dataverse/src/derivations/DerivationFromSource.ts deleted file mode 100644 index 52828bc..0000000 --- a/packages/dataverse/src/derivations/DerivationFromSource.ts +++ /dev/null @@ -1,72 +0,0 @@ -import type {VoidFn} from '../types' -import AbstractDerivation from './AbstractDerivation' - -const noop = () => {} - -/** - * Represents a derivation based on a tappable (subscribable) data source. - */ -export default class DerivationFromSource extends AbstractDerivation { - private _untapFromChanges: () => void - private _cachedValue: undefined | V - private _hasCachedValue: boolean - - /** - * @param _tapToSource - A function that takes a listener and subscribes it to the underlying data source. - * @param _getValueFromSource - A function that returns the current value of the data source. - */ - constructor( - private readonly _tapToSource: (listener: (newValue: V) => void) => VoidFn, - private readonly _getValueFromSource: () => V, - ) { - super() - this._untapFromChanges = noop - this._cachedValue = undefined - this._hasCachedValue = false - } - - /** - * @internal - */ - _recalculate() { - if (this.isHot) { - if (!this._hasCachedValue) { - this._cachedValue = this._getValueFromSource() - this._hasCachedValue = true - } - return this._cachedValue as V - } else { - return this._getValueFromSource() - } - } - - /** - * @internal - */ - _keepHot() { - this._hasCachedValue = false - this._cachedValue = undefined - - this._untapFromChanges = this._tapToSource((newValue) => { - this._hasCachedValue = true - this._cachedValue = newValue - this._markAsStale(this) - }) - } - - /** - * @internal - */ - _becomeCold() { - this._untapFromChanges() - this._untapFromChanges = noop - - this._hasCachedValue = false - this._cachedValue = undefined - } - - /** - * @internal - */ - _reactToDependencyBecomingStale() {} -} diff --git a/packages/dataverse/src/derivations/prism/prism.ts b/packages/dataverse/src/derivations/prism/prism.ts index 28ed3c7..3be4dc7 100644 --- a/packages/dataverse/src/derivations/prism/prism.ts +++ b/packages/dataverse/src/derivations/prism/prism.ts @@ -28,7 +28,6 @@ class HotHandle { this._dependents.delete(d) } addDependent(d: IDependent) { - // having no dependents means the prism is currently cold this._dependents.add(d) } private _didMarkDependentsAsStale: boolean = false @@ -328,7 +327,7 @@ class PrismDerivation implements IDerivation { if (state.hot) { val = state.handle.getValue() } else { - val = ColdStuff.calculateColdPrism(this._fn) + val = calculateColdPrism(this._fn) } reportResolutionEnd(this) @@ -383,6 +382,11 @@ interface PrismScope { state(key: string, initialValue: T): [T, (val: T) => void] ref(key: string, initialValue: T): IRef sub(key: string): PrismScope + source( + key: string, + subscribe: (fn: (val: V) => void) => VoidFn, + getValue: () => V, + ): V } class HotScope implements PrismScope { @@ -482,6 +486,12 @@ class HotScope implements PrismScope { } this.effects.clear() } + + source( + key: string, + subscribe: (fn: (val: V) => void) => VoidFn, + getValue: () => V, + ): V {} } function cleanupScopeStack(scope: HotScope) { @@ -694,6 +704,19 @@ const possibleDerivationToValue = < } } +function source( + key: string, + subscribe: (fn: (val: V) => void) => VoidFn, + getValue: () => V, +): V { + const scope = hookScopeStack.peek() + if (!scope) { + throw new Error(`prism.source() is called outside of a prism() call.`) + } + + return scope.source(key, subscribe, getValue) +} + type IPrismFn = { (fn: () => T): IDerivation ref: typeof ref @@ -704,6 +727,7 @@ type IPrismFn = { scope: typeof scope sub: typeof sub inPrism: typeof inPrism + source: typeof source } /** @@ -736,28 +760,33 @@ class ColdScope implements PrismScope { sub(key: string): ColdScope { return new ColdScope() } + source( + key: string, + subscribe: (fn: (val: V) => void) => VoidFn, + getValue: () => V, + ): V { + return getValue() + } } -namespace ColdStuff { - export function calculateColdPrism(fn: () => V): V { - const scope = new ColdScope() - hookScopeStack.push(scope) - let value: V - try { - value = fn() - } catch (error) { - console.error(error) - } finally { - const topOfTheStack = hookScopeStack.pop() - if (topOfTheStack !== scope) { - console.warn( - // @todo guide the user to report the bug in an issue - `The Prism hook stack has slipped. This is a bug.`, - ) - } +function calculateColdPrism(fn: () => V): V { + const scope = new ColdScope() + hookScopeStack.push(scope) + let value: V + try { + value = fn() + } catch (error) { + console.error(error) + } finally { + const topOfTheStack = hookScopeStack.pop() + if (topOfTheStack !== scope) { + console.warn( + // @todo guide the user to report the bug in an issue + `The Prism hook stack has slipped. This is a bug.`, + ) } - return value! } + return value! } prism.ref = ref @@ -768,5 +797,6 @@ prism.state = state prism.scope = scope prism.sub = sub prism.inPrism = inPrism +prism.source = source export default prism diff --git a/packages/dataverse/src/index.ts b/packages/dataverse/src/index.ts index bdadf9b..ca87c4e 100644 --- a/packages/dataverse/src/index.ts +++ b/packages/dataverse/src/index.ts @@ -9,7 +9,6 @@ export {default as Atom, val, valueDerivation} from './Atom' export {default as Box} from './Box' export type {IBox} from './Box' export {default as AbstractDerivation} from './derivations/AbstractDerivation' -export {default as DerivationFromSource} from './derivations/DerivationFromSource' export {isDerivation} from './derivations/IDerivation' export type {IDerivation} from './derivations/IDerivation' export {default as iterateAndCountTicks} from './derivations/iterateAndCountTicks'