jotai-effect
Version:
224 lines • 8.87 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.atomEffect = atomEffect;
const vanilla_1 = require("jotai/vanilla");
const internals_1 = require("jotai/vanilla/internals");
const env_1 = require("./env.js");
function getBuildingBlocks(store) {
const buildingBlocks = (0, internals_1.INTERNAL_getBuildingBlocksRev1)(store);
return [
buildingBlocks[1], // mountedAtoms
buildingBlocks[3], // changedAtoms
(0, internals_1.INTERNAL_initializeStoreHooks)(buildingBlocks[6]), // storeHooks
buildingBlocks[11], // ensureAtomState
buildingBlocks[14], // readAtomState
buildingBlocks[16], // writeAtomState
buildingBlocks[17], // mountDependencies
buildingBlocks[15], // invalidateDependents
buildingBlocks[13], // recomputeInvalidatedAtoms
buildingBlocks[12], // flushCallbacks
];
}
function atomEffect(effect) {
const refAtom = (0, vanilla_1.atom)(() => []);
const effectAtom = (0, vanilla_1.atom)(function effectAtomRead(get) {
const [dependencies, atomState, mountedAtoms] = get(refAtom);
if (mountedAtoms.has(effectAtom)) {
dependencies.forEach(get);
++atomState.n;
}
});
effectAtom.effect = effect;
effectAtom.unstable_onInit = (store) => {
const deps = new Set();
let inProgress = 0;
let isRecursing = false;
let hasChanged = false;
let fromCleanup = false;
let runCleanup;
function runEffect() {
if (inProgress) {
return;
}
deps.clear();
let isSync = true;
const getter = (a) => {
var _a;
if (fromCleanup) {
return store.get(a);
}
if ((0, internals_1.INTERNAL_isSelfAtom)(effectAtom, a)) {
const aState = ensureAtomState(a);
if (!(0, internals_1.INTERNAL_isAtomStateInitialized)(aState)) {
if ((0, internals_1.INTERNAL_hasInitialValue)(a)) {
(0, internals_1.INTERNAL_setAtomStateValueOrPromise)(a, a.init, ensureAtomState);
}
else {
// NOTE invalid derived atoms can reach here
throw new Error('no atom init');
}
}
return (0, internals_1.INTERNAL_returnAtomValue)(aState);
}
// a !== atom
const aState = readAtomState(a);
try {
return (0, internals_1.INTERNAL_returnAtomValue)(aState);
}
finally {
atomState.d.set(a, aState.n);
(_a = mountedAtoms.get(a)) === null || _a === void 0 ? void 0 : _a.t.add(effectAtom);
if (isSync) {
deps.add(a);
}
else {
if (mountedAtoms.has(a)) {
mountDependencies(effectAtom);
recomputeInvalidatedAtoms();
flushCallbacks();
}
}
}
};
getter.peek = store.get;
const setter = (a, ...args) => {
var _a;
const aState = ensureAtomState(a);
try {
++inProgress;
if ((0, internals_1.INTERNAL_isSelfAtom)(effectAtom, a)) {
if (!(0, internals_1.INTERNAL_hasInitialValue)(a)) {
// NOTE technically possible but restricted as it may cause bugs
throw new Error('atom not writable');
}
const prevEpochNumber = aState.n;
const v = args[0];
(0, internals_1.INTERNAL_setAtomStateValueOrPromise)(a, v, ensureAtomState);
mountDependencies(a);
if (prevEpochNumber !== aState.n) {
changedAtoms.add(a);
(_a = storeHooks.c) === null || _a === void 0 ? void 0 : _a.call(storeHooks, a);
invalidateDependents(a);
}
return undefined;
}
else {
return writeAtomState(a, ...args);
}
}
finally {
if (!isSync) {
recomputeInvalidatedAtoms();
flushCallbacks();
}
--inProgress;
}
};
setter.recurse = (a, ...args) => {
if (fromCleanup) {
if ((0, env_1.isDev)()) {
throw new Error('set.recurse is not allowed in cleanup');
}
return undefined;
}
try {
isRecursing = true;
mountDependencies(effectAtom);
return setter(a, ...args);
}
finally {
recomputeInvalidatedAtoms();
isRecursing = false;
if (hasChanged) {
hasChanged = false;
runEffect();
}
}
};
try {
runCleanup === null || runCleanup === void 0 ? void 0 : runCleanup();
const cleanup = effectAtom.effect(getter, setter);
if (typeof cleanup !== 'function') {
return;
}
runCleanup = () => {
if (inProgress) {
return;
}
try {
isSync = true;
fromCleanup = true;
return cleanup();
}
finally {
isSync = false;
fromCleanup = false;
runCleanup = undefined;
}
};
}
finally {
isSync = false;
deps.forEach((depAtom) => {
atomState.d.set(depAtom, ensureAtomState(depAtom).n);
});
mountDependencies(effectAtom);
recomputeInvalidatedAtoms();
}
}
const [mountedAtoms, changedAtoms, storeHooks, ensureAtomState, readAtomState, writeAtomState, mountDependencies, invalidateDependents, recomputeInvalidatedAtoms, flushCallbacks,] = getBuildingBlocks(store);
const atomEffectChannel = ensureAtomEffectChannel(store);
const atomState = ensureAtomState(effectAtom);
// initialize atomState
atomState.v = undefined;
Object.assign(store.get(refAtom), [deps, atomState, mountedAtoms]);
storeHooks.m.add(effectAtom, function atomOnMount() {
// mounted
atomEffectChannel.add(runEffect);
if (runCleanup) {
atomEffectChannel.delete(runCleanup);
}
});
storeHooks.u.add(effectAtom, function atomOnUnmount() {
// unmounted
atomEffectChannel.delete(runEffect);
if (runCleanup) {
atomEffectChannel.add(runCleanup);
}
});
storeHooks.c.add(effectAtom, function atomOnUpdate() {
// changed
if (isRecursing) {
hasChanged = true;
}
else {
atomEffectChannel.add(runEffect);
}
});
};
if ((0, env_1.isDev)()) {
Object.defineProperty(refAtom, 'debugLabel', {
get: () => effectAtom.debugLabel ? `${effectAtom.debugLabel}:ref` : undefined,
});
refAtom.debugPrivate = true;
}
return effectAtom;
}
const atomEffectChannelStoreMap = new WeakMap();
function ensureAtomEffectChannel(store) {
const storeHooks = getBuildingBlocks(store)[2];
let atomEffectChannel = atomEffectChannelStoreMap.get(store);
if (!atomEffectChannel) {
atomEffectChannel = new Set();
atomEffectChannelStoreMap.set(store, atomEffectChannel);
storeHooks.f.add(function storeOnFlush() {
// flush
for (const fn of atomEffectChannel) {
atomEffectChannel.delete(fn);
fn();
}
});
}
return atomEffectChannel;
}
//# sourceMappingURL=atomEffect.js.map
;