@plitzi/nexus
Advanced tools
| import { StoreApi } from './types'; | ||
| export type DevStore = StoreApi<Record<string, unknown>>; | ||
| export type DevStoreEntry = { | ||
| uid: string; | ||
| store: DevStore; | ||
| scopeId?: string; | ||
| name?: string; | ||
| }; | ||
| export declare const registerDevStore: (store: StoreApi<any>, scopeId?: string, name?: string) => (() => void); | ||
| export declare const subscribeDevStores: (listener: () => void) => (() => void); | ||
| export declare const getDevStoresSnapshot: () => ReadonlyArray<DevStoreEntry>; |
| //#region src/devStoreRegistry.ts | ||
| var e = /* @__PURE__ */ new Map(), t = /* @__PURE__ */ new Set(), n = 0, r = [], i = () => { | ||
| r = [...e.values()]; | ||
| for (let e of t) e(); | ||
| }, a = (t, r, a) => { | ||
| let o = t; | ||
| return e.set(o, { | ||
| uid: `dev-store-${++n}`, | ||
| store: o, | ||
| scopeId: r, | ||
| name: a | ||
| }), i(), () => { | ||
| e.delete(o), i(); | ||
| }; | ||
| }, o = (e) => (t.add(e), () => { | ||
| t.delete(e); | ||
| }), s = () => r; | ||
| //#endregion | ||
| export { s as getDevStoresSnapshot, a as registerDevStore, o as subscribeDevStores }; |
| declare const DevStoreScopeContext: import('react').Context<string | undefined>; | ||
| export default DevStoreScopeContext; |
| import { createContext as e } from "react"; | ||
| //#region src/react/DevStoreScopeContext.ts | ||
| var t = e(void 0); | ||
| t.displayName = "DevStoreScopeContext"; | ||
| //#endregion | ||
| export { t as default }; |
+13
-0
| # @plitzi/nexus | ||
| ## 0.32.6 | ||
| ### Patch Changes | ||
| - v0.32.6 | ||
| ## 0.32.5 | ||
@@ -43,2 +49,9 @@ | ||
| Astro 7, Vue and Svelte. | ||
| - **`unmount` write option.** Every write now takes an optional third argument `SetStateOptions = { canPropagate?; | ||
| unmount? }`. `set(path, undefined, { unmount: true })` **deletes** the key at `path` (splicing array indices) instead | ||
| of leaving a dead `undefined`, so registries keyed by dynamic id leave no stale entries. Nested deletes keep siblings | ||
| with structural sharing, interceptors can veto them, and writing a value back recreates the path. | ||
| - **Read-only paths.** `createStore(init, { readOnly: [...] })` freezes paths: a write to a read-only path — or to an | ||
| ancestor/descendant of one — throws in development and no-ops in production. Prefix-safe matching, enforced per scope | ||
| on that scope's own writes, zero cost when unused. | ||
@@ -45,0 +58,0 @@ ### Breaking Changes |
@@ -8,2 +8,3 @@ import { GetState, SetState, StoreApi, StoreMiddleware } from '../types'; | ||
| middlewares?: StoreMiddleware<TState>[]; | ||
| readOnly?: ReadonlyArray<string>; | ||
| /** When true, hydrate handlers are collected but NOT run during creation. | ||
@@ -10,0 +11,0 @@ * Call `store.hydrate()` manually (StoreProvider does this in a useEffect). |
@@ -34,2 +34,5 @@ import e from "./helpers/Subscribers.mjs"; | ||
| }, | ||
| deleteOwnKey: (e) => { | ||
| delete d[e], O(), f = void 0; | ||
| }, | ||
| parent: x, | ||
@@ -40,2 +43,3 @@ listeners: p, | ||
| interceptors: v, | ||
| readOnly: u?.readOnly ?? [], | ||
| reportError: C, | ||
@@ -61,2 +65,3 @@ invalidateDescendants: _, | ||
| getState: E, | ||
| getOwnState: T, | ||
| getPath: D, | ||
@@ -68,3 +73,3 @@ setState: F, | ||
| withBase: (e) => { | ||
| let t = (t, n) => F(t === void 0 ? e : `${e}.${t}`, n); | ||
| let t = (t, n, r) => F(t === void 0 ? e : `${e}.${t}`, n, r); | ||
| return { | ||
@@ -71,0 +76,0 @@ getState: (t) => { |
@@ -9,2 +9,3 @@ import { default as PathTrie } from './PathTrie'; | ||
| mutateOwnKey: (key: string, value: unknown) => void; | ||
| deleteOwnKey: (key: string) => void; | ||
| parent: StoreApi<TState> | undefined; | ||
@@ -15,2 +16,3 @@ listeners: Subscribers<Listener>; | ||
| interceptors: WriteInterceptor<TState>[]; | ||
| readOnly: ReadonlyArray<string>; | ||
| reportError: StoreErrorReporter<TState>; | ||
@@ -17,0 +19,0 @@ invalidateDescendants: () => void; |
| import e from "../../helpers/parsePath.mjs"; | ||
| import t from "../../helpers/getByPath.mjs"; | ||
| import { UNCHANGED as n, writeByPath as r } from "./writeByPath.mjs"; | ||
| import i from "../../helpers/setByPath.mjs"; | ||
| import { CANCEL as a } from "../../types/StoreTypes.mjs"; | ||
| import { isDev as n } from "../../env.mjs"; | ||
| import { UNCHANGED as r, deleteByPath as i, writeByPath as a } from "./writeByPath.mjs"; | ||
| import o from "../../helpers/setByPath.mjs"; | ||
| import { CANCEL as s } from "../../types/StoreTypes.mjs"; | ||
| //#region src/createStore/helpers/createSetState.ts | ||
| var o = Symbol("no-error"), s = (e, t, n) => { | ||
| let r = 0, i = o; | ||
| var c = Symbol("no-error"), l = (e, t, n) => { | ||
| let r = 0, i = c; | ||
| for (; r < n;) try { | ||
| for (; r < n; r++) e[r](t); | ||
| } catch (e) { | ||
| i === o && (i = e), r++; | ||
| i === c && (i = e), r++; | ||
| } | ||
| return i; | ||
| }, c = "__proto__", l = (e, t) => { | ||
| }, u = "__proto__", d = (e, t) => { | ||
| let n = e; | ||
@@ -23,9 +24,19 @@ for (let e = 0, r = t.length; e < r; e++) { | ||
| }; | ||
| function u(u) { | ||
| let { getOwnState: d, getOwnSnapshot: f, setOwnState: p, mutateOwnKey: m, parent: h, listeners: g, pathListeners: _, changeListeners: v, interceptors: y, reportError: b, invalidateDescendants: x, onDelegateToParent: S } = u, C = (e, t, n) => { | ||
| function f(f) { | ||
| let { getOwnState: p, getOwnSnapshot: m, setOwnState: h, mutateOwnKey: g, deleteOwnKey: _, parent: v, listeners: y, pathListeners: b, changeListeners: x, interceptors: S, readOnly: C, reportError: w, invalidateDescendants: T, onDelegateToParent: E } = f, D = (e) => { | ||
| for (let t = 0, n = C.length; t < n; t++) { | ||
| let n = C[t]; | ||
| if (e === n || e.startsWith(`${n}.`) || n.startsWith(`${e}.`)) return !0; | ||
| } | ||
| return !1; | ||
| }, O = (e) => { | ||
| if (C.length === 0 || !D(e)) return !1; | ||
| if (n) throw Error(`@plitzi/nexus: refused to write to read-only path "${e}"`); | ||
| return !0; | ||
| }, k = (e, t, n) => { | ||
| let r = t; | ||
| for (let t = 0, i = y.length; t < i; t++) { | ||
| for (let t = 0, i = S.length; t < i; t++) { | ||
| let i; | ||
| try { | ||
| i = y[t]({ | ||
| i = S[t]({ | ||
| path: e, | ||
@@ -36,12 +47,12 @@ value: r, | ||
| } catch (t) { | ||
| return b(t, "beforeChange", e), a; | ||
| return w(t, "beforeChange", e), s; | ||
| } | ||
| if (i === a) return a; | ||
| if (i === s) return s; | ||
| i !== void 0 && (r = i); | ||
| } | ||
| return r; | ||
| }, w = 0, T = /* @__PURE__ */ new Set(), E = (e, t) => { | ||
| }, A = 0, j = /* @__PURE__ */ new Set(), M = (e, t) => { | ||
| let { items: n } = e; | ||
| if (w > 0) { | ||
| for (let e = 0, t = n.length; e < t; e++) T.add(n[e]); | ||
| if (A > 0) { | ||
| for (let e = 0, t = n.length; e < t; e++) j.add(n[e]); | ||
| return; | ||
@@ -52,4 +63,4 @@ } | ||
| try { | ||
| let e = s(n, t, n.length); | ||
| e !== o && b(e, "notify", t); | ||
| let e = l(n, t, n.length); | ||
| e !== c && w(e, "notify", t); | ||
| } finally { | ||
@@ -59,15 +70,15 @@ e.end(); | ||
| } | ||
| }, D = (e) => { | ||
| w++; | ||
| }, N = (e) => { | ||
| A++; | ||
| try { | ||
| return e(); | ||
| } finally { | ||
| if (w--, w === 0 && T.size > 0) { | ||
| let e = [...T]; | ||
| T.clear(); | ||
| let t = s(e, void 0, e.length); | ||
| t !== o && b(t, "notify", void 0); | ||
| if (A--, A === 0 && j.size > 0) { | ||
| let e = [...j]; | ||
| j.clear(); | ||
| let t = l(e, void 0, e.length); | ||
| t !== c && w(t, "notify", void 0); | ||
| } | ||
| } | ||
| }, O = (e, n, r) => { | ||
| }, P = (e, n, r) => { | ||
| let i = { | ||
@@ -79,83 +90,111 @@ path: e, | ||
| nextValue: e === void 0 ? r : t(r, e) | ||
| }, { items: a } = v; | ||
| v.begin(); | ||
| }, { items: a } = x; | ||
| x.begin(); | ||
| try { | ||
| let t = s(a, i, a.length); | ||
| t !== o && b(t, "onChange", e); | ||
| let t = l(a, i, a.length); | ||
| t !== c && w(t, "onChange", e); | ||
| } finally { | ||
| v.end(); | ||
| x.end(); | ||
| } | ||
| }, k = (e, t) => { | ||
| _.walkAncestors(t, (t) => E(t, e)); | ||
| }, A = (n, r, i, a) => { | ||
| let o = _.getDescendants(n); | ||
| }, F = (e, t) => { | ||
| b.walkAncestors(t, (t) => M(t, e)); | ||
| }, I = (n, r, i, a) => { | ||
| let o = b.getDescendants(n); | ||
| if (o) for (let s of o) { | ||
| let o = _.direct.get(s); | ||
| let o = b.direct.get(s); | ||
| if (!o) continue; | ||
| let c = e(s).slice(a); | ||
| t(r, c) !== t(i, c) && E(o, n); | ||
| t(r, c) !== t(i, c) && M(o, n); | ||
| } | ||
| }, j = (e, n, r, o) => { | ||
| let s = e ? t(r, e) : void 0, c = e ? typeof n == "function" ? n(s) : n : void 0; | ||
| if (e && y.length > 0) { | ||
| let t = C(e, c, s); | ||
| if (t === a) return; | ||
| c = t; | ||
| }, L = (e, r, i, a) => { | ||
| let c = e ? t(i, e) : void 0, l = e ? typeof r == "function" ? r(c) : r : void 0; | ||
| if (e && S.length > 0) { | ||
| let t = k(e, l, c); | ||
| if (t === s) return; | ||
| l = t; | ||
| } | ||
| if (e && s === c) return; | ||
| let l = e ? i(r, e, c) : typeof n == "function" ? n(r) : { | ||
| ...r, | ||
| ...n | ||
| if (e && c === l) return; | ||
| let u = e ? o(i, e, l) : typeof r == "function" ? r(i) : { | ||
| ...i, | ||
| ...r | ||
| }; | ||
| if (!e && y.length > 0) { | ||
| let e = C(void 0, l, r); | ||
| if (e === a) return; | ||
| l = e; | ||
| if (!e && S.length > 0) { | ||
| let e = k(void 0, u, i); | ||
| if (e === s) return; | ||
| u = e; | ||
| } | ||
| l !== r && (p(l), v.length > 0 && O(e, r, l), x(), o && (E(g, e), _.size > 0 && _.direct.forEach((n, i) => { | ||
| t(r, i) !== t(l, i) && E(n, e); | ||
| }))); | ||
| if (u !== i) { | ||
| if (C.length > 0) for (let e = 0, r = C.length; e < r; e++) { | ||
| let r = C[e]; | ||
| if (t(i, r) !== t(u, r)) { | ||
| if (n) throw Error(`@plitzi/nexus: refused to write to read-only path "${C[e]}"`); | ||
| return; | ||
| } | ||
| } | ||
| h(u), x.length > 0 && P(e, i, u), T(), a && (M(y, e), b.size > 0 && b.direct.forEach((n, r) => { | ||
| t(i, r) !== t(u, r) && M(n, e); | ||
| })); | ||
| } | ||
| }; | ||
| return { | ||
| setState: (i, o, s = !0) => { | ||
| let u = d(); | ||
| if (typeof i == "string" && i.indexOf(c) !== -1 && e(i).indexOf(c) !== -1) throw Error(`@plitzi/nexus: refused to write to unsafe path "${i}" (\`__proto__\` segment)`); | ||
| if (h && typeof i == "string" && !(i.indexOf(".") === -1 ? Object.hasOwn(u, i) : l(u, e(i)))) { | ||
| S?.(i), h.setState(i, o, s); | ||
| setState: (n, o, c = {}) => { | ||
| let { canPropagate: l = !0, unmount: f = !1 } = c, C = p(); | ||
| if (typeof n == "string" && n.indexOf(u) !== -1 && e(n).indexOf(u) !== -1) throw Error(`@plitzi/nexus: refused to write to unsafe path "${n}" (\`__proto__\` segment)`); | ||
| if (typeof n == "string" && O(n)) return; | ||
| if (v && typeof n == "string" && !(n.indexOf(".") === -1 ? Object.hasOwn(C, n) : d(C, e(n)))) { | ||
| E?.(n), v.setState(n, o, { | ||
| canPropagate: l, | ||
| unmount: f | ||
| }); | ||
| return; | ||
| } | ||
| if (typeof i != "string") { | ||
| j(i, o, u, s); | ||
| if (typeof n != "string") { | ||
| L(n, o, C, l); | ||
| return; | ||
| } | ||
| if (i.indexOf(".") === -1) { | ||
| let e = u[i], t = typeof o == "function" ? o(e) : o, n = t; | ||
| if (y.length > 0) { | ||
| let r = C(i, t, e); | ||
| if (r === a) return; | ||
| n = r; | ||
| if (n.indexOf(".") === -1) { | ||
| if (f) { | ||
| if (!Object.hasOwn(C, n)) return; | ||
| let e = C[n]; | ||
| if (S.length > 0 && k(n, void 0, e) === s) return; | ||
| let t = x.length > 0 ? m() : void 0; | ||
| if (_(n), t !== void 0 && P(n, t, m()), l && (M(y, n), b.size > 0)) { | ||
| let t = b.direct.get(n); | ||
| t && M(t, n), I(n, e, void 0, 1); | ||
| } | ||
| T(); | ||
| return; | ||
| } | ||
| if (e === n) return; | ||
| let r = v.length > 0 ? f() : void 0; | ||
| if (m(i, n), r !== void 0 && O(i, r, f()), s && (E(g, i), _.size > 0)) { | ||
| let t = _.direct.get(i); | ||
| t && E(t, i), A(i, e, n, 1); | ||
| let e = C[n], t = typeof o == "function" ? o(e) : o, r = t; | ||
| if (S.length > 0) { | ||
| let i = k(n, t, e); | ||
| if (i === s) return; | ||
| r = i; | ||
| } | ||
| x(); | ||
| if (e === r) return; | ||
| let i = x.length > 0 ? m() : void 0; | ||
| if (g(n, r), i !== void 0 && P(n, i, m()), l && (M(y, n), b.size > 0)) { | ||
| let t = b.direct.get(n); | ||
| t && M(t, n), I(n, e, r, 1); | ||
| } | ||
| T(); | ||
| return; | ||
| } | ||
| let b = e(i), w; | ||
| if (y.length > 0) { | ||
| let e = t(u, i), n = typeof o == "function" ? o(e) : o, s = C(i, n, e); | ||
| if (s === a) return; | ||
| w = r(u, i, b, s, !1); | ||
| } else w = r(u, i, b, o, typeof o == "function"); | ||
| if (w === n) return; | ||
| let T = w; | ||
| p(T), v.length > 0 && O(i, u, T), s && (E(g, i), _.size > 0 && (k(i, b), A(i, u, T, 0))), x(); | ||
| let w = e(n), D; | ||
| if (f) { | ||
| if (S.length > 0 && k(n, void 0, t(C, n)) === s) return; | ||
| D = i(C, w); | ||
| } else if (S.length > 0) { | ||
| let e = t(C, n), r = typeof o == "function" ? o(e) : o, i = k(n, r, e); | ||
| if (i === s) return; | ||
| D = a(C, n, w, i, !1); | ||
| } else D = a(C, n, w, o, typeof o == "function"); | ||
| if (D === r) return; | ||
| let A = D; | ||
| h(A), x.length > 0 && P(n, C, A), l && (M(y, n), b.size > 0 && (F(n, w), I(n, C, A, 0))), T(); | ||
| }, | ||
| batch: D | ||
| batch: N | ||
| }; | ||
| } | ||
| //#endregion | ||
| export { u as createSetState }; | ||
| export { f as createSetState }; |
@@ -0,14 +1,16 @@ | ||
| import { isTest as e } from "../../env.mjs"; | ||
| //#region src/createStore/helpers/scopeCollisions.ts | ||
| var e = () => { | ||
| let e = /* @__PURE__ */ new Map(); | ||
| return { claimDelegatedWrite(t, n) { | ||
| let r = e.get(t); | ||
| if (r !== void 0 && r !== n) { | ||
| console.warn(`@plitzi/nexus: scope collision — two sibling scopes under the same parent both delegate a write to "${t}", clobbering each other through the shared parent. Write to a path each scope owns, or move the shared value into the parent and update it there.`); | ||
| var t = (e) => `@plitzi/nexus: scope collision — two sibling scopes under the same parent both delegate a write to "${e}", clobbering each other through the shared parent. Write to a path each scope owns, or move the shared value into the parent and update it there.`, n = () => { | ||
| let n = /* @__PURE__ */ new Map(); | ||
| return { claimDelegatedWrite(r, i) { | ||
| let a = n.get(r); | ||
| if (a !== void 0 && a !== i) { | ||
| if (e) throw Error(t(r)); | ||
| console.warn(t(r)); | ||
| return; | ||
| } | ||
| e.set(t, n); | ||
| n.set(r, i); | ||
| } }; | ||
| }; | ||
| //#endregion | ||
| export { e as createScopeClaims }; | ||
| export { n as createScopeClaims }; |
@@ -11,2 +11,3 @@ export declare const UNCHANGED: unique symbol; | ||
| export declare const setCodegenEnabled: (enabled: boolean | undefined) => void; | ||
| export declare const deleteByPath: (root: unknown, segments: readonly string[]) => Record<string, unknown> | typeof UNCHANGED; | ||
| export declare const writeByPath: (root: unknown, path: string, segments: readonly string[], value: unknown, isFn: boolean) => Record<string, unknown> | typeof UNCHANGED; |
@@ -43,3 +43,15 @@ //#region src/createStore/helpers/writeByPath.ts | ||
| return f.size >= p && f.delete(f.keys().next().value), f.set(e, i), i; | ||
| }, h = (t, n, i, a, o) => { | ||
| }, h = (t, r, i) => { | ||
| if (typeof t != "object" || !t) return e; | ||
| let a = t, o = r[i]; | ||
| if (!Object.hasOwn(a, o)) return e; | ||
| if (i === r.length - 1) { | ||
| let e = n(a); | ||
| return Array.isArray(e) ? e.splice(Number(o), 1) : delete e[o], e; | ||
| } | ||
| let s = h(a[o], r, i + 1); | ||
| if (s === e) return e; | ||
| let c = n(a); | ||
| return c[o] = s, c; | ||
| }, g = (e, t) => h(e, t, 0), _ = (t, n, i, a, o) => { | ||
| let s = m(n, i); | ||
@@ -53,2 +65,2 @@ if (s) { | ||
| //#endregion | ||
| export { e as UNCHANGED, d as setCodegenEnabled, h as writeByPath }; | ||
| export { e as UNCHANGED, g as deleteByPath, d as setCodegenEnabled, _ as writeByPath }; |
+2
-0
@@ -15,2 +15,4 @@ export { default as createStore } from './createStore/createStore'; | ||
| export { createServerSnapshot, isServerSnapshot } from './rsc'; | ||
| export { registerDevStore, subscribeDevStores, getDevStoresSnapshot } from './devStoreRegistry'; | ||
| export type { DevStore, DevStoreEntry } from './devStoreRegistry'; | ||
| export * from './types'; | ||
@@ -17,0 +19,0 @@ export type { CreateStoreOptions } from './createStore/createStore'; |
+2
-1
@@ -15,2 +15,3 @@ import { createAsync as e } from "./async/createAsync.mjs"; | ||
| import { createServerSnapshot as m, isServerSnapshot as h } from "./rsc/index.mjs"; | ||
| export { d as CANCEL, l as cascade, e as createAsync, t as createDerived, n as createEntityAdapter, r as createEntityStore, p as createRecorder, m as createServerSnapshot, f as createStore, i as getStoreHistory, a as historyMiddleware, h as isServerSnapshot, o as loggerMiddleware, s as persistMiddleware, c as reduxDevToolsMiddleware, u as setCodegenEnabled }; | ||
| import { getDevStoresSnapshot as g, registerDevStore as _, subscribeDevStores as v } from "./devStoreRegistry.mjs"; | ||
| export { d as CANCEL, l as cascade, e as createAsync, t as createDerived, n as createEntityAdapter, r as createEntityStore, p as createRecorder, m as createServerSnapshot, f as createStore, g as getDevStoresSnapshot, i as getStoreHistory, a as historyMiddleware, h as isServerSnapshot, o as loggerMiddleware, s as persistMiddleware, c as reduxDevToolsMiddleware, _ as registerDevStore, u as setCodegenEnabled, v as subscribeDevStores }; |
@@ -32,7 +32,7 @@ import { useResolvedStore as e } from "./shared.mjs"; | ||
| ...a | ||
| }), t); | ||
| }), { canPropagate: t }); | ||
| else if (c) { | ||
| let n = i(e.getState()); | ||
| e.setState(n, a, t); | ||
| } else e.setState(i, a, t); | ||
| e.setState(n, a, { canPropagate: t }); | ||
| } else e.setState(i, a, { canPropagate: t }); | ||
| }; | ||
@@ -39,0 +39,0 @@ p === "render" ? v && y() : (v && g && y(!1), n(() => { |
| export { default as StoreProvider, StoreContext, StoreRegistryContext } from './StoreProvider'; | ||
| export { default as DevStoreScopeContext } from './DevStoreScopeContext'; | ||
| export { findStoreInRegistry } from './StoreContext'; | ||
@@ -15,4 +16,5 @@ export { createStoreHook } from './createStoreHook'; | ||
| export { default as useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect'; | ||
| export type { StoreApi } from '../types'; | ||
| export type { StoreProviderProps } from './StoreProvider'; | ||
| export type { StoreRegistry } from './StoreContext'; | ||
| export type { UseStoreHistoryReturn } from './hooks/useStoreHistory'; |
+17
-16
@@ -1,16 +0,17 @@ | ||
| import { StoreContext as e, StoreRegistryContext as t, findStoreInRegistry as n } from "./StoreContext.mjs"; | ||
| import { defaultMultiEqualityFn as r } from "./hooks/shared.mjs"; | ||
| import i from "./useIsomorphicLayoutEffect.mjs"; | ||
| import a from "./hooks/useStoreSync.mjs"; | ||
| import o from "./StoreProvider.mjs"; | ||
| import s from "./hooks/useStore.mjs"; | ||
| import c from "./hooks/useStoreGetter.mjs"; | ||
| import l from "./hooks/useStoreSetter.mjs"; | ||
| import { createStoreHook as u } from "./createStoreHook.mjs"; | ||
| import d from "./hooks/useStoreById.mjs"; | ||
| import { useAsync as f } from "./hooks/useAsync.mjs"; | ||
| import { useAsyncValue as p } from "./hooks/useAsyncValue.mjs"; | ||
| import { useDerived as m } from "./hooks/useDerived.mjs"; | ||
| import { useStoreHistory as h } from "./hooks/useStoreHistory.mjs"; | ||
| import { useEntity as g, useEntityAll as _, useEntityIds as v, useEntityOne as y } from "./hooks/useEntity.mjs"; | ||
| export { e as StoreContext, o as StoreProvider, t as StoreRegistryContext, u as createStoreHook, r as defaultMultiEqualityFn, n as findStoreInRegistry, f as useAsync, p as useAsyncValue, m as useDerived, g as useEntity, _ as useEntityAll, v as useEntityIds, y as useEntityOne, i as useIsomorphicLayoutEffect, s as useStore, d as useStoreById, c as useStoreGetter, h as useStoreHistory, l as useStoreSetter, a as useStoreSync }; | ||
| import e from "./DevStoreScopeContext.mjs"; | ||
| import { StoreContext as t, StoreRegistryContext as n, findStoreInRegistry as r } from "./StoreContext.mjs"; | ||
| import { defaultMultiEqualityFn as i } from "./hooks/shared.mjs"; | ||
| import a from "./useIsomorphicLayoutEffect.mjs"; | ||
| import o from "./hooks/useStoreSync.mjs"; | ||
| import s from "./StoreProvider.mjs"; | ||
| import c from "./hooks/useStore.mjs"; | ||
| import l from "./hooks/useStoreGetter.mjs"; | ||
| import u from "./hooks/useStoreSetter.mjs"; | ||
| import { createStoreHook as d } from "./createStoreHook.mjs"; | ||
| import f from "./hooks/useStoreById.mjs"; | ||
| import { useAsync as p } from "./hooks/useAsync.mjs"; | ||
| import { useAsyncValue as m } from "./hooks/useAsyncValue.mjs"; | ||
| import { useDerived as h } from "./hooks/useDerived.mjs"; | ||
| import { useStoreHistory as g } from "./hooks/useStoreHistory.mjs"; | ||
| import { useEntity as _, useEntityAll as v, useEntityIds as y, useEntityOne as b } from "./hooks/useEntity.mjs"; | ||
| export { e as DevStoreScopeContext, t as StoreContext, s as StoreProvider, n as StoreRegistryContext, d as createStoreHook, i as defaultMultiEqualityFn, r as findStoreInRegistry, p as useAsync, m as useAsyncValue, h as useDerived, _ as useEntity, v as useEntityAll, y as useEntityIds, b as useEntityOne, a as useIsomorphicLayoutEffect, c as useStore, f as useStoreById, l as useStoreGetter, g as useStoreHistory, u as useStoreSetter, o as useStoreSync }; |
@@ -22,6 +22,7 @@ import { StoreContext, StoreRegistryContext } from './StoreContext'; | ||
| middlewares?: StoreMiddleware<TState>[]; | ||
| name?: string; | ||
| children?: ReactNode; | ||
| }; | ||
| declare const StoreProvider: <TState extends object = any>({ store, id, segment, isolate, path, value, inherit, autoSync, middlewares, children }: StoreProviderProps<TState>) => import("react").JSX.Element; | ||
| declare const StoreProvider: <TState extends object = any>({ store, id, segment, isolate, path, value, inherit, autoSync, middlewares, name, children }: StoreProviderProps<TState>) => import("react").JSX.Element; | ||
| export { StoreContext, StoreRegistryContext }; | ||
| export default StoreProvider; |
| import { isDev as e } from "../env.mjs"; | ||
| import t from "../createStore/index.mjs"; | ||
| import { isServerSnapshot as n, stripServerFlag as r } from "../rsc/index.mjs"; | ||
| import { StoreContext as i, StoreRegistryContext as a, findStoreInRegistry as o } from "./StoreContext.mjs"; | ||
| import s from "./hooks/useStoreSync.mjs"; | ||
| import { createContext as c, useContext as l, useEffect as u, useMemo as d, useRef as f } from "react"; | ||
| import { jsx as p } from "react/jsx-runtime"; | ||
| import { registerDevStore as i } from "../devStoreRegistry.mjs"; | ||
| import a from "./DevStoreScopeContext.mjs"; | ||
| import { StoreContext as o, StoreRegistryContext as s, findStoreInRegistry as c } from "./StoreContext.mjs"; | ||
| import l from "./hooks/useStoreSync.mjs"; | ||
| import { createContext as u, useContext as d, useEffect as f, useMemo as p, useRef as m } from "react"; | ||
| import { jsx as h } from "react/jsx-runtime"; | ||
| //#region src/react/StoreProvider.tsx | ||
| var m = c(void 0), h = (e) => e.cascade === !0, g = ({ store: c, id: g, segment: _, isolate: v, path: y, value: b, inherit: x, autoSync: S = !0, middlewares: C, children: w }) => { | ||
| let T = l(i), E = l(m), D = l(a), O = T?.scopePath ?? "", k = d(() => _ === void 0 ? O : O ? `${O}/${_}` : _, [O, _]), A = f(void 0), j = x === "live", M = d(() => { | ||
| let e = x === "snapshot" && T ? T.getState() : {}, t = typeof b == "function" ? b(e) : { | ||
| var g = u(void 0), _ = (e) => e.cascade === !0, v = ({ store: u, id: v, segment: y, isolate: b, path: x, value: S, inherit: C, autoSync: w = !0, middlewares: T, name: E, children: D }) => { | ||
| let O = d(o), k = d(g), A = d(s), j = d(a), M = O?.scopePath ?? "", N = p(() => y === void 0 ? M : M ? `${M}/${y}` : y, [M, y]), P = m(void 0), F = C === "live", I = p(() => { | ||
| let e = C === "snapshot" && O ? O.getState() : {}, t = typeof S == "function" ? S(e) : { | ||
| ...e, | ||
| ...b | ||
| ...S | ||
| }; | ||
| return n(t) ? r(t) : t; | ||
| }, [ | ||
| x, | ||
| T, | ||
| b | ||
| ]), N = C ?? [], P = E ? [...E, ...N] : N, F = d(() => { | ||
| let e = N.filter(h); | ||
| return e.length === 0 ? E : [...E ?? [], ...e]; | ||
| }, [E, C]); | ||
| A.current ||= c ?? t(() => M, { | ||
| id: g, | ||
| scopePath: k, | ||
| exclusive: j ? v : void 0, | ||
| parent: j ? T : void 0, | ||
| middlewares: P.length > 0 ? P : void 0, | ||
| C, | ||
| O, | ||
| S | ||
| ]), L = T ?? [], R = k ? [...k, ...L] : L, z = p(() => { | ||
| let e = L.filter(_); | ||
| return e.length === 0 ? k : [...k ?? [], ...e]; | ||
| }, [k, T]); | ||
| P.current ||= u ?? t(() => I, { | ||
| id: v, | ||
| scopePath: N, | ||
| exclusive: F ? b : void 0, | ||
| parent: F ? O : void 0, | ||
| middlewares: R.length > 0 ? R : void 0, | ||
| deferHydrate: !0 | ||
| }); | ||
| let I = d(() => g ? { | ||
| id: g, | ||
| store: A.current, | ||
| parent: D | ||
| } : D, [g, D]), L = f(!1); | ||
| u(() => { | ||
| e && g && !L.current && o(D, g) && (L.current = !0, console.warn(`@plitzi/nexus: duplicate StoreProvider id "${g}" — it shadows an ancestor provider with the same id, so a descendant's { storeId: "${g}" } or useStoreById("${g}") resolves to this (nearer) store. Use a distinct id, or remove one of the providers.`)); | ||
| }, [g, D]), u(() => (j && !c && A.current?.reconnect?.(), () => { | ||
| j && !c && A.current?.destroy?.(); | ||
| }), [j, c]); | ||
| let R = f(!1); | ||
| u(() => { | ||
| !R.current && A.current?.hydrate && (A.current.hydrate(), R.current = !0); | ||
| }, []), s(y, y ? b : M, { | ||
| enabled: !!b && S, | ||
| store: A.current | ||
| let B = p(() => v ? { | ||
| id: v, | ||
| store: P.current, | ||
| parent: A | ||
| } : A, [v, A]), V = m(!1); | ||
| f(() => { | ||
| e && v && !V.current && c(A, v) && (V.current = !0, console.warn(`@plitzi/nexus: duplicate StoreProvider id "${v}" — it shadows an ancestor provider with the same id, so a descendant's { storeId: "${v}" } or useStoreById("${v}") resolves to this (nearer) store. Use a distinct id, or remove one of the providers.`)); | ||
| }, [v, A]), f(() => (F && !u && P.current?.reconnect?.(), () => { | ||
| F && !u && P.current?.destroy?.(); | ||
| }), [F, u]), f(() => { | ||
| if (e) return i(P.current, j, E); | ||
| }, [j, E]); | ||
| let H = m(!1); | ||
| f(() => { | ||
| !H.current && P.current?.hydrate && (P.current.hydrate(), H.current = !0); | ||
| }, []), l(x, x ? S : I, { | ||
| enabled: !!S && w, | ||
| store: P.current | ||
| }); | ||
| let z = /* @__PURE__ */ p(i, { | ||
| value: A.current, | ||
| children: w | ||
| let U = /* @__PURE__ */ h(o, { | ||
| value: P.current, | ||
| children: D | ||
| }); | ||
| return I !== D && (z = /* @__PURE__ */ p(a, { | ||
| value: I, | ||
| children: z | ||
| })), F !== E && (z = /* @__PURE__ */ p(m, { | ||
| value: F, | ||
| children: z | ||
| })), z; | ||
| return B !== A && (U = /* @__PURE__ */ h(s, { | ||
| value: B, | ||
| children: U | ||
| })), z !== k && (U = /* @__PURE__ */ h(g, { | ||
| value: z, | ||
| children: U | ||
| })), U; | ||
| }; | ||
| //#endregion | ||
| export { i as StoreContext, a as StoreRegistryContext, g as default }; | ||
| export { o as StoreContext, s as StoreRegistryContext, v as default }; |
@@ -59,5 +59,9 @@ import { default as PathTrie } from '../createStore/helpers/PathTrie'; | ||
| export type Listener = (changedPath?: Path) => void; | ||
| export type SetStateOptions = { | ||
| canPropagate?: boolean; | ||
| unmount?: boolean; | ||
| }; | ||
| export type SetState<T> = { | ||
| (path: undefined, value: T | ((prev: T) => T), canPropagate?: boolean): void; | ||
| <P extends PathOf<T>>(path: P, value: PathValue<T, P> | ((prev: PathValue<T, P>) => PathValue<T, P>), canPropagate?: boolean): void; | ||
| (path: undefined, value: T | ((prev: T) => T), options?: SetStateOptions): void; | ||
| <P extends PathOf<T>>(path: P, value: PathValue<T, P> | ((prev: PathValue<T, P>) => PathValue<T, P>), options?: SetStateOptions): void; | ||
| }; | ||
@@ -94,2 +98,3 @@ export type GetState<T> = () => T; | ||
| getState: GetState<T>; | ||
| getOwnState: () => T; | ||
| getPath: <P extends PathOf<T>>(path: P) => PathValue<T, P> | undefined; | ||
@@ -170,10 +175,10 @@ setState: SetState<T>; | ||
| export type SetStateFn<TState extends object> = { | ||
| (path: undefined, value: TState | ((prev: TState) => TState)): void; | ||
| <P extends PathOf<TState>>(path: P, value: PathValue<TState, P> | ((prev: PathValue<TState, P>) => PathValue<TState, P>)): void; | ||
| (path: undefined, value: TState | ((prev: TState) => TState), options?: SetStateOptions): void; | ||
| <P extends PathOf<TState>>(path: P, value: PathValue<TState, P> | ((prev: PathValue<TState, P>) => PathValue<TState, P>), options?: SetStateOptions): void; | ||
| }; | ||
| export type SetFromBaseFn<TBase> = TBase extends object ? { | ||
| (subPath: undefined, value: TBase | ((prev: TBase) => TBase)): void; | ||
| <SubP extends PathOf<TBase>>(subPath: SubP, value: PathValue<TBase, SubP> | ((prev: PathValue<TBase, SubP>) => PathValue<TBase, SubP>)): void; | ||
| } : (subPath: undefined, value: TBase) => void; | ||
| (subPath: undefined, value: TBase | ((prev: TBase) => TBase), options?: SetStateOptions): void; | ||
| <SubP extends PathOf<TBase>>(subPath: SubP, value: PathValue<TBase, SubP> | ((prev: PathValue<TBase, SubP>) => PathValue<TBase, SubP>), options?: SetStateOptions): void; | ||
| } : (subPath: undefined, value: TBase, options?: SetStateOptions) => void; | ||
| export type UseStoreSetterOptions<TState extends object = object> = StoreHookBaseOptions<TState>; | ||
| export {}; |
+14
-6
| { | ||
| "name": "@plitzi/nexus", | ||
| "version": "0.32.5", | ||
| "version": "0.32.6", | ||
| "license": "Apache-2.0", | ||
@@ -26,9 +26,9 @@ "files": [ | ||
| "devDependencies": { | ||
| "eslint": "^9.39.4", | ||
| "react": "^19.2.7", | ||
| "react-dom": "^19.2.7", | ||
| "eslint": "^9.39.5", | ||
| "react": "^19.2.8", | ||
| "react-dom": "^19.2.8", | ||
| "typescript": "^6.0.3", | ||
| "vite": "^8.1.4", | ||
| "vite": "^8.1.5", | ||
| "vitest": "^4.1.10", | ||
| "vue": "^3.5.39" | ||
| "vue": "^3.5.40" | ||
| }, | ||
@@ -132,2 +132,6 @@ "peerDependencies": { | ||
| }, | ||
| "./devStoreRegistry": { | ||
| "types": "./dist/devStoreRegistry.d.ts", | ||
| "import": "./dist/devStoreRegistry.mjs" | ||
| }, | ||
| "./entities": { | ||
@@ -237,2 +241,6 @@ "types": "./dist/entities/index.d.ts", | ||
| }, | ||
| "./react/DevStoreScopeContext": { | ||
| "types": "./dist/react/DevStoreScopeContext.d.ts", | ||
| "import": "./dist/react/DevStoreScopeContext.mjs" | ||
| }, | ||
| "./react/StoreContext": { | ||
@@ -239,0 +247,0 @@ "types": "./dist/react/StoreContext.d.ts", |
+99
-1
@@ -55,5 +55,8 @@ <p align="center"> | ||
| store.set('count', n => n + 1); // updater form | ||
| store.set('user.name', undefined, { unmount: true }); // DELETE the key (see "Removing a path") | ||
| const off = store.watch('user.name', name => render(name)); // fires only for this path | ||
| ``` | ||
| Every write takes an optional third argument, `SetStateOptions = { canPropagate?; unmount? }` — see [Removing a path](#removing-a-path-unmount). To freeze paths against writes, see [Read-only paths](#read-only-paths). | ||
| ## What each piece is for | ||
@@ -133,2 +136,5 @@ | ||
| const store = createStore<MyState>({ count: 0 }, { id: 'root' }); | ||
| // Freeze paths against writes (see "Read-only paths" below) | ||
| const store = createStore<MyState>({ count: 0 }, { readOnly: ['config.theme'] }); | ||
| ``` | ||
@@ -167,2 +173,7 @@ | ||
| </StoreProvider> | ||
| // Label it for devtools — cosmetic only, unlike `id` it carries no addressing and never collides | ||
| <StoreProvider name="Form:hero" inherit="live" value={{ record }}> | ||
| <Child /> | ||
| </StoreProvider> | ||
| ``` | ||
@@ -174,2 +185,5 @@ | ||
| > **`name`** is a human label of where the store comes from, shown by the devtools store picker (see | ||
| > [DevTools integration](#devtools-integration)). Purely cosmetic — stripped from production. | ||
| ## Scoped stores (live scope chain) | ||
@@ -200,2 +214,13 @@ | ||
| ``` | ||
| - **Own layer vs merged view (`getOwnState`)** — `getState()` returns the full **merged** view (own + | ||
| inherited). `getOwnState()` returns **only this scope's own layer** — the keys it seeds/writes locally, | ||
| without the parent fall-through. For a root store the two are identical; for a scoped store `getOwnState()` | ||
| isolates the scope's contribution (what a devtools inspector shows to tell nested scopes apart). Both are | ||
| cached and reference-stable between changes. | ||
| ```ts | ||
| const root = createStore<S>({ user, theme: 'dark' }); | ||
| const item = createStore<S>({ record }, { parent: root }); | ||
| item.getState(); // { user, theme: 'dark', record } ← merged | ||
| item.getOwnState(); // { record } ← own layer only | ||
| ``` | ||
| - **Shadowing** — a scope's own **leaf** value hides the parent's at that path (reads and subscriptions); | ||
@@ -379,2 +404,40 @@ sibling leaves under a shared object branch are preserved (see deep-merge above). | ||
| ## Removing a path (`unmount`) | ||
| Every write takes an optional third argument: `SetStateOptions = { canPropagate?; unmount? }`. `canPropagate: false` commits silently (no subscriber wakes); `unmount: true` **deletes** the key at `path` instead of writing `undefined`, so the path leaves no dead entry — an object key or array slot that would otherwise linger as `undefined`. | ||
| Reach for it whenever you register/unregister things under a dynamic key (a source registry, a per-id cache) and don't want `Object.keys` / `Object.values` to see stale `undefined` holes. | ||
| ```ts | ||
| store.set('sources.abc', { id: 'abc', meta }); // register | ||
| store.set('sources.abc', undefined, { unmount: true }); // unregister — key is gone | ||
| Object.hasOwn(store.get('sources'), 'abc'); // false | ||
| ``` | ||
| - **Nested paths** delete only the leaf and keep siblings, with structural sharing (`a.b` removed → `a` is a new object; untouched subtrees are shared by reference). | ||
| - A numeric leaf on an **array** is **spliced** out (later indices shift down), never left as a hole. | ||
| - Removing an **absent** key is a **no-op** — no change is emitted, no subscriber wakes. | ||
| - Writing a value back later **recreates** the path, rebuilding any container the delete had emptied. In a scope chain the re-write delegates to the owning scope just like the original write. | ||
| - `beforeChange` interceptors still run (with `value: undefined`), so a validation or [read-only](#read-only-paths) guard can veto the removal. | ||
| - `unmount` is **ignored** for a whole-state write (`set(undefined, next)`). | ||
| ## Read-only paths | ||
| Pass `readOnly` to `createStore` to freeze one or more paths. A write to a read-only path — or to an **ancestor** or **descendant** of one — **throws in development** (surfacing the bug at its source) and is **silently dropped in production** (so it never crashes a render). | ||
| ```ts | ||
| const store = createStore<State>(initial, { readOnly: ['config.theme', 'license'] }); | ||
| store.set('config.theme', 'dark'); // dev: throws · prod: no-op | ||
| store.set('config', { theme: 'x' }); // ancestor — also blocked (would replace the frozen subtree) | ||
| store.set('config.theme.shade', 'x'); // descendant — also blocked | ||
| store.set('config.other', 'ok'); // sibling — allowed | ||
| ``` | ||
| - **Prefix-safe matching**: `readOnly: ['config']` blocks `config` and `config.*` but never `configuration`; `['a.b']` never blocks `a.bc`. | ||
| - The **updater form** throws *before* the updater runs; an `unmount` of a protected path is blocked too. | ||
| - A **whole-state replace** (`set(undefined, next)`) is checked per read-only path by **reference** — blocked if the value at that path changes identity (rebuilding an equal-but-new object still counts as a change). | ||
| - Enforcement is **per scope, on that scope's own writes**. A child that delegates a write up is rejected by the parent's `readOnly`; a key the child owns locally is not governed by an ancestor's `readOnly`. | ||
| - The check is **skipped entirely** when `readOnly` is omitted — zero cost by default. | ||
| ## `createStoreHook` | ||
@@ -676,2 +739,36 @@ | ||
| ## DevTools integration | ||
| A **dev-only** global registry lets a devtools panel enumerate and inspect every store — including scoped stores mounted **below** the panel, where React context can't reach them. Everything here is stripped from production builds (guarded by `isDev`), so it carries no runtime cost when shipped. | ||
| Every `StoreProvider` registers its store on mount and removes it on unmount. Read the registry with `useSyncExternalStore`: | ||
| ```ts | ||
| import { subscribeDevStores, getDevStoresSnapshot } from '@plitzi/nexus'; | ||
| import type { DevStoreEntry } from '@plitzi/nexus'; | ||
| // snapshot is a stable array (identity only changes when a store is added/removed) | ||
| const useDevStores = (): ReadonlyArray<DevStoreEntry> => | ||
| useSyncExternalStore(subscribeDevStores, getDevStoresSnapshot, getDevStoresSnapshot); | ||
| ``` | ||
| Each `DevStoreEntry` is `{ uid, store, scopeId?, name? }`: | ||
| - **`uid`** — a stable id for selection in a picker. | ||
| - **`store`** — the `StoreApi`; inspect it with `getState()` (merged view) or `getOwnState()` (this scope's own layer — see [Scoped stores](#scoped-stores-live-scope-chain)). | ||
| - **`name`** — the `<StoreProvider name>` label, e.g. `"Form:hero"` — a human hint of where the store comes from. | ||
| - **`scopeId`** — an app-defined grouping tag (see `DevStoreScopeContext` below), e.g. the app/SDK instance the store belongs to, so a panel can group stores by origin. | ||
| **`DevStoreScopeContext`** tags every store registered beneath it with a `scopeId`. Wrap a subtree to attribute its stores to one origin (a panel then groups by it): | ||
| ```tsx | ||
| import { DevStoreScopeContext } from '@plitzi/nexus/react'; | ||
| <DevStoreScopeContext value={instanceId}> | ||
| <App /> {/* every StoreProvider below registers with scopeId = instanceId */} | ||
| </DevStoreScopeContext> | ||
| ``` | ||
| Combine the three — `name` (where it comes from), `getOwnState()` (what it uniquely holds) and `scopeId` (which app owns it) — to build a store picker that tells otherwise-identical nested scopes apart. This is exactly what the Plitzi `sdk-dev-tools` Store tab does. | ||
| ## CSP & `new Function` | ||
@@ -935,4 +1032,5 @@ | ||
| | `MultiPathReturn<T, Paths>` | `[values, ...setters]` tuple | | ||
| | `StoreApi<T>` | `{ getState, getPath, setState, subscribe, subscribePath, subscribeChange, destroy? }` | | ||
| | `StoreApi<T>` | `{ getState, getOwnState, getPath, setState, subscribe, subscribePath, subscribeChange, destroy? }` | | ||
| | `StoreChange<T>` | `{ path, prev, next }` — payload of `subscribeChange` / middleware `onChange` | | ||
| | `DevStoreEntry` | `{ uid, store, scopeId?, name? }` — a store registered in the dev-only registry (see [DevTools integration](#devtools-integration)) | | ||
| | `StoreMiddleware<T>` | `(api) => { beforeChange?, onChange? } \| void` — a middleware factory | | ||
@@ -939,0 +1037,0 @@ | `WriteContext<T>` | `{ path, value, prev }` — payload of a `beforeChange` interceptor | |
Unidentified License
LicenseSomething that seems like a license was found, but its contents could not be matched with a known license.
Unidentified License
LicenseSomething that seems like a license was found, but its contents could not be matched with a known license.
178395
7.43%133
3.1%2720
4.45%1042
10.38%