UNPKG

@zedux/react

Version:

A Molecular State Engine for React

1,402 lines (1,389 loc) 70.3 kB
import T, { createContext as B, useContext as ce, useMemo as Te, useEffect as X, useId as Ne, useState as ae } from "react"; const $e = (i) => { const e = Object.getPrototypeOf(i); return e ? Object.getPrototypeOf(e) ? "complex object" : "object" : "prototype-less object"; }, Nt = (i) => { const e = typeof i; return e !== "object" ? e : i ? Array.isArray(i) ? "array" : $e(i) : "null"; }, j = (i) => { const e = (t) => { const s = { type: e.type }; return typeof t < "u" && (s.payload = t), s; }; return e.type = i, e; }, de = (i, e) => typeof i == "string" ? i : i.type, Me = (i, e) => i.map((t) => de(t)), $t = (i) => { const e = {}, t = (s = i, n) => { const r = e[n.type] || []; return De(r, s, n); }; return t.reduce = (s, n) => { const r = Array.isArray(s) ? Me(s) : [de(s)]; return Pe(e, r, n), t; }, t; }, Pe = (i, e, t) => { e.forEach((s) => { i[s] || (i[s] = []), i[s].push(t); }); }, De = (i, e, t) => i.reduce((s, n) => n(s, t.payload, t), e), y = (i, e) => { var t; return ((t = i == null ? void 0 : i.constructor) === null || t === void 0 ? void 0 : t.$$typeof) === e.$$typeof; }, q = (i) => { if (typeof i != "object" || !i) return !1; const e = Object.getPrototypeOf(i); return e ? Object.getPrototypeOf(e) === null : !1; }, w = "@@zedux/", _ = { batch: `${w}batch`, delegate: `${w}delegate`, hydrate: `${w}hydrate`, ignore: `${w}ignore`, inherit: `${w}inherit`, merge: `${w}merge`, prime: `${w}prime` }, $ = 1, W = 2, ue = 3, he = 4, Be = Symbol.for("zedux.store"), ze = (i, e, t) => { const s = {}; return Object.entries(i).forEach(([n, r]) => { const o = [...t, n]; s[n] = le(r, e, o); }), s; }, Ge = (i, e, t, s) => i === W ? { type: i } : i === ue ? { type: i, reducer: e } : { type: i, destroy: t(s, e), reducer: We(e), store: e }, Ue = (i) => typeof i == "function" ? ue : i && y(i, S) ? he : q(i) ? $ : W, le = (i, e, t = []) => { const s = Ue(i); return s !== $ ? Ge(s, i, e, t) : { type: s, children: ze(i, e, t) }; }, We = (i) => (t, s) => { (s.type === _.hydrate || s.type === _.merge) && (s = { type: _.hydrate, payload: t }); const n = { metaType: _.inherit, payload: s }; return i.dispatch(n); }, He = (i, { create: e, get: t, isNode: s, set: n, size: r }) => (o = e(), c) => { let a = e(), d = !1; return Object.keys(i).forEach((u) => { const { reducer: h } = i[u], l = s(o) ? t(o, u) : void 0, g = h(l, c); a = n(a, u, g), d || (d = g !== l); }), d || (d = !s(o) || !Object.keys(i).length && !!r(o)), d ? a : o; }, V = (i) => { if (!i) return; const { children: e, destroy: t } = i; t && t(), e && Object.values(e).forEach(V); }, se = (i, e, t) => { const s = Object.assign({}, i.children); return Object.keys(e.children).forEach((n) => { var r; const o = e.children[n], c = (r = i.children) === null || r === void 0 ? void 0 : r[n], a = fe(c, o, t); if (a.type === W) { delete s[n]; return; } s[n] = a; }), { children: s, reducer: He(s, t), type: $ }; }, fe = (i, e, t) => e.type !== $ ? (V(i), e) : !i || i.type !== $ ? (V(i), se({ type: W }, e, t)) : se(i, e, t), pe = (i, e, t) => { if (!t.isNode(i) || !t.isNode(e)) return [e, e !== i]; let s = !1; const n = t.clone(i); return t.iterate(e, (r, o) => { const c = t.get(n, r), [a, d] = t.isNode(o) ? ( // Recursively merge the nested nodes. pe(c, o, t) ) : ( // Not a nested node (anymore, at least) [o, o !== c] ); d && (s || (s = d), t.set(n, r, a)); }), [s ? n : i, s]; }, Le = (i, e, t) => !e || !t ? i.payload : (e.payload = i.payload, t), Fe = (i, e) => { for (; i.metaType; ) { if (i.metaType === e) return i.metaData; i = i.payload; } }, Ke = (i) => { for (; i.metaType; ) i = i.payload; return i; }, Ve = (i, e) => { let t = i, s = null, n = null; for (; t.metaType; ) { if (t.metaType === e) return Le(t, s, n); const r = Object.assign({}, t); s && (s.payload = r), s = r, t = t.payload, n || (n = s); } return i; }, Z = "Minified Error", Ze = (i, e) => { for (const t of e) { if (i.type !== $) throw new ReferenceError(Z); if (i = i.children[t], !i) throw new ReferenceError(Z); } return i; }, Ye = (i, e) => { const t = Fe(e, _.delegate); if (!t || !i) return !1; const s = Ze(i, t); if (s.type !== he) throw new TypeError(Z); s.store.dispatch(Ve(e, _.delegate)); }, _e = (i, e, t, s) => { if (!e.length) return t; const n = s.clone(i), r = e[0]; return s.set(n, r, _e(s.get(i, r), e.slice(1), t, s)); }, Xe = (i) => Object.assign({}, i), qe = () => ({}), Qe = (i, e) => i[e], Je = q, et = (i, e) => { Object.entries(i).forEach(([t, s]) => e(t, s)); }, tt = (i, e, t) => (i[e] = t, i), st = (i) => Object.keys(i).length, nt = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, clone: Xe, create: qe, get: Qe, isNode: Je, iterate: et, set: tt, size: st }, Symbol.toStringTag, { value: "Module" })), it = { scheduleNow: (i) => i.task() }, ne = { type: _.prime }, z = (i, e) => new S(i, e); class S { constructor(e, t) { this._isSolo = !0, this._subscribers = [], this.dispatch = (s) => this._isSolo ? this._dispatch(s) : (this._scheduler.scheduleNow({ task: () => this._dispatch(s), type: 0 // UpdateStore (0) }), this._state), this.setState = (s, n) => this._isSolo ? this._setState(s, n) : (this._scheduler.scheduleNow({ task: () => this._setState(s, n), type: 0 // UpdateStore (0) }), this._state), this._state = t, this._scheduler = S._scheduler || it, e && this.use(e); } actionStream() { return { [Symbol.observable]() { return this; }, "@@observable"() { return this; }, subscribe: (e) => this.subscribe({ effects: ({ action: t, error: s }) => { var n, r; s && typeof e != "function" ? (n = e.error) === null || n === void 0 || n.call(e, s) : t && (typeof e == "function" ? e(t) : (r = e.next) === null || r === void 0 || r.call(e, t)); } }) }; } /** Returns the current state of the store. Do not mutate the returned value. */ getState() { return this._state; } /** Applies a partial state update to the store. Accepts either a deep partial state object or a function that accepts the current state and returns a deep partial state object. This method only recursively traverses normal JS objects. If your store deeply nests any other data structure, including arrays or maps, you'll have to deeply merge them yourself using `store.setState()`. Dispatches the special `merge` action to the store's reducers. This action's `payload` property will be set to the resolved partial state update. Effects subscribers can record this action to implement time travel. IMPORTANT: Deep setting cannot remove properties from the state tree. Use `store.setState()` for that. Throws an error if called from the reducer layer. Returns the new state. Unlike setState, setStateDeep is not bound. You must call it with context - e.g. by using dot-notation: `store.setStateDeep(...)` */ setStateDeep(e, t) { return this._isSolo ? this._setState(e, t, !0) : (this._scheduler.scheduleNow({ task: () => this._setState(e, t, !0), type: 0 // UpdateStore (0) }), this._state); } /** Registers a subscriber with the store. The subscriber will be notified every time the store's state changes. Returns a subscription object. Calling `subscription.unsubscribe()` unregisters the subscriber. */ subscribe(e) { const t = typeof e == "function" ? { next: e } : e; return this._subscribers.push(t), { unsubscribe: () => { this._subscribers = this._subscribers.filter((n) => n !== t); } }; } /** Merges a hierarchy descriptor into the existing hierarchy descriptor. Intelligently diffs the two hierarchies and only creates/recreates the necessary reducers. Dispatches the special `prime` action to the store. */ use(e) { const t = le(e, (n, r) => this._registerChildStore(n, r)), s = this._tree = fe(this._tree, t, this.constructor.hierarchyConfig); return (this._rootReducer = s.reducer) && this._dispatchAction(ne, ne, this._state), this; } /** * Only for internal use. */ _register(e) { const t = this._parents || (this._parents = []); return t.push(e), this._isSolo = !1, () => { const s = t.indexOf(e); s > -1 && t.splice(s, 1); }; } [Symbol.observable]() { return this; } "@@observable"() { return this; } _dispatch(e) { return Ye(this._tree, e) !== !1 ? this._state : this._routeAction(e); } _dispatchAction(e, t, s) { this._isDispatching = !0; let n, r = s; try { this._rootReducer && (r = this._rootReducer(s, t)); } catch (o) { throw n = o, o; } finally { this._isDispatching = !1, this._notify(r, e, n); } return r; } _dispatchHydration(e, t, s) { const n = t === _.hydrate ? e : pe(this._state, e, this.constructor.hierarchyConfig)[0]; if (n === this._state && !s) return this._state; const r = { meta: s, payload: n, type: t }; return this._dispatchAction(r, r, n); } _dispatchStateSetter(e, t, s) { let n; try { n = e(this._state); } catch (r) { throw r; } return this._dispatchHydration(n, s ? _.merge : _.hydrate, t); } _doNotify(e) { const { _subscribers: t } = this, s = e.newState !== e.oldState; for (const n of t) e.error && n.error && n.error(e.error), s && n.next && n.next(e.newState, e.oldState, e.action), n.effects && n.effects(e); } _notify(e, t, s) { var n; const r = { action: t, error: s, newState: e, oldState: this._state, store: this }; if (this._state = e, this._isSolo) return this._doNotify(r); this._scheduler.scheduleNow({ task: () => this._doNotify(r), type: 1 // InformSubscribers (1) }), (n = this._parents) === null || n === void 0 || n.forEach((o) => o(r)); } _registerChildStore(e, t) { this._isSolo = !1; const s = ({ action: n, error: r, newState: o, oldState: c }) => { if (this._isDispatching) return; const a = o === c ? this._state : _e(this._state, e, o, this.constructor.hierarchyConfig), d = { metaType: _.delegate, metaData: e, payload: n }; this._notify(a, d, r); }; return t._register(s); } _routeAction(e) { const t = Ke(e); return t.type === _.hydrate || t.type === _.merge ? this._dispatchHydration(t.payload, t.type, t.meta) : this._dispatchAction(e, t, this._state); } _setState(e, t, s = !1) { return typeof e == "function" ? this._dispatchStateSetter(e, t, s) : this._dispatchHydration(e, s ? _.merge : _.hydrate, t); } } S.hierarchyConfig = nt; S.$$typeof = Be; const v = { ecosystemWiped: j("ecosystemWiped"), edgeCreated: j("edgeCreated"), edgeRemoved: j("edgeRemoved"), evaluationFinished: j("evaluationFinished"), instanceReused: j("instanceReused"), // either cache or instance will always be defined, depending on the node type stateChanged: j("stateChanged"), statusChanged: j("statusChanged") }, ye = (i) => ({ error: i, isError: !0, isLoading: !1, isSuccess: !1, status: "error" }), Y = (i) => ({ data: i, isError: !1, isLoading: !0, isSuccess: !1, status: "loading" }), ge = (i) => ({ data: i, isError: !1, isLoading: !1, isSuccess: !0, status: "success" }), Q = 1, N = 2, C = 4, J = (i, e) => !i || !e || i.length !== e.length || i.some((t, s) => e[s] !== t), b = "@@zedux"; class G { constructor(e) { this.promise = void 0, this.value = e, this.store = y(e, S) ? e : void 0, y(e, G) && Object.assign(this, e); } addExports(e) { return this.exports ? this.exports = Object.assign(Object.assign({}, this.exports), e) : this.exports = e, this; } setExports(e) { return this.exports = e, this; } setPromise(e) { return this.promise = e, this; } setTtl(e) { return this.ttl = e, this; } } G.$$typeof = Symbol.for(`${b}/AtomApi`); class M { addDependent({ callback: e, operation: t = "addDependent" } = {}) { const s = this.ecosystem._idGenerator.generateNodeId(); return this.ecosystem._graph.addEdge(s, this.id, t, Q | N, e), () => this.ecosystem._graph.removeEdge(s, this.id); } } M.$$typeof = Symbol.for(`${b}/AtomInstanceBase`); const me = 1, ee = 2, ve = (i) => y(i, S) ? me : ee, rt = (i) => { const e = ve(i), t = e === me ? i : z(); return e === ee && t.setState(typeof i == "function" ? () => i : i), [e, t]; }; class Se extends M { constructor(e, t, s, n) { super(), this.ecosystem = e, this.template = t, this.id = s, this.params = n, this.status = "Initializing", this.nextReasons = [], this.dispatch = (r) => this.store.dispatch(r), this.setState = (r, o) => this.store.setState(r, o), this.setStateDeep = (r, o) => this.store.setStateDeep(r, o), this._scheduleEvaluation = (r, o) => { this.status !== "Destroyed" && (this.nextReasons.push(r), !(this.nextReasons.length > 1) && this.ecosystem._scheduler.schedule({ id: this.id, task: this.evaluationTask, type: 2 // EvaluateGraphNode (2) }, o)); }, this.evaluationTask = () => this._evaluationTask(), this._createdAt = e._idGenerator.now(), this.exports = this.exports, this.promise = this.promise, this.store = this.store, this._promiseStatus = this._promiseStatus; } /** * Detach this atom instance from the ecosystem and clean up all graph edges * and other subscriptions/effects created by this atom instance. * * Destruction will bail out if this atom instance still has dependents. Pass * `true` to force-destroy the atom instance anyway. */ destroy(e) { var t, s, n, r; if (this.status === "Destroyed" || !e && (!((t = this.ecosystem._graph.nodes[this.id]) === null || t === void 0) && t.refCount)) return; (s = this._cancelDestruction) === null || s === void 0 || s.call(this), this._cancelDestruction = void 0, this._setStatus("Destroyed"), this.nextReasons.length && this.ecosystem._scheduler.unschedule(this.evaluationTask); const o = []; (n = this._injectors) === null || n === void 0 || n.forEach((c) => { var a; if (c.type !== "@@zedux/effect") { o.push(c); return; } (a = c.cleanup) === null || a === void 0 || a.call(c); }), o.forEach((c) => { var a; (a = c.cleanup) === null || a === void 0 || a.call(c); }), this.ecosystem._graph.removeDependencies(this.id), (r = this._subscription) === null || r === void 0 || r.unsubscribe(), this.ecosystem._destroyAtomInstance(this.id); } /** * An alias for `instance.store.getState()`. Returns the current state of this * atom instance's store. */ getState() { return this.store.getState(); } /** * Force this atom instance to reevaluate. */ invalidate(e = "invalidate", t = "External") { this._scheduleEvaluation({ operation: e, sourceType: t, type: "cache invalidated" }, !1), this.ecosystem._scheduler.flush(); } get _infusedSetter() { if (this._set) return this._set; const e = (t, s) => this.setState(t, s); return this._set = Object.assign(e, this.exports); } _init() { const e = this._doEvaluate(); [this._stateType, this.store] = rt(e), this._subscription = this.store.subscribe((s, n, r) => { if (this._isEvaluating) { this._bufferedUpdate = { newState: s, oldState: n, action: r }; return; } this._handleStateChange(s, n, r); }), this._setStatus("Active"), this.ecosystem._graph.flushUpdates(); const t = this.ecosystem._consumeHydration(this); this.template.manualHydration || typeof t > "u" || this.store.setState(t); } /** * When a standard atom instance's refCount hits 0 and a ttl is set, we set a * timeout to destroy this atom instance. */ _scheduleDestruction() { if (this.status !== "Active") return; this._setStatus("Stale"); const e = this._getTtl(); if (e == null || e === -1) return; if (e === 0) return this.destroy(); if (typeof e == "number") { const s = setTimeout(() => { this._cancelDestruction = void 0, this.destroy(); }, e); this._cancelDestruction = () => { this._setStatus("Active"), this._cancelDestruction = void 0, clearTimeout(s); }; return; } if (typeof e.then == "function") { let s = !1; Promise.allSettled([e]).then(() => { this._cancelDestruction = void 0, s || this.destroy(); }), this._cancelDestruction = () => { this._setStatus("Active"), this._cancelDestruction = void 0, s = !0; }; return; } const t = e.subscribe(() => { this._cancelDestruction = void 0, this.destroy(); }); this._cancelDestruction = () => { this._setStatus("Active"), this._cancelDestruction = void 0, t.unsubscribe(); }; } _doEvaluate() { const { _evaluationStack: e, _graph: t } = this.ecosystem; this._nextInjectors = []; let s; e.start(this), this._isEvaluating = !0, t.bufferUpdates(this.id); try { s = this._evaluate(); } catch (n) { throw this._nextInjectors.forEach((r) => { var o; (o = r.cleanup) === null || o === void 0 || o.call(r); }), this._nextInjectors = void 0, t.destroyBuffer(), n; } finally { e.finish(), this._isEvaluating = !1, this._bufferedUpdate && (this._handleStateChange(this._bufferedUpdate.newState, this._bufferedUpdate.oldState, this._bufferedUpdate.action), this._bufferedUpdate = void 0), this.prevReasons = this.nextReasons, this.nextReasons = []; } return this._injectors = this._nextInjectors, this._nextInjectors = void 0, this.status !== "Initializing" && t.flushUpdates(), s; } /** * A standard atom's value can be one of: * * - A raw value * - A Zedux store * - A function that returns a raw value * - A function that returns a Zedux store * - A function that returns an AtomApi */ _evaluate() { var e; const { _value: t } = this.template; if (typeof t != "function") return t; try { const s = t(...this.params); if (!y(s, G)) return s; const n = this.api = s; return this.status === "Initializing" && n.exports && (this.exports = n.exports), typeof ((e = n.value) === null || e === void 0 ? void 0 : e.then) == "function" ? this._setPromise(n.value, !0) : (n.promise && this._setPromise(n.promise), n.value); } catch (s) { throw console.error(`Zedux: Error while evaluating atom "${this.template.key}" with params:`, this.params, s), s; } } _evaluationTask() { const e = this._doEvaluate(); ve(e) === ee && this.store.setState(typeof e == "function" ? () => e : e); } _getTtl() { var e, t, s; if (((e = this.api) === null || e === void 0 ? void 0 : e.ttl) == null) return (t = this.template.ttl) !== null && t !== void 0 ? t : (s = this.ecosystem.atomDefaults) === null || s === void 0 ? void 0 : s.ttl; const { ttl: n } = this.api; return typeof n == "function" ? n() : n; } _handleStateChange(e, t, s) { this.ecosystem._graph.scheduleDependents(this.id, this.nextReasons, e, t, !1), this.ecosystem._mods.stateChanged && this.ecosystem.modBus.dispatch(v.stateChanged({ action: s, instance: this, newState: e, oldState: t, reasons: this.nextReasons })), s.meta !== _.batch && this.ecosystem._scheduler.flush(); } _setStatus(e) { const t = this.status; this.status = e, this.ecosystem._mods.statusChanged && this.ecosystem.modBus.dispatch(v.statusChanged({ newStatus: e, node: this, oldStatus: t })); } _setPromise(e, t) { var s; const n = (s = this.store) === null || s === void 0 ? void 0 : s.getState(); if (e === this.promise) return n; this.promise = e, e.then((o) => { this.promise === e && (this._promiseStatus = "success", t && this.store.setState(ge(o))); }).catch((o) => { this.promise === e && (this._promiseStatus = "error", this._promiseError = o, t && this.store.setState(ye(o))); }); const r = Y(n == null ? void 0 : n.data); return this._promiseStatus = r.status, this.ecosystem._graph.scheduleDependents(this.id, this.nextReasons, void 0, void 0, !0, "promise changed", "Updated", !0), r; } } const ot = (i, e, t) => new dt(i, e, t); let p = []; const P = () => p[p.length - 1].node, ct = (i) => p = i; class at { constructor(e) { this.ecosystem = e; const { _graph: t, selectors: s } = e, n = (c, a) => { const { id: d, store: u } = e.getInstance(c, a); return p.length && t.addEdge(p[p.length - 1].node.id, d, "get", 0), u.getState(); }, r = (c, a, d) => { var u; const h = e.getInstance(c, a); return p.length && t.addEdge(p[p.length - 1].node.id, h.id, (d == null ? void 0 : d[1]) || "getInstance", (u = d == null ? void 0 : d[0]) !== null && u !== void 0 ? u : C), h; }, o = (c, ...a) => { if (!p.length) return e.select(c, ...a); const d = s.getCache(c, a); return t.addEdge(p[p.length - 1].node.id, d.id, "select", 0), d.result; }; this.atomGetters = { ecosystem: e, get: n, getInstance: r, select: o }; } finish() { const e = p.pop(), { _idGenerator: t, _mods: s, modBus: n } = this.ecosystem; if (p.length || (S._scheduler = void 0), !e || !s.evaluationFinished) return; const r = e.start ? t.now(!0) - e.start : 0, o = { node: e.node, time: r }; n.dispatch(v.evaluationFinished(o)); } read() { return p[p.length - 1]; } start(e) { const { _idGenerator: t, _mods: s, _scheduler: n } = this.ecosystem, r = { node: e, start: s.evaluationFinished ? t.now(!0) : void 0 }; p.push(r), S._scheduler = n; } } const be = () => P().ecosystem._evaluationStack.atomGetters; class te { constructor(e, t, s) { this.key = e, this._value = t, this._config = s, Object.assign(this, s); } } te.$$typeof = Symbol.for(`${b}/AtomTemplateBase`); class dt extends te { constructor(e, t, s) { super(e, (...n) => t(be(), ...n), s), this._get = t; } _createInstance(e, t, s) { return new Se(e, this, t, s); } getInstanceId(e, t) { const s = this.key; return t != null && t.length ? `${s}-${e._idGenerator.hashParams(t, e.complexParams)}` : s; } override(e) { const t = ot(this.key, e || this._get, this._config); return t._isOverride = !0, t; } } const ut = (i, e, t) => new ht(i, e, t); class ht extends te { /** * This method should be overridden when creating custom atom classes that * create a custom atom instance class. Return a new instance of your atom * instance class. */ _createInstance(e, t, s) { return new Se(e, this, t, s); } getInstanceId(e, t) { const s = this.key; return t != null && t.length ? `${s}-${e._idGenerator.hashParams(t, e.complexParams)}` : s; } override(e) { const t = ut(this.key, e, this._config); return t._isOverride = !0, t; } } let I = z(null, {}); const Ee = (i) => I.getState()[i], Mt = () => ({ stack: p, store: I }), Pt = (i) => { I = i.store, ct(i.stack); }, Dt = () => { Object.values(I.getState().ecosystems).forEach((e) => { e.destroy(!0); }); }, L = ({ ecosystem: i, nodes: e }, t) => { const s = e[t]; s && !s.refCount && (s.isSelector ? i.selectors._destroySelector(t) : i._instances[t]._scheduleDestruction()); }, lt = ({ ecosystem: i, nodes: e }, t) => { var s, n; e[t].isSelector || (n = (s = i._instances[t])._cancelDestruction) === null || n === void 0 || n.call(s); }; class ft { constructor(e) { this.ecosystem = e, this.nodes = {}, this.updateStack = []; } /** * Draw a new edge between two nodes in the graph. This is how dependencies * are created between atoms, selectors, and external nodes like React * components. */ addEdge(e, t, s, n, r) { const { ecosystem: o, updateStack: c } = this, a = { callback: r, createdAt: o._idGenerator.now(), flags: n, operation: s }; if (!c.length) return this.finishAddingEdge(e, t, a); const { dependencies: d, key: u } = c[c.length - 1], h = d.get(t); return !h || h.flags > n ? (d.set(t, a), a) : h; } // Should only be used internally addNode(e, t) { this.nodes[e] || (this.nodes[e] = { dependencies: /* @__PURE__ */ new Map(), dependents: /* @__PURE__ */ new Map(), isSelector: t, refCount: 0, weight: 1 // this node doesn't have dependencies yet; its weight is 1 }); } /** * Prevent new graph edges from being added immediately. Instead, buffer them * so we can prevent duplicates or unnecessary edges. Call `.flushUpdates()` * to finish buffering. * * This is used during atom and AtomSelector evaluation to make the graph as * efficient as possible. */ bufferUpdates(e) { this.updateStack.push({ key: e, dependencies: /* @__PURE__ */ new Map() }); } /** * If an atom instance or AtomSelector errors during evaluation, we need to * destroy any instances or AtomSelectors created during that evaluation that * now have no dependents. */ destroyBuffer() { const { dependencies: e, key: t } = this.updateStack[this.updateStack.length - 1], s = this.nodes[t].dependencies; for (const n of e.keys()) s.get(n) || L(this, n); this.updateStack.pop(); } /** * Stop buffering updates for the node passed to `.bufferUpdates()` and add * the buffered edges to the graph. */ flushUpdates() { const { nodes: e, updateStack: t } = this, { dependencies: s, key: n } = t[t.length - 1], r = e[n].dependencies; for (const o of r.keys()) { const c = e[o].dependents.get(n); if (c.flags & (Q | N)) continue; const a = s.get(o); (!a || a.flags !== c.flags) && this.removeEdge(n, o); } for (const [o, c] of s.entries()) r.get(o) || this.finishAddingEdge(n, o, c); t.pop(); } removeDependencies(e) { const t = this.nodes[e]; if (!t) return; const s = t.dependencies; if (s) for (const n of s.keys()) this.removeEdge(e, n); } /** * Should only be used internally. Remove the graph edge between two nodes. * The dependent may not exist as a node in the graph if it's external, e.g. a * React component * * For some reason in React 18+, React destroys parents before children. This * means a parent EcosystemProvider may have already unmounted and wiped the * whole graph; this edge may already be destroyed. */ removeEdge(e, t) { const s = this.nodes[t], n = this.nodes[e]; if (n && n.dependencies.delete(t), !s) return; const r = s.dependents.get(e); if (!r) { s.isSelector && L(this, t); return; } s.dependents.delete(e), s.refCount--, r.flags & C || this.recalculateNodeWeight(e, -s.weight); const { _instances: o, _mods: c, _scheduler: a, modBus: d, selectors: u } = this.ecosystem; r.task && a.unschedule(r.task), c.edgeRemoved && d.dispatch(v.edgeRemoved({ dependency: o[t] || u._items[t], dependent: o[e] || u._items[e] || e, edge: r })), L(this, t); } // Should only be used internally removeNode(e) { const t = this.nodes[e]; if (t) { this.scheduleDependents(e, [], void 0, void 0, !0, "node destroyed", "Destroyed", !0); for (const [s, n] of t.dependents.entries()) { n.flags & C || this.recalculateNodeWeight(s, -t.weight); const r = this.nodes[s]; r && r.dependencies.delete(e); } delete this.nodes[e]; } } /** * Schedules a job to update all dependents of a node. This is called e.g. * when an atom instance or AtomSelector updates, when an atom instance is * force-destroyed, or when an atom instance's promise changes. */ scheduleDependents(e, t, s, n, r, o = "state changed", c = "Updated", a = !1) { const { _instances: d, _scheduler: u, selectors: h } = this.ecosystem, l = d[e], g = h._items[e], m = this.nodes[e]; for (const [R, f] of m.dependents.entries()) { if (f.task) { if (c !== "Destroyed") continue; u.unschedule(f.task); } if (f.flags & C && !a) continue; const A = { newState: s, oldState: n, operation: f.operation, reasons: t, sourceId: e, sourceType: m.isSelector ? "AtomSelector" : "Atom", type: o }; if (!(f.flags & N)) { if (this.nodes[R].isSelector) { h._scheduleEvaluation(R, A, r); continue; } d[R]._scheduleEvaluation(A, r); continue; } const D = () => { var x; f.task = void 0, (x = f.callback) === null || x === void 0 || x.call( f, c, l ? l.store.getState() : g.result, // don't use the snapshotted newState above A ); }; u.schedule({ flags: f.flags, task: D, type: 3 // UpdateExternalDependent (3) }, r), f.task = D; } } /** * Actually add an edge to the graph. When we buffer graph updates, we're * really just deferring the calling of this method. */ finishAddingEdge(e, t, s) { const n = this.nodes[t]; if (!n) return; s.flags & N || this.nodes[e].dependencies.set(t, !0), n.dependents.set(e, s), n.refCount++, lt(this, t), s.flags & C || this.recalculateNodeWeight(e, n.weight); const { _instances: r, _mods: o, modBus: c, selectors: a } = this.ecosystem; return o.edgeCreated && c.dispatch(v.edgeCreated({ dependency: r[t] || a._items[t], dependent: r[e] || a._items[e] || e, edge: s })), s; } /** * When a non-static edge is added or removed, every node below that edge (the * dependent, its dependents, etc) in the graph needs to have its weight * recalculated. */ recalculateNodeWeight(e, t) { const s = this.nodes[e]; if (s) { s.weight += t; for (const n of s.dependents.keys()) this.recalculateNodeWeight(n, t); } } } class pt { constructor() { this.idCounter = 0, this.weakCache = /* @__PURE__ */ new WeakMap(), this.generateId = (e) => ( // we can slice from 2 to 12 if needed, but keeping it short for now: `${e}-${++this.idCounter}${Math.random().toString(36).slice(2, 8)}` ); } generateNodeId() { return this.generateId("no"); } /** * Turn an array of anything into a predictable string. If any item is an atom * instance, it will be serialized as the instance's id. If * acceptComplexParams is true, map class instances and functions to a * consistent id for the reference. * * Note that recursive objects are not supported - they would add way too much * overhead here and are really just unnecessary. */ hashParams(e, t) { return JSON.stringify(e, (s, n) => y(n, M) ? n.id : n && (q(n) ? Object.keys(n).sort().reduce((r, o) => (r[o] = n[o], r), {}) : !t || Array.isArray(n) ? n : typeof n == "function" ? this.cacheFn(n) : typeof (n == null ? void 0 : n.constructor) == "function" ? this.cacheClass(n) : n)); } /** * Generate a timestamp. Pass true to make it a high res timestamp if possible * * This method and `IdGenerator#generateId()` are the only methods in Zedux * that produce random values. * * Override these when testing to create reproducible graphs/dehydrations that * can be used easily in snapshot testing. See our setup in the Zedux repo at * `<repo root>/packages/react/test/utils/ecosystem.ts` for an example. */ now(e) { return e && typeof performance < "u" ? performance.now() : Date.now(); } cacheClass(e) { let t = this.weakCache.get(e); return t || (t = this.generateId(e.constructor.name || "UnknownClass"), this.weakCache.set(e, t), t); } cacheFn(e) { let t = this.weakCache.get(e); return t || (t = this.generateId(e.name || "anonFn"), this.weakCache.set(e, t), t); } } class _t { constructor(e) { this.ecosystem = e, this.jobs = [], this.nows = []; } /** * Kill any current timeout and run all jobs immediately. * * IMPORTANT: Setting and clearing timeouts is expensive. We need to always * pass `shouldSetTimeout: false` to scheduler.schedule() when we're going * to immediately flush */ flush() { this._isRunning || (this._handle && (clearTimeout(this._handle), this._handle = void 0), this.runJobs()); } /** * Insert a job into the queue. Insertion point depends on job's type and * weight. * * IMPORTANT: Setting and clearing timeouts is expensive. We need to always * pass `shouldSetTimeout: false` when we're going to immediately flush */ schedule(e, t = !0) { e.type === 4 ? this.jobs.push(e) : this.insertJob(e), t && this.jobs.length === 1 && this.setTimeout(); } /** * UpdateStore (0) jobs must run immediately but also need the scheduler to be * running all "now" jobs. * * InformSubscriber (1) jobs must run immediately after the current task. */ scheduleNow(e) { if (this._isRunningNows && e.type === 0) return e.task(); this.nows[e.type === 1 ? "push" : "unshift"](e), this.runJobs(!0); } unschedule(e) { const t = this.jobs.findIndex((s) => s.task === e); t !== -1 && this.jobs.splice(t, 1); } wipe() { this.jobs = this.jobs.filter( (e) => e.type === 3 // UpdateExternalDependent (3) ); } // An O(log n) replacement for this.jobs.findIndex() findIndex(e, t = Math.ceil(this.jobs.length / 2) - 1, s = 1) { const n = this.jobs[t]; if (n == null) return t; const r = e(n); if (!r) return t; const o = Math.pow(2, s); if (o > this.jobs.length) return t + (r === 1 ? 1 : 0); const a = Math.round(this.jobs.length / o), d = Math.min(this.jobs.length - 1, Math.max(0, t + Math.ceil(a / 2) * r)); return this.findIndex(e, d, s + 1); } /** * Schedule an EvaluateGraphNode (2) or UpdateExternalDependent (3) job */ insertJob(e) { var t; const { nodes: s } = this.ecosystem._graph, n = (t = e.flags) !== null && t !== void 0 ? t : 0, r = e.id ? s[e.id].weight : 0, o = this.findIndex((c) => { if (c.type !== e.type) return +(e.type - c.type > 0) || -1; if (c.id) { const a = s[c.id].weight; return r < a ? -1 : +(r > a); } return n < c.flags ? -1 : +(n > c.flags); }); o === -1 ? this.jobs.push(e) : this.jobs.splice(o, 0, e); } /** * Run either all "full" jobs or all "now" jobs. Since the jobs are split, we * can essentially have two schedulers running at once. "Now" jobs must always * run before any "full" jobs, so the "full" jobs runner has to flush any * "now"s that come up while it's flushing "full"s. * * Don't run "full" jobs while "now"s are running. It leads to "now"s being * deferred until after "full"s finish. This is backwards and can lead to * reevaluation loops. */ runJobs(e) { if (this._isRunningNows) { this._runAfterNows = !e; return; } const t = e ? this.nows : this.jobs, s = e ? "_isRunningNows" : "_isRunning", n = this.nows; this[s] = !0; try { for (; t.length; ) (n.length ? n : t).shift().task(); } finally { this[s] = !1; } this._runAfterNows && (this._runAfterNows = !1, this.runJobs()); } setTimeout() { this._isRunning || (this._handle = setTimeout(() => { this._handle = void 0, this.runJobs(); })); } } const yt = (i, e) => i === e; class E { constructor(e, t, s) { this.id = e, this.selectorRef = t, this.args = s, this.nextReasons = []; } } E.$$typeof = Symbol.for(`${b}/SelectorCache`); class gt { constructor(e) { this.ecosystem = e, this._items = {}, this._refBaseKeys = /* @__PURE__ */ new WeakMap(); } addDependent(e, { callback: t, operation: s = "addDependent" } = {}) { const { _graph: n, _idGenerator: r } = this.ecosystem, o = r.generateNodeId(); return n.addEdge(o, e.id, s, Q | N, t), () => n.removeEdge(o, e.id); } /** * Get an object mapping all ids in this selectorCache to their current * values. * * Pass a selector to only return caches of that selector. * * Pass a partial SelectorCache id string to only return caches whose id * contains the passed key (case-insensitive). * * IMPORTANT: Don't use this for SSR. SelectorCaches are not designed to be * shared across environments. Selectors should be simple derivations that * will be predictably recreated from rehydrated atom instances. * * In other words, `ecosystem.dehydrate()` is all you need for SSR. Don't * worry about selectors. This method is solely an inspection/debugging util. */ dehydrate(e) { const t = this.findAll(e); return Object.keys(t).forEach((s) => { t[s] = t[s].result; }), t; } /** * Destroys the cache for the given selector + args combo (if it exists). * * Destruction bails out by default if the selector's ref count is > 0. Pass * `true` as the 3rd param to force destruction. */ destroyCache(e, t, s) { const n = y(e, E) ? e.id : this.getCacheId(e, t), r = y(e, E) ? e : this._items[n]; if (!r || r.isDestroyed) return; const o = this.ecosystem._graph.nodes[n]; (s || !o.refCount) && this._destroySelector(n); } find(e, t) { if (y(e, E)) return e; if (typeof e == "string") return Object.values(this.findAll(e))[0]; const s = this.getCacheId(e, t, !0); return s && this._items[s]; } /** * Get an object of all currently-cached AtomSelectors. * * Pass a selector reference or string to filter by caches whose id * weakly matches the passed selector name. */ findAll(e = "") { const t = {}, s = typeof e == "string" ? e.toLowerCase() : y(e, E) ? e.id : this.getBaseKey(e, !0) || this._getIdealCacheId(e); return Object.values(this._items).sort((n, r) => n.id.localeCompare(r.id)).forEach((n) => { (!s || n.id.toLowerCase().includes(s)) && (t[n.id] = n); }), t; } /** * Get the cached args and result for the given AtomSelector (or * AtomSelectorConfig). Runs the selector, sets up the graph, and caches the * initial value if this selector hasn't been cached before. */ getCache(e, t = []) { if (y(e, E)) return e; const s = e, n = this.getCacheId(s, t); let r = this._items[n]; return r || (r = new E(n, s, t), this._items[n] = r, this.ecosystem._graph.addNode(n, !0), this.runSelector(n, t, !0), r); } /** * Get the fully qualified id for the given selector+params combo */ getCacheId(e, t, s) { const { complexParams: n, _idGenerator: r } = this.ecosystem, o = t != null && t.length ? r.hashParams(t, n) : "", c = this.getBaseKey(e, s); return o ? `${c}-${o}` : c; } /** * Should only be used internally. Removes the selector from the cache and * the graph */ _destroySelector(e) { const t = this._items[e]; if (!t) return; const { _graph: s, _scheduler: n, _mods: r, modBus: o } = this.ecosystem; t.nextReasons.length && t.task && n.unschedule(t.task), s.removeDependencies(e), s.removeNode(e), delete this._items[e], t.isDestroyed = !0, r.statusChanged && o.dispatch(v.statusChanged({ newStatus: "Destroyed", node: t, oldStatus: "Active" })); } /** * Get the string key we would ideally use as the id of the given * AtomSelector function or AtomSelectorConfig object - doesn't necessarily * mean we end up caching using this key. */ _getIdealCacheId(e) { var t; const s = e.name || ((t = e.selector) === null || t === void 0 ? void 0 : t.name); return s !== "selector" && s || void 0; } /** * Should only be used internally */ _scheduleEvaluation(e, t, s) { const n = this._items[e]; if (n.nextReasons.push(t), n.nextReasons.length > 1) return; const r = () => { n.task = void 0, this.runSelector(e, n.args); }; n.task = r, this.ecosystem._scheduler.schedule({ id: e, task: r, type: 2 // EvaluateGraphNode (2) }, s); } /** * Should only be used internally */ _swapRefs(e, t, s = []) { const n = this._refBaseKeys.get(e.selectorRef); n && (this._refBaseKeys.set(t, n), this._refBaseKeys.delete(e.selectorRef), e.selectorRef = t, this.runSelector(e.id, s, !1, !0)); } /** * Destroy all cached selectors. Should probably only be used internally. * Prefer `ecosystem.reset()`. */ _wipe() { Object.keys(this._items).forEach((e) => { this._destroySelector(e); }), this._refBaseKeys = /* @__PURE__ */ new WeakMap(); } /** * Get a base key that can be used to generate consistent ids for the given * selector */ getBaseKey(e, t) { const s = this._refBaseKeys.get(e); if (s || t) return s; const n = this._getIdealCacheId(e) || "unnamed", r = this.ecosystem._idGenerator.generateId(`@@selector-${n}`); return this._refBaseKeys.set(e, r), r; } /** * Run an AtomSelector and, depending on the selector's resultsComparator, * update its cached result. Updates the graph efficiently (using * `.bufferUpdates()`) */ runSelector(e, t, s, n) { const { _evaluationStack: r, _graph: o, _mods: c, modBus: a } = this.ecosystem; o.bufferUpdates(e); const d = this._items[e], u = typeof d.selectorRef == "function" ? d.selectorRef : d.selectorRef.selector, h = typeof d.selectorRef != "function" && d.selectorRef.resultsComparator || yt; r.start(d); try { const l = u(r.atomGetters, ...t); !s && !h(l, d.result) ? (n || o.scheduleDependents(e, d.nextReasons, l, d.result), c.stateChanged && a.dispatch(v.stateChanged({ cache: d, newState: l, oldState: d.result, reasons: d.nextReasons })), d.result = l) : s && (d.result = l, c.statusChanged && a.dispatch(v.statusChanged({ newStatus: "Active", node: d, oldStatus: "Initializing" }))); } catch (l) { throw o.destroyBuffer(), console.error(`Zedux encountered an error while running selector with id "${e}":`, l), l; } finally { r.finish(), d.prevReasons = d.nextReasons, d.nextReasons = []; } o.flushUpdates(); } } const mt = Object.keys(v).reduce((i, e) => (i[e] = 0, i), {}), F = (i) => i.reduce((e, t) => (e[t.key] = t, e), {}); class vt { constructor(e) { var t; this.modBus = z(), this.overrides = {}, this.selectors = new gt(this), this._graph = new ft(this), this._evaluationStack = new at(this), this._idGenerator = new pt(), this._instances = {}, this._mods = Object.assign({}, mt), this._refCount = 0, this._scheduler = new _t(this), this._storage = {}, this.isInitialized = !1, this.plugins = [], Object.assign(this, e), this.id || (this.id = this._idGenerator.generateId("es")), e.overrides && this.setOverrides(e.overrides), this.context = this.context, this.isInitialized = !0, this.cleanup = (t = e.onReady) === null || t === void 0 ? void 0 : t.call(e, this); } /** * Merge the passed atom overrides into the ecosystem's current list of * overrides. Force-destroys all atom instances currently in the ecosystem * that should now be overridden. * * This can't be used to remove overrides. Use `.setOverrides()` or * `.removeOverrides()` for that. */ addOverrides(e) { this.overrides = Object.assign(Object.assign({}, this.overrides), F(e)), e.forEach((t) => { const s = this.findAll(t); Object.values(s).forEach((n) => n.destroy(!0)); }); } /** * Batch all state updates that happen synchronously during the passed * callback's execution. Flush all updates when the passed callback completes. * * Has no effect if the scheduler is already running - updates are always * batched when the scheduler is running. */ batch(e) { const t = this._scheduler, s = t._isRunning; t._isRunning = !0; const n = e(); return t._isRunning = s, t.flush(), n; } /** * Retrieve an object mapping atom instance ids to their current values. * * Calls the `dehydrate` atom config option (on atoms that have one) to * transform state to a serializable form. Pass `transform: false` to prevent * this. * * Atoms can be excluded from dehydration by passing `exclude` and/or * `excludeFlags` options: * * ```ts * myEcosystem.dehydrate({ * exclude: [myAtom, 'my-fuzzy-search-string'], * excludeFlags: ['no-ssr'] * }) * ``` * * An atom passed to `exclude` will exclude all instances of that atom. A * string passed to `exclude` will exclude all instances whose id contains the * string (case-insensitive) * * You can dehydrate only a subset of all atoms by passing `include` and/or * `includeFlags` options: * * ```ts * myEcosystem.dehydrate({ * include: [myAtom, 'my-fuzzy-search-string'], * includeFlags: ['ssr'] * }) * ``` * * An atom passed to `include` will include all instances of that atom. A * string passed to `include` will include all instances whose id contains the * string (case-insensitive) * * Excludes takes precedence over includes. * * By default, dehydration will call any configured `dehydrate` atom config * options to transform atom instance state. Pass `{ transform: false }` to * prevent this. */ dehydrate({ exclude: e, excludeFlags: t, include: s, includeFlags: n, transform: r = !0 } = {}) { return Object.values(this._instances).filter(({ id: c, template: a }) => e && e.some((d) => typeof d == "string" ? c.toLowerCase().includes(d.toLowerCase()) : a.key === d.key) || t && t.some((d) => { var u; return (u = a.flags) === null || u === void 0 ? void 0 : u.includes(d); }) ? !1 : !!(!s && !n || s && s.some((d) => typeof d == "string" ? c.toLowerCase().includes(d.toLowerCase()) : a.key === d.key) || n && n.some((d) => { var u; return (u = a.flags) === null || u === void 0 ? void 0 : u.includes(d); }))).reduce((c, { id: a, store: d, template: u }) => { const h = d.getState(); return c[a] = r && u.dehydrate ? u.dehydrate(h) : h, c; }, {}); } /** * Destroy this ecosystem - destroy all this ecosystem's atom instances, * remove and clean up all plugins, and remove this ecosystem from the * internal store. * * Destruction will bail out by default if this ecosystem is still being * provided via an <EcosystemProvider>. Pass `true` as the first parameter to * force destruction anyway. */ destroy(e) { !e && this._refCount > 0 || (this.wipe(), !I.getState()[this.id]) || (this.plugins.forEach(({ cleanup: s }) => s()), this.plugins = [], I.setState((s) => { const n = Object.assign({}, s); return delete n[this.id], n; })); } find(e, t) { const s = typeof e == "string"; if (!s) { const r = e.getInstanceId(this, t), o = this._instances[r]; if (o) return o; } if (t) return this._instances[s ? e : `${e.key}-${this._idGenerator.hashParams(t, this.complexParams)}`]; const n = this.findAll(e); return s && n[e] || Object.values(n)[0]; } /** * Get an object of all atom instances in this ecosystem keyed by their id. * * Pass an atom template to only find instances of that atom. Pass an atom key * string to only return instances whose id weakly matches the passed key. */ findAll(e) { var t, s; const n = (t = e) === null || t === void 0 ? void 0 : t.key, r = n || ((s = e) === null || s === void 0 ? void 0 : s.toLowerCase()), o = {}; return Object.values(this._instances).filter((c) => !r || (n ? c.template.key === e.key : c.id.toLowerCase().includes(r))).sort((c, a) => c.id.localeCompare(a.id)).forEach((c) => { o[c.id] = c; }), o; } /** * Returns an atom instance's value. Creates the atom instance if it doesn't * exist yet. Doesn't register any graph dependencies. */ get(e, t) { return y(e, M) ? e.store.getState() : this.getInstance(e, t).store.getState(); } /** * Returns an atom instance. Creates the atom instance if it doesn't exist * yet. Doesn't register any graph dependencies. */ getInstance(e, t) { if (y(e, M)) return e.status === "Destroyed" ? this.getInstance(e.template, e.params) : e; const s = e.getInstanceId(this, t), n = this._instances[s]; if (n) return this._mods.instanceReused && this.modBus.dispatch(v.instanceReused({ instance: n, template: e })), n; const r = this.resolveAtom(e); this._graph.addNode(s); const o = r._createInstance(this, s, t || []); return this._instances[s] = o, o._init(), o; } /** * Hydrate the state of atoms in this ecosystem with an object mapping atom * instance ids to their hydrated state. This object will usually be the * result of a call to `ecosystem.dehydrate()`. * * This is the key to SSR. The ecosystem's initial state can be dehydrated on * the server, sent to the client in serialized form, deserialized, and passed * to `ecosystem.hydrate()`. Every atom instance that evaluates after this