fluidstate-alien
Version:
Alien Signals-based reactive layer for fluidstate
411 lines (410 loc) • 10.3 kB
JavaScript
;
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