UNPKG

jotai-devtools

Version:

A powerful toolkit to enhance your development experience with Jotai

516 lines (482 loc) 18 kB
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/utils/useAtomsSnapshot.ts var _react = require('react'); // src/utils/hooks/useDevToolsStore.ts var _jotai = require('jotai'); // src/utils/internals/compose-with-devtools.ts var _vanilla = require('jotai/vanilla'); var _internals = require('jotai/vanilla/internals'); var isDevToolsStore = (store) => { return "subscribeStore" in store; }; var __composeWithDevTools = (store) => { const { sub, set, get } = store; const storeListeners = /* @__PURE__ */ new Set(); const recentlySetAtomsMap = /* @__PURE__ */ new WeakMap(); const reduceCountOrRemoveRecentlySetAtom = (atom, onFound) => { const foundCount = recentlySetAtomsMap.get(atom); if (typeof foundCount === "number") { if (foundCount > 1) { recentlySetAtomsMap.set(atom, foundCount - 1); } else { recentlySetAtomsMap.delete(atom); } _optionalChain([onFound, 'optionalCall', _ => _()]); } }; const increaseCountRecentlySetAtom = (atom) => { const foundCount = recentlySetAtomsMap.get(atom); recentlySetAtomsMap.set(atom, (foundCount || 0) + 1); }; store.sub = (...args) => { const unsub = sub(...args); storeListeners.forEach((l) => l({ type: "sub" })); return () => { unsub(); reduceCountOrRemoveRecentlySetAtom(args[0]); storeListeners.forEach((l) => l({ type: "unsub" })); }; }; store.get = (...args) => { const value = get(...args); reduceCountOrRemoveRecentlySetAtom(args[0], () => { if (value instanceof Promise) { value.finally(() => { Promise.resolve().then(() => { storeListeners.forEach((l) => l({ type: "async-get" })); }); }); } }); return value; }; store.set = (...args) => { const value = set(...args); increaseCountRecentlySetAtom(args[0]); storeListeners.forEach((l) => l({ type: "set" })); return value; }; store.subscribeStore = (l) => { storeListeners.add(l); return () => { storeListeners.delete(l); }; }; store.getMountedAtoms = () => { return store.get_mounted_atoms(); }; store.getAtomState = (atom) => { const aState = store.get_internal_weak_map().get(atom); if (aState) { return { v: aState.v, e: aState.e, d: new Set(aState.d.keys()) }; } return void 0; }; store.getMountedAtomState = (atom) => { const aState = store.get_internal_weak_map().get(atom); if (aState && "m" in aState) { return { l: aState.m.l, t: aState.m.t }; } return void 0; }; store.restoreAtoms = (values) => { store.restore_atoms(values); storeListeners.forEach((l) => l({ type: "restore" })); }; return store; }; var createDevStore = () => { let inRestoreAtom = 0; const storeHooks = _internals.INTERNAL_initializeStoreHooks.call(void 0, {}); const atomStateMap = /* @__PURE__ */ new WeakMap(); const mountedAtoms = /* @__PURE__ */ new WeakMap(); const store = _internals.INTERNAL_buildStoreRev1.call(void 0, atomStateMap, mountedAtoms, void 0, void 0, void 0, void 0, storeHooks, void 0, (atom, get, set, ...args) => { if (inRestoreAtom) { return set(atom, ...args); } return atom.write(get, set, ...args); } ); const debugMountedAtoms = /* @__PURE__ */ new Set(); storeHooks.m.add(void 0, (atom) => { debugMountedAtoms.add(atom); const atomState = atomStateMap.get(atom); atomState.m = mountedAtoms.get(atom); }); storeHooks.u.add(void 0, (atom) => { debugMountedAtoms.delete(atom); const atomState = atomStateMap.get(atom); delete atomState.m; }); const devStore = { // store dev methods (these are tentative and subject to change without notice) get_internal_weak_map: () => atomStateMap, get_mounted_atoms: () => debugMountedAtoms, restore_atoms: (values) => { const restoreAtom = { read: () => null, write: (_get, set) => { ++inRestoreAtom; try { for (const [atom, value] of values) { if ("init" in atom) { set(atom, value); } } } finally { --inRestoreAtom; } } }; store.set(restoreAtom); } }; return { ...store, ...devStore }; }; var isDevStore = (store) => { return "get_internal_weak_map" in store; }; _vanilla.INTERNAL_overrideCreateStore.call(void 0, (prev) => { return createDevStore; }); var composeWithDevTools = (store) => { if (isDevToolsStore(store)) { return store; } if (isDevStore(store)) { return __composeWithDevTools(store); } return store; }; // src/utils/hooks/useDevToolsStore.ts var useDevToolsStore = (options) => { const store = _jotai.useStore.call(void 0, options); return composeWithDevTools(store); }; // src/utils/useAtomsSnapshot.ts var isEqualAtomsValues = (left, right) => left.size === right.size && Array.from(left).every(([left2, v]) => Object.is(right.get(left2), v)); var isEqualAtomsDependents = (left, right) => left.size === right.size && Array.from(left).every(([a, dLeft]) => { const dRight = right.get(a); return dRight && dLeft.size === dRight.size && Array.from(dLeft).every((d) => dRight.has(d)); }); function useAtomsSnapshot({ shouldShowPrivateAtoms = false, ...options } = {}) { const store = useDevToolsStore(options); const [atomsSnapshot, setAtomsSnapshot] = _react.useState.call(void 0, () => ({ values: /* @__PURE__ */ new Map(), dependents: /* @__PURE__ */ new Map() })); const duringReactRenderPhase = _react.useRef.call(void 0, true); duringReactRenderPhase.current = true; _react.useLayoutEffect.call(void 0, () => { duringReactRenderPhase.current = false; }); _react.useEffect.call(void 0, () => { if (!isDevToolsStore(store)) return; let prevValues = /* @__PURE__ */ new Map(); let prevDependents = /* @__PURE__ */ new Map(); const callback = () => { const values = /* @__PURE__ */ new Map(); const dependents = /* @__PURE__ */ new Map(); for (const atom of store.getMountedAtoms() || []) { if (!shouldShowPrivateAtoms && atom.debugPrivate) { continue; } const atomState = store.getAtomState(atom); if (atomState) { if ("v" in atomState) { values.set(atom, atomState.v); } } const mounted = store.getMountedAtomState(atom); if (mounted) { let atomDependents = mounted.t; if (!shouldShowPrivateAtoms) { atomDependents = new Set( Array.from(atomDependents.values()).filter( /* NOTE: This just removes private atoms from the dependents list, instead of hiding them from the dependency chain and showing the nested dependents of the private atoms. */ (dependent) => !dependent.debugPrivate ) ); } dependents.set(atom, atomDependents); } } if (isEqualAtomsValues(prevValues, values) && isEqualAtomsDependents(prevDependents, dependents)) { return; } prevValues = values; prevDependents = dependents; const deferrableAtomSetAction = () => setAtomsSnapshot({ values, dependents }); if (duringReactRenderPhase.current) { Promise.resolve().then(deferrableAtomSetAction); } else { deferrableAtomSetAction(); } }; const unsubscribe = store.subscribeStore(callback); callback(); return unsubscribe; }, [store, shouldShowPrivateAtoms]); return atomsSnapshot; } // src/utils/useGotoAtomsSnapshot.ts function useGotoAtomsSnapshot(options) { const store = useDevToolsStore(options); return _react.useCallback.call(void 0, (snapshot) => { if (isDevToolsStore(store)) { store.restoreAtoms(snapshot.values); } }, [store] ); } // src/utils/useAtomsDebugValue.ts var atomToPrintable = (atom) => atom.debugLabel || atom.toString(); var stateToPrintable = ([store, atoms]) => Object.fromEntries( atoms.flatMap((atom) => { const mounted = isDevToolsStore(store) && store.getMountedAtomState(atom); if (!mounted) { return []; } const dependents = mounted.t; const atomState = store.getAtomState(atom) || {}; return [ [ atomToPrintable(atom), { ..."e" in atomState && { error: atomState.e }, ..."v" in atomState && { value: atomState.v }, dependents: Array.from(dependents).map(atomToPrintable) } ] ]; }) ); var useAtomsDebugValue = (options) => { const enabled = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _2 => _2.enabled]), () => ( process.env.NODE_ENV !== "production")); const store = useDevToolsStore(options); const [atoms, setAtoms] = _react.useState.call(void 0, []); const duringReactRenderPhase = _react.useRef.call(void 0, true); duringReactRenderPhase.current = true; _react.useLayoutEffect.call(void 0, () => { duringReactRenderPhase.current = false; }); _react.useEffect.call(void 0, () => { if (!enabled || !isDevToolsStore(store)) { return; } const callback = () => { const deferrableAtomSetAction = () => setAtoms(Array.from(store.getMountedAtoms() || [])); if (duringReactRenderPhase.current) { Promise.resolve().then(deferrableAtomSetAction); } else { deferrableAtomSetAction(); } }; const unsubscribe = store.subscribeStore(callback); callback(); return unsubscribe; }, [enabled, store]); _react.useDebugValue.call(void 0, [store, atoms], stateToPrintable); }; // src/utils/useAtomDevtools.ts var _react3 = require('jotai/react'); // src/utils/redux-extension/createReduxConnection.ts var createReduxConnection = (extension, name) => { if (!extension) return void 0; const connection = extension.connect({ name }); return Object.assign(connection, { shouldInit: true }); }; // src/utils/redux-extension/getReduxExtension.ts var getReduxExtension = (enabled = process.env.NODE_ENV !== "production") => { if (!enabled) { return void 0; } const reduxExtension = window.__REDUX_DEVTOOLS_EXTENSION__; if (!reduxExtension && process.env.NODE_ENV !== "production") { console.warn("Please install/enable Redux devtools extension"); return void 0; } return reduxExtension; }; // src/utils/useAtomDevtools.ts function useAtomDevtools(anAtom, options) { const { enabled, name } = options || {}; const extension = getReduxExtension(enabled); const [value, setValue] = _react3.useAtom.call(void 0, anAtom, options); const lastValue = _react.useRef.call(void 0, value); const isTimeTraveling = _react.useRef.call(void 0, false); const devtools = _react.useRef.call(void 0, ); const atomName = name || anAtom.debugLabel || anAtom.toString(); _react.useEffect.call(void 0, () => { if (!extension) { return; } const setValueIfWritable = (value2) => { if (typeof setValue === "function") { setValue(value2); return; } console.warn( "[Warn] you cannot do write operations (Time-travelling, etc) in read-only atoms\n", anAtom ); }; devtools.current = createReduxConnection(extension, atomName); const unsubscribe = _optionalChain([devtools, 'access', _3 => _3.current, 'optionalAccess', _4 => _4.subscribe, 'call', _5 => _5((message) => { if (message.type === "ACTION" && message.payload) { try { setValueIfWritable(JSON.parse(message.payload)); } catch (e) { console.error( "please dispatch a serializable value that JSON.parse() support\n", e ); } } else if (message.type === "DISPATCH" && message.state) { if (_optionalChain([message, 'access', _6 => _6.payload, 'optionalAccess', _7 => _7.type]) === "JUMP_TO_ACTION" || _optionalChain([message, 'access', _8 => _8.payload, 'optionalAccess', _9 => _9.type]) === "JUMP_TO_STATE") { isTimeTraveling.current = true; setValueIfWritable(JSON.parse(message.state)); } } else if (message.type === "DISPATCH" && _optionalChain([message, 'access', _10 => _10.payload, 'optionalAccess', _11 => _11.type]) === "COMMIT") { _optionalChain([devtools, 'access', _12 => _12.current, 'optionalAccess', _13 => _13.init, 'call', _14 => _14(lastValue.current)]); } else if (message.type === "DISPATCH" && _optionalChain([message, 'access', _15 => _15.payload, 'optionalAccess', _16 => _16.type]) === "IMPORT_STATE") { const computedStates = _optionalChain([message, 'access', _17 => _17.payload, 'access', _18 => _18.nextLiftedState, 'optionalAccess', _19 => _19.computedStates]) || []; computedStates.forEach(({ state }, index) => { if (index === 0) { _optionalChain([devtools, 'access', _20 => _20.current, 'optionalAccess', _21 => _21.init, 'call', _22 => _22(state)]); } else { setValueIfWritable(state); } }); } })]); return unsubscribe; }, [anAtom, extension, atomName, setValue]); _react.useEffect.call(void 0, () => { if (!devtools.current) { return; } lastValue.current = value; if (devtools.current.shouldInit) { devtools.current.init(value); devtools.current.shouldInit = false; } else if (isTimeTraveling.current) { isTimeTraveling.current = false; } else { devtools.current.send( `${atomName} - ${(/* @__PURE__ */ new Date()).toLocaleString()}`, value ); } }, [anAtom, extension, atomName, value]); } // src/utils/useAtomsDevtools.ts var atomToPrintable2 = (atom) => atom.debugLabel && !String(atom).includes(":") ? `${atom}:${atom.debugLabel}` : `${atom}`; var getDevtoolsState = (atomsSnapshot) => { const values = {}; atomsSnapshot.values.forEach((v, atom) => { values[atomToPrintable2(atom)] = v; }); const dependents = {}; atomsSnapshot.dependents.forEach((d, atom) => { dependents[atomToPrintable2(atom)] = Array.from(d).map(atomToPrintable2); }); return { values, dependents }; }; function useAtomsDevtools(name, options) { const { enabled } = options || {}; const extension = getReduxExtension(enabled); const atomsSnapshot = useAtomsSnapshot(options); const goToSnapshot = useGotoAtomsSnapshot(options); const isTimeTraveling = _react.useRef.call(void 0, false); const isRecording = _react.useRef.call(void 0, true); const devtools = _react.useRef.call(void 0, ); const snapshots = _react.useRef.call(void 0, []); _react.useEffect.call(void 0, () => { if (!extension) { return; } const getSnapshotAt = (index = snapshots.current.length - 1) => { const snapshot = snapshots.current[index >= 0 ? index : 0]; if (!snapshot) { throw new Error("snapshot index out of bounds"); } return snapshot; }; devtools.current = createReduxConnection(extension, name); const devtoolsUnsubscribe = _optionalChain([devtools, 'access', _23 => _23.current, 'optionalAccess', _24 => _24.subscribe, 'call', _25 => _25((message) => { switch (message.type) { case "DISPATCH": switch (_optionalChain([message, 'access', _26 => _26.payload, 'optionalAccess', _27 => _27.type])) { case "RESET": break; case "COMMIT": _optionalChain([devtools, 'access', _28 => _28.current, 'optionalAccess', _29 => _29.init, 'call', _30 => _30(getDevtoolsState(getSnapshotAt()))]); snapshots.current = []; break; case "JUMP_TO_ACTION": case "JUMP_TO_STATE": isTimeTraveling.current = true; goToSnapshot(getSnapshotAt(message.payload.actionId - 1)); break; case "PAUSE_RECORDING": isRecording.current = !isRecording.current; break; } } })]); return () => { _optionalChain([extension, 'optionalAccess', _31 => _31.disconnect, 'optionalCall', _32 => _32()]); _optionalChain([devtoolsUnsubscribe, 'optionalCall', _33 => _33()]); }; }, [extension, goToSnapshot, name]); _react.useEffect.call(void 0, () => { if (!devtools.current) { return; } if (devtools.current.shouldInit) { devtools.current.init(void 0); devtools.current.shouldInit = false; return; } if (isTimeTraveling.current) { isTimeTraveling.current = false; } else if (isRecording.current) { snapshots.current.push(atomsSnapshot); devtools.current.send( { type: `${snapshots.current.length}`, updatedAt: (/* @__PURE__ */ new Date()).toLocaleString() }, getDevtoolsState(atomsSnapshot) ); } }, [atomsSnapshot]); } exports.isDevToolsStore = isDevToolsStore; exports.useDevToolsStore = useDevToolsStore; exports.useAtomsSnapshot = useAtomsSnapshot; exports.useGotoAtomsSnapshot = useGotoAtomsSnapshot; exports.useAtomsDebugValue = useAtomsDebugValue; exports.useAtomDevtools = useAtomDevtools; exports.useAtomsDevtools = useAtomsDevtools;