@plitzi/nexus 0.31.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/LICENSE +74 -0
- package/README.md +563 -0
- package/dist/StoreContext.d.ts +10 -0
- package/dist/StoreContext.mjs +11 -0
- package/dist/StoreProvider.d.ts +24 -0
- package/dist/StoreProvider.mjs +51 -0
- package/dist/async/createAsync.d.ts +19 -0
- package/dist/async/createAsync.mjs +29 -0
- package/dist/async/hooks/useAsync.d.ts +2 -0
- package/dist/async/hooks/useAsync.mjs +7 -0
- package/dist/async/hooks/useAsyncValue.d.ts +2 -0
- package/dist/async/hooks/useAsyncValue.mjs +13 -0
- package/dist/async/index.d.ts +4 -0
- package/dist/async/index.mjs +4 -0
- package/dist/createStore/createStore.d.ts +90 -0
- package/dist/createStore/createStore.mjs +104 -0
- package/dist/createStore/helpers/PathTrie.d.ts +13 -0
- package/dist/createStore/helpers/PathTrie.mjs +43 -0
- package/dist/createStore/helpers/Subscribers.d.ts +15 -0
- package/dist/createStore/helpers/Subscribers.mjs +59 -0
- package/dist/createStore/helpers/createChainReads.d.ts +10 -0
- package/dist/createStore/helpers/createChainReads.mjs +37 -0
- package/dist/createStore/helpers/createSetState.d.ts +22 -0
- package/dist/createStore/helpers/createSetState.mjs +163 -0
- package/dist/createStore/helpers/deepMerge.d.ts +2 -0
- package/dist/createStore/helpers/deepMerge.mjs +9 -0
- package/dist/createStore/helpers/forwardParentChanges.d.ts +7 -0
- package/dist/createStore/helpers/forwardParentChanges.mjs +23 -0
- package/dist/createStore/helpers/scopeCollisions.d.ts +4 -0
- package/dist/createStore/helpers/scopeCollisions.mjs +14 -0
- package/dist/createStore/helpers/writeByPath.d.ts +11 -0
- package/dist/createStore/helpers/writeByPath.mjs +51 -0
- package/dist/createStore/hooks/shared.d.ts +12 -0
- package/dist/createStore/hooks/shared.mjs +85 -0
- package/dist/createStore/hooks/useStore.d.ts +73 -0
- package/dist/createStore/hooks/useStore.mjs +57 -0
- package/dist/createStore/hooks/useStoreById.d.ts +3 -0
- package/dist/createStore/hooks/useStoreById.mjs +7 -0
- package/dist/createStore/hooks/useStoreGetter.d.ts +9 -0
- package/dist/createStore/hooks/useStoreGetter.mjs +38 -0
- package/dist/createStore/hooks/useStoreSetter.d.ts +5 -0
- package/dist/createStore/hooks/useStoreSetter.mjs +11 -0
- package/dist/createStore/hooks/useStoreSync.d.ts +7 -0
- package/dist/createStore/hooks/useStoreSync.mjs +55 -0
- package/dist/createStore/index.d.ts +3 -0
- package/dist/createStore/index.mjs +5 -0
- package/dist/derived/createDerived.d.ts +10 -0
- package/dist/derived/createDerived.mjs +21 -0
- package/dist/derived/hooks/useDerived.d.ts +2 -0
- package/dist/derived/hooks/useDerived.mjs +7 -0
- package/dist/derived/index.d.ts +3 -0
- package/dist/derived/index.mjs +3 -0
- package/dist/entities/createEntityAdapter.d.ts +32 -0
- package/dist/entities/createEntityAdapter.mjs +73 -0
- package/dist/entities/index.d.ts +2 -0
- package/dist/entities/index.mjs +2 -0
- package/dist/helpers/createObservable.d.ts +9 -0
- package/dist/helpers/createObservable.mjs +18 -0
- package/dist/helpers/getByPath.d.ts +4 -0
- package/dist/helpers/getByPath.mjs +40 -0
- package/dist/helpers/isPathAffected.d.ts +3 -0
- package/dist/helpers/isPathAffected.mjs +4 -0
- package/dist/helpers/parsePath.d.ts +2 -0
- package/dist/helpers/parsePath.mjs +7 -0
- package/dist/helpers/setByPath.d.ts +3 -0
- package/dist/helpers/setByPath.mjs +19 -0
- package/dist/helpers/shallowEqual.d.ts +2 -0
- package/dist/helpers/shallowEqual.mjs +11 -0
- package/dist/helpers/useIsomorphicLayoutEffect.d.ts +3 -0
- package/dist/helpers/useIsomorphicLayoutEffect.mjs +5 -0
- package/dist/history/hooks/useStoreHistory.d.ts +8 -0
- package/dist/history/hooks/useStoreHistory.mjs +24 -0
- package/dist/history/index.d.ts +4 -0
- package/dist/history/index.mjs +3 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.mjs +21 -0
- package/dist/middleware/cascade.d.ts +2 -0
- package/dist/middleware/cascade.mjs +4 -0
- package/dist/middleware/historyMiddleware.d.ts +28 -0
- package/dist/middleware/historyMiddleware.mjs +81 -0
- package/dist/middleware/index.d.ts +8 -0
- package/dist/middleware/index.mjs +6 -0
- package/dist/middleware/loggerMiddleware.d.ts +7 -0
- package/dist/middleware/loggerMiddleware.mjs +20 -0
- package/dist/middleware/persistMiddleware.d.ts +16 -0
- package/dist/middleware/persistMiddleware.mjs +36 -0
- package/dist/middleware/reduxDevToolsMiddleware.d.ts +6 -0
- package/dist/middleware/reduxDevToolsMiddleware.mjs +26 -0
- package/dist/types/StoreTypes.d.ts +143 -0
- package/dist/types/StoreTypes.mjs +4 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.mjs +2 -0
- package/package.json +257 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# @plitzi/nexus
|
|
2
|
+
|
|
3
|
+
## 0.31.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- v0.31.1
|
|
8
|
+
|
|
9
|
+
## 0.31.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- v0.31.0
|
|
14
|
+
|
|
15
|
+
## 0.30.19
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- v0.30.19
|
|
20
|
+
|
|
21
|
+
## 0.30.18
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- v0.30.18
|
|
26
|
+
|
|
27
|
+
## 0.30.17
|
|
28
|
+
|
|
29
|
+
### Patch Changes
|
|
30
|
+
|
|
31
|
+
- v0.31.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity.
|
|
18
|
+
|
|
19
|
+
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
|
20
|
+
permissions granted by this License.
|
|
21
|
+
|
|
22
|
+
"Source" form shall mean the preferred form for making modifications.
|
|
23
|
+
|
|
24
|
+
"Object" form shall mean any form resulting from mechanical
|
|
25
|
+
transformation or translation of a Source form.
|
|
26
|
+
|
|
27
|
+
2. Grant of Copyright License.
|
|
28
|
+
|
|
29
|
+
Subject to the terms and conditions of this License, each Contributor
|
|
30
|
+
hereby grants to You a perpetual, worldwide, non-exclusive,
|
|
31
|
+
no-charge, royalty-free, irrevocable copyright license to reproduce,
|
|
32
|
+
prepare Derivative Works of, publicly display, publicly perform,
|
|
33
|
+
sublicense, and distribute the Work and such Derivative Works.
|
|
34
|
+
|
|
35
|
+
3. Grant of Patent License.
|
|
36
|
+
|
|
37
|
+
Subject to the terms and conditions of this License, each Contributor
|
|
38
|
+
hereby grants You a perpetual, worldwide, non-exclusive,
|
|
39
|
+
no-charge, royalty-free, irrevocable (except as stated in this section)
|
|
40
|
+
patent license to make, have made, use, offer to sell, sell, import,
|
|
41
|
+
and otherwise transfer the Work.
|
|
42
|
+
|
|
43
|
+
4. Redistribution.
|
|
44
|
+
|
|
45
|
+
You may reproduce and distribute copies of the Work or Derivative Works
|
|
46
|
+
provided that you include a copy of this License and provide proper attribution.
|
|
47
|
+
|
|
48
|
+
5. Submission of Contributions.
|
|
49
|
+
|
|
50
|
+
Unless explicitly stated otherwise, any Contribution intentionally submitted
|
|
51
|
+
shall be licensed under this License.
|
|
52
|
+
|
|
53
|
+
6. Trademarks.
|
|
54
|
+
|
|
55
|
+
This License does not grant permission to use the trade names, trademarks,
|
|
56
|
+
service marks, or product names of the Licensor.
|
|
57
|
+
|
|
58
|
+
7. Disclaimer of Warranty.
|
|
59
|
+
|
|
60
|
+
Unless required by applicable law or agreed to in writing, Licensor
|
|
61
|
+
provides the Work "AS IS" without warranties or conditions of any kind.
|
|
62
|
+
|
|
63
|
+
8. Limitation of Liability.
|
|
64
|
+
|
|
65
|
+
In no event and under no legal theory shall the Licensor be liable for
|
|
66
|
+
damages arising from the use of the Work.
|
|
67
|
+
|
|
68
|
+
9. Accepting Warranty or Additional Liability.
|
|
69
|
+
|
|
70
|
+
While redistributing the Work or Derivative Works, You may choose to
|
|
71
|
+
offer and charge a fee for acceptance of support, warranty, indemnity,
|
|
72
|
+
or other liability obligations.
|
|
73
|
+
|
|
74
|
+
END OF TERMS AND CONDITIONS
|
package/README.md
ADDED
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
# @plitzi/nexus
|
|
2
|
+
|
|
3
|
+
A lightweight, type-safe React store built on `useSyncExternalStore`. You subscribe to **dot-notation paths** and re-render only when that exact value changes — no selectors, no reducers, no action types. On top of that core it ships scoped stores, time-travel, derived values, an entity adapter, and a middleware pipeline (logger / persist / history).
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @plitzi/nexus # peer deps: react@^18 || ^19, react-dom@^18 || ^19
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { createStoreHook } from '@plitzi/nexus';
|
|
11
|
+
|
|
12
|
+
type State = { count: number; user: { name: string } };
|
|
13
|
+
const { useStore } = createStoreHook<State>();
|
|
14
|
+
|
|
15
|
+
function Counter() {
|
|
16
|
+
const [count, setCount] = useStore('count'); // typed as number
|
|
17
|
+
return <button onClick={() => setCount(n => n + 1)}>{count}</button>;
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## What each piece is for
|
|
22
|
+
|
|
23
|
+
| Import | What it's for |
|
|
24
|
+
|---|---|
|
|
25
|
+
| `createStore` | Create a vanilla store (no React needed). |
|
|
26
|
+
| `createStoreHook<T>()` | Bind your state type once → fully-typed `useStore*` hooks. |
|
|
27
|
+
| `StoreProvider` | Put a store on React context; optionally scope/sync/record it. |
|
|
28
|
+
| `useStore` | Subscribe + write a path (or paths). Re-renders on change only. |
|
|
29
|
+
| `useStoreSetter` | A stable setter — write without subscribing/re-rendering. |
|
|
30
|
+
| `useStoreGetter` | A stable getter — read current values in callbacks, no re-render. |
|
|
31
|
+
| `useStoreById` | Get a named ancestor store's `StoreApi` by `id` (reachable across disconnected providers). |
|
|
32
|
+
| `useStoreSync` | Mirror an external value (props) **into** the store. |
|
|
33
|
+
| `createDerived` / `useDerived` | A memoized value computed from store paths (reselect-style). |
|
|
34
|
+
| `createEntityAdapter` | CRUD updaters + selectors for a normalized `Record<id, T>` map. |
|
|
35
|
+
| `loggerMiddleware` / `persistMiddleware` / `historyMiddleware` / `reduxDevToolsMiddleware` | Middlewares: log, persist to storage, record time-travel, connect to Redux DevTools. |
|
|
36
|
+
| `getStoreHistory` / `useStoreHistory` | Undo / redo / jump-to-snapshot action log (enabled by `historyMiddleware`). |
|
|
37
|
+
|
|
38
|
+
## Architecture
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
StoreContext.ts context object (no deps → no cycles)
|
|
42
|
+
createStore.ts factory + createStoreHook
|
|
43
|
+
StoreProvider.tsx context provider with optional sync
|
|
44
|
+
hooks/
|
|
45
|
+
useStore.ts subscribe + read
|
|
46
|
+
useStoreSync.ts sync external value into the store (write-only)
|
|
47
|
+
useStoreGetter.ts non-reactive snapshot getter
|
|
48
|
+
useStoreSetter.ts fire-and-forget setter
|
|
49
|
+
shared.ts snapshot factories, subscription helpers
|
|
50
|
+
derived/ createDerived + useDerived (memoized computed values)
|
|
51
|
+
entities/ createEntityAdapter (normalized-map CRUD + selectors)
|
|
52
|
+
middleware/ logger / persist / history (all on subscribeChange)
|
|
53
|
+
history/ getStoreHistory + useStoreHistory (time-travel, enabled by historyMiddleware)
|
|
54
|
+
helpers/ getByPath, setByPath, parsePath, shallowEqual…
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
> **One change substrate.** Every committed write flows through `store.subscribeChange((change) => …)` where `change` is `{ path, prev, next }`. `loggerMiddleware`, `persistMiddleware`, and `historyMiddleware` are all just observers on this channel — and so is any middleware you write. It costs nothing on the hot path when no observer is attached. Writes can also be **intercepted** before they commit via a middleware's `beforeChange` to transform or cancel them — see [Intercepting writes](#intercepting-writes-beforechange).
|
|
58
|
+
|
|
59
|
+
## `createStore`
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
const store = createStore<MyState>({ count: 0, user: { name: 'Alice' } });
|
|
63
|
+
// or with an initializer function:
|
|
64
|
+
const store = createStore<MyState>((setState, getState) => ({ count: 0 }));
|
|
65
|
+
|
|
66
|
+
// Scoped store: reads fall through to a parent store (see "Scoped stores" below)
|
|
67
|
+
const child = createStore<MyState>({ record }, { parent: rootStore });
|
|
68
|
+
|
|
69
|
+
// With middlewares (loggerMiddleware / persistMiddleware / historyMiddleware / your own)
|
|
70
|
+
const store = createStore<MyState>({ count: 0 }, {
|
|
71
|
+
middlewares: [persistMiddleware({ key: 'app' }), historyMiddleware(), loggerMiddleware()]
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// With an id, so descendants can target it by id (see "Named stores" below)
|
|
75
|
+
const store = createStore<MyState>({ count: 0 }, { id: 'root' });
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`StoreApi` exposes `getState`, `getPath`, `setState`, `subscribe`, `subscribePath`, `subscribeChange`,
|
|
79
|
+
`destroy?()`, and an optional `id`. Call `destroy()` to detach a scoped store from its parent and clear its
|
|
80
|
+
listeners (prevents leaks for short-lived scopes like list items). `StoreProvider` calls it automatically on
|
|
81
|
+
unmount.
|
|
82
|
+
|
|
83
|
+
## `StoreProvider`
|
|
84
|
+
|
|
85
|
+
Wraps children with a store context. Creates a new store by default; pass `store` to provide an existing one.
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
<StoreProvider value={{ count: 0 }}>
|
|
89
|
+
<App />
|
|
90
|
+
</StoreProvider>
|
|
91
|
+
|
|
92
|
+
// Inherit from the parent — two modes via the `inherit` prop:
|
|
93
|
+
<StoreProvider inherit="snapshot" value={{ extra: true }}> {/* copy once at init, isolated */}
|
|
94
|
+
<Child />
|
|
95
|
+
</StoreProvider>
|
|
96
|
+
<StoreProvider inherit="live" value={{ record }}> {/* live scope chain (see below) */}
|
|
97
|
+
<Child />
|
|
98
|
+
</StoreProvider>
|
|
99
|
+
|
|
100
|
+
// Sync a sub-path from parent props
|
|
101
|
+
<StoreProvider path="schema.flat" value={flatMap}>
|
|
102
|
+
<Child />
|
|
103
|
+
</StoreProvider>
|
|
104
|
+
|
|
105
|
+
// Name it so descendants can reach it by id, even past a disconnected provider (see "Named stores")
|
|
106
|
+
<StoreProvider id="root" value={builderState}>
|
|
107
|
+
<App />
|
|
108
|
+
</StoreProvider>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
> **`inherit` modes:** `'snapshot'` copies parent keys **once** at init — isolated thereafter: writes stay
|
|
112
|
+
> local and parent updates do **not** propagate (use for draft/diverge editors). `'live'` keeps a **live
|
|
113
|
+
> link** — inherited keys stay in sync and writes delegate to the owning scope.
|
|
114
|
+
|
|
115
|
+
## Scoped stores (live scope chain)
|
|
116
|
+
|
|
117
|
+
A store can have a `parent`. Reads resolve through the chain and writes target the owning scope. This
|
|
118
|
+
lets nested scopes (e.g. a list item providing its own `record`) shadow shared/global state while still
|
|
119
|
+
reading it live — without prop-drilling or N value-contexts.
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
const root = createStore<S>({ user: { name: 'Alice' }, theme: 'dark' });
|
|
123
|
+
const item = createStore<S>({ record }, { parent: root });
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Semantics**
|
|
127
|
+
|
|
128
|
+
- **Read fall-through (deep-merge)** — `getState()` deep-merges the chain: own values win, but nested
|
|
129
|
+
object branches are **merged**, not replaced, so a scope can contribute a sub-key without clobbering the
|
|
130
|
+
parent's siblings under the same branch.
|
|
131
|
+
```ts
|
|
132
|
+
item.getState().record; // own
|
|
133
|
+
item.getState().user; // inherited from root (live)
|
|
134
|
+
|
|
135
|
+
// deep-merge of a shared branch:
|
|
136
|
+
const root = createStore<S>({ runtime: { sources: { variables: { a: 1 } } } });
|
|
137
|
+
const item = createStore<S>({ runtime: { sources: { record } } }, { parent: root });
|
|
138
|
+
item.getState().runtime.sources; // { variables: { a: 1 }, record } ← merged, not shadowed
|
|
139
|
+
```
|
|
140
|
+
- **Shadowing** — a scope's own **leaf** value hides the parent's at that path (reads and subscriptions);
|
|
141
|
+
sibling leaves under a shared object branch are preserved (see deep-merge above).
|
|
142
|
+
```ts
|
|
143
|
+
const item = createStore<S>({ theme: 'light' }, { parent: root });
|
|
144
|
+
item.getState().theme; // 'light' (shadows root's 'dark')
|
|
145
|
+
root.getState().theme; // 'dark' (untouched)
|
|
146
|
+
```
|
|
147
|
+
- **Write delegation (recursive to owner / root)** — `setState(path)` walks up to the nearest scope that
|
|
148
|
+
owns the path; if no ancestor owns it, it delegates all the way to the **root**. So a deeply-nested scope
|
|
149
|
+
can write a shared branch (e.g. `runtime.sources.*`) and it lands at the root without any scope having to
|
|
150
|
+
pre-seed that branch.
|
|
151
|
+
```ts
|
|
152
|
+
item.setState('user.name', 'Bob'); // delegates to the scope that owns `user`
|
|
153
|
+
item.setState('runtime.sources.x', v); // delegates to root even if no ancestor seeded `runtime`
|
|
154
|
+
item.setState('record', next); // stays local to the item scope (it owns `record`)
|
|
155
|
+
```
|
|
156
|
+
- **Multi-level subscriptions** — consumers of a scope re-render when an inherited key changes in any
|
|
157
|
+
ancestor; shadowed keys are ignored. Equality still filters no-op updates.
|
|
158
|
+
- **Cleanup** — call `store.destroy()` (or let `<StoreProvider inherit="live">` do it on unmount) to detach
|
|
159
|
+
from the parent and avoid listener leaks for short-lived scopes.
|
|
160
|
+
|
|
161
|
+
Via `StoreProvider` (the common case):
|
|
162
|
+
|
|
163
|
+
```tsx
|
|
164
|
+
<StoreProvider value={{ user, theme: 'dark' }}> {/* root scope */}
|
|
165
|
+
<StoreProvider inherit="live" value={{ record }}> {/* item scope */}
|
|
166
|
+
{/* useStore('user') → inherited, live */}
|
|
167
|
+
{/* useStore('record') → own */}
|
|
168
|
+
<ItemView />
|
|
169
|
+
</StoreProvider>
|
|
170
|
+
</StoreProvider>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Named stores & reaching ancestors (`id` / `storeId` / `useStoreById`)
|
|
174
|
+
|
|
175
|
+
Give a store an **`id`** and any descendant can target it — even across a **disconnected** (`inherit`-less)
|
|
176
|
+
provider that would otherwise shadow it. The id lives in a parallel registry propagated by context
|
|
177
|
+
*independently of `inherit`*, so it never breaks the scope chain and costs nothing per provider unless used.
|
|
178
|
+
|
|
179
|
+
```tsx
|
|
180
|
+
<StoreProvider id="root" value={builderState}>
|
|
181
|
+
...
|
|
182
|
+
<StoreProvider value={{ local: 1 }}> {/* disconnected — no inherit */}
|
|
183
|
+
<Leaf />
|
|
184
|
+
</StoreProvider>
|
|
185
|
+
</StoreProvider>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
// inside Leaf — reach the named root store past the disconnected provider:
|
|
190
|
+
const [theme, setTheme] = useStore('theme', { storeId: 'root' }); // reactive read + write
|
|
191
|
+
const getUser = useStoreGetter('user', { storeId: 'root' }); // imperative read
|
|
192
|
+
const setUser = useStoreSetter('user', { storeId: 'root' }); // imperative write
|
|
193
|
+
|
|
194
|
+
// the raw StoreApi — for createDerived / batch / subscribe / passing around:
|
|
195
|
+
const rootStore = useStoreById('root');
|
|
196
|
+
rootStore.batch(() => { ... });
|
|
197
|
+
const itemCount = createDerived(rootStore, ['items'], ([items]) => items.length);
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
- **`storeId` option** — available on every hook (`useStore`, `useStoreGetter`, `useStoreSetter`,
|
|
201
|
+
`useStoreSync`). Resolves the nearest ancestor store registered under that id. An explicit `store` option
|
|
202
|
+
still wins over `storeId`.
|
|
203
|
+
- **`useStoreById(id?)`** — returns the raw `StoreApi`. With no `id`, returns the nearest provider's store.
|
|
204
|
+
Use it when you need the store object itself (`createDerived`/`createAsync`, `batch`, `subscribe`); the
|
|
205
|
+
options above only read/write *through* it.
|
|
206
|
+
- **`store.id`** — the identity is also set on the store, handy for logging/devtools.
|
|
207
|
+
- **Lifecycle** — registration is context-scoped: an id stops resolving the moment its provider unmounts.
|
|
208
|
+
There is no global registry, so nothing to clean up and no leaks.
|
|
209
|
+
- **Duplicate ids (dev)** — registering an id that shadows an ancestor with the same id logs a warning in
|
|
210
|
+
development (stripped in production); the nearer store wins. Sibling subtrees may reuse an id freely.
|
|
211
|
+
|
|
212
|
+
## `useStore`
|
|
213
|
+
|
|
214
|
+
Subscribe to store values. Triggers re-render only when the selected value changes.
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
// Full state
|
|
218
|
+
const [state, setState] = useStore();
|
|
219
|
+
|
|
220
|
+
// Single path
|
|
221
|
+
const [name, setName] = useStore('user.name');
|
|
222
|
+
|
|
223
|
+
// With default value
|
|
224
|
+
const [el, setEl] = useStore(`schema.flat.${id}` as PathOf<MyState>, { defaultValue: {} });
|
|
225
|
+
|
|
226
|
+
// Dynamic path (function resolves the path string at runtime)
|
|
227
|
+
const [val, setVal] = useStore(s => `style.platform.${s.displayMode}` as PathOf<MyState>);
|
|
228
|
+
|
|
229
|
+
// Transform the value (memoized, no extra re-renders)
|
|
230
|
+
const [upper] = useStore('user.name', { transformer: v => v.toUpperCase() });
|
|
231
|
+
|
|
232
|
+
// Multi-path
|
|
233
|
+
const [[name, count], setName, setCount] = useStore(['user.name', 'count']);
|
|
234
|
+
|
|
235
|
+
// Multi-path with dynamic path
|
|
236
|
+
const [[name, val], setName, setVal] = useStore([
|
|
237
|
+
'user.name',
|
|
238
|
+
s => `style.${s.displayMode}` as PathOf<MyState>
|
|
239
|
+
]);
|
|
240
|
+
|
|
241
|
+
// Multi-path with transformer
|
|
242
|
+
const [derived] = useStore(['user.name', 'count'], {
|
|
243
|
+
transformer: ([name, count]) => `${name} (${count})`
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Options
|
|
247
|
+
useStore('user.name', { enabled: false }); // unsubscribed
|
|
248
|
+
useStore('user.name', { mode: 'mount' }); // read once on mount
|
|
249
|
+
useStore('user.name', { store: myStore }); // explicit store
|
|
250
|
+
useStore('user.name', { storeId: 'root' }); // a named ancestor store (see "Named stores")
|
|
251
|
+
useStore('user.name', { equalityFn: Object.is }); // custom equality
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## `useStoreSync`
|
|
255
|
+
|
|
256
|
+
Syncs an external value into the store on every render (write-only, no subscription). Returns `void`.
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
// Sync full state (merges)
|
|
260
|
+
useStoreSync(undefined, fullState);
|
|
261
|
+
|
|
262
|
+
// Sync a single path
|
|
263
|
+
useStoreSync('schema', schema);
|
|
264
|
+
|
|
265
|
+
// Sync multiple paths
|
|
266
|
+
useStoreSync(['schema', 'style'], [schema, style]);
|
|
267
|
+
|
|
268
|
+
// Options
|
|
269
|
+
useStoreSync('schema', schema, { mode: 'mount' }); // sync on mount only
|
|
270
|
+
useStoreSync('schema', schema, { enabled: false }); // disabled
|
|
271
|
+
useStoreSync('schema', schema, { syncStrategy: 'render' }); // sync during render (no layout effect)
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## `useStoreGetter`
|
|
275
|
+
|
|
276
|
+
Non-reactive. Returns a stable getter function that reads the current store value at call time (no re-renders).
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
// Full state getter
|
|
280
|
+
const get = useStoreGetter();
|
|
281
|
+
get(); // → MyState
|
|
282
|
+
get('user.name'); // → string
|
|
283
|
+
get('user.name', 'default'); // → string | 'default'
|
|
284
|
+
|
|
285
|
+
// Scoped getter
|
|
286
|
+
const getFlat = useStoreGetter('schema.flat');
|
|
287
|
+
getFlat(); // → FlatMap
|
|
288
|
+
getFlat(id); // → Element
|
|
289
|
+
getFlat(id, fallback); // → Element | fallback
|
|
290
|
+
|
|
291
|
+
// Multi-entry tuple
|
|
292
|
+
const [getName, getCount] = useStoreGetter(['user.name', 'count']);
|
|
293
|
+
getName(); // → string
|
|
294
|
+
getCount(); // → number
|
|
295
|
+
|
|
296
|
+
// Mixed paths + functions
|
|
297
|
+
const [getFlat, getCount] = useStoreGetter(['schema.flat', s => s.count]);
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## `useStoreSetter`
|
|
301
|
+
|
|
302
|
+
Non-reactive. Returns a stable setter. No re-renders on write.
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
// Full setState
|
|
306
|
+
const setState = useStoreSetter();
|
|
307
|
+
setState('user.name', 'Bob');
|
|
308
|
+
setState('user.name', prev => prev + '!');
|
|
309
|
+
setState(undefined, fullState);
|
|
310
|
+
|
|
311
|
+
// Scoped setter
|
|
312
|
+
const setFlat = useStoreSetter('schema.flat');
|
|
313
|
+
setFlat(id, element); // sets schema.flat.${id}
|
|
314
|
+
setFlat(`${id}.attributes`, attrs); // sets schema.flat.${id}.attributes
|
|
315
|
+
setFlat(undefined, flatObj); // replaces schema.flat
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
## `createStoreHook`
|
|
319
|
+
|
|
320
|
+
Factory that binds `TState` at the module level, giving fully-typed hooks without repeating the generic at every call site.
|
|
321
|
+
|
|
322
|
+
```ts
|
|
323
|
+
const { useStore, useStoreSync, useStoreGetter, useStoreSetter } = createStoreHook<MyState>();
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## `createDerived` / `useDerived`
|
|
327
|
+
|
|
328
|
+
**What it's for:** a value *computed* from store paths — totals, filtered lists, formatted strings. It computes once, memoizes, and only wakes subscribers when the **result** changes (a dependency edit that doesn't affect the output costs nothing downstream). Think reselect / Jotai derived atoms / MobX `computed`, shared across every consumer instead of recomputed per component.
|
|
329
|
+
|
|
330
|
+
```ts
|
|
331
|
+
import { createDerived, useDerived } from '@plitzi/nexus';
|
|
332
|
+
|
|
333
|
+
const total = createDerived(
|
|
334
|
+
store,
|
|
335
|
+
['items'], // dependency paths (typed)
|
|
336
|
+
([items]) => Object.values(items).reduce((s, i) => s + i.qty * i.price, 0)
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
total.get(); // current value (recomputed lazily if a dep changed)
|
|
340
|
+
const off = total.subscribe(() => render()); // wakes only when `total` changes
|
|
341
|
+
total.destroy(); // detach from the store
|
|
342
|
+
|
|
343
|
+
// React — the computation is shared, not repeated per component:
|
|
344
|
+
function CartTotal() {
|
|
345
|
+
const value = useDerived(total);
|
|
346
|
+
return <span>${value}</span>;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Multiple deps + custom equality (skip object-identity churn):
|
|
350
|
+
const ids = createDerived(store, ['items'], ([m]) => Object.keys(m), {
|
|
351
|
+
equalityFn: (a, b) => a.length === b.length && a.every((x, i) => x === b[i])
|
|
352
|
+
});
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
> Use `createDerived` for a value shared across components; reach for `useStore('path', { transformer })` when the transform is local to one component.
|
|
356
|
+
|
|
357
|
+
## `createEntityAdapter`
|
|
358
|
+
|
|
359
|
+
**What it's for:** the boilerplate around a normalized `Record<id, entity>` map. Write ops return **immutable updaters** you hand straight to `setState`; selectors read a map. (It doesn't change the cost of an immutable map write — see [Performance](#performance) — it just removes the hand-rolled spread/merge.)
|
|
360
|
+
|
|
361
|
+
```ts
|
|
362
|
+
import { createEntityAdapter } from '@plitzi/nexus';
|
|
363
|
+
|
|
364
|
+
type Todo = { id: string; text: string; done: boolean };
|
|
365
|
+
const todos = createEntityAdapter<Todo>(); // selectId defaults to `e => e.id`
|
|
366
|
+
|
|
367
|
+
store.setState('todos', todos.addMany([a, b]));
|
|
368
|
+
store.setState('todos', todos.updateOne({ id: 'a', changes: { done: true } })); // shallow-merge
|
|
369
|
+
store.setState('todos', todos.upsertOne(c));
|
|
370
|
+
store.setState('todos', todos.removeOne('b'));
|
|
371
|
+
|
|
372
|
+
const map = store.getPath('todos');
|
|
373
|
+
todos.selectAll(map); // Todo[]
|
|
374
|
+
todos.selectById(map, 'a'); // Todo | undefined
|
|
375
|
+
todos.selectTotal(map); // number
|
|
376
|
+
|
|
377
|
+
// Custom id field + sort order for selectAll / selectIds:
|
|
378
|
+
createEntityAdapter<Row>({ selectId: r => r.key, sortComparer: (a, b) => a.name.localeCompare(b.name) });
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
Ops: `addOne/addMany` (ignore existing), `setOne/setMany/setAll` (replace), `updateOne/updateMany` (merge changes), `upsertOne/upsertMany` (add or merge), `removeOne/removeMany/removeAll`. Each returns the **same map reference** when nothing changed, so the store skips the write.
|
|
382
|
+
|
|
383
|
+
## Middleware (`loggerMiddleware` / `persistMiddleware` / `historyMiddleware`)
|
|
384
|
+
|
|
385
|
+
**What it's for:** cross-cutting logic on every write, centralized in one place. A middleware is `(api) => { beforeChange?, onChange? } | void`; its setup body runs once after creation (so it can hydrate). It can do two things: **intercept** a write before it commits (`beforeChange`) and **observe** a write after it commits (`onChange`, the `{ path, prev, next }` change). logger, persist, and history are built-in observers; write your own the same way. Middlewares are per-store — in a scope chain, attach them where the writes you care about commit (shared keys delegate to the owning scope, so the owning scope's interceptors run).
|
|
386
|
+
|
|
387
|
+
```ts
|
|
388
|
+
import { createStore, loggerMiddleware, persistMiddleware, historyMiddleware } from '@plitzi/nexus';
|
|
389
|
+
|
|
390
|
+
const store = createStore<State>(initial, {
|
|
391
|
+
middlewares: [
|
|
392
|
+
persistMiddleware({ key: 'app' }), // put persist FIRST so it hydrates before others observe
|
|
393
|
+
historyMiddleware(),
|
|
394
|
+
loggerMiddleware()
|
|
395
|
+
]
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
// Write your own — an observer of every committed change:
|
|
399
|
+
const analytics: StoreMiddleware<State> = api => ({
|
|
400
|
+
onChange: ({ path, prev, next }) => track('store.change', { path })
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// Or subscribe imperatively to the same substrate:
|
|
404
|
+
const off = store.subscribeChange(({ path, prev, next }) => {});
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Intercepting writes (`beforeChange`)
|
|
408
|
+
|
|
409
|
+
`onChange` only **observes** committed changes — it can't stop or rewrite them. A middleware's `beforeChange` runs **before** the write commits and can transform the value, or cancel the write entirely by returning `CANCEL`. Returning `undefined` lets the value through unchanged. It's just another middleware, so you write your own the same way you'd write a logger.
|
|
410
|
+
|
|
411
|
+
```ts
|
|
412
|
+
import { createStore, CANCEL } from '@plitzi/nexus';
|
|
413
|
+
|
|
414
|
+
const guard: StoreMiddleware<State> = () => ({
|
|
415
|
+
beforeChange: ({ path, value, prev }) => {
|
|
416
|
+
if (path === 'role' && !isAdmin) {
|
|
417
|
+
return CANCEL; // block the write — nothing commits, no observer fires
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (path === 'ui.size') {
|
|
421
|
+
return Math.min(value as number, 100); // clamp before it commits
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return undefined; // let everything else through unchanged
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
const store = createStore<State>(initial, { middlewares: [guard] });
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
`beforeChange` receives a `WriteContext`: `{ path, value, prev }` — the changed `path` (`undefined` for a whole-state write, where `value` is the full next state), the resolved `value` about to be written (function setters are already applied, so you see the concrete value), and the current `prev` at that path.
|
|
432
|
+
|
|
433
|
+
When several middlewares declare a `beforeChange`, they run in **middleware order**, each seeing the previous one's result — so an earlier one can transform a value and a later one can still cancel it. Like every middleware, they run on the store that commits the write (in a scope chain, a write delegated to the owning scope runs that scope's interceptors).
|
|
434
|
+
|
|
435
|
+
### `persistMiddleware`
|
|
436
|
+
|
|
437
|
+
Mirrors the store to a key/value storage and rehydrates on creation.
|
|
438
|
+
|
|
439
|
+
```ts
|
|
440
|
+
persistMiddleware<State>({
|
|
441
|
+
key: 'app',
|
|
442
|
+
storage, // default: localStorage, no-op on SSR. Any { getItem, setItem, removeItem }
|
|
443
|
+
partialize: s => ({ user: s.user }), // persist only part of the state
|
|
444
|
+
version: 2, // bump when the shape changes…
|
|
445
|
+
migrate: (old, v) => ({ user: old }), // …and map an older payload forward
|
|
446
|
+
merge: (persisted, current) => ({ ...current, ...persisted }),
|
|
447
|
+
debounce: 200 // coalesce rapid writes (ms); 0 = write synchronously
|
|
448
|
+
});
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### `loggerMiddleware`
|
|
452
|
+
|
|
453
|
+
```ts
|
|
454
|
+
loggerMiddleware<State>({
|
|
455
|
+
filter: change => change.path !== 'mouse', // log only some changes
|
|
456
|
+
sink: change => console.log(change.path, change.next) // default sink is console.log
|
|
457
|
+
});
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### `reduxDevToolsMiddleware`
|
|
461
|
+
|
|
462
|
+
Mirrors the store to the [Redux DevTools](https://github.com/reduxjs/redux-devtools) browser extension: every committed change is sent as an action (labelled by the changed path) and time-travel from the DevTools UI (jump / rollback) is written back into the store. It's a **no-op** when the extension isn't installed (production, SSR, browsers without it), so it's safe to leave wired in.
|
|
463
|
+
|
|
464
|
+
```ts
|
|
465
|
+
import { createStore, reduxDevToolsMiddleware } from '@plitzi/nexus';
|
|
466
|
+
|
|
467
|
+
const store = createStore<State>(initial, {
|
|
468
|
+
middlewares: [reduxDevToolsMiddleware({ name: 'my-app' })]
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
store.setState('count', 1); // → action "count" in DevTools
|
|
472
|
+
store.setState('user.name', 'Bob'); // → action "user.name"
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
```ts
|
|
476
|
+
reduxDevToolsMiddleware<State>({
|
|
477
|
+
name: 'my-app', // instance name in the DevTools dropdown (default 'nexus')
|
|
478
|
+
action: change => `set:${change.path}` // how to label each action (default: the changed path)
|
|
479
|
+
});
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
Intended for the root store; like `persist`/`history` it's per-store and not cascaded.
|
|
483
|
+
|
|
484
|
+
## Time-travel (`historyMiddleware`)
|
|
485
|
+
|
|
486
|
+
Records an action log you can undo / redo / jump through. Enable it by adding `historyMiddleware()` to the store; read it reactively with `useStoreHistory`, or imperatively with `getStoreHistory(store)` (which returns the handle the middleware registered, or `undefined` when it isn't enabled). Without the middleware, `useStoreHistory` returns an empty, no-op view.
|
|
487
|
+
|
|
488
|
+
```tsx
|
|
489
|
+
import { useStoreHistory } from '@plitzi/nexus';
|
|
490
|
+
|
|
491
|
+
function HistoryPanel() {
|
|
492
|
+
const { entries, index, canUndo, canRedo, undo, redo, travelTo } = useStoreHistory<State>();
|
|
493
|
+
|
|
494
|
+
return (
|
|
495
|
+
<>
|
|
496
|
+
<button disabled={!canUndo} onClick={undo}>Undo</button>
|
|
497
|
+
<button disabled={!canRedo} onClick={redo}>Redo</button>
|
|
498
|
+
{entries.map((entry, i) => (
|
|
499
|
+
<button key={i} onClick={() => travelTo(i)} data-active={i === index}>
|
|
500
|
+
{entry.path} = {String(entry.value)}
|
|
501
|
+
</button>
|
|
502
|
+
))}
|
|
503
|
+
</>
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
`<StoreProvider history>` (or `history="schema"` to scope it to a subtree) starts recording from mount.
|
|
509
|
+
|
|
510
|
+
## CSP & `new Function`
|
|
511
|
+
|
|
512
|
+
By default `@plitzi/nexus` uses a compiled codegen path (`new Function`) for structural-sharing writes, which is orders of magnitude faster than the recursive fallback for deep paths. When a strict [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) blocks `new Function`, the store auto-detects the error at first use and falls back to a recursive writer with identical behaviour — you **don't need to do anything** for CSP environments to work.
|
|
513
|
+
|
|
514
|
+
If you want to avoid even the one-time probe, force the recursive fallback directly:
|
|
515
|
+
|
|
516
|
+
```ts
|
|
517
|
+
import { setCodegenEnabled } from '@plitzi/nexus';
|
|
518
|
+
|
|
519
|
+
// Skip codegen probe, go straight to recursive writer (no new Function)
|
|
520
|
+
setCodegenEnabled(false);
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
| Value | Behaviour |
|
|
524
|
+
|---|---|
|
|
525
|
+
| `undefined` (default) | Auto-detect: probe `new Function` once; cache result. |
|
|
526
|
+
| `false` | Bypass probe entirely — recursive writer only. Safe for strict CSP. |
|
|
527
|
+
| `true` | Force codegen even if probe fails (testing only). |
|
|
528
|
+
|
|
529
|
+
Call `setCodegenEnabled(undefined)` to restore auto-detection.
|
|
530
|
+
|
|
531
|
+
## Performance
|
|
532
|
+
|
|
533
|
+
Notification is `O(depth)` — a few `Map` lookups for the changed path's prefixes — **regardless of how many subscribers exist**, so a million watchers cost the same as one for an unrelated change. The *write* copies the containers on the changed path (immutable structural sharing, the same cost Redux / Zustand / Jotai pay), so model state as a **tree / normalized map** to keep each write touching a small path. Single-segment writes mutate the live working copy in place (O(1)); snapshots from `getState()` are always distinct clones, so they stay immutable for React.
|
|
534
|
+
|
|
535
|
+
## Types
|
|
536
|
+
|
|
537
|
+
All types live in `src/types/StoreTypes.ts`:
|
|
538
|
+
|
|
539
|
+
| Type | Description |
|
|
540
|
+
|---|---|
|
|
541
|
+
| `PathOf<T>` | Union of all valid dot-paths in `T` |
|
|
542
|
+
| `PathValue<T, P>` | Value type at path `P` in `T` |
|
|
543
|
+
| `PathOrFn<T>` | `PathOf<T>` or a function `(state: T) => PathOf<T>` |
|
|
544
|
+
| `PathValues<T, Paths>` | Tuple of values for an array of paths |
|
|
545
|
+
| `PathSetter<T, P>` | Setter function for a single path |
|
|
546
|
+
| `PathSetters<T, Paths>` | Tuple of setters for an array of paths |
|
|
547
|
+
| `MultiPathReturn<T, Paths>` | `[values, ...setters]` tuple |
|
|
548
|
+
| `StoreApi<T>` | `{ getState, getPath, setState, subscribe, subscribePath, subscribeChange, destroy? }` |
|
|
549
|
+
| `StoreChange<T>` | `{ path, prev, next }` — payload of `subscribeChange` / middleware `onChange` |
|
|
550
|
+
| `StoreMiddleware<T>` | `(api) => { beforeChange?, onChange? } \| void` — a middleware factory |
|
|
551
|
+
| `WriteContext<T>` | `{ path, value, prev }` — payload of a `beforeChange` interceptor |
|
|
552
|
+
| `WriteInterceptor<T>` | `(ctx: WriteContext<T>) => unknown` — return a value, `CANCEL`, or `undefined` |
|
|
553
|
+
| `CANCEL` | Sentinel an interceptor returns to abort a write |
|
|
554
|
+
| `Derived<R>` | `{ get, subscribe, destroy }` — a `createDerived` handle |
|
|
555
|
+
| `EntityAdapter<T>` | CRUD updaters + selectors from `createEntityAdapter` |
|
|
556
|
+
| `EntityUpdater<T>` | `(map) => map` — an immutable entity-map update for `setState` |
|
|
557
|
+
| `SetState<T>` | Full `setState` overload signature |
|
|
558
|
+
| `UseStoreOptions` | Options for `useStore` (mode, enabled, equalityFn, defaultValue, transformer) |
|
|
559
|
+
| `UseStoreMultiOptions` | Options for multi-path `useStore` |
|
|
560
|
+
| `UseStoreSyncOptions` | Options for `useStoreSync` (mode, enabled, syncStrategy) |
|
|
561
|
+
| `UseStoreSyncMultiOptions` | Options for multi-path `useStoreSync` |
|
|
562
|
+
| `UseStoreGetterOptions` | Options for `useStoreGetter` |
|
|
563
|
+
| `UseStoreSetterOptions` | Options for `useStoreSetter` |
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { StoreApi } from './types';
|
|
2
|
+
declare const StoreContext: import('react').Context<StoreApi<any> | undefined>;
|
|
3
|
+
export type StoreRegistry = {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly store: StoreApi<any>;
|
|
6
|
+
readonly parent: StoreRegistry | undefined;
|
|
7
|
+
};
|
|
8
|
+
declare const StoreRegistryContext: import('react').Context<StoreRegistry | undefined>;
|
|
9
|
+
declare const findStoreInRegistry: (registry: StoreRegistry | undefined, id: string) => StoreApi<any> | undefined;
|
|
10
|
+
export { StoreContext, StoreRegistryContext, findStoreInRegistry };
|