UNPKG

jotai

Version:

👻 Next gen state management that will spook you

839 lines (828 loc) • 31.8 kB
System.register(['react', 'jotai/vanilla'], (function (exports) { 'use strict'; var createContext, useState, useEffect, useRef, createElement, useContext, useReducer, useDebugValue, useCallback, atom$1; return { setters: [function (module) { createContext = module.createContext; useState = module.useState; useEffect = module.useEffect; useRef = module.useRef; createElement = module.createElement; useContext = module.useContext; useReducer = module.useReducer; useDebugValue = module.useDebugValue; useCallback = module.useCallback; }, function (module) { atom$1 = module.atom; }], execute: (function () { exports({ atom: atom, useAtom: useAtom, useAtomValue: useAtomValue, useSetAtom: useSetAtom }); const SUSPENSE_PROMISE = Symbol(); const isSuspensePromise = (promise) => !!promise[SUSPENSE_PROMISE]; const isSuspensePromiseAlreadyCancelled = (suspensePromise) => !suspensePromise[SUSPENSE_PROMISE].c; const cancelSuspensePromise = (suspensePromise) => { var _a; const { b: basePromise, c: cancelPromise } = suspensePromise[SUSPENSE_PROMISE]; if (cancelPromise) { cancelPromise(); (_a = promiseAbortMap.get(basePromise)) == null ? void 0 : _a(); } }; const isEqualSuspensePromise = (oldSuspensePromise, newSuspensePromise) => { const oldOriginalPromise = oldSuspensePromise[SUSPENSE_PROMISE].o; const newOriginalPromise = newSuspensePromise[SUSPENSE_PROMISE].o; return oldOriginalPromise === newOriginalPromise || oldSuspensePromise === newOriginalPromise || isSuspensePromise(oldOriginalPromise) && isEqualSuspensePromise(oldOriginalPromise, newSuspensePromise); }; const createSuspensePromise = (basePromise, promise) => { const suspensePromiseExtra = { b: basePromise, o: promise, c: null }; const suspensePromise = new Promise((resolve) => { suspensePromiseExtra.c = () => { suspensePromiseExtra.c = null; resolve(); }; promise.finally(suspensePromiseExtra.c); }); suspensePromise[SUSPENSE_PROMISE] = suspensePromiseExtra; return suspensePromise; }; const copySuspensePromise = (suspensePromise) => createSuspensePromise( suspensePromise[SUSPENSE_PROMISE].b, suspensePromise[SUSPENSE_PROMISE].o ); const promiseAbortMap = /* @__PURE__ */ new WeakMap(); const registerPromiseAbort = exports('SECRET_INTERNAL_registerPromiseAbort', (basePromise, abort) => { promiseAbortMap.set(basePromise, abort); }); const hasInitialValue = (atom) => "init" in atom; const READ_ATOM = "r"; const WRITE_ATOM = "w"; const COMMIT_ATOM = "c"; const SUBSCRIBE_ATOM = "s"; const RESTORE_ATOMS = "h"; const DEV_SUBSCRIBE_STATE = "n"; const DEV_GET_MOUNTED_ATOMS = "l"; const DEV_GET_ATOM_STATE = "a"; const DEV_GET_MOUNTED = "m"; const createStore = (initialValues) => { const committedAtomStateMap = /* @__PURE__ */ new WeakMap(); const mountedMap = /* @__PURE__ */ new WeakMap(); const pendingMap = /* @__PURE__ */ new Map(); let stateListeners; let mountedAtoms; { stateListeners = /* @__PURE__ */ new Set(); mountedAtoms = /* @__PURE__ */ new Set(); } if (initialValues) { for (const [atom, value] of initialValues) { const atomState = { v: value, r: 0, y: true, d: /* @__PURE__ */ new Map() }; { Object.freeze(atomState); if (!hasInitialValue(atom)) { console.warn( "Found initial value for derived atom which can cause unexpected behavior", atom ); } } committedAtomStateMap.set(atom, atomState); } } const suspensePromiseCacheMap = /* @__PURE__ */ new WeakMap(); const addSuspensePromiseToCache = (version, atom, suspensePromise) => { let cache = suspensePromiseCacheMap.get(atom); if (!cache) { cache = /* @__PURE__ */ new Map(); suspensePromiseCacheMap.set(atom, cache); } suspensePromise.then(() => { if (cache.get(version) === suspensePromise) { cache.delete(version); if (!cache.size) { suspensePromiseCacheMap.delete(atom); } } }); cache.set(version, suspensePromise); }; const cancelAllSuspensePromiseInCache = (atom) => { const versionSet = /* @__PURE__ */ new Set(); const cache = suspensePromiseCacheMap.get(atom); if (cache) { suspensePromiseCacheMap.delete(atom); cache.forEach((suspensePromise, version) => { cancelSuspensePromise(suspensePromise); versionSet.add(version); }); } return versionSet; }; const versionedAtomStateMapMap = /* @__PURE__ */ new WeakMap(); const getVersionedAtomStateMap = (version) => { let versionedAtomStateMap = versionedAtomStateMapMap.get(version); if (!versionedAtomStateMap) { versionedAtomStateMap = /* @__PURE__ */ new Map(); versionedAtomStateMapMap.set(version, versionedAtomStateMap); } return versionedAtomStateMap; }; const getAtomState = (version, atom) => { if (version) { const versionedAtomStateMap = getVersionedAtomStateMap(version); let atomState = versionedAtomStateMap.get(atom); if (!atomState) { atomState = getAtomState(version.p, atom); if (atomState && "p" in atomState && isSuspensePromiseAlreadyCancelled(atomState.p)) { atomState = void 0; } if (atomState) { versionedAtomStateMap.set(atom, atomState); } } return atomState; } return committedAtomStateMap.get(atom); }; const setAtomState = (version, atom, atomState) => { { Object.freeze(atomState); } if (version) { const versionedAtomStateMap = getVersionedAtomStateMap(version); versionedAtomStateMap.set(atom, atomState); } else { const prevAtomState = committedAtomStateMap.get(atom); committedAtomStateMap.set(atom, atomState); if (!pendingMap.has(atom)) { pendingMap.set(atom, prevAtomState); } } }; const createReadDependencies = (version, prevReadDependencies = /* @__PURE__ */ new Map(), dependencies) => { if (!dependencies) { return prevReadDependencies; } const readDependencies = /* @__PURE__ */ new Map(); let changed = false; dependencies.forEach((atom) => { var _a; const revision = ((_a = getAtomState(version, atom)) == null ? void 0 : _a.r) || 0; readDependencies.set(atom, revision); if (prevReadDependencies.get(atom) !== revision) { changed = true; } }); if (prevReadDependencies.size === readDependencies.size && !changed) { return prevReadDependencies; } return readDependencies; }; const setAtomValue = (version, atom, value, dependencies, suspensePromise) => { const atomState = getAtomState(version, atom); if (atomState) { if (suspensePromise && (!("p" in atomState) || !isEqualSuspensePromise(atomState.p, suspensePromise))) { return atomState; } if ("p" in atomState) { cancelSuspensePromise(atomState.p); } } const nextAtomState = { v: value, r: (atomState == null ? void 0 : atomState.r) || 0, y: true, d: createReadDependencies(version, atomState == null ? void 0 : atomState.d, dependencies) }; let changed = !(atomState == null ? void 0 : atomState.y); if (!atomState || !("v" in atomState) || !Object.is(atomState.v, value)) { changed = true; ++nextAtomState.r; if (nextAtomState.d.has(atom)) { nextAtomState.d = new Map(nextAtomState.d).set(atom, nextAtomState.r); } } else if (nextAtomState.d !== atomState.d && (nextAtomState.d.size !== atomState.d.size || !Array.from(nextAtomState.d.keys()).every((a) => atomState.d.has(a)))) { changed = true; Promise.resolve().then(() => { flushPending(version); }); } if (atomState && !changed) { return atomState; } setAtomState(version, atom, nextAtomState); return nextAtomState; }; const setAtomReadError = (version, atom, error, dependencies, suspensePromise) => { const atomState = getAtomState(version, atom); if (atomState) { if (suspensePromise && (!("p" in atomState) || !isEqualSuspensePromise(atomState.p, suspensePromise))) { return atomState; } if ("p" in atomState) { cancelSuspensePromise(atomState.p); } } const nextAtomState = { e: error, r: ((atomState == null ? void 0 : atomState.r) || 0) + 1, y: true, d: createReadDependencies(version, atomState == null ? void 0 : atomState.d, dependencies) }; setAtomState(version, atom, nextAtomState); return nextAtomState; }; const setAtomSuspensePromise = (version, atom, suspensePromise, dependencies) => { const atomState = getAtomState(version, atom); if (atomState && "p" in atomState) { if (isEqualSuspensePromise(atomState.p, suspensePromise) && !isSuspensePromiseAlreadyCancelled(atomState.p)) { if (!atomState.y) { return { ...atomState, y: true }; } return atomState; } cancelSuspensePromise(atomState.p); } addSuspensePromiseToCache(version, atom, suspensePromise); const nextAtomState = { p: suspensePromise, r: ((atomState == null ? void 0 : atomState.r) || 0) + 1, y: true, d: createReadDependencies(version, atomState == null ? void 0 : atomState.d, dependencies) }; setAtomState(version, atom, nextAtomState); return nextAtomState; }; const setAtomPromiseOrValue = (version, atom, promiseOrValue, dependencies) => { if (promiseOrValue instanceof Promise) { const suspensePromise = createSuspensePromise( promiseOrValue, promiseOrValue.then((value) => { setAtomValue(version, atom, value, dependencies, suspensePromise); }).catch((e) => { if (e instanceof Promise) { if (isSuspensePromise(e)) { return e.then(() => { readAtomState(version, atom, true); }); } return e; } setAtomReadError(version, atom, e, dependencies, suspensePromise); }) ); return setAtomSuspensePromise( version, atom, suspensePromise, dependencies ); } return setAtomValue( version, atom, promiseOrValue, dependencies ); }; const setAtomInvalidated = (version, atom) => { const atomState = getAtomState(version, atom); if (atomState) { const nextAtomState = { ...atomState, y: false }; setAtomState(version, atom, nextAtomState); } else { console.warn("[Bug] could not invalidate non existing atom", atom); } }; const readAtomState = (version, atom, force) => { if (!force) { const atomState = getAtomState(version, atom); if (atomState) { if (atomState.y && "p" in atomState && !isSuspensePromiseAlreadyCancelled(atomState.p)) { return atomState; } atomState.d.forEach((_, a) => { if (a !== atom) { if (!mountedMap.has(a)) { readAtomState(version, a); } else { const aState = getAtomState(version, a); if (aState && !aState.y) { readAtomState(version, a); } } } }); if (Array.from(atomState.d).every(([a, r]) => { const aState = getAtomState(version, a); return aState && !("p" in aState) && aState.r === r; })) { if (!atomState.y) { return { ...atomState, y: true }; } return atomState; } } } const dependencies = /* @__PURE__ */ new Set(); try { const promiseOrValue = atom.read((a) => { dependencies.add(a); const aState = a === atom ? getAtomState(version, a) : readAtomState(version, a); if (aState) { if ("e" in aState) { throw aState.e; } if ("p" in aState) { throw aState.p; } return aState.v; } if (hasInitialValue(a)) { return a.init; } throw new Error("no atom init"); }); return setAtomPromiseOrValue(version, atom, promiseOrValue, dependencies); } catch (errorOrPromise) { if (errorOrPromise instanceof Promise) { const suspensePromise = isSuspensePromise(errorOrPromise) && isSuspensePromiseAlreadyCancelled(errorOrPromise) ? copySuspensePromise(errorOrPromise) : createSuspensePromise(errorOrPromise, errorOrPromise); return setAtomSuspensePromise( version, atom, suspensePromise, dependencies ); } return setAtomReadError(version, atom, errorOrPromise, dependencies); } }; const readAtom = (readingAtom, version) => { const atomState = readAtomState(version, readingAtom); return atomState; }; const addAtom = (version, addingAtom) => { let mounted = mountedMap.get(addingAtom); if (!mounted) { mounted = mountAtom(version, addingAtom); } return mounted; }; const canUnmountAtom = (atom, mounted) => !mounted.l.size && (!mounted.t.size || mounted.t.size === 1 && mounted.t.has(atom)); const delAtom = (version, deletingAtom) => { const mounted = mountedMap.get(deletingAtom); if (mounted && canUnmountAtom(deletingAtom, mounted)) { unmountAtom(version, deletingAtom); } }; const invalidateDependents = (version, atom) => { const mounted = mountedMap.get(atom); mounted == null ? void 0 : mounted.t.forEach((dependent) => { if (dependent !== atom) { setAtomInvalidated(version, dependent); invalidateDependents(version, dependent); } }); }; const writeAtomState = (version, atom, update) => { let isSync = true; const writeGetter = (a, options) => { const aState = readAtomState(version, a); if ("e" in aState) { throw aState.e; } if ("p" in aState) { if (options == null ? void 0 : options.unstable_promise) { return aState.p.then(() => { const s = getAtomState(version, a); if (s && "p" in s && s.p === aState.p) { return new Promise((resolve) => setTimeout(resolve)).then( () => writeGetter(a, options) ); } return writeGetter(a, options); }); } { console.info( "Reading pending atom state in write operation. We throw a promise for now.", a ); } throw aState.p; } if ("v" in aState) { return aState.v; } { console.warn( "[Bug] no value found while reading atom in write operation. This is probably a bug.", a ); } throw new Error("no value found"); }; const setter = (a, v) => { let promiseOrVoid2; if (a === atom) { if (!hasInitialValue(a)) { throw new Error("atom not writable"); } const versionSet = cancelAllSuspensePromiseInCache(a); versionSet.forEach((cancelledVersion) => { if (cancelledVersion !== version) { setAtomPromiseOrValue(cancelledVersion, a, v); } }); const prevAtomState = getAtomState(version, a); const nextAtomState = setAtomPromiseOrValue(version, a, v); if (prevAtomState !== nextAtomState) { invalidateDependents(version, a); } } else { promiseOrVoid2 = writeAtomState(version, a, v); } if (!isSync) { flushPending(version); } return promiseOrVoid2; }; const promiseOrVoid = atom.write(writeGetter, setter, update); isSync = false; return promiseOrVoid; }; const writeAtom = (writingAtom, update, version) => { const promiseOrVoid = writeAtomState(version, writingAtom, update); flushPending(version); return promiseOrVoid; }; const isActuallyWritableAtom = (atom) => !!atom.write; const mountAtom = (version, atom, initialDependent) => { const mounted = { t: new Set(initialDependent && [initialDependent]), l: /* @__PURE__ */ new Set() }; mountedMap.set(atom, mounted); { mountedAtoms.add(atom); } const atomState = readAtomState(void 0, atom); atomState.d.forEach((_, a) => { const aMounted = mountedMap.get(a); if (aMounted) { aMounted.t.add(atom); } else { if (a !== atom) { mountAtom(version, a, atom); } } }); if (isActuallyWritableAtom(atom) && atom.onMount) { const setAtom = (update) => writeAtom(atom, update, version); const onUnmount = atom.onMount(setAtom); version = void 0; if (onUnmount) { mounted.u = onUnmount; } } return mounted; }; const unmountAtom = (version, 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(version, atom); if (atomState) { if ("p" in atomState) { cancelSuspensePromise(atomState.p); } atomState.d.forEach((_, a) => { if (a !== atom) { const mounted = mountedMap.get(a); if (mounted) { mounted.t.delete(atom); if (canUnmountAtom(a, mounted)) { unmountAtom(version, a); } } } }); } else { console.warn("[Bug] could not find atom state to unmount", atom); } }; const mountDependencies = (version, atom, atomState, prevReadDependencies) => { const dependencies = new Set(atomState.d.keys()); prevReadDependencies == null ? void 0 : prevReadDependencies.forEach((_, a) => { if (dependencies.has(a)) { dependencies.delete(a); return; } const mounted = mountedMap.get(a); if (mounted) { mounted.t.delete(atom); if (canUnmountAtom(a, mounted)) { unmountAtom(version, a); } } }); dependencies.forEach((a) => { const mounted = mountedMap.get(a); if (mounted) { mounted.t.add(atom); } else if (mountedMap.has(atom)) { mountAtom(version, a, atom); } }); }; const flushPending = (version) => { if (version) { const versionedAtomStateMap = getVersionedAtomStateMap(version); versionedAtomStateMap.forEach((atomState, atom) => { const committedAtomState = committedAtomStateMap.get(atom); if (atomState !== committedAtomState) { const mounted = mountedMap.get(atom); mounted == null ? void 0 : mounted.l.forEach((listener) => listener(version)); } }); return; } while (pendingMap.size) { const pending = Array.from(pendingMap); pendingMap.clear(); pending.forEach(([atom, prevAtomState]) => { const atomState = getAtomState(void 0, atom); if (atomState && atomState.d !== (prevAtomState == null ? void 0 : prevAtomState.d)) { mountDependencies(void 0, atom, atomState, prevAtomState == null ? void 0 : prevAtomState.d); } if (prevAtomState && !prevAtomState.y && (atomState == null ? void 0 : atomState.y)) { return; } const mounted = mountedMap.get(atom); mounted == null ? void 0 : mounted.l.forEach((listener) => listener()); }); } { stateListeners.forEach((l) => l()); } }; const commitVersionedAtomStateMap = (version) => { const versionedAtomStateMap = getVersionedAtomStateMap(version); versionedAtomStateMap.forEach((atomState, atom) => { const prevAtomState = committedAtomStateMap.get(atom); if (!prevAtomState || atomState.r > prevAtomState.r || atomState.y !== prevAtomState.y || atomState.r === prevAtomState.r && atomState.d !== prevAtomState.d) { committedAtomStateMap.set(atom, atomState); if (atomState.d !== (prevAtomState == null ? void 0 : prevAtomState.d)) { mountDependencies(version, atom, atomState, prevAtomState == null ? void 0 : prevAtomState.d); } } }); }; const commitAtom = (_atom, version) => { if (version) { commitVersionedAtomStateMap(version); } flushPending(void 0); }; const subscribeAtom = (atom, callback, version) => { const mounted = addAtom(version, atom); const listeners = mounted.l; listeners.add(callback); return () => { listeners.delete(callback); delAtom(version, atom); }; }; const restoreAtoms = (values, version) => { for (const [atom, value] of values) { if (hasInitialValue(atom)) { setAtomPromiseOrValue(version, atom, value); invalidateDependents(version, atom); } } flushPending(version); }; { return { [READ_ATOM]: readAtom, [WRITE_ATOM]: writeAtom, [COMMIT_ATOM]: commitAtom, [SUBSCRIBE_ATOM]: subscribeAtom, [RESTORE_ATOMS]: restoreAtoms, [DEV_SUBSCRIBE_STATE]: (l) => { stateListeners.add(l); return () => { stateListeners.delete(l); }; }, [DEV_GET_MOUNTED_ATOMS]: () => mountedAtoms.values(), [DEV_GET_ATOM_STATE]: (a) => committedAtomStateMap.get(a), [DEV_GET_MOUNTED]: (a) => mountedMap.get(a) }; } }; const createStoreForExport = exports('unstable_createStore', (initialValues) => { const store = createStore(initialValues); const get = (atom) => { const atomState = store[READ_ATOM](atom); if ("e" in atomState) { throw atomState.e; } if ("p" in atomState) { return void 0; } return atomState.v; }; const asyncGet = (atom) => new Promise((resolve, reject) => { const atomState = store[READ_ATOM](atom); if ("e" in atomState) { reject(atomState.e); } else if ("p" in atomState) { resolve(atomState.p.then(() => asyncGet(atom))); } else { resolve(atomState.v); } }); const set = (atom, update) => store[WRITE_ATOM](atom, update); const sub = (atom, callback) => store[SUBSCRIBE_ATOM](atom, callback); return { get, asyncGet, set, sub, SECRET_INTERNAL_store: store }; }); const createScopeContainer = (initialValues, unstable_createStore) => { const store = unstable_createStore ? unstable_createStore(initialValues).SECRET_INTERNAL_store : createStore(initialValues); return { s: store }; }; const ScopeContextMap = /* @__PURE__ */ new Map(); const getScopeContext = exports('SECRET_INTERNAL_getScopeContext', (scope) => { if (!ScopeContextMap.has(scope)) { ScopeContextMap.set(scope, createContext(createScopeContainer())); } return ScopeContextMap.get(scope); }); const Provider = exports('Provider', ({ children, initialValues, scope, unstable_createStore, unstable_enableVersionedWrite }) => { const [version, setVersion] = useState({}); useEffect(() => { const scopeContainer = scopeContainerRef.current; if (scopeContainer.w) { scopeContainer.s[COMMIT_ATOM](null, version); delete version.p; scopeContainer.v = version; } }, [version]); const scopeContainerRef = useRef(); if (!scopeContainerRef.current) { const scopeContainer = createScopeContainer( initialValues, unstable_createStore ); if (unstable_enableVersionedWrite) { let retrying = 0; scopeContainer.w = (write) => { setVersion((parentVersion) => { const nextVersion = retrying ? parentVersion : { p: parentVersion }; write(nextVersion); return nextVersion; }); }; scopeContainer.v = version; scopeContainer.r = (fn) => { ++retrying; fn(); --retrying; }; } scopeContainerRef.current = scopeContainer; } const ScopeContainerContext = getScopeContext(scope); return createElement( ScopeContainerContext.Provider, { value: scopeContainerRef.current }, children ); }); function atom(read, write) { return atom$1(read, write); } function useAtomValue(atom, scope) { const ScopeContext = getScopeContext(scope); const scopeContainer = useContext(ScopeContext); const { s: store, v: versionFromProvider } = scopeContainer; const getAtomValue = (version2) => { const atomState = store[READ_ATOM](atom, version2); if (!atomState.y) { throw new Error("should not be invalidated"); } if ("e" in atomState) { throw atomState.e; } if ("p" in atomState) { throw atomState.p; } if ("v" in atomState) { return atomState.v; } throw new Error("no atom value"); }; const [[version, valueFromReducer, atomFromReducer], rerenderIfChanged] = useReducer( (prev, nextVersion) => { const nextValue = getAtomValue(nextVersion); if (Object.is(prev[1], nextValue) && prev[2] === atom) { return prev; } return [nextVersion, nextValue, atom]; }, versionFromProvider, (initialVersion) => { const initialValue = getAtomValue(initialVersion); return [initialVersion, initialValue, atom]; } ); let value = valueFromReducer; if (atomFromReducer !== atom) { rerenderIfChanged(version); value = getAtomValue(version); } useEffect(() => { const { v: versionFromProvider2 } = scopeContainer; if (versionFromProvider2) { store[COMMIT_ATOM](atom, versionFromProvider2); } const unsubscribe = store[SUBSCRIBE_ATOM]( atom, rerenderIfChanged, versionFromProvider2 ); rerenderIfChanged(versionFromProvider2); return unsubscribe; }, [store, atom, scopeContainer]); useEffect(() => { store[COMMIT_ATOM](atom, version); }); useDebugValue(value); return value; } function useSetAtom(atom, scope) { const ScopeContext = getScopeContext(scope); const { s: store, w: versionedWrite } = useContext(ScopeContext); const setAtom = useCallback( (update) => { if (!("write" in atom)) { throw new Error("not writable atom"); } const write = (version) => store[WRITE_ATOM](atom, update, version); return versionedWrite ? versionedWrite(write) : write(); }, [store, versionedWrite, atom] ); return setAtom; } function useAtom(atom, scope) { if ("scope" in atom) { console.warn( "atom.scope is deprecated. Please do useAtom(atom, scope) instead." ); scope = atom.scope; } return [ useAtomValue(atom, scope), useSetAtom(atom, scope) ]; } }) }; }));