jotai
Version:
👻 Primitive and flexible state management for React
567 lines (562 loc) • 20.9 kB
JavaScript
System.register([], (function (exports) {
'use strict';
return {
execute: (function () {
exports('atom', atom);
let keyCount = 0;
function atom(read, write) {
const key = `atom${++keyCount}`;
const config = {
toString: () => key
};
if (typeof read === "function") {
config.read = read;
} else {
config.init = read;
config.read = (get) => get(config);
config.write = (get, set, arg) => set(
config,
typeof arg === "function" ? arg(get(config)) : arg
);
}
if (write) {
config.write = write;
}
return config;
}
const hasInitialValue = (atom) => "init" in atom;
const isActuallyWritableAtom = (atom) => !!atom.write;
const cancelPromiseMap = /* @__PURE__ */ new WeakMap();
const registerCancelPromise = (promise, cancel) => {
cancelPromiseMap.set(promise, cancel);
promise.catch(() => {
}).finally(() => cancelPromiseMap.delete(promise));
};
const cancelPromise = (promise, next) => {
const cancel = cancelPromiseMap.get(promise);
if (cancel) {
cancelPromiseMap.delete(promise);
cancel(next);
}
};
const resolvePromise = (promise, value) => {
promise.status = "fulfilled";
promise.value = value;
};
const rejectPromise = (promise, e) => {
promise.status = "rejected";
promise.reason = e;
};
const isPromiseLike = (x) => typeof (x == null ? void 0 : x.then) === "function";
const isEqualAtomValue = (a, b) => "v" in a && "v" in b && Object.is(a.v, b.v);
const isEqualAtomError = (a, b) => "e" in a && "e" in b && Object.is(a.e, b.e);
const hasPromiseAtomValue = (a) => "v" in a && a.v instanceof Promise;
const isEqualPromiseAtomValue = (a, b) => "v" in a && "v" in b && a.v.orig && a.v.orig === b.v.orig;
const returnAtomValue = (atomState) => {
if ("e" in atomState) {
throw atomState.e;
}
return atomState.v;
};
const createStore = exports('createStore', () => {
const atomStateMap = /* @__PURE__ */ new WeakMap();
const mountedMap = /* @__PURE__ */ new WeakMap();
const pendingMap = /* @__PURE__ */ new Map();
let storeListenersRev1;
let storeListenersRev2;
let mountedAtoms;
{
storeListenersRev1 = /* @__PURE__ */ new Set();
storeListenersRev2 = /* @__PURE__ */ new Set();
mountedAtoms = /* @__PURE__ */ new Set();
}
const getAtomState = (atom) => atomStateMap.get(atom);
const setAtomState = (atom, atomState) => {
{
Object.freeze(atomState);
}
const prevAtomState = atomStateMap.get(atom);
atomStateMap.set(atom, atomState);
if (!pendingMap.has(atom)) {
pendingMap.set(atom, prevAtomState);
}
if (prevAtomState && hasPromiseAtomValue(prevAtomState)) {
const next = "v" in atomState ? atomState.v instanceof Promise ? atomState.v : Promise.resolve(atomState.v) : Promise.reject(atomState.e);
cancelPromise(prevAtomState.v, next);
}
};
const updateDependencies = (atom, nextAtomState, nextDependencies) => {
const dependencies = /* @__PURE__ */ new Map();
let changed = false;
nextDependencies.forEach((aState, a) => {
if (!aState && a === atom) {
aState = nextAtomState;
}
if (aState) {
dependencies.set(a, aState);
if (nextAtomState.d.get(a) !== aState) {
changed = true;
}
} else {
console.warn("[Bug] atom state not found");
}
});
if (changed || nextAtomState.d.size !== dependencies.size) {
nextAtomState.d = dependencies;
}
};
const setAtomValue = (atom, value, nextDependencies) => {
const prevAtomState = getAtomState(atom);
const nextAtomState = {
d: (prevAtomState == null ? void 0 : prevAtomState.d) || /* @__PURE__ */ new Map(),
v: value
};
if (nextDependencies) {
updateDependencies(atom, nextAtomState, nextDependencies);
}
if (prevAtomState && isEqualAtomValue(prevAtomState, nextAtomState) && prevAtomState.d === nextAtomState.d) {
return prevAtomState;
}
if (prevAtomState && hasPromiseAtomValue(prevAtomState) && hasPromiseAtomValue(nextAtomState) && isEqualPromiseAtomValue(prevAtomState, nextAtomState)) {
if (prevAtomState.d === nextAtomState.d) {
return prevAtomState;
} else {
nextAtomState.v = prevAtomState.v;
}
}
setAtomState(atom, nextAtomState);
return nextAtomState;
};
const setAtomValueOrPromise = (atom, valueOrPromise, nextDependencies, abortPromise) => {
if (isPromiseLike(valueOrPromise)) {
let continuePromise;
const promise = new Promise((resolve, reject) => {
let settled = false;
valueOrPromise.then(
(v) => {
if (!settled) {
settled = true;
const prevAtomState = getAtomState(atom);
const nextAtomState = setAtomValue(
atom,
promise,
nextDependencies
);
resolvePromise(promise, v);
resolve(v);
if ((prevAtomState == null ? void 0 : prevAtomState.d) !== nextAtomState.d) {
mountDependencies(atom, nextAtomState, prevAtomState == null ? void 0 : prevAtomState.d);
}
}
},
(e) => {
if (!settled) {
settled = true;
const prevAtomState = getAtomState(atom);
const nextAtomState = setAtomValue(
atom,
promise,
nextDependencies
);
rejectPromise(promise, e);
reject(e);
if ((prevAtomState == null ? void 0 : prevAtomState.d) !== nextAtomState.d) {
mountDependencies(atom, nextAtomState, prevAtomState == null ? void 0 : prevAtomState.d);
}
}
}
);
continuePromise = (next) => {
if (!settled) {
settled = true;
next.then(
(v) => resolvePromise(promise, v),
(e) => rejectPromise(promise, e)
);
resolve(next);
}
};
});
promise.orig = valueOrPromise;
promise.status = "pending";
registerCancelPromise(promise, (next) => {
if (next) {
continuePromise(next);
}
abortPromise == null ? void 0 : abortPromise();
});
return setAtomValue(atom, promise, nextDependencies);
}
return setAtomValue(atom, valueOrPromise, nextDependencies);
};
const setAtomError = (atom, error, nextDependencies) => {
const prevAtomState = getAtomState(atom);
const nextAtomState = {
d: (prevAtomState == null ? void 0 : prevAtomState.d) || /* @__PURE__ */ new Map(),
e: error
};
if (nextDependencies) {
updateDependencies(atom, nextAtomState, nextDependencies);
}
if (prevAtomState && isEqualAtomError(prevAtomState, nextAtomState) && prevAtomState.d === nextAtomState.d) {
return prevAtomState;
}
setAtomState(atom, nextAtomState);
return nextAtomState;
};
const readAtomState = (atom) => {
const atomState = getAtomState(atom);
if (atomState) {
atomState.d.forEach((_, a) => {
if (a !== atom && !mountedMap.has(a)) {
readAtomState(a);
}
});
if (Array.from(atomState.d).every(
([a, s]) => a === atom || getAtomState(a) === s
)) {
return atomState;
}
}
const nextDependencies = /* @__PURE__ */ new Map();
let isSync = true;
const getter = (a) => {
if (a === atom) {
const aState2 = getAtomState(a);
if (aState2) {
nextDependencies.set(a, aState2);
return returnAtomValue(aState2);
}
if (hasInitialValue(a)) {
nextDependencies.set(a, void 0);
return a.init;
}
throw new Error("no atom init");
}
const aState = readAtomState(a);
nextDependencies.set(a, aState);
return returnAtomValue(aState);
};
let controller;
let setSelf;
const options = {
get signal() {
if (!controller) {
controller = new AbortController();
}
return controller.signal;
},
get setSelf() {
if (!isActuallyWritableAtom(atom)) {
console.warn("setSelf function cannot be used with read-only atom");
}
if (!setSelf && isActuallyWritableAtom(atom)) {
setSelf = (...args) => {
if (isSync) {
console.warn("setSelf function cannot be called in sync");
}
if (!isSync) {
return writeAtom(atom, ...args);
}
};
}
return setSelf;
}
};
try {
const valueOrPromise = atom.read(getter, options);
return setAtomValueOrPromise(
atom,
valueOrPromise,
nextDependencies,
() => controller == null ? void 0 : controller.abort()
);
} catch (error) {
return setAtomError(atom, error, nextDependencies);
} finally {
isSync = false;
}
};
const readAtom = (atom) => returnAtomValue(readAtomState(atom));
const addAtom = (atom) => {
let mounted = mountedMap.get(atom);
if (!mounted) {
mounted = mountAtom(atom);
}
return mounted;
};
const canUnmountAtom = (atom, mounted) => !mounted.l.size && (!mounted.t.size || mounted.t.size === 1 && mounted.t.has(atom));
const delAtom = (atom) => {
const mounted = mountedMap.get(atom);
if (mounted && canUnmountAtom(atom, mounted)) {
unmountAtom(atom);
}
};
const recomputeDependents = (atom) => {
const dependencyMap = /* @__PURE__ */ new Map();
const dirtyMap = /* @__PURE__ */ new WeakMap();
const loop1 = (a) => {
const mounted = mountedMap.get(a);
mounted == null ? void 0 : mounted.t.forEach((dependent) => {
if (dependent !== a) {
dependencyMap.set(
dependent,
(dependencyMap.get(dependent) || /* @__PURE__ */ new Set()).add(a)
);
dirtyMap.set(dependent, (dirtyMap.get(dependent) || 0) + 1);
loop1(dependent);
}
});
};
loop1(atom);
const loop2 = (a) => {
const mounted = mountedMap.get(a);
mounted == null ? void 0 : mounted.t.forEach((dependent) => {
var _a;
if (dependent !== a) {
let dirtyCount = dirtyMap.get(dependent);
if (dirtyCount) {
dirtyMap.set(dependent, --dirtyCount);
}
if (!dirtyCount) {
let isChanged = !!((_a = dependencyMap.get(dependent)) == null ? void 0 : _a.size);
if (isChanged) {
const prevAtomState = getAtomState(dependent);
const nextAtomState = readAtomState(dependent);
isChanged = !prevAtomState || !isEqualAtomValue(prevAtomState, nextAtomState);
}
if (!isChanged) {
dependencyMap.forEach((s) => s.delete(dependent));
}
}
loop2(dependent);
}
});
};
loop2(atom);
};
const writeAtomState = (atom, ...args) => {
let isSync = true;
const getter = (a) => returnAtomValue(readAtomState(a));
const setter = (a, ...args2) => {
let r;
if (a === atom) {
if (!hasInitialValue(a)) {
throw new Error("atom not writable");
}
const prevAtomState = getAtomState(a);
const nextAtomState = setAtomValueOrPromise(a, args2[0]);
if (!prevAtomState || !isEqualAtomValue(prevAtomState, nextAtomState)) {
recomputeDependents(a);
}
} else {
r = writeAtomState(a, ...args2);
}
if (!isSync) {
const flushed = flushPending();
{
storeListenersRev2.forEach(
(l) => l({ type: "async-write", flushed })
);
}
}
return r;
};
const result = atom.write(getter, setter, ...args);
isSync = false;
return result;
};
const writeAtom = (atom, ...args) => {
const result = writeAtomState(atom, ...args);
const flushed = flushPending();
{
storeListenersRev2.forEach(
(l) => l({ type: "write", flushed })
);
}
return result;
};
const mountAtom = (atom, initialDependent) => {
const mounted = {
t: new Set(initialDependent && [initialDependent]),
l: /* @__PURE__ */ new Set()
};
mountedMap.set(atom, mounted);
{
mountedAtoms.add(atom);
}
readAtomState(atom).d.forEach((_, a) => {
const aMounted = mountedMap.get(a);
if (aMounted) {
aMounted.t.add(atom);
} else {
if (a !== atom) {
mountAtom(a, atom);
}
}
});
readAtomState(atom);
if (isActuallyWritableAtom(atom) && atom.onMount) {
const onUnmount = atom.onMount((...args) => writeAtom(atom, ...args));
if (onUnmount) {
mounted.u = onUnmount;
}
}
return mounted;
};
const unmountAtom = (atom) => {
var _a;
const onUnmount = (_a = mountedMap.get(atom)) == null ? void 0 : _a.u;
if (onUnmount) {
onUnmount();
}
mountedMap.delete(atom);
{
mountedAtoms.delete(atom);
}
const atomState = getAtomState(atom);
if (atomState) {
if (hasPromiseAtomValue(atomState)) {
cancelPromise(atomState.v);
}
atomState.d.forEach((_, a) => {
if (a !== atom) {
const mounted = mountedMap.get(a);
if (mounted) {
mounted.t.delete(atom);
if (canUnmountAtom(a, mounted)) {
unmountAtom(a);
}
}
}
});
} else {
console.warn("[Bug] could not find atom state to unmount", atom);
}
};
const mountDependencies = (atom, atomState, prevDependencies) => {
const depSet = new Set(atomState.d.keys());
prevDependencies == null ? void 0 : prevDependencies.forEach((_, a) => {
if (depSet.has(a)) {
depSet.delete(a);
return;
}
const mounted = mountedMap.get(a);
if (mounted) {
mounted.t.delete(atom);
if (canUnmountAtom(a, mounted)) {
unmountAtom(a);
}
}
});
depSet.forEach((a) => {
const mounted = mountedMap.get(a);
if (mounted) {
mounted.t.add(atom);
} else if (mountedMap.has(atom)) {
mountAtom(a, atom);
}
});
};
const flushPending = () => {
let flushed;
{
flushed = /* @__PURE__ */ new Set();
}
while (pendingMap.size) {
const pending = Array.from(pendingMap);
pendingMap.clear();
pending.forEach(([atom, prevAtomState]) => {
const atomState = getAtomState(atom);
if (atomState) {
if (atomState.d !== (prevAtomState == null ? void 0 : prevAtomState.d)) {
mountDependencies(atom, atomState, prevAtomState == null ? void 0 : prevAtomState.d);
}
const mounted = mountedMap.get(atom);
if (mounted && !// TODO This seems pretty hacky. Hope to fix it.
// Maybe we could `mountDependencies` in `setAtomState`?
(prevAtomState && !hasPromiseAtomValue(prevAtomState) && (isEqualAtomValue(prevAtomState, atomState) || isEqualAtomError(prevAtomState, atomState)))) {
mounted.l.forEach((listener) => listener());
{
flushed.add(atom);
}
}
} else {
console.warn("[Bug] no atom state to flush");
}
});
}
{
storeListenersRev1.forEach((l) => l("state"));
return flushed;
}
};
const subscribeAtom = (atom, listener) => {
const mounted = addAtom(atom);
const flushed = flushPending();
const listeners = mounted.l;
listeners.add(listener);
{
storeListenersRev1.forEach((l) => l("sub"));
storeListenersRev2.forEach(
(l) => l({ type: "sub", flushed })
);
}
return () => {
listeners.delete(listener);
delAtom(atom);
{
storeListenersRev1.forEach((l) => l("unsub"));
storeListenersRev2.forEach((l) => l({ type: "unsub" }));
}
};
};
{
return {
get: readAtom,
set: writeAtom,
sub: subscribeAtom,
// store dev methods (these are tentative and subject to change without notice)
dev_subscribe_store: (l, rev) => {
if (rev !== 2) {
console.warn(
"The current StoreListener revision is 2. The older ones are deprecated."
);
storeListenersRev1.add(l);
return () => {
storeListenersRev1.delete(l);
};
}
storeListenersRev2.add(l);
return () => {
storeListenersRev2.delete(l);
};
},
dev_get_mounted_atoms: () => mountedAtoms.values(),
dev_get_atom_state: (a) => atomStateMap.get(a),
dev_get_mounted: (a) => mountedMap.get(a),
dev_restore_atoms: (values) => {
for (const [atom, valueOrPromise] of values) {
if (hasInitialValue(atom)) {
setAtomValueOrPromise(atom, valueOrPromise);
recomputeDependents(atom);
}
}
const flushed = flushPending();
storeListenersRev2.forEach(
(l) => l({ type: "restore", flushed })
);
}
};
}
});
let defaultStore;
const getDefaultStore = exports('getDefaultStore', () => {
if (!defaultStore) {
defaultStore = createStore();
}
return defaultStore;
});
})
};
}));