UNPKG

fluidstate-alien

Version:

Alien Signals-based reactive layer for fluidstate

411 lines (410 loc) 10.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "AtomOptions", { enumerable: true, get: function () { return _fluidstate.AtomOptions; } }); exports.AtomType = void 0; Object.defineProperty(exports, "ComputedAtomOptions", { enumerable: true, get: function () { return _fluidstate.ComputedAtomOptions; } }); Object.defineProperty(exports, "Link", { enumerable: true, get: function () { return _alienSignals.Link; } }); Object.defineProperty(exports, "ReactionOptions", { enumerable: true, get: function () { return _fluidstate.ReactionOptions; } }); Object.defineProperty(exports, "ReactiveFlags", { enumerable: true, get: function () { return _alienSignals.ReactiveFlags; } }); Object.defineProperty(exports, "ReactiveNode", { enumerable: true, get: function () { return _alienSignals.ReactiveNode; } }); exports.atom = atom; exports.checkDirty = exports.batchDepth = void 0; exports.computed = computed; exports.computedCore = void 0; Object.defineProperty(exports, "createReactiveSystem", { enumerable: true, get: function () { return _alienSignals.createReactiveSystem; } }); exports.effect = effect; exports.endBatch = endBatch; exports.endTracking = void 0; exports.getCurrentSub = getCurrentSub; exports.propagate = exports.link = exports.isEffect = exports.isComputed = exports.isAtom = void 0; exports.reportChanged = reportChanged; exports.reportObserved = reportObserved; exports.runInitialEffect = runInitialEffect; exports.setCurrentSub = setCurrentSub; exports.shallowPropagate = void 0; exports.startBatch = startBatch; exports.unlink = exports.startTracking = void 0; var _alienSignals = require("alien-signals"); var _fluidstate = require("fluidstate"); /** * Note: Much of this file is a copy of alien signals' higher-level abstractions. * The biggest differences from it are: * - Removal of the effect scopes abstraction - it is not needed for `fluidstate` * - Replacement of the signals abstraction with the atom abstraction (i.e. a signal * without value) * - Adding the ability to schedule effects * - Adding the ability to have "onBecomeObservedListener" and * "onBecomeUnobservedListener" listeners on atoms and computeds * - Adding the ability to customize `equals` check for computeds * and replacing the `===` default equals check with `Object.is` */ var EffectFlags; (function (EffectFlags) { EffectFlags[EffectFlags["Queued"] = 64] = "Queued"; })(EffectFlags || (EffectFlags = {})); let AtomType; exports.AtomType = AtomType; (function (AtomType) { AtomType[AtomType["Atom"] = 0] = "Atom"; AtomType[AtomType["Computed"] = 1] = "Computed"; AtomType[AtomType["Effect"] = 2] = "Effect"; })(AtomType || (exports.AtomType = AtomType = {})); const isComputed = node => { return "atomType" in node && node.atomType === AtomType.Computed; }; exports.isComputed = isComputed; const isEffect = node => { return "atomType" in node && node.atomType === AtomType.Effect; }; exports.isEffect = isEffect; const isAtom = node => { return "atomType" in node && node.atomType === AtomType.Atom; }; exports.isAtom = isAtom; const queuedEffects = []; const { link, unlink, propagate, checkDirty, endTracking, startTracking, shallowPropagate } = (0, _alienSignals.createReactiveSystem)({ update(signal) { if (isComputed(signal)) { return updateComputed(signal); } else { updateAtom(signal); return true; } }, notify, unwatched(node) { if (isComputed(node)) { let toRemove = node.deps; if (toRemove !== undefined) { node.flags = 17; do { toRemove = unlink(toRemove, node); } while (toRemove !== undefined); } } else if (!isAtom(node)) { effectOper.call(node); } } }); exports.shallowPropagate = shallowPropagate; exports.startTracking = startTracking; exports.endTracking = endTracking; exports.checkDirty = checkDirty; exports.propagate = propagate; exports.unlink = unlink; exports.link = link; let batchDepth = 0; exports.batchDepth = batchDepth; let notifyIndex = 0; let queuedEffectsLength = 0; let activeSub; function getCurrentSub() { return activeSub; } function setCurrentSub(sub) { const prevSub = activeSub; activeSub = sub; return prevSub; } function startBatch() { exports.batchDepth = ++batchDepth; } function endBatch() { if (!(exports.batchDepth = --batchDepth)) { flush(); } } function atom(options) { return { atomType: AtomType.Atom, subs: undefined, subsTail: undefined, flags: 1, watchCount: 0, options }; } const computedCore = (getter, options) => { const computed = { atomType: AtomType.Computed, value: undefined, subs: undefined, subsTail: undefined, deps: undefined, depsTail: undefined, flags: 17, getter, watchCount: 0, options }; const get = computedOper.bind(computed); return { computed, get: get }; }; exports.computedCore = computedCore; function computed(getter, options) { return computedCore(getter, options)["get"]; } function effect(fn, options) { const { get, computed } = computedCore(fn, { equals: () => false }); const e = { isInitialized: false, isStopped: false, atomType: AtomType.Effect, fn: get, computed, subs: undefined, subsTail: undefined, deps: undefined, depsTail: undefined, flags: 2, options }; if (e.options?.scheduler) { e.options.scheduler(runInitialEffect.bind(null, e)); } else { runInitialEffect(e); } return effectOper.bind(e); } function runInitialEffect(e) { if (e.isStopped) { return; } onWatched(e.computed); if (activeSub !== undefined) { link(e, activeSub); } const prev = setCurrentSub(e); try { e.fn(); } finally { setCurrentSub(prev); } } function updateComputed(c) { const prevSub = setCurrentSub(c); startTracking(c); try { const oldValue = c.value; return !(c.options?.equals ?? Object.is)(oldValue, c.value = c.getter(oldValue)); } finally { if (c.watchCount) { for (let link = c.depsTail !== undefined ? c.depsTail.nextDep : c.deps; link !== undefined; link = link.nextDep) { const dep = link.dep; onUnwatched(dep); } } setCurrentSub(prevSub); endTracking(c); } } function updateAtom(s) { s.flags = 1; } function notify(e) { const flags = e.flags; if (!(flags & EffectFlags.Queued)) { e.flags = flags | EffectFlags.Queued; const subs = e.subs; if (subs !== undefined) { notify(subs.sub); } else { queuedEffects[queuedEffectsLength++] = e; } } } function run(e, flags) { if (flags & 16 || flags & 32 && checkDirty(e.deps, e)) { const prev = setCurrentSub(e); startTracking(e); try { e.fn(); } finally { setCurrentSub(prev); endTracking(e); } return; } else if (flags & 32) { e.flags = flags & ~32; } let link = e.deps; while (link !== undefined) { const dep = link.dep; const depFlags = dep.flags; if (isEffect(dep) && depFlags & EffectFlags.Queued) { run(dep, dep.flags = depFlags & ~EffectFlags.Queued); } link = link.nextDep; } } function flush() { while (notifyIndex < queuedEffectsLength) { const effect = queuedEffects[notifyIndex]; queuedEffects[notifyIndex++] = undefined; if (effect.options?.scheduler) { effect.options.scheduler(forceFlush.bind(null, effect)); } else { forceFlush(effect); } } notifyIndex = 0; queuedEffectsLength = 0; } function forceFlush(effect) { run(effect, effect.flags &= ~EffectFlags.Queued); } function computedOper() { const flags = this.flags; if (flags & 16 || flags & 32 && checkDirty(this.deps, this)) { if (updateComputed(this)) { const subs = this.subs; if (subs !== undefined) { shallowPropagate(subs); } } } else if (flags & 32) { this.flags = flags & ~32; } if (activeSub !== undefined) { const lastLink = this.subsTail; link(this, activeSub); const newLink = this.subsTail; if (newLink !== lastLink) { const newSub = newLink.sub; if (isEffect(newSub) || isComputed(newSub) && newSub.watchCount) { onWatched(this); } } } return this.value; } function reportChanged() { this.flags = 17; const subs = this.subs; if (subs !== undefined) { propagate(subs); if (!batchDepth) { flush(); } } } function reportObserved() { if (this.flags & 16) { updateAtom(this); const subs = this.subs; if (subs !== undefined) { shallowPropagate(subs); } } if (activeSub !== undefined) { const lastLink = this.subsTail; link(this, activeSub); const newLink = this.subsTail; if (newLink !== lastLink) { const newSub = newLink.sub; if (isEffect(newSub) || isComputed(newSub) && newSub.watchCount) { onWatched(this); } } } return this.watchCount > 0; } function onWatched(atom) { if (isAtom(atom)) { if (atom.watchCount++ === 0) { atom.options?.onBecomeObservedListener?.(); } } else if (isComputed(atom)) { if (atom.watchCount++ === 0) { atom.options?.onBecomeObservedListener?.(); for (let link = atom.deps; link !== undefined; link = link.nextDep) { const dep = link.dep; onWatched(dep); } } } } function onUnwatched(atom) { if (isAtom(atom)) { if (--atom.watchCount === 0) { atom.options?.onBecomeUnobservedListener?.(); } } else if (isComputed(atom)) { if (--atom.watchCount === 0) { atom.options?.onBecomeUnobservedListener?.(); for (let link = atom.deps; link !== undefined; link = link.nextDep) { const dep = link.dep; onUnwatched(dep); } } } } function effectOper() { if (!this.isInitialized) { this.isStopped = true; } onUnwatched(this.computed); let dep = this.deps; while (dep !== undefined) { dep = unlink(dep, this); } const sub = this.subs; if (sub !== undefined) { unlink(sub); } this.flags = 0; } //# sourceMappingURL=alien-signals.js.map