stroid 0.1.0 → 0.1.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 +33 -0
- package/README.md +375 -468
- package/dist/_tsup-dts-rollup.d.cts +2411 -0
- package/dist/_tsup-dts-rollup.d.ts +2411 -0
- package/dist/async.cjs +29 -24
- package/dist/async.cjs.map +1 -0
- package/dist/async.d.cts +9 -40
- package/dist/async.d.ts +9 -40
- package/dist/async.js +29 -24
- package/dist/async.js.map +1 -0
- package/dist/computed.cjs +12 -10
- package/dist/computed.cjs.map +1 -0
- package/dist/computed.d.cts +7 -29
- package/dist/computed.d.ts +7 -29
- package/dist/computed.js +12 -10
- package/dist/computed.js.map +1 -0
- package/dist/core.cjs +17 -23
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +15 -5
- package/dist/core.d.ts +15 -5
- package/dist/core.js +17 -23
- package/dist/core.js.map +1 -0
- package/dist/devtools.cjs +2 -1
- package/dist/devtools.cjs.map +1 -0
- package/dist/devtools.d.cts +5 -19
- package/dist/devtools.d.ts +5 -19
- package/dist/devtools.js +2 -1
- package/dist/devtools.js.map +1 -0
- package/dist/feature.cjs +2 -0
- package/dist/feature.cjs.map +1 -0
- package/dist/feature.d.cts +14 -0
- package/dist/feature.d.ts +14 -0
- package/dist/feature.js +2 -0
- package/dist/feature.js.map +1 -0
- package/dist/helpers.cjs +17 -13
- package/dist/helpers.cjs.map +1 -0
- package/dist/helpers.d.cts +3 -29
- package/dist/helpers.d.ts +3 -29
- package/dist/helpers.js +17 -13
- package/dist/helpers.js.map +1 -0
- package/dist/index.cjs +26 -21
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +33 -130
- package/dist/index.d.ts +33 -130
- package/dist/index.js +26 -21
- package/dist/index.js.map +1 -0
- package/dist/install.cjs +2 -0
- package/dist/install.cjs.map +1 -0
- package/dist/install.d.cts +4 -0
- package/dist/install.d.ts +4 -0
- package/dist/install.js +2 -0
- package/dist/install.js.map +1 -0
- package/dist/persist.cjs +2 -1
- package/dist/persist.cjs.map +1 -0
- package/dist/persist.d.cts +1 -2
- package/dist/persist.d.ts +1 -2
- package/dist/persist.js +2 -1
- package/dist/persist.js.map +1 -0
- package/dist/react.cjs +27 -22
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +7 -52
- package/dist/react.d.ts +7 -52
- package/dist/react.js +27 -22
- package/dist/react.js.map +1 -0
- package/dist/runtime-admin.cjs +2 -1
- package/dist/runtime-admin.cjs.map +1 -0
- package/dist/runtime-admin.d.cts +2 -4
- package/dist/runtime-admin.d.ts +2 -4
- package/dist/runtime-admin.js +2 -1
- package/dist/runtime-admin.js.map +1 -0
- package/dist/runtime-tools.cjs +3 -2
- package/dist/runtime-tools.cjs.map +1 -0
- package/dist/runtime-tools.d.cts +9 -39
- package/dist/runtime-tools.d.ts +9 -39
- package/dist/runtime-tools.js +3 -2
- package/dist/runtime-tools.js.map +1 -0
- package/dist/selectors.cjs +2 -1
- package/dist/selectors.cjs.map +1 -0
- package/dist/selectors.d.cts +2 -4
- package/dist/selectors.d.ts +2 -4
- package/dist/selectors.js +2 -1
- package/dist/selectors.js.map +1 -0
- package/dist/server.cjs +11 -9
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +2 -14
- package/dist/server.d.ts +2 -14
- package/dist/server.js +11 -9
- package/dist/server.js.map +1 -0
- package/dist/sync.cjs +2 -1
- package/dist/sync.cjs.map +1 -0
- package/dist/sync.d.cts +1 -2
- package/dist/sync.d.ts +1 -2
- package/dist/sync.js +2 -1
- package/dist/sync.js.map +1 -0
- package/dist/testing.cjs +17 -13
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +4 -16
- package/dist/testing.d.ts +4 -16
- package/dist/testing.js +17 -13
- package/dist/testing.js.map +1 -0
- package/package.json +12 -3
- package/dist/async-cache-DFHwcBQL.d.cts +0 -52
- package/dist/async-cache-DFHwcBQL.d.ts +0 -52
- package/dist/computed-BbAZm1Dq.d.cts +0 -17
- package/dist/computed-CccdgY5j.d.ts +0 -17
- package/dist/options-CB35e3Xo.d.cts +0 -245
- package/dist/options-CB35e3Xo.d.ts +0 -245
- package/dist/types/adapters/options.d.ts +0 -247
- package/dist/types/async/clone.d.ts +0 -2
- package/dist/types/async/errors.d.ts +0 -3
- package/dist/types/async/inflight.d.ts +0 -13
- package/dist/types/async/rate.d.ts +0 -5
- package/dist/types/async/request.d.ts +0 -3
- package/dist/types/async-cache.d.ts +0 -57
- package/dist/types/async-fetch.d.ts +0 -37
- package/dist/types/async-registry.d.ts +0 -96
- package/dist/types/async-retry.d.ts +0 -10
- package/dist/types/async.d.ts +0 -2
- package/dist/types/computed-graph.d.ts +0 -31
- package/dist/types/computed.d.ts +0 -15
- package/dist/types/config.d.ts +0 -2
- package/dist/types/core.d.ts +0 -1
- package/dist/types/devfreeze.d.ts +0 -1
- package/dist/types/feature-registry.d.ts +0 -69
- package/dist/types/features/lifecycle.d.ts +0 -28
- package/dist/types/index.d.ts +0 -6
- package/dist/types/integrations/query.d.ts +0 -8
- package/dist/types/internals/config.d.ts +0 -62
- package/dist/types/internals/diagnostics.d.ts +0 -21
- package/dist/types/internals/hooks-warnings.d.ts +0 -6
- package/dist/types/internals/reporting.d.ts +0 -8
- package/dist/types/internals/store-admin.d.ts +0 -7
- package/dist/types/internals/store-ops.d.ts +0 -3
- package/dist/types/runtime-admin.d.ts +0 -2
- package/dist/types/store-lifecycle/bind.d.ts +0 -3
- package/dist/types/store-lifecycle/hooks.d.ts +0 -44
- package/dist/types/store-lifecycle/identity.d.ts +0 -23
- package/dist/types/store-lifecycle/registry.d.ts +0 -39
- package/dist/types/store-lifecycle/types.d.ts +0 -39
- package/dist/types/store-lifecycle/validation.d.ts +0 -45
- package/dist/types/store-lifecycle.d.ts +0 -20
- package/dist/types/store-name.d.ts +0 -19
- package/dist/types/store-notify.d.ts +0 -12
- package/dist/types/store-read.d.ts +0 -12
- package/dist/types/store-registry.d.ts +0 -74
- package/dist/types/store-transaction.d.ts +0 -12
- package/dist/types/store-write.d.ts +0 -45
- package/dist/types/store.d.ts +0 -7
- package/dist/types/utils/clone.d.ts +0 -4
- package/dist/types/utils/hash.d.ts +0 -8
- package/dist/types/utils/path.d.ts +0 -5
- package/dist/types/utils/validation.d.ts +0 -14
- package/dist/types/utils.d.ts +0 -5
- package/dist/types-grvlY4BX.d.cts +0 -37
- package/dist/types-grvlY4BX.d.ts +0 -37
package/README.md
CHANGED
|
@@ -1,608 +1,515 @@
|
|
|
1
1
|
# Stroid
|
|
2
2
|
|
|
3
|
-
[](https://
|
|
4
|
-
[](https://www.npmjs.com/package/stroid)
|
|
3
|
+
[](https://npmjs.com/package/stroid)
|
|
5
4
|
[](https://bundlephobia.com/package/stroid)
|
|
6
|
-
[](https://
|
|
7
|
-
[](
|
|
8
|
-
[](https://github.com/Himesh-Bhattarai/stroid/issues)
|
|
10
|
-
[](https://github.com/Himesh-Bhattarai/stroid)
|
|
11
|
-
[](https://github.com/Himesh-Bhattarai/stroid)
|
|
12
|
-
[](https://github.com/Himesh-Bhattarai/stroid/graphs/contributors)
|
|
13
|
-
[](https://github.com/Himesh-Bhattarai/stroid/commits/main)
|
|
14
|
-
[](https://github.com/Himesh-Bhattarai/stroid/commits/main)
|
|
15
|
-
[](https://github.com/Himesh-Bhattarai/stroid)
|
|
16
|
-
[](https://www.npmjs.com/package/stroid)
|
|
17
|
-
|
|
18
|
-
Stroid is a named-store state library for JavaScript and React. Core stays small; optional layers unlock persist, async caching, sync, and devtools.
|
|
19
|
-
|
|
20
|
-
## Table of Contents
|
|
21
|
-
|
|
22
|
-
- [Install](#install)
|
|
23
|
-
- [Minimal Usage](#minimal-usage)
|
|
24
|
-
- [Module Imports](#module-imports)
|
|
25
|
-
- [Stroid At a Glance](#stroid-at-a-glance)
|
|
26
|
-
- [Public API Index](#public-api-index)
|
|
27
|
-
- [Subpath API Index](#subpath-api-index)
|
|
28
|
-
- [Options Index](#options-index)
|
|
29
|
-
- [Types Index](#types-index)
|
|
30
|
-
- [Behavior Notes](#behavior-notes)
|
|
31
|
-
- [Short Recipes](#short-recipes)
|
|
32
|
-
- [Docs](#docs)
|
|
33
|
-
- [Docs Index (Full)](#docs-index-full)
|
|
34
|
-
- [Changelog and License](#changelog-and-license)
|
|
5
|
+
[](https://npmjs.com/package/stroid)
|
|
6
|
+
[](./LICENSE)
|
|
7
|
+
[](https://github.com/Himesh-Bhattarai/stroid/actions)
|
|
35
8
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
npm install stroid
|
|
40
|
-
```
|
|
9
|
+
**Named-store state engine for TypeScript and React.**
|
|
10
|
+
Every store has a name. Write to it from anywhere — hooks, utilities, server, tests. Optional layers add persistence, sync, async fetch, SSR isolation, and devtools without touching your core logic.
|
|
41
11
|
|
|
42
|
-
|
|
12
|
+
> 🚀 **Power in 4 lines:** Create a store, read/write it, optionally persist, sync, or hydrate for SSR.
|
|
43
13
|
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
setStore("counter", "count", 1);
|
|
49
|
-
|
|
50
|
-
console.log(getStore("counter"));
|
|
14
|
+
```tsx
|
|
15
|
+
createStore("user", { name: "Ava", role: "admin" }) // define once
|
|
16
|
+
setStore("user", "name", "Kai") // write from anywhere
|
|
17
|
+
const name = useStore("user", s => s.name) // React hook
|
|
51
18
|
```
|
|
52
19
|
|
|
53
|
-
|
|
20
|
+
---
|
|
54
21
|
|
|
55
|
-
|
|
22
|
+
## Layers
|
|
56
23
|
|
|
57
|
-
```
|
|
58
|
-
|
|
24
|
+
```
|
|
25
|
+
┌─────────────────────────────────────────────────────────┐
|
|
26
|
+
│ your app │
|
|
27
|
+
├─────────────────────────────────────────────────────────┤
|
|
28
|
+
│ useStore useSelector useAsyncStore useFormStore │ stroid/react
|
|
29
|
+
├─────────────────────────────────────────────────────────┤
|
|
30
|
+
│ createStore setStore getStore setStoreBatch │ stroid ← core
|
|
31
|
+
│ createComputed createSelector createEntityStore │
|
|
32
|
+
├──────────────┬──────────────┬───────────────────────────┤
|
|
33
|
+
│ stroid/persist│ stroid/sync │ stroid/async │ opt-in features
|
|
34
|
+
│ localStorage │ BroadcastCh │ fetch + cache + retry │
|
|
35
|
+
├──────────────┴──────────────┴───────────────────────────┤
|
|
36
|
+
│ stroid/server createStoreForRequest (AsyncLocalStorage)│ SSR
|
|
37
|
+
├─────────────────────────────────────────────────────────┤
|
|
38
|
+
│ stroid/devtools stroid/testing stroid/runtime-tools │ tooling
|
|
39
|
+
└─────────────────────────────────────────────────────────┘
|
|
59
40
|
```
|
|
60
41
|
|
|
61
|
-
|
|
42
|
+
Each row is independent. Use only what you need.
|
|
43
|
+
|
|
44
|
+
Note: `stroid/core` exports only `createStore`, `setStore`, `getStore`, and `deleteStore`. Import from `stroid` for the full core runtime (batching, reset, hydration, and hooks).
|
|
62
45
|
|
|
63
|
-
|
|
64
|
-
import { useStore } from "stroid/react";
|
|
65
|
-
import { fetchStore } from "stroid/async";
|
|
66
|
-
import { createSelector } from "stroid/selectors";
|
|
67
|
-
import { createComputed } from "stroid/computed";
|
|
68
|
-
import { createEntityStore } from "stroid/helpers";
|
|
69
|
-
import { createMockStore } from "stroid/testing";
|
|
70
|
-
import { listStores } from "stroid/runtime-tools";
|
|
71
|
-
import { clearAllStores } from "stroid/runtime-admin";
|
|
72
|
-
import { createStoreForRequest } from "stroid/server";
|
|
73
|
-
```
|
|
46
|
+
---
|
|
74
47
|
|
|
75
|
-
|
|
48
|
+
## Install
|
|
76
49
|
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
import "stroid/sync";
|
|
80
|
-
import "stroid/devtools";
|
|
50
|
+
```bash
|
|
51
|
+
npm install stroid
|
|
81
52
|
```
|
|
82
53
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
Core API:
|
|
86
|
-
- createStore, createStoreStrict, setStore, setStoreBatch, getStore, deleteStore, resetStore, hasStore, hydrateStores
|
|
87
|
-
- store(name) and namespace(ns) helpers for typed handles
|
|
88
|
-
- createComputed, invalidateComputed, deleteComputed, isComputedStore
|
|
89
|
-
- configureStroid and queryIntegrations helpers
|
|
90
|
-
|
|
91
|
-
Runtime layers:
|
|
92
|
-
- stroid/react: useStore, useStoreField, useSelector, useStoreStatic, useAsyncStore, useFormStore, useAsyncStoreSuspense
|
|
93
|
-
- stroid/async: fetchStore, refetchStore, enableRevalidateOnFocus, getAsyncMetrics
|
|
94
|
-
- stroid/selectors: createSelector, subscribeWithSelector
|
|
95
|
-
|
|
96
|
-
Store-attached features (side-effect imports):
|
|
97
|
-
- stroid/persist
|
|
98
|
-
- stroid/sync
|
|
99
|
-
- stroid/devtools
|
|
100
|
-
|
|
101
|
-
Operational tools:
|
|
102
|
-
- stroid/runtime-tools
|
|
103
|
-
- stroid/runtime-admin
|
|
104
|
-
- stroid/server
|
|
105
|
-
- stroid/helpers
|
|
106
|
-
- stroid/testing
|
|
107
|
-
- stroid/computed
|
|
108
|
-
|
|
109
|
-
## Public API Index
|
|
110
|
-
|
|
111
|
-
Root export names (stroid and stroid/core):
|
|
112
|
-
- `createStore(name, initialState, options?)`
|
|
113
|
-
- `createStoreStrict(name, initialState, options?)`
|
|
114
|
-
- `setStore(nameOrHandle, updateOrPath, value?)`
|
|
115
|
-
- `setStoreBatch(fn)`
|
|
116
|
-
- `getStore(nameOrHandle, path?)`
|
|
117
|
-
- `deleteStore(nameOrHandle)`
|
|
118
|
-
- `resetStore(nameOrHandle)`
|
|
119
|
-
- `hasStore(nameOrHandle)`
|
|
120
|
-
- `hydrateStores(snapshot, options?, trustOptions?)`
|
|
121
|
-
- `store(name)`
|
|
122
|
-
- `namespace(ns)`
|
|
123
|
-
- `createComputed(name, deps, compute, options?)`
|
|
124
|
-
- `invalidateComputed(name)`
|
|
125
|
-
- `deleteComputed(name)`
|
|
126
|
-
- `isComputedStore(name)`
|
|
127
|
-
- `configureStroid(config)`
|
|
128
|
-
- `queryIntegrations (reactQueryKey, createReactQueryFetcher, swrKey, createSwrFetcher)`
|
|
129
|
-
|
|
130
|
-
Root exported types:
|
|
131
|
-
- `Path`
|
|
132
|
-
- `PathValue`
|
|
133
|
-
- `PartialDeep`
|
|
134
|
-
- `StoreDefinition`
|
|
135
|
-
- `StoreValue`
|
|
136
|
-
- `StoreKey`
|
|
137
|
-
- `StoreName`
|
|
138
|
-
- `StateFor`
|
|
139
|
-
- `StoreStateMap`
|
|
140
|
-
- `StrictStoreMap`
|
|
141
|
-
- `WriteResult`
|
|
142
|
-
- `PersistOptions`
|
|
143
|
-
- `StoreOptions`
|
|
144
|
-
- `SyncOptions`
|
|
145
|
-
|
|
146
|
-
## Subpath API Index
|
|
147
|
-
|
|
148
|
-
### stroid/react
|
|
149
|
-
|
|
150
|
-
- `useStore`
|
|
151
|
-
- `useStoreField`
|
|
152
|
-
- `useSelector`
|
|
153
|
-
- `useStoreStatic`
|
|
154
|
-
- `useAsyncStore`
|
|
155
|
-
- `useFormStore`
|
|
156
|
-
- `useAsyncStoreSuspense`
|
|
157
|
-
|
|
158
|
-
### stroid/async
|
|
159
|
-
|
|
160
|
-
- `fetchStore`
|
|
161
|
-
- `refetchStore`
|
|
162
|
-
- `enableRevalidateOnFocus`
|
|
163
|
-
- `getAsyncMetrics`
|
|
164
|
-
- `_resetAsyncStateForTests`
|
|
165
|
-
- `FetchOptions (type)`
|
|
166
|
-
- `FetchInput (type)`
|
|
167
|
-
- `AsyncStateSnapshot (type)`
|
|
168
|
-
- `AsyncStateAdapter (type)`
|
|
169
|
-
|
|
170
|
-
### stroid/selectors
|
|
171
|
-
|
|
172
|
-
- `createSelector`
|
|
173
|
-
- `subscribeWithSelector`
|
|
174
|
-
|
|
175
|
-
### stroid/computed
|
|
176
|
-
|
|
177
|
-
- `createComputed`
|
|
178
|
-
- `invalidateComputed`
|
|
179
|
-
- `deleteComputed`
|
|
180
|
-
- `isComputedStore`
|
|
181
|
-
- `_resetComputedForTests`
|
|
182
|
-
- `getFullComputedGraph`
|
|
183
|
-
- `getComputedDepsFor`
|
|
184
|
-
|
|
185
|
-
### stroid/helpers
|
|
186
|
-
|
|
187
|
-
- `createCounterStore`
|
|
188
|
-
- `createListStore`
|
|
189
|
-
- `createEntityStore`
|
|
190
|
-
|
|
191
|
-
### stroid/testing
|
|
192
|
-
|
|
193
|
-
- `createMockStore`
|
|
194
|
-
- `withMockedTime`
|
|
195
|
-
- `resetAllStoresForTest`
|
|
196
|
-
- `benchmarkStoreSet`
|
|
197
|
-
|
|
198
|
-
### stroid/runtime-tools
|
|
199
|
-
|
|
200
|
-
- `listStores`
|
|
201
|
-
- `getStoreMeta`
|
|
202
|
-
- `getInitialState`
|
|
203
|
-
- `getMetrics`
|
|
204
|
-
- `getSubscriberCount`
|
|
205
|
-
- `getAsyncInflightCount`
|
|
206
|
-
- `getPersistQueueDepth`
|
|
207
|
-
- `getComputedGraph`
|
|
208
|
-
- `getComputedDeps`
|
|
209
|
-
|
|
210
|
-
### stroid/runtime-admin
|
|
211
|
-
|
|
212
|
-
- `clearAllStores`
|
|
213
|
-
- `clearStores`
|
|
214
|
-
|
|
215
|
-
### stroid/server
|
|
216
|
-
|
|
217
|
-
- `createStoreForRequest`
|
|
218
|
-
|
|
219
|
-
### stroid/persist
|
|
54
|
+
> **Note:** `main` is locked between releases. Active development is on the `dev` branch — PRs and forks should target `dev`. Commit messages follow [STATUS.md](./STATUS.md) conventions.
|
|
220
55
|
|
|
221
|
-
|
|
56
|
+
---
|
|
222
57
|
|
|
223
|
-
|
|
58
|
+
## Quick API Reference
|
|
224
59
|
|
|
225
|
-
|
|
60
|
+
| API | Purpose |
|
|
61
|
+
|-----|---------|
|
|
62
|
+
| `createStore(name, state, options?)` | Define a store |
|
|
63
|
+
| `setStore(name, path, value)` | Write a value by path |
|
|
64
|
+
| `setStore(name, draft => { })` | Mutate with a function |
|
|
65
|
+
| `replaceStore(name, value)` | Replace an entire store |
|
|
66
|
+
| `getStore(name, path?)` | Read a store (or a path inside it) |
|
|
67
|
+
| `setStoreBatch(fn)` | Atomic multi-store write, rollback on error |
|
|
68
|
+
| `useStore(name, selector?)` | React hook — subscribes to a store |
|
|
69
|
+
| `useSelector(name, fn)` | React hook — fine-grained derived value |
|
|
70
|
+
| `fetchStore(name, url, options?)` | Async fetch wired to store state |
|
|
71
|
+
| `createComputed(name, deps, fn)` | Reactive derived store |
|
|
72
|
+
| `createStoreForRequest(fn)` | Per-request SSR registry |
|
|
73
|
+
| `hydrateStores(snapshot)` | Rehydrate on client from server state |
|
|
226
74
|
|
|
227
|
-
|
|
75
|
+
---
|
|
228
76
|
|
|
229
|
-
|
|
77
|
+
## Quick Start
|
|
230
78
|
|
|
231
|
-
|
|
79
|
+
Three levels. Start where you are.
|
|
232
80
|
|
|
233
|
-
|
|
234
|
-
- Default store scope is request; global stores must be opted in.
|
|
235
|
-
- Snapshot mode defaults to deep cloning for subscriptions and selector snapshots.
|
|
236
|
-
- hydrateStores requires trust options; use allowUntrusted or validate for SSR data.
|
|
237
|
-
- fetchStore writes the AsyncStateSnapshot shape by default unless stateAdapter is provided.
|
|
238
|
-
- Auto-create for fetchStore is controlled by FetchOptions.autoCreate or global config.
|
|
239
|
-
- Persist defaults to localStorage when enabled in the browser.
|
|
240
|
-
- Sync uses BroadcastChannel and warns if unavailable.
|
|
241
|
-
- Computed deps can be store names or handles; missing deps yield null until created.
|
|
242
|
-
- Store option validate replaces legacy schema and validator options.
|
|
81
|
+
---
|
|
243
82
|
|
|
244
|
-
|
|
83
|
+
### Level 1 — The Basics
|
|
245
84
|
|
|
246
|
-
|
|
85
|
+
**Create a store. Read it. Write to it.**
|
|
247
86
|
|
|
248
87
|
```ts
|
|
249
|
-
import { createStore,
|
|
88
|
+
import { createStore, getStore, setStore } from "stroid"
|
|
250
89
|
|
|
251
|
-
createStore("
|
|
252
|
-
setStore("profile", "age", 31);
|
|
90
|
+
createStore("counter", { count: 0 })
|
|
253
91
|
|
|
254
|
-
|
|
92
|
+
setStore("counter", "count", 1)
|
|
93
|
+
console.log(getStore("counter")) // { count: 1 }
|
|
255
94
|
```
|
|
256
95
|
|
|
257
|
-
|
|
96
|
+
**Use it in React.**
|
|
258
97
|
|
|
259
|
-
```
|
|
260
|
-
import {
|
|
98
|
+
```tsx
|
|
99
|
+
import { useStore } from "stroid/react"
|
|
261
100
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
101
|
+
function Counter() {
|
|
102
|
+
const count = useStore("counter", s => s.count)
|
|
103
|
+
return (
|
|
104
|
+
<button onClick={() => setStore("counter", "count", count + 1)}>
|
|
105
|
+
{count}
|
|
106
|
+
</button>
|
|
107
|
+
)
|
|
108
|
+
}
|
|
267
109
|
```
|
|
268
110
|
|
|
269
|
-
|
|
111
|
+
**Batch multiple writes — one notification, atomic rollback.**
|
|
270
112
|
|
|
271
113
|
```ts
|
|
272
|
-
import { setStoreBatch, setStore } from "stroid"
|
|
114
|
+
import { setStoreBatch, setStore } from "stroid"
|
|
273
115
|
|
|
274
116
|
setStoreBatch(() => {
|
|
275
|
-
setStore("
|
|
276
|
-
setStore("
|
|
277
|
-
|
|
117
|
+
setStore("cart", { items: [{ id: 1, price: 12 }] })
|
|
118
|
+
setStore("ui", "loading", false)
|
|
119
|
+
setStore("user", "lastSeen", Date.now())
|
|
120
|
+
// if any write throws → all three roll back
|
|
121
|
+
})
|
|
278
122
|
```
|
|
279
123
|
|
|
280
|
-
|
|
124
|
+
**Typed store handle — trade string keys for compile-time safety.**
|
|
281
125
|
|
|
282
126
|
```ts
|
|
283
|
-
import { createStore, setStore } from "stroid"
|
|
284
|
-
|
|
285
|
-
createStore("user", { profile: { name: "Ava" } });
|
|
286
|
-
setStore("user", "profile.name", "Kai");
|
|
287
|
-
```
|
|
127
|
+
import { store, createStore, setStore, getStore } from "stroid"
|
|
288
128
|
|
|
289
|
-
|
|
129
|
+
const counter = store<"counter", { count: number }>("counter")
|
|
290
130
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
createStore("user", { profile: { name: "Ava" } }, { pathCreate: true });
|
|
295
|
-
setStore("user", "profile.age", 32);
|
|
131
|
+
createStore("counter", { count: 0 })
|
|
132
|
+
setStore(counter, draft => { draft.count += 1 })
|
|
133
|
+
console.log(getStore(counter, "count")) // 1
|
|
296
134
|
```
|
|
297
135
|
|
|
298
|
-
|
|
136
|
+
**Type-safe string store names (module augmentation).**
|
|
299
137
|
|
|
300
|
-
|
|
301
|
-
|
|
138
|
+
If you prefer `useStore("user")` and `setStore("user", ...)` with compile-time checking,
|
|
139
|
+
augment `StoreStateMap` or `StrictStoreMap` in a `.d.ts` file:
|
|
302
140
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
141
|
+
```ts
|
|
142
|
+
// src/stroid.d.ts
|
|
143
|
+
declare module "stroid" {
|
|
144
|
+
interface StoreStateMap {
|
|
145
|
+
user: {
|
|
146
|
+
name: string
|
|
147
|
+
role: "admin" | "user"
|
|
148
|
+
}
|
|
149
|
+
}
|
|
306
150
|
}
|
|
151
|
+
|
|
152
|
+
// Optional strict opt-in for locked store names:
|
|
153
|
+
// declare module "stroid" { interface StrictStoreMap { user: ... } }
|
|
154
|
+
// If you import from "stroid/core", add the same module augmentation there.
|
|
307
155
|
```
|
|
308
156
|
|
|
309
|
-
### Selectors
|
|
310
157
|
|
|
311
|
-
|
|
312
|
-
import { createSelector } from "stroid/selectors";
|
|
158
|
+
---
|
|
313
159
|
|
|
314
|
-
|
|
315
|
-
console.log(selectName());
|
|
316
|
-
```
|
|
160
|
+
### Level 2 — Real Features
|
|
317
161
|
|
|
318
|
-
|
|
162
|
+
**Persist to localStorage — survives page reload.**
|
|
319
163
|
|
|
320
|
-
|
|
321
|
-
import { createComputed } from "stroid/computed";
|
|
164
|
+
> ⚡ **Tip:** Add `import "stroid/persist"` once at your app entry (e.g. `main.tsx`) to enable persistence globally. Any store with a `persist` option will activate automatically.
|
|
322
165
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
166
|
+
```ts
|
|
167
|
+
import { createStore } from "stroid"
|
|
168
|
+
import "stroid/persist"
|
|
169
|
+
|
|
170
|
+
createStore("settings", { theme: "dark", lang: "en" }, {
|
|
171
|
+
persist: {
|
|
172
|
+
key: "app-settings",
|
|
173
|
+
allowPlaintext: true,
|
|
174
|
+
version: 2,
|
|
175
|
+
migrate: (old, v) => v === 1 ? { ...old, lang: "en" } : old,
|
|
176
|
+
}
|
|
177
|
+
})
|
|
326
178
|
```
|
|
327
179
|
|
|
328
|
-
|
|
180
|
+
**Sync across browser tabs — zero wiring.**
|
|
329
181
|
|
|
330
|
-
|
|
331
|
-
import { createStore } from "stroid";
|
|
332
|
-
import "stroid/persist";
|
|
182
|
+
> ⚡ **Tip:** Add `import "stroid/sync"` once at app entry. Any store with `sync: true` or `sync: { channel }` will start broadcasting automatically.
|
|
333
183
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
184
|
+
```ts
|
|
185
|
+
import { createStore } from "stroid"
|
|
186
|
+
import "stroid/sync"
|
|
187
|
+
|
|
188
|
+
createStore("presence", { online: true, cursor: null }, {
|
|
189
|
+
sync: { channel: "presence-sync" }
|
|
190
|
+
// Lamport clock conflict resolution built in.
|
|
191
|
+
// Stale messages from closed tabs auto-rejected.
|
|
192
|
+
})
|
|
337
193
|
```
|
|
338
194
|
|
|
339
|
-
|
|
195
|
+
**Persist + sync together.**
|
|
340
196
|
|
|
341
197
|
```ts
|
|
342
|
-
import { createStore } from "stroid"
|
|
343
|
-
import "stroid/
|
|
344
|
-
|
|
345
|
-
|
|
198
|
+
import { createStore } from "stroid"
|
|
199
|
+
import "stroid/persist"
|
|
200
|
+
import "stroid/sync"
|
|
201
|
+
|
|
202
|
+
createStore("settings", { theme: "dark", lang: "en" }, {
|
|
203
|
+
persist: { key: "app-settings", allowPlaintext: true },
|
|
204
|
+
sync: { channel: "settings-sync" },
|
|
205
|
+
})
|
|
206
|
+
// Change in one tab → persisted locally + broadcast to all other tabs.
|
|
346
207
|
```
|
|
347
208
|
|
|
348
|
-
|
|
209
|
+
**Async fetch — SWR-style, wired directly to store state.**
|
|
210
|
+
|
|
211
|
+
> ⚡ **Tip:** `fetchStore` manages `loading`, `error`, `data`, and `status` fields automatically. No separate state machine needed — just read `useStore("user")`.
|
|
349
212
|
|
|
350
213
|
```ts
|
|
351
|
-
import { createStore }
|
|
352
|
-
import { fetchStore }
|
|
214
|
+
import { createStore } from "stroid"
|
|
215
|
+
import { fetchStore } from "stroid/async"
|
|
216
|
+
import { useStore } from "stroid/react"
|
|
353
217
|
|
|
354
|
-
createStore("user", { data: null, loading: false, error: null, status: "idle" })
|
|
355
|
-
fetchStore("user", "/api/user");
|
|
356
|
-
```
|
|
218
|
+
createStore("user", { data: null, loading: false, error: null, status: "idle" })
|
|
357
219
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
```ts
|
|
361
|
-
import { fetchStore } from "stroid/async";
|
|
220
|
+
const controller = new AbortController()
|
|
362
221
|
|
|
363
222
|
fetchStore("user", "/api/user", {
|
|
364
|
-
|
|
365
|
-
|
|
223
|
+
signal: controller.signal,
|
|
224
|
+
ttl: 30_000, // 30s cache
|
|
225
|
+
staleWhileRevalidate: true, // show stale, revalidate in background
|
|
226
|
+
dedupe: true, // concurrent calls share one request
|
|
227
|
+
retry: 3, // auto-retry on failure
|
|
228
|
+
retryDelay: 400,
|
|
229
|
+
transform: res => res.data, // shape the response
|
|
230
|
+
onSuccess: data => console.log("fetched", data),
|
|
231
|
+
onError: err => Sentry.captureException(err),
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
function UserCard() {
|
|
235
|
+
const user = useStore("user")
|
|
236
|
+
if (user?.loading) return <Spinner />
|
|
237
|
+
if (user?.error) return <Error message={user.error} />
|
|
238
|
+
return <div>{user?.data?.name}</div>
|
|
239
|
+
}
|
|
366
240
|
```
|
|
367
241
|
|
|
368
|
-
|
|
242
|
+
**Computed stores — reactive, cached, cycle-safe.**
|
|
369
243
|
|
|
370
244
|
```ts
|
|
371
|
-
import {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
245
|
+
import { createStore } from "stroid"
|
|
246
|
+
import { createComputed } from "stroid/computed"
|
|
247
|
+
|
|
248
|
+
createStore("cart", { items: [] })
|
|
249
|
+
createStore("discount", { pct: 10 })
|
|
250
|
+
|
|
251
|
+
createComputed(
|
|
252
|
+
"cartTotal",
|
|
253
|
+
["cart", "discount"],
|
|
254
|
+
(cart, discount) => {
|
|
255
|
+
const raw = cart.items.reduce((sum, i) => sum + i.price, 0)
|
|
256
|
+
return raw * (1 - discount.pct / 100)
|
|
257
|
+
}
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
// cartTotal updates whenever cart or discount changes.
|
|
261
|
+
// Circular dependency detected at definition time.
|
|
262
|
+
// Flush order is topologically sorted — always correct.
|
|
379
263
|
```
|
|
380
264
|
|
|
381
|
-
|
|
265
|
+
**Entity store — built-in CRUD for collections.**
|
|
382
266
|
|
|
383
267
|
```ts
|
|
384
|
-
import { createEntityStore } from "stroid/helpers"
|
|
268
|
+
import { createEntityStore } from "stroid/helpers"
|
|
385
269
|
|
|
386
|
-
const users = createEntityStore("users")
|
|
387
|
-
users.upsert({ id: "1", name: "Ava" });
|
|
388
|
-
console.log(users.get("1"));
|
|
389
|
-
```
|
|
270
|
+
const users = createEntityStore("users")
|
|
390
271
|
|
|
391
|
-
|
|
272
|
+
users.upsert({ id: "1", name: "Ava", role: "admin" })
|
|
273
|
+
users.upsert({ id: "2", name: "Kai", role: "user" })
|
|
392
274
|
|
|
393
|
-
|
|
394
|
-
|
|
275
|
+
console.log(users.get("1")) // { id: "1", name: "Ava", role: "admin" }
|
|
276
|
+
console.log(users.getAll()) // [{ id: "1" }, { id: "2" }]
|
|
395
277
|
|
|
396
|
-
|
|
397
|
-
const metrics = getMetrics(names[0]);
|
|
398
|
-
console.log(metrics);
|
|
278
|
+
users.remove("2")
|
|
399
279
|
```
|
|
400
280
|
|
|
401
|
-
|
|
281
|
+
---
|
|
402
282
|
|
|
403
|
-
|
|
404
|
-
import { clearAllStores } from "stroid/runtime-admin";
|
|
405
|
-
|
|
406
|
-
clearAllStores();
|
|
407
|
-
```
|
|
283
|
+
### Level 3 — Production Patterns
|
|
408
284
|
|
|
409
|
-
|
|
285
|
+
**SSR with per-request isolation — no cross-request leaks.**
|
|
410
286
|
|
|
411
287
|
```ts
|
|
412
|
-
|
|
288
|
+
// app/api/render/route.ts (Next.js App Router)
|
|
289
|
+
import { createStoreForRequest } from "stroid/server"
|
|
290
|
+
import { renderToString } from "react-dom/server"
|
|
291
|
+
|
|
292
|
+
export async function GET(req: Request) {
|
|
293
|
+
const session = await getSession(req)
|
|
294
|
+
|
|
295
|
+
// Each request gets a fully isolated registry.
|
|
296
|
+
// AsyncLocalStorage ensures concurrent requests
|
|
297
|
+
// never share store values or subscribers.
|
|
298
|
+
const stores = createStoreForRequest((api) => {
|
|
299
|
+
api.create("user", { name: session.user.name, role: session.user.role })
|
|
300
|
+
api.create("cart", { items: [] })
|
|
301
|
+
api.create("flags", session.featureFlags)
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
const html = stores.hydrate(() => renderToString(<App />))
|
|
305
|
+
const state = stores.snapshot() // plain JSON → send to client
|
|
306
|
+
|
|
307
|
+
return Response.json({ html, state })
|
|
308
|
+
}
|
|
413
309
|
|
|
414
|
-
|
|
415
|
-
|
|
310
|
+
// Client: rehydrate from server snapshot
|
|
311
|
+
hydrateStores(window.__STROID_STATE__)
|
|
416
312
|
|
|
417
|
-
|
|
313
|
+
Tip: For typed SSR APIs, either augment `StoreStateMap` or pass a generic:
|
|
314
|
+
`createStoreForRequest<{ user: UserState }>((api) => { ... })`.
|
|
418
315
|
```
|
|
419
316
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
Quick links:
|
|
423
|
-
- [Book Contents](docs/FRONT_MATTER/CONTENTS.md)
|
|
424
|
-
- [Start Here](docs/BODY_MATTER/BEGINNER_GUIDE/START_HERE.md)
|
|
425
|
-
- [Install and Imports](docs/BODY_MATTER/BEGINNER_GUIDE/INSTALL_AND_IMPORTS.md)
|
|
426
|
-
- [Core of Stroid](docs/BODY_MATTER/CORE_OF_STROID/INTRODUCTION.md)
|
|
427
|
-
- [React Layer](docs/BODY_MATTER/REACT_OF_STROID/INTRODUCTION.md)
|
|
428
|
-
- [Async Layer](docs/BODY_MATTER/ASYNC_OF_STROID/INTRODUCTION.md)
|
|
429
|
-
- [Persistence](docs/BODY_MATTER/PERSIST_OF_STROID/INTRODUCTION.md)
|
|
430
|
-
- [Sync](docs/BODY_MATTER/SYNC_OF_STROID/INTRODUCTION.md)
|
|
431
|
-
- [Runtime Operations](docs/BODY_MATTER/RUNTIME_OPERATIONS_OF_STROID/INTRODUCTION.md)
|
|
432
|
-
- [Server and SSR](docs/BODY_MATTER/SERVER_OF_STROID/INTRODUCTION.md)
|
|
433
|
-
- [Helpers](docs/BODY_MATTER/HELPERS_AND_CHAIN_OF_STROID/INTRODUCTION.md)
|
|
434
|
-
- [Testing](docs/BODY_MATTER/TESTING_OF_STROID/INTRODUCTION.md)
|
|
435
|
-
- [Selectors](docs/BODY_MATTER/SELECTORS_OF_STROID/INTRODUCTION.md)
|
|
436
|
-
- [Devtools](docs/BODY_MATTER/DEVTOOLS_OF_STROID/INTRODUCTION.md)
|
|
437
|
-
|
|
438
|
-
## Docs Index (Full)
|
|
439
|
-
|
|
440
|
-
### Architecture
|
|
441
|
-
|
|
442
|
-
- [Architecture](docs/ARCHITECTURE/ARCHITECTURE.md)
|
|
317
|
+
**Middleware — intercept, transform, or veto any write.**
|
|
443
318
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
319
|
+
```ts
|
|
320
|
+
createStore("cart", { items: [], total: 0 }, {
|
|
321
|
+
middleware: (ctx) => {
|
|
322
|
+
// ctx.action = "set" | "reset" | "hydrate"
|
|
323
|
+
// ctx.prev = previous state
|
|
324
|
+
// ctx.next = incoming state
|
|
325
|
+
// return MIDDLEWARE_ABORT to cancel the write
|
|
326
|
+
if (ctx.action === "set" && ctx.next.items.length > 100) {
|
|
327
|
+
ctx.options.onError?.("Cart limit exceeded")
|
|
328
|
+
return MIDDLEWARE_ABORT
|
|
329
|
+
}
|
|
330
|
+
// log every write to your analytics
|
|
331
|
+
analytics.track("cart.updated", { prev: ctx.prev, next: ctx.next })
|
|
332
|
+
return ctx.next
|
|
333
|
+
}
|
|
334
|
+
})
|
|
335
|
+
```
|
|
451
336
|
|
|
452
|
-
|
|
337
|
+
**Persist with encryption — no plaintext secrets in localStorage.**
|
|
453
338
|
|
|
454
|
-
|
|
339
|
+
```ts
|
|
340
|
+
import { createStore } from "stroid"
|
|
341
|
+
import "stroid/persist"
|
|
342
|
+
|
|
343
|
+
createStore("vault", { apiKey: "", token: "" }, {
|
|
344
|
+
persist: {
|
|
345
|
+
key: "secure-vault",
|
|
346
|
+
encrypt: (data) => myAES.encrypt(JSON.stringify(data)),
|
|
347
|
+
decrypt: (raw) => JSON.parse(myAES.decrypt(raw)),
|
|
348
|
+
// sensitiveData: true blocks persist entirely if no encrypt is provided
|
|
349
|
+
sensitiveData: true,
|
|
350
|
+
onStorageCleared: ({ name, reason }) => {
|
|
351
|
+
// fires when localStorage is cleared externally (another tab, devtools, etc.)
|
|
352
|
+
console.warn(`${name} storage cleared: ${reason}`)
|
|
353
|
+
redirectToLogin()
|
|
354
|
+
},
|
|
355
|
+
}
|
|
356
|
+
})
|
|
357
|
+
```
|
|
455
358
|
|
|
456
|
-
|
|
359
|
+
**Observability — inspect any store at runtime.**
|
|
457
360
|
|
|
458
|
-
-
|
|
459
|
-
- [Fetch Flow](docs/BODY_MATTER/ASYNC_OF_STROID/FETCH_FLOW.md)
|
|
460
|
-
- [Introduction](docs/BODY_MATTER/ASYNC_OF_STROID/INTRODUCTION.md)
|
|
461
|
-
- [Real Use](docs/BODY_MATTER/ASYNC_OF_STROID/REAL_USE.md)
|
|
361
|
+
> ⚡ **Tip:** Add `import "stroid/devtools"` at app entry to enable time-travel history and store inspection. Use `getMetrics(name)` in production to track notification performance per store.
|
|
462
362
|
|
|
463
|
-
|
|
363
|
+
```ts
|
|
364
|
+
import { getMetrics, getSubscriberCount, getComputedGraph } from "stroid/runtime-tools"
|
|
464
365
|
|
|
465
|
-
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
- [React Usage](docs/BODY_MATTER/BEGINNER_GUIDE/REACT_USAGE.md)
|
|
469
|
-
- [Start Here](docs/BODY_MATTER/BEGINNER_GUIDE/START_HERE.md)
|
|
366
|
+
// Per-store performance metrics
|
|
367
|
+
const m = getMetrics("cart")
|
|
368
|
+
// { notifyCount: 42, totalNotifyMs: 8.3, lastNotifyMs: 0.2 }
|
|
470
369
|
|
|
471
|
-
|
|
370
|
+
// How many components are subscribed right now
|
|
371
|
+
console.log(getSubscriberCount("cart")) // 3
|
|
472
372
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
- [React Bindings](docs/BODY_MATTER/BINARY_TO_BEING/REACT_BINDINGS.md)
|
|
478
|
-
- [Runtime Architecture](docs/BODY_MATTER/BINARY_TO_BEING/RUNTIME_ARCHITECTURE.md)
|
|
479
|
-
- [Selectors](docs/BODY_MATTER/BINARY_TO_BEING/SELECTORS.md)
|
|
480
|
-
- [Store System](docs/BODY_MATTER/BINARY_TO_BEING/STORE_SYSTEM.md)
|
|
481
|
-
- [Tooling And Debugging](docs/BODY_MATTER/BINARY_TO_BEING/TOOLING_AND_DEBUGGING.md)
|
|
482
|
-
- [Why State Management Fails In Large Apps](docs/BODY_MATTER/BINARY_TO_BEING/WHY_STATE_MANAGEMENT_FAILS_IN_LARGE_APPS.md)
|
|
373
|
+
// Full computed dependency graph
|
|
374
|
+
console.log(getComputedGraph())
|
|
375
|
+
// { nodes: ["cartTotal"], edges: [{ from: "cart", to: "cartTotal" }] }
|
|
376
|
+
```
|
|
483
377
|
|
|
484
|
-
|
|
378
|
+
**Global flush configuration — tune for your app's load profile.**
|
|
485
379
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
380
|
+
```ts
|
|
381
|
+
import { configureStroid } from "stroid"
|
|
382
|
+
|
|
383
|
+
configureStroid({
|
|
384
|
+
// Route internal logs to your observability platform
|
|
385
|
+
logSink: {
|
|
386
|
+
warn: msg => Sentry.captureMessage(msg, "warning"),
|
|
387
|
+
critical: msg => Sentry.captureException(new Error(msg)),
|
|
388
|
+
},
|
|
389
|
+
|
|
390
|
+
// Priority stores notify subscribers first
|
|
391
|
+
flush: {
|
|
392
|
+
priorityStores: ["auth", "user"],
|
|
393
|
+
},
|
|
394
|
+
|
|
395
|
+
// Revalidate async stores when tab regains focus
|
|
396
|
+
revalidateOnFocus: {
|
|
397
|
+
debounceMs: 500,
|
|
398
|
+
maxConcurrent: 3,
|
|
399
|
+
staggerMs: 100,
|
|
400
|
+
},
|
|
401
|
+
})
|
|
402
|
+
```
|
|
490
403
|
|
|
491
|
-
|
|
404
|
+
**Large store performance (recommendations).**
|
|
492
405
|
|
|
493
|
-
-
|
|
494
|
-
-
|
|
495
|
-
-
|
|
496
|
-
- [Real Use](docs/BODY_MATTER/CORE_OF_STROID/REAL_USE.md)
|
|
406
|
+
- Split stores by domain to keep hot updates small.
|
|
407
|
+
- For large lists, prefer `snapshot: "shallow"` per store or `configureStroid({ snapshotStrategy: "shallow" })` globally.
|
|
408
|
+
- Prefer path updates and targeted selectors (`useSelector`, `useStoreField`) over whole-store subscriptions.
|
|
497
409
|
|
|
498
|
-
|
|
410
|
+
**Optional structural sharing for mutator updates.**
|
|
499
411
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
- [Redux Devtools And Boundaries](docs/BODY_MATTER/DEVTOOLS_OF_STROID/REDUX_DEVTOOLS_AND_BOUNDARIES.md)
|
|
412
|
+
```ts
|
|
413
|
+
import { configureStroid } from "stroid"
|
|
414
|
+
import { produce } from "immer"
|
|
504
415
|
|
|
505
|
-
|
|
416
|
+
configureStroid({ mutatorProduce: produce })
|
|
417
|
+
```
|
|
506
418
|
|
|
507
|
-
|
|
508
|
-
- [Helper Factories](docs/BODY_MATTER/HELPERS_AND_CHAIN_OF_STROID/HELPER_FACTORIES.md)
|
|
509
|
-
- [Introduction](docs/BODY_MATTER/HELPERS_AND_CHAIN_OF_STROID/INTRODUCTION.md)
|
|
510
|
-
- [Real Use](docs/BODY_MATTER/HELPERS_AND_CHAIN_OF_STROID/REAL_USE.md)
|
|
419
|
+
If you prefer a shorthand, set `globalThis.__STROID_IMMER_PRODUCE__ = produce` once and use `configureStroid({ mutatorProduce: "immer" })`.
|
|
511
420
|
|
|
512
|
-
|
|
421
|
+
**Testing — deterministic, isolated, zero globals.**
|
|
513
422
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
- [Runtime Layers](docs/BODY_MATTER/OPT_IN_FEATURES_OF_STROID/RUNTIME_LAYERS.md)
|
|
517
|
-
- [Store Features](docs/BODY_MATTER/OPT_IN_FEATURES_OF_STROID/STORE_FEATURES.md)
|
|
423
|
+
```ts
|
|
424
|
+
import { createMockStore, resetAllStoresForTest } from "stroid/testing"
|
|
518
425
|
|
|
519
|
-
|
|
426
|
+
beforeEach(() => resetAllStoresForTest())
|
|
520
427
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
- [Real Use](docs/BODY_MATTER/PERSIST_OF_STROID/REAL_USE.md)
|
|
524
|
-
- [Storage And Migrations](docs/BODY_MATTER/PERSIST_OF_STROID/STORAGE_AND_MIGRATIONS.md)
|
|
428
|
+
test("cart total updates when item added", () => {
|
|
429
|
+
const cart = createMockStore("cart", { items: [] })
|
|
525
430
|
|
|
526
|
-
|
|
431
|
+
setStore("cart", "items", [{ id: 1, price: 50 }])
|
|
527
432
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
- [Why The Mind Needs Structure](docs/BODY_MATTER/PHILOSOPHY_OF_STROID/WHY_THE_MIND_NEEDS_STRUCTURE.md)
|
|
433
|
+
expect(getStore("cart", "items")).toHaveLength(1)
|
|
434
|
+
expect(getStore("cartTotal")).toBe(45) // with 10% discount
|
|
435
|
+
})
|
|
436
|
+
```
|
|
533
437
|
|
|
534
|
-
|
|
438
|
+
---
|
|
535
439
|
|
|
536
|
-
|
|
537
|
-
- [Hooks](docs/BODY_MATTER/REACT_OF_STROID/HOOKS.md)
|
|
538
|
-
- [Introduction](docs/BODY_MATTER/REACT_OF_STROID/INTRODUCTION.md)
|
|
539
|
-
- [Real Use](docs/BODY_MATTER/REACT_OF_STROID/REAL_USE.md)
|
|
440
|
+
## Module Imports
|
|
540
441
|
|
|
541
|
-
|
|
442
|
+
```ts
|
|
443
|
+
// Core
|
|
444
|
+
import { createStore, setStore, getStore, deleteStore,
|
|
445
|
+
resetStore, hasStore, setStoreBatch, hydrateStores } from "stroid"
|
|
542
446
|
|
|
543
|
-
|
|
447
|
+
// React
|
|
448
|
+
import { useStore, useSelector, useStoreField,
|
|
449
|
+
useAsyncStore, useFormStore, useAsyncStoreSuspense } from "stroid/react"
|
|
544
450
|
|
|
545
|
-
|
|
451
|
+
// Async
|
|
452
|
+
import { fetchStore, refetchStore, enableRevalidateOnFocus } from "stroid/async"
|
|
546
453
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
- [Real Use](docs/BODY_MATTER/RUNTIME_OPERATIONS_OF_STROID/REAL_USE.md)
|
|
454
|
+
// Selectors & Computed
|
|
455
|
+
import { createSelector, subscribeWithSelector } from "stroid/selectors"
|
|
456
|
+
import { createComputed, deleteComputed } from "stroid/computed"
|
|
551
457
|
|
|
552
|
-
|
|
458
|
+
// Features (side-effect imports — register once at app entry)
|
|
459
|
+
import "stroid/persist"
|
|
460
|
+
import "stroid/sync"
|
|
461
|
+
import "stroid/devtools"
|
|
553
462
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
- [Real Use](docs/BODY_MATTER/SELECTORS_OF_STROID/REAL_USE.md)
|
|
557
|
-
- [Subscribe With Selector](docs/BODY_MATTER/SELECTORS_OF_STROID/SUBSCRIBE_WITH_SELECTOR.md)
|
|
463
|
+
// Server / SSR
|
|
464
|
+
import { createStoreForRequest } from "stroid/server"
|
|
558
465
|
|
|
559
|
-
|
|
466
|
+
// Helpers & Testing
|
|
467
|
+
import { createEntityStore, createCounterStore } from "stroid/helpers"
|
|
468
|
+
import { createMockStore, resetAllStoresForTest } from "stroid/testing"
|
|
560
469
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
470
|
+
// Runtime
|
|
471
|
+
import { listStores, getMetrics, getComputedGraph } from "stroid/runtime-tools"
|
|
472
|
+
import { clearAllStores } from "stroid/runtime-admin"
|
|
473
|
+
```
|
|
565
474
|
|
|
566
|
-
|
|
475
|
+
---
|
|
567
476
|
|
|
568
|
-
|
|
569
|
-
- [Introduction](docs/BODY_MATTER/SYNC_OF_STROID/INTRODUCTION.md)
|
|
570
|
-
- [Real Use](docs/BODY_MATTER/SYNC_OF_STROID/REAL_USE.md)
|
|
571
|
-
- [Sync Options](docs/BODY_MATTER/SYNC_OF_STROID/SYNC_OPTIONS.md)
|
|
477
|
+
## Behavior Notes
|
|
572
478
|
|
|
573
|
-
|
|
479
|
+
- **Features are explicit.** `persist`, `sync`, and `devtools` require a side-effect import. Nothing loads you didn't ask for.
|
|
480
|
+
- **Snapshot mode defaults to deep clone.** Subscribers and selectors always receive immutable snapshots.
|
|
481
|
+
- **`setStoreBatch` is transactional.** All writes stage first. Commit happens only if the batch completes without error. On failure, all writes roll back.
|
|
482
|
+
- **`setStore(name, data)` merges objects.** It shallow-merges into object stores. Use `replaceStore(name, value)` to replace the whole store.
|
|
483
|
+
- **Typed string store names are opt-in.** If you want `setStore("user", "profile.name", ...)` to be checked, augment `StoreStateMap` or use typed store handles.
|
|
484
|
+
- **SSR stores are request-scoped by default.** Global SSR stores require `{ allowSSRGlobalStore: true }`.
|
|
485
|
+
- **`fetchStore` deduplicates by default.** Concurrent calls with the same store name share one in-flight request.
|
|
486
|
+
- **Computed deps can be store names or handles.** Missing deps yield `null` until the dependency store is created.
|
|
487
|
+
- **Persist defaults to `localStorage`.** Provide a custom `driver` for `sessionStorage`, `IndexedDB`, or any storage adapter.
|
|
488
|
+
- **Sync uses `BroadcastChannel`.** Warns and no-ops gracefully when unavailable (Safari private mode, Node).
|
|
574
489
|
|
|
575
|
-
|
|
576
|
-
- [Mocks And Time](docs/BODY_MATTER/TESTING_OF_STROID/MOCKS_AND_TIME.md)
|
|
577
|
-
- [Real Use](docs/BODY_MATTER/TESTING_OF_STROID/REAL_USE.md)
|
|
578
|
-
- [Resets And Benchmarks](docs/BODY_MATTER/TESTING_OF_STROID/RESETS_AND_BENCHMARKS.md)
|
|
490
|
+
---
|
|
579
491
|
|
|
580
|
-
|
|
492
|
+
## Docs
|
|
581
493
|
|
|
582
|
-
|
|
583
|
-
- [Performance And Reality](docs/BODY_MATTER/THE_GLITCH_IN_MATRIX/PERFORMANCE_AND_REALITY.md)
|
|
584
|
-
- [Real Use](docs/BODY_MATTER/THE_GLITCH_IN_MATRIX/REAL_USE.md)
|
|
585
|
-
- [Tradeoffs And Limits](docs/BODY_MATTER/THE_GLITCH_IN_MATRIX/TRADEOFFS_AND_LIMITS.md)
|
|
494
|
+
Full documentation, architecture guide, and examples:
|
|
586
495
|
|
|
587
|
-
|
|
496
|
+
- [Start Here](./docs/start-here.md)
|
|
497
|
+
- [Core API](./docs/core.md)
|
|
498
|
+
- [React Layer](./docs/react.md)
|
|
499
|
+
- [Async Layer](./docs/async.md)
|
|
500
|
+
- [Persistence](./docs/persist.md)
|
|
501
|
+
- [Cross-tab Sync](./docs/sync.md)
|
|
502
|
+
- [Server & SSR](./docs/server.md)
|
|
503
|
+
- [Computed Stores](./docs/computed.md)
|
|
504
|
+
- [Selectors](./docs/selectors.md)
|
|
505
|
+
- [Testing](./docs/testing.md)
|
|
506
|
+
- [Devtools](./docs/devtools.md)
|
|
507
|
+
- [Runtime Tools](./docs/runtime.md)
|
|
588
508
|
|
|
589
|
-
|
|
590
|
-
- [Acknowledge](docs/FRONT_MATTER/ACKNOWLEDGE.md)
|
|
591
|
-
- [Contents](docs/FRONT_MATTER/CONTENTS.md)
|
|
592
|
-
- [Copyright](docs/FRONT_MATTER/COPYRIGHT.md)
|
|
593
|
-
- [Dedication](docs/FRONT_MATTER/DEDICATION.md)
|
|
594
|
-
- [Epigraph](docs/FRONT_MATTER/EPIGRAPH.md)
|
|
595
|
-
- [Foreword](docs/FRONT_MATTER/FOREWORD.md)
|
|
596
|
-
- [Front Cover Page](docs/FRONT_MATTER/FRONT_COVER_PAGE.md)
|
|
597
|
-
- [How To Use](docs/FRONT_MATTER/HOW_TO_USE.md)
|
|
598
|
-
- [Introduction](docs/FRONT_MATTER/INTRODUCTION.md)
|
|
599
|
-
- [List Of Table](docs/FRONT_MATTER/LIST_OF_TABLE.md)
|
|
600
|
-
- [Praise](docs/FRONT_MATTER/PRAISE.md)
|
|
601
|
-
- [Preface](docs/FRONT_MATTER/PREFACE.md)
|
|
602
|
-
- [Title Page](docs/FRONT_MATTER/TITLE_PAGE.md)
|
|
509
|
+
---
|
|
603
510
|
|
|
604
|
-
## Changelog
|
|
511
|
+
## Changelog & License
|
|
605
512
|
|
|
606
|
-
- [CHANGELOG](CHANGELOG.md)
|
|
607
|
-
- [
|
|
513
|
+
- [CHANGELOG](./CHANGELOG.md)
|
|
514
|
+
- [MIT License](./LICENSE)
|
|
608
515
|
- [Issues](https://github.com/Himesh-Bhattarai/stroid/issues)
|