stroid 0.1.3 โ 0.1.4
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 +312 -265
- package/README.md +1017 -115
- package/dist/async.d.ts +1 -1
- package/dist/async.js +1 -33
- package/dist/chunk-645IESIU.js +2 -0
- package/dist/chunk-6ELWGQ4Q.js +2 -0
- package/dist/chunk-BW32TJGE.js +13 -0
- package/dist/chunk-FOQKGHPS.js +26 -0
- package/dist/chunk-KQCSFGHJ.js +2 -0
- package/dist/chunk-M2NJVI36.js +2 -0
- package/dist/chunk-PVATWAY4.js +2 -0
- package/dist/chunk-X3JR32JD.js +2 -0
- package/dist/chunk-YU5GMPCC.js +2 -0
- package/dist/computed-types.d.ts +42 -0
- package/dist/computed-types.js +2 -0
- package/dist/computed.d.ts +9 -2
- package/dist/computed.js +1 -12
- package/dist/core.js +1 -22
- package/dist/devtools.d.ts +1 -1
- package/dist/devtools.js +1 -1
- package/dist/feature.js +1 -1
- package/dist/helpers.js +1 -22
- package/dist/index-internal.d.ts +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -35
- package/dist/install.js +1 -1
- package/dist/options.d.ts +1 -1
- package/dist/persist.js +1 -1
- package/dist/psr.d.ts +48 -0
- package/dist/psr.js +2 -0
- package/dist/react/index.d.ts +3 -1
- package/dist/react/index.js +5 -36
- package/dist/registry.d.ts +1 -0
- package/dist/runtime-admin.js +1 -1
- package/dist/runtime-patch.d.ts +29 -0
- package/dist/runtime-tools.d.ts +6 -1
- package/dist/runtime-tools.js +1 -3
- package/dist/selectors.js +1 -1
- package/dist/server.d.ts +4 -1
- package/dist/server.js +1 -12
- package/dist/store-registry.d.ts +8 -0
- package/dist/sync.js +1 -1
- package/dist/testing.js +1 -22
- package/dist/types/adapters/options.d.ts +335 -0
- package/dist/types/async/cache.d.ts +40 -0
- package/dist/types/async/clone.d.ts +10 -0
- package/dist/types/async/errors.d.ts +3 -0
- package/dist/types/async/fetch.d.ts +37 -0
- package/dist/types/async/inflight.d.ts +30 -0
- package/dist/types/async/rate.d.ts +5 -0
- package/dist/types/async/registry.d.ts +117 -0
- package/dist/types/async/request.d.ts +11 -0
- package/dist/types/async/retry.d.ts +10 -0
- package/dist/types/async.d.ts +10 -0
- package/dist/types/computed/computed-graph.d.ts +33 -0
- package/dist/types/computed/index.d.ts +21 -0
- package/dist/types/computed/types.d.ts +40 -0
- package/dist/types/config.d.ts +10 -0
- package/dist/types/core/index.d.ts +11 -0
- package/dist/types/core/lifecycle-hooks.d.ts +16 -0
- package/dist/types/core/runtime-patch.d.ts +67 -0
- package/dist/types/core/store-admin-impl.d.ts +9 -0
- package/dist/types/core/store-admin.d.ts +9 -0
- package/dist/types/core/store-core.d.ts +13 -0
- package/dist/types/core/store-create.d.ts +16 -0
- package/dist/types/core/store-hydrate-impl.d.ts +35 -0
- package/dist/types/core/store-hydrate.d.ts +9 -0
- package/dist/types/core/store-lifecycle/hooks.d.ts +20 -0
- package/dist/types/core/store-lifecycle/identity.d.ts +23 -0
- package/dist/types/core/store-lifecycle/registry.d.ts +54 -0
- package/dist/types/core/store-lifecycle/types.d.ts +67 -0
- package/dist/types/core/store-lifecycle/validation.d.ts +53 -0
- package/dist/types/core/store-name.d.ts +28 -0
- package/dist/types/core/store-notify.d.ts +13 -0
- package/dist/types/core/store-read.d.ts +18 -0
- package/dist/types/core/store-registry.d.ts +115 -0
- package/dist/types/core/store-replace-impl.d.ts +11 -0
- package/dist/types/core/store-replace.d.ts +9 -0
- package/dist/types/core/store-set-impl.d.ts +13 -0
- package/dist/types/core/store-set.d.ts +9 -0
- package/dist/types/core/store-shared/core.d.ts +13 -0
- package/dist/types/core/store-shared/notify.d.ts +12 -0
- package/dist/types/core/store-transaction.d.ts +28 -0
- package/dist/types/core/store-write-shared.d.ts +25 -0
- package/dist/types/core/store-write.d.ts +13 -0
- package/dist/types/features/feature-registry.d.ts +91 -0
- package/dist/types/features/lifecycle.d.ts +40 -0
- package/dist/types/index.d.ts +17 -0
- package/dist/types/integrations/query.d.ts +8 -0
- package/dist/types/internals/computed-order.d.ts +3 -0
- package/dist/types/internals/config.d.ts +116 -0
- package/dist/types/internals/diagnostics.d.ts +21 -0
- package/dist/types/internals/reporting.d.ts +9 -0
- package/dist/types/internals/store-admin.d.ts +7 -0
- package/dist/types/internals/store-ops.d.ts +13 -0
- package/dist/types/internals/test-reset.d.ts +2 -0
- package/dist/types/internals/write-context.d.ts +20 -0
- package/dist/types/notification/delivery.d.ts +3 -0
- package/dist/types/notification/index.d.ts +10 -0
- package/dist/types/notification/metrics.d.ts +12 -0
- package/dist/types/notification/priority.d.ts +9 -0
- package/dist/types/notification/scheduler.d.ts +11 -0
- package/dist/types/notification/snapshot.d.ts +8 -0
- package/dist/types/runtime-admin/index.d.ts +2 -0
- package/dist/types/runtime-tools/index.d.ts +63 -0
- package/dist/types/store.d.ts +16 -0
- package/dist/types/types/utility.d.ts +17 -0
- package/dist/types/utils/clone.d.ts +4 -0
- package/dist/types/utils/devfreeze.d.ts +2 -0
- package/dist/types/utils/hash.d.ts +8 -0
- package/dist/types/utils/path.d.ts +5 -0
- package/dist/types/utils/validation.d.ts +14 -0
- package/dist/types/utils.d.ts +13 -0
- package/dist/types.d.ts +2 -2
- package/package.json +31 -20
- package/dist/async.js.map +0 -1
- package/dist/computed.js.map +0 -1
- package/dist/core.js.map +0 -1
- package/dist/devtools.js.map +0 -1
- package/dist/feature.js.map +0 -1
- package/dist/helpers.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/install.js.map +0 -1
- package/dist/persist.js.map +0 -1
- package/dist/react/index.js.map +0 -1
- package/dist/runtime-admin.js.map +0 -1
- package/dist/runtime-tools.js.map +0 -1
- package/dist/selectors.js.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/sync.js.map +0 -1
- package/dist/testing.js.map +0 -1
- package/dist/tsdoc-metadata.json +0 -11
package/README.md
CHANGED
|
@@ -1,188 +1,1090 @@
|
|
|
1
|
-
|
|
1
|
+
๏ปฟ<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
<img src="https://img.shields.io/npm/v/stroid?color=7F77DD&label=stroid&style=flat-square" alt="npm version" />
|
|
4
|
+
<img src="https://img.shields.io/bundlephobia/minzip/stroid?color=1D9E75&label=minzipped&style=flat-square" alt="bundle size" />
|
|
5
|
+
<img src="https://img.shields.io/npm/types/stroid?color=4A90E2&style=flat-square" alt="types" />
|
|
6
|
+
<img src="https://img.shields.io/npm/l/stroid?color=3B8BD4&style=flat-square" alt="license" />
|
|
7
|
+
<img src="https://img.shields.io/github/actions/workflow/status/Himesh-Bhattarai/stroid/ci.yml?color=639922&label=tests&style=flat-square" alt="tests" />
|
|
8
|
+
<img src="https://img.shields.io/npm/dm/stroid?color=2E7D32&label=downloads&style=flat-square" alt="npm downloads" />
|
|
9
|
+
<img src="https://img.shields.io/node/v/stroid?color=455A64&style=flat-square" alt="node version" />
|
|
10
|
+
<img src="https://img.shields.io/codecov/c/github/Himesh-Bhattarai/stroid?style=flat-square&label=coverage" alt="coverage" />
|
|
11
|
+
<img src="https://img.shields.io/github/last-commit/Himesh-Bhattarai/stroid?style=flat-square&label=last%20commit" alt="last commit" />
|
|
12
|
+
<img src="https://img.shields.io/github/stars/Himesh-Bhattarai/stroid?style=flat-square&label=stars" alt="stars" />
|
|
13
|
+
<img src="https://img.shields.io/github/contributors/Himesh-Bhattarai/stroid?style=flat-square" alt="contributors" />
|
|
14
|
+
<img src="https://img.shields.io/github/issues/Himesh-Bhattarai/stroid?style=flat-square" alt="issues" />
|
|
15
|
+
<img src="https://img.shields.io/snyk/vulnerabilities/github/Himesh-Bhattarai/stroid?style=flat-square" alt="vulnerabilities" />
|
|
16
|
+
<!-- <a href="https://your-demo-link.com">
|
|
17
|
+
<img src="https://img.shields.io/badge/demo-live-ff69b4?style=flat-square" alt="live demo" />
|
|
18
|
+
</a> -->
|
|
19
|
+
<br /><br />
|
|
8
20
|
|
|
21
|
+
# ๐ฃ Stroid - State Engine for TypeScript and React
|
|
9
22
|
**Named-store state engine for TypeScript and React.**
|
|
10
23
|
|
|
11
|
-
Every store has a name. Write to it from anywhere
|
|
24
|
+
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 coupling to core logic.
|
|
25
|
+
<br />
|
|
26
|
+
[**Get Started**](#30-second-quickstart) | [**Why Stroid**](#why-stroid) | [**API Reference**](#full-api-reference) | [**PSR**](#psr---write-governance) | [**DevTools**](#devtools) | [**Examples**](#real-world-examples)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
> [!IMPORTANT]
|
|
32
|
+
> ## ๐ง What Is Stroid?
|
|
33
|
+
>
|
|
34
|
+
> A structured state management system focused on predictability, SSR safety, and debugging clarity.
|
|
35
|
+
>
|
|
36
|
+
> - Core store runtime (`createStore`, `setStore`, `getStore`)
|
|
37
|
+
> - React hooks (`useStore`, `useSelector`)
|
|
38
|
+
> - Async fetch/cache/revalidate
|
|
39
|
+
> - Optional features
|
|
40
|
+
> - SSR request isolation
|
|
41
|
+
> - Native PSR contract
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
<a id="30-second-quickstart"></a>
|
|
45
|
+
## โก 30-Second Quickstart
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
>[!NOTE]
|
|
50
|
+
>```bash
|
|
51
|
+
>npm install stroid
|
|
52
|
+
>```
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
>[!NOTE]
|
|
56
|
+
>```ts
|
|
57
|
+
>import { createStore, setStore, getStore, configureStroid } from "stroid";
|
|
58
|
+
>import { installPersist } from "stroid/persist";
|
|
59
|
+
>import { installSync } from "stroid/sync";
|
|
60
|
+
>
|
|
61
|
+
>configureStroid({
|
|
62
|
+
> asyncAutoCreate: false,
|
|
63
|
+
> defaultSnapshotMode: "deep",
|
|
64
|
+
>});
|
|
65
|
+
>
|
|
66
|
+
>installPersist();
|
|
67
|
+
>installSync();
|
|
68
|
+
>//create store
|
|
69
|
+
>createStore("auth", { user: null, token: null });
|
|
70
|
+
>//create store with persist
|
|
71
|
+
>createStore("settings", { theme: "dark" }, { persist: true });
|
|
72
|
+
>//create store with sync and persist.
|
|
73
|
+
>createStore("session", { active: true }, { persist: true, sync: true });
|
|
74
|
+
>
|
|
75
|
+
>setStore("auth", "user", { id: "u1", name: "Asha" });
|
|
76
|
+
>const auth = getStore("auth");
|
|
77
|
+
>
|
|
78
|
+
>```
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
### Stroid PSR
|
|
82
|
+
|
|
83
|
+
Stroid ships a native PSR contract in `stroid/psr`.
|
|
84
|
+
It exposes committed snapshots, patch application APIs, and runtime graph/timing data used for governance flows.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## ๐บ๏ธ Ecosystem Map
|
|
89
|
+
|
|
90
|
+
Stroid is organized into focused sub-packages. Import only what you need.
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
stroid <- core public runtime
|
|
94
|
+
|- stroid/react <- React hooks
|
|
95
|
+
|- stroid/core <- minimal core surface
|
|
96
|
+
|- stroid/psr <- native PSR contract
|
|
97
|
+
|- stroid/async <- fetch/cache/revalidate
|
|
98
|
+
|- stroid/selectors <- selector helpers
|
|
99
|
+
|- stroid/computed <- computed stores
|
|
100
|
+
|- stroid/persist <- installPersist()
|
|
101
|
+
|- stroid/sync <- installSync()
|
|
102
|
+
|- stroid/devtools <- installDevtools(), history API
|
|
103
|
+
|- stroid/server <- SSR request-scoped registry
|
|
104
|
+
|- stroid/helpers <- entity/list/counter helpers
|
|
105
|
+
|- stroid/testing <- test helpers
|
|
106
|
+
|- stroid/runtime-tools <- observability APIs
|
|
107
|
+
|- stroid/runtime-admin <- clear helpers
|
|
108
|
+
|- stroid/feature <- feature plugin API
|
|
109
|
+
|- stroid/install <- installAllFeatures()
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
<a id="why-stroid"></a>
|
|
115
|
+
## ๐ค Why Stroid?
|
|
116
|
+
|
|
117
|
+
### Honest comparison
|
|
118
|
+
|
|
119
|
+
| Feature | **Stroid** | Redux Toolkit | Zustand | Jotai | Valtio |
|
|
120
|
+
|---|:---:|:---:|:---:|:---:|:---:|
|
|
121
|
+
| Write without reducers | โ
| โ | โ
| โ
| โ
|
|
|
122
|
+
| Named global stores | โ
| โ
| โ ๏ธ manual | โ | โ |
|
|
123
|
+
| Write governance (PSR) | โ
| โ | โ | โ | โ |
|
|
124
|
+
| Built-in DevTools extension | โ
| โ
| โ ๏ธ limited | โ | โ |
|
|
125
|
+
| Computed / derived state | โ
| โ
| โ ๏ธ manual | โ
| โ
|
|
|
126
|
+
| Async data built-in | โ
| โ
RTK Query | โ | โ ๏ธ | โ |
|
|
127
|
+
| SSR / request isolation | โ
| โ ๏ธ | โ ๏ธ | โ
| โ ๏ธ |
|
|
128
|
+
| Atomic rollback guarantee | โ
| โ | โ | โ | โ |
|
|
129
|
+
| Race resistance proof | โ
| โ | โ | โ | โ |
|
|
130
|
+
| Determinism replay | โ
| โ | โ | โ | โ |
|
|
131
|
+
| Ring-buffer event timeline | โ
| โ | โ | โ | โ |
|
|
132
|
+
| Bundle size (core import closure) | 77.6kb raw / 25.1kb gzip | ~11kb | ~1kb | ~3kb | ~3kb |
|
|
133
|
+
| TypeScript-first | โ
| โ
| โ
| โ
| โ
|
|
|
134
|
+
|
|
135
|
+
NOTE: BUNDLE SIZE: 25.1 gzip include whole "Stroid". Stroid is treeshakable so, stroid size will determine by your import.
|
|
136
|
+
|
|
137
|
+
> โ ๏ธ = possible with extra setup ยท โ = not supported natively
|
|
138
|
+
|
|
139
|
+
Stroid exposes governance-oriented write flows through `stroid/psr`, including committed snapshot reads, patch application APIs, runtime graph inspection, and timing contracts.
|
|
140
|
+
Benchmark report: [docs/STROID/BENCHMARK.md](./docs/STROID/BENCHMARK.md).
|
|
141
|
+
|
|
142
|
+
Stroid is a fit when you need these together:
|
|
143
|
+
- Named global stores with direct writes
|
|
144
|
+
- Optional feature installs instead of mandatory side effects
|
|
145
|
+
- Strict hydration trust gate (`hydrateStores(..., ..., { allowTrusted: true })`)
|
|
146
|
+
- Request-scoped SSR runtime (`createStoreForRequest`) with server guards
|
|
147
|
+
- PSR-style patch application and runtime graph inspection (`stroid/psr`)
|
|
148
|
+
|
|
149
|
+
If you only need ultra-minimal local state, `stroid/core` exists for a smaller surface.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
<a id="full-api-reference"></a>
|
|
154
|
+
## ๐ Full API Reference
|
|
155
|
+
|
|
156
|
+
All examples use real APIs from this repository's current source.
|
|
157
|
+
|
|
158
|
+
## โ๏ธ Core - `stroid`
|
|
159
|
+
|
|
160
|
+
### `createStore`
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
import { createStore } from "stroid";
|
|
164
|
+
|
|
165
|
+
createStore("cart", {
|
|
166
|
+
items: [],
|
|
167
|
+
total: 0,
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Creates a named store and registers its initial state.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
### `setStore`
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
import { setStore } from "stroid";
|
|
179
|
+
|
|
180
|
+
setStore("cart", "total", 499);
|
|
181
|
+
setStore("cart", { currency: "NPR" });
|
|
182
|
+
setStore("cart", (draft: any) => {
|
|
183
|
+
draft.items.push({ id: "pizza", qty: 1 });
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
Updates existing store state by path, partial object merge, or mutator function.
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
### `getStore`
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
import { getStore } from "stroid";
|
|
194
|
+
|
|
195
|
+
const cart = getStore("cart");
|
|
196
|
+
const total = getStore("cart", "total");
|
|
197
|
+
```
|
|
198
|
+
Reads current store state, optionally at a nested path.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
### `hasStore`
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
import { hasStore, createStore } from "stroid";
|
|
206
|
+
|
|
207
|
+
if (!hasStore("cart")) {
|
|
208
|
+
createStore("cart", { items: [], total: 0 });
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
Checks whether a store is already registered.
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
### `resetStore`
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
import { resetStore } from "stroid";
|
|
219
|
+
|
|
220
|
+
resetStore("cart");
|
|
221
|
+
```
|
|
222
|
+
Resets a store back to its original initial state.
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
### `deleteStore`
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
import { deleteStore } from "stroid";
|
|
230
|
+
|
|
231
|
+
deleteStore("cart");
|
|
232
|
+
```
|
|
233
|
+
Removes a store and its runtime metadata/subscriptions.
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
### `setStoreBatch`
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
import { setStoreBatch, setStore } from "stroid";
|
|
241
|
+
|
|
242
|
+
setStoreBatch(() => {
|
|
243
|
+
setStore("checkout", "coupon", "SAVE20");
|
|
244
|
+
setStore("checkout", "deliveryType", "priority");
|
|
245
|
+
setStore("checkout", "tip", 50);
|
|
246
|
+
});
|
|
247
|
+
```
|
|
248
|
+
`setStoreBatch` accepts only synchronous callbacks.
|
|
249
|
+
Runs multiple synchronous writes in one transaction-style batch.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
### `hydrateStores`
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
import { hydrateStores } from "stroid";
|
|
257
|
+
|
|
258
|
+
hydrateStores(
|
|
259
|
+
{
|
|
260
|
+
cart: { items: [{ id: "pizza", qty: 1 }], total: 499 },
|
|
261
|
+
profile: { name: "Asha" },
|
|
262
|
+
},
|
|
263
|
+
{},
|
|
264
|
+
{ allowTrusted: true }
|
|
265
|
+
);
|
|
266
|
+
```
|
|
267
|
+
Hydrates many stores from a trusted snapshot payload.
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
### `configureStroid`
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
import { configureStroid } from "stroid";
|
|
275
|
+
|
|
276
|
+
configureStroid({
|
|
277
|
+
asyncAutoCreate: false,
|
|
278
|
+
strictMutatorReturns: true,
|
|
279
|
+
defaultSnapshotMode: "deep",
|
|
280
|
+
});
|
|
281
|
+
```
|
|
282
|
+
Sets global runtime behavior such as async and snapshot defaults.
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## โ๏ธ React Hooks - `stroid/react`
|
|
287
|
+
|
|
288
|
+
### `useStore`
|
|
289
|
+
|
|
290
|
+
```tsx
|
|
291
|
+
import { useStore } from "stroid/react";
|
|
292
|
+
|
|
293
|
+
function CartPanel() {
|
|
294
|
+
const cart = useStore("cart");
|
|
295
|
+
return <div>{cart ? `${cart.items.length} items` : "Cart empty"}</div>;
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
Subscribes React components to a store value (full store, path, or selector form).
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
### `useSelector`
|
|
303
|
+
|
|
304
|
+
```tsx
|
|
305
|
+
import { useSelector } from "stroid/react";
|
|
306
|
+
|
|
307
|
+
function CartTotal() {
|
|
308
|
+
const total = useSelector("cart", (s: any) => s?.total ?? 0);
|
|
309
|
+
return <strong>Rs. {total}</strong>;
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
Subscribes to a derived slice and re-renders only when selected output changes.
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
### `useStoreField`
|
|
317
|
+
|
|
318
|
+
```tsx
|
|
319
|
+
import { useStoreField } from "stroid/react";
|
|
320
|
+
|
|
321
|
+
function DeliveryTypeChip() {
|
|
322
|
+
const deliveryType = useStoreField("checkout", "deliveryType");
|
|
323
|
+
return <span>{deliveryType ?? "standard"}</span>;
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
Subscribes directly to one field/path inside a store.
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
### `useStoreStatic`
|
|
331
|
+
|
|
332
|
+
```tsx
|
|
333
|
+
import { useStoreStatic } from "stroid/react";
|
|
334
|
+
|
|
335
|
+
function DebugPanel() {
|
|
336
|
+
const snapshot = useStoreStatic("cart");
|
|
337
|
+
return <pre>{JSON.stringify(snapshot, null, 2)}</pre>;
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
Reads a snapshot once without live subscription updates.
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
### `useAsyncStore`
|
|
345
|
+
|
|
346
|
+
```tsx
|
|
347
|
+
import { useEffect } from "react";
|
|
348
|
+
import { useAsyncStore } from "stroid/react";
|
|
349
|
+
import { fetchStore } from "stroid/async";
|
|
350
|
+
|
|
351
|
+
function Menu() {
|
|
352
|
+
useEffect(() => {
|
|
353
|
+
void fetchStore("menu", "https://api.example.com/menu");
|
|
354
|
+
}, []);
|
|
355
|
+
|
|
356
|
+
const { loading, error, data } = useAsyncStore("menu");
|
|
357
|
+
|
|
358
|
+
if (loading) return <p>Loading menu...</p>;
|
|
359
|
+
if (error) return <p>Failed to load menu</p>;
|
|
360
|
+
return <MenuList items={data ?? []} />;
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
Reads async store shape (`data/loading/error/status`) from an existing store.
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
### `useAsyncStoreSuspense`
|
|
368
|
+
|
|
369
|
+
```tsx
|
|
370
|
+
import { useAsyncStoreSuspense } from "stroid/react";
|
|
371
|
+
|
|
372
|
+
function MenuSuspense() {
|
|
373
|
+
const menu = useAsyncStoreSuspense<Array<{ id: string; name: string }>>(
|
|
374
|
+
"menu",
|
|
375
|
+
"https://api.example.com/menu"
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
return <MenuList items={menu} />;
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
Integrates async store reads with React Suspense by throwing pending work.
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
### `useFormStore`
|
|
386
|
+
|
|
387
|
+
```tsx
|
|
388
|
+
import { createStore } from "stroid";
|
|
389
|
+
import { useFormStore } from "stroid/react";
|
|
390
|
+
|
|
391
|
+
createStore("loginForm", { email: "", password: "" });
|
|
392
|
+
|
|
393
|
+
function LoginForm() {
|
|
394
|
+
const email = useFormStore("loginForm", "email");
|
|
395
|
+
const password = useFormStore("loginForm", "password");
|
|
396
|
+
|
|
397
|
+
return (
|
|
398
|
+
<form>
|
|
399
|
+
<input value={email.value ?? ""} onChange={email.onChange} />
|
|
400
|
+
<input value={password.value ?? ""} onChange={password.onChange} type="password" />
|
|
401
|
+
<button disabled={!email.value || !password.value}>Sign in</button>
|
|
402
|
+
</form>
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
```
|
|
406
|
+
Binds a store field to form-style `value` and `onChange` helpers.
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
### `RegistryScope`
|
|
411
|
+
|
|
412
|
+
```tsx
|
|
413
|
+
import { RegistryScope } from "stroid/react";
|
|
414
|
+
|
|
415
|
+
function App({ registry }: { registry: any }) {
|
|
416
|
+
return (
|
|
417
|
+
<RegistryScope value={registry}>
|
|
418
|
+
<CartPanel />
|
|
419
|
+
<ProfilePanel />
|
|
420
|
+
</RegistryScope>
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
```
|
|
424
|
+
Scopes a React subtree to a specific store registry context.
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
## ๐ฒSe Selectors & Computed
|
|
429
|
+
|
|
430
|
+
### `createSelector` - `stroid/selectors`
|
|
431
|
+
|
|
432
|
+
```ts
|
|
433
|
+
import { createSelector } from "stroid/selectors";
|
|
434
|
+
|
|
435
|
+
const selectItemCount = createSelector("cart", (s: any) => s?.items?.length ?? 0);
|
|
436
|
+
const count = selectItemCount();
|
|
437
|
+
```
|
|
438
|
+
Builds a memoized selector function for derived store reads.
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
### `subscribeWithSelector` - `stroid/selectors`
|
|
443
|
+
|
|
444
|
+
```ts
|
|
445
|
+
import { subscribeWithSelector } from "stroid/selectors";
|
|
446
|
+
|
|
447
|
+
const stop = subscribeWithSelector(
|
|
448
|
+
"cart",
|
|
449
|
+
(s: any) => s?.total,
|
|
450
|
+
Object.is,
|
|
451
|
+
(next, prev) => {
|
|
452
|
+
if (typeof prev === "number" && next > prev) {
|
|
453
|
+
// run side effect
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
stop();
|
|
459
|
+
```
|
|
460
|
+
Runs a listener only when the selected value changes by equality check.
|
|
461
|
+
|
|
462
|
+
---
|
|
463
|
+
|
|
464
|
+
### `createComputed` - `stroid/computed`
|
|
465
|
+
|
|
466
|
+
```ts
|
|
467
|
+
import { createComputed } from "stroid/computed";
|
|
468
|
+
import { getStore } from "stroid";
|
|
469
|
+
|
|
470
|
+
createComputed("deliveryFee", ["cart"], (cart: any) => (cart?.total ?? 0) > 1000 ? 0 : 60);
|
|
471
|
+
|
|
472
|
+
const fee = getStore("deliveryFee");
|
|
473
|
+
```
|
|
474
|
+
Creates a computed store derived from one or more dependency stores.
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
### `invalidateComputed` / `deleteComputed` / `isComputedStore`
|
|
479
|
+
|
|
480
|
+
```ts
|
|
481
|
+
import { invalidateComputed, deleteComputed, isComputedStore } from "stroid/computed";
|
|
482
|
+
|
|
483
|
+
invalidateComputed("deliveryFee");
|
|
484
|
+
deleteComputed("deliveryFee");
|
|
485
|
+
isComputedStore("deliveryFee");
|
|
486
|
+
```
|
|
487
|
+
Invalidates, removes, or checks computed-store registrations.
|
|
488
|
+
|
|
489
|
+
---
|
|
490
|
+
|
|
491
|
+
## โฑ๏ธ Async - `stroid/async`
|
|
492
|
+
|
|
493
|
+
```ts
|
|
494
|
+
import { fetchStore, refetchStore, enableRevalidateOnFocus } from "stroid/async";
|
|
495
|
+
|
|
496
|
+
await fetchStore("menu", "https://api.example.com/menu");
|
|
497
|
+
await refetchStore("menu");
|
|
498
|
+
const stopFocusRevalidate = enableRevalidateOnFocus("menu");
|
|
499
|
+
|
|
500
|
+
stopFocusRevalidate();
|
|
501
|
+
```
|
|
502
|
+
Fetches/refetches remote data into stores with cache, dedupe, retries, and focus revalidation.
|
|
503
|
+
|
|
504
|
+
---
|
|
505
|
+
|
|
506
|
+
## PSR - Write Governance
|
|
507
|
+
|
|
508
|
+
```ts
|
|
509
|
+
import { applyStorePatch, applyStorePatchesAtomic } from "stroid/psr";
|
|
510
|
+
|
|
511
|
+
applyStorePatch({
|
|
512
|
+
id: "cart-total-set",
|
|
513
|
+
store: "cart",
|
|
514
|
+
path: ["total"],
|
|
515
|
+
op: "set",
|
|
516
|
+
value: 549,
|
|
517
|
+
meta: { timestamp: Date.now(), source: "setStore" },
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
applyStorePatchesAtomic([
|
|
521
|
+
{
|
|
522
|
+
id: "wallet-set",
|
|
523
|
+
store: "wallet",
|
|
524
|
+
path: ["balance"],
|
|
525
|
+
op: "set",
|
|
526
|
+
value: 900,
|
|
527
|
+
meta: { timestamp: Date.now(), source: "setStore" },
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
id: "order-set",
|
|
531
|
+
store: "order",
|
|
532
|
+
path: ["status"],
|
|
533
|
+
op: "set",
|
|
534
|
+
value: "paid",
|
|
535
|
+
meta: { timestamp: Date.now(), source: "setStore" },
|
|
536
|
+
},
|
|
537
|
+
]);
|
|
538
|
+
```
|
|
539
|
+
Applies patch-based governed writes with atomic multi-patch support.
|
|
540
|
+
|
|
541
|
+
---
|
|
542
|
+
|
|
543
|
+
## ๐ก๏ธ Features - `stroid/feature`
|
|
544
|
+
|
|
545
|
+
### Install - Opt-In Capabilities
|
|
546
|
+
|
|
547
|
+
```ts
|
|
548
|
+
import { installPersist } from "stroid/persist";
|
|
549
|
+
import { installSync } from "stroid/sync";
|
|
550
|
+
import { installDevtools } from "stroid/devtools";
|
|
551
|
+
|
|
552
|
+
installPersist();
|
|
553
|
+
installSync();
|
|
554
|
+
installDevtools();
|
|
555
|
+
```
|
|
556
|
+
Installs optional persist/sync/devtools features explicitly at app entry.
|
|
557
|
+
|
|
558
|
+
---
|
|
559
|
+
|
|
560
|
+
## ๐ SSR - `stroid/server`
|
|
561
|
+
|
|
562
|
+
```ts
|
|
563
|
+
import { createStoreForRequest } from "stroid/server";
|
|
564
|
+
|
|
565
|
+
const requestScope = createStoreForRequest((api) => {
|
|
566
|
+
api.create("session", { userId: "u-1" });
|
|
567
|
+
api.create("cart", { items: [], total: 0 });
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
const html = requestScope.hydrate(() => renderToString(<App />));
|
|
571
|
+
const snapshot = requestScope.snapshot();
|
|
572
|
+
```
|
|
573
|
+
Creates per-request store scopes for SSR-safe hydrate/snapshot flows.
|
|
574
|
+
|
|
575
|
+
---
|
|
576
|
+
|
|
577
|
+
## ๐ข Helpers - `stroid/helpers`
|
|
578
|
+
|
|
579
|
+
```ts
|
|
580
|
+
import { createEntityStore, createListStore, createCounterStore } from "stroid/helpers";
|
|
581
|
+
|
|
582
|
+
const users = createEntityStore<{ id?: string; name: string }>("users");
|
|
583
|
+
users.upsert({ id: "u1", name: "Asha" });
|
|
584
|
+
users.remove("u1");
|
|
585
|
+
|
|
586
|
+
const tasks = createListStore("tasks", [] as string[]);
|
|
587
|
+
tasks.push("pick up order");
|
|
588
|
+
tasks.removeAt(0);
|
|
589
|
+
tasks.clear();
|
|
590
|
+
|
|
591
|
+
const retries = createCounterStore("retries", 0);
|
|
592
|
+
retries.inc();
|
|
593
|
+
retries.dec();
|
|
594
|
+
retries.set(5);
|
|
595
|
+
const retryValue = retries.get();
|
|
596
|
+
```
|
|
597
|
+
Provides ready-made entity, list, and counter store helpers.
|
|
598
|
+
|
|
599
|
+
---
|
|
600
|
+
|
|
601
|
+
## ๐งช Testing - `stroid/testing`
|
|
602
|
+
|
|
603
|
+
```ts
|
|
604
|
+
import {
|
|
605
|
+
createMockStore,
|
|
606
|
+
resetAllStoresForTest,
|
|
607
|
+
withMockedTime,
|
|
608
|
+
benchmarkStoreSet,
|
|
609
|
+
} from "stroid/testing";
|
|
610
|
+
|
|
611
|
+
const mockOrder = createMockStore("order", { status: "draft" });
|
|
612
|
+
mockOrder.set({ status: "confirmed" });
|
|
613
|
+
|
|
614
|
+
withMockedTime(1700000000000, () => {
|
|
615
|
+
// Date.now() is fixed in this callback
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
const result = benchmarkStoreSet({ name: "cart" } as any, 300);
|
|
619
|
+
const avgMs = result.avgMs;
|
|
620
|
+
|
|
621
|
+
resetAllStoresForTest();
|
|
622
|
+
```
|
|
623
|
+
Provides test helpers for mock stores, time control, reset, and micro-benchmarks.
|
|
624
|
+
|
|
625
|
+
---
|
|
626
|
+
|
|
627
|
+
## ๐ Runtime Observability - `stroid/runtime-tools`
|
|
628
|
+
|
|
629
|
+
```ts
|
|
630
|
+
import {
|
|
631
|
+
listStores,
|
|
632
|
+
getStoreMeta,
|
|
633
|
+
getMetrics,
|
|
634
|
+
getSubscriberCount,
|
|
635
|
+
getStoreHealth,
|
|
636
|
+
findColdStores,
|
|
637
|
+
getComputedGraph,
|
|
638
|
+
getComputedDeps,
|
|
639
|
+
getPersistQueueDepth,
|
|
640
|
+
} from "stroid/runtime-tools";
|
|
641
|
+
|
|
642
|
+
const stores = listStores();
|
|
643
|
+
const meta = getStoreMeta("cart");
|
|
644
|
+
const metrics = getMetrics("cart");
|
|
645
|
+
const subscribers = getSubscriberCount("cart");
|
|
646
|
+
const health = getStoreHealth();
|
|
647
|
+
const cold = findColdStores();
|
|
648
|
+
const graph = getComputedGraph();
|
|
649
|
+
const deps = getComputedDeps("deliveryFee");
|
|
650
|
+
const persistDepth = getPersistQueueDepth("cart");
|
|
651
|
+
```
|
|
652
|
+
Exposes runtime diagnostics for stores, metrics, health, and computed graph state.
|
|
653
|
+
|
|
654
|
+
---
|
|
655
|
+
|
|
656
|
+
## ๐ง Runtime Admin - `stroid/runtime-admin`
|
|
657
|
+
|
|
658
|
+
```ts
|
|
659
|
+
import { clearAllStores, clearStores } from "stroid/runtime-admin";
|
|
660
|
+
|
|
661
|
+
clearStores("cart*");
|
|
662
|
+
clearAllStores();
|
|
663
|
+
```
|
|
664
|
+
Clears stores in bulk by pattern or globally.
|
|
665
|
+
|
|
666
|
+
---
|
|
667
|
+
|
|
668
|
+
## ๐ DevTools Bridge - `stroid/devtools`
|
|
12
669
|
|
|
13
670
|
```ts
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
671
|
+
import { installDevtools, getHistory, clearHistory } from "stroid/devtools";
|
|
672
|
+
|
|
673
|
+
installDevtools();
|
|
674
|
+
|
|
675
|
+
const cartHistory = getHistory("cart");
|
|
676
|
+
// Array<HistoryEntry> where entry has: ts, action, prev, next, diff
|
|
677
|
+
|
|
678
|
+
clearHistory("cart");
|
|
679
|
+
```
|
|
680
|
+
Connects to devtools runtime and exposes local history read/clear APIs.
|
|
681
|
+
|
|
682
|
+
---
|
|
683
|
+
|
|
684
|
+
## ๐ Feature Plugin API - `stroid/feature`
|
|
685
|
+
|
|
686
|
+
```ts
|
|
687
|
+
import {
|
|
688
|
+
registerStoreFeature,
|
|
689
|
+
hasRegisteredStoreFeature,
|
|
690
|
+
getRegisteredFeatureNames,
|
|
691
|
+
} from "stroid/feature";
|
|
692
|
+
|
|
693
|
+
registerStoreFeature("auditFeature", () => ({
|
|
694
|
+
onStoreCreate(ctx) {
|
|
695
|
+
// fires when a store is created
|
|
696
|
+
},
|
|
697
|
+
onStoreWrite(ctx) {
|
|
698
|
+
// fires on store write
|
|
699
|
+
},
|
|
700
|
+
}));
|
|
701
|
+
|
|
702
|
+
const hasAudit = hasRegisteredStoreFeature("auditFeature");
|
|
703
|
+
const featureNames = getRegisteredFeatureNames();
|
|
704
|
+
```
|
|
705
|
+
Registers custom feature runtimes with lifecycle hooks.
|
|
706
|
+
|
|
707
|
+
---
|
|
708
|
+
|
|
709
|
+
<a id="psr---write-governance"></a>
|
|
710
|
+
## ๐ก๏ธ PSR - Write Governance - `stroid/psr`
|
|
711
|
+
|
|
712
|
+
PSR (`stroid/psr`) is the public contract for:
|
|
713
|
+
- Committed snapshots
|
|
714
|
+
- Post-commit subscriptions
|
|
715
|
+
- Serializable patch application
|
|
716
|
+
- Runtime graph and timing contract inspection
|
|
717
|
+
|
|
718
|
+
### PSR API
|
|
719
|
+
|
|
720
|
+
```ts
|
|
721
|
+
import {
|
|
722
|
+
getStoreSnapshot,
|
|
723
|
+
getStoreSnapshotNoTrack,
|
|
724
|
+
subscribeStore,
|
|
725
|
+
applyStorePatch,
|
|
726
|
+
applyStorePatchesAtomic,
|
|
727
|
+
getRuntimeGraph,
|
|
728
|
+
getComputedDescriptor,
|
|
729
|
+
evaluateComputed,
|
|
730
|
+
getTimingContract,
|
|
731
|
+
} from "stroid/psr";
|
|
732
|
+
|
|
733
|
+
const committed = getStoreSnapshot("cart");
|
|
734
|
+
const committedNoTrack = getStoreSnapshotNoTrack("cart");
|
|
735
|
+
|
|
736
|
+
const unsubscribe = subscribeStore("cart", (next) => {
|
|
737
|
+
// handle committed updates
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
const graph = getRuntimeGraph();
|
|
741
|
+
const checkoutNode = graph.nodes.find(
|
|
742
|
+
(n) => n.storeId === "checkoutTotal" && (n.type === "computed" || n.type === "async-boundary")
|
|
743
|
+
);
|
|
744
|
+
|
|
745
|
+
if (checkoutNode) {
|
|
746
|
+
const descriptor = getComputedDescriptor(checkoutNode.id);
|
|
747
|
+
const preview = evaluateComputed(checkoutNode.id, {
|
|
748
|
+
cart: { items: [{ id: "pizza", qty: 2 }], total: 998 },
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
const timing = getTimingContract("cart");
|
|
17
753
|
```
|
|
754
|
+
Exposes committed snapshots, subscriptions, patch APIs, runtime graph, and timing contract.
|
|
755
|
+
---
|
|
756
|
+
|
|
757
|
+
<a id="devtools"></a>
|
|
758
|
+
## ๐ฌ DevTools - `stroid/devtools`
|
|
759
|
+
|
|
760
|
+
`stroid/devtools` integrates with the Redux DevTools browser extension and also keeps in-memory history per store.
|
|
761
|
+
|
|
762
|
+
What you get from code:
|
|
763
|
+
- `installDevtools()` to enable the devtools feature runtime
|
|
764
|
+
- `getHistory(name, limit?)` to read recorded store history
|
|
765
|
+
- `clearHistory(name?)` to clear one store or all stores
|
|
766
|
+
|
|
767
|
+
Setup:
|
|
768
|
+
1. Install Redux DevTools extension in your browser.
|
|
769
|
+
2. Call `installDevtools()` once in app entry.
|
|
770
|
+
3. Open browser DevTools and inspect the connected `stroid` session.
|
|
18
771
|
|
|
19
772
|
---
|
|
20
773
|
|
|
21
|
-
|
|
774
|
+
<a id="real-world-examples"></a>
|
|
775
|
+
## ๐ Real-World Examples
|
|
776
|
+
|
|
777
|
+
### Food delivery cart (full flow)
|
|
778
|
+
|
|
779
|
+
```tsx
|
|
780
|
+
// app/entry.ts
|
|
781
|
+
import { configureStroid } from "stroid";
|
|
782
|
+
import { installPersist } from "stroid/persist";
|
|
783
|
+
import { installDevtools } from "stroid/devtools";
|
|
784
|
+
|
|
785
|
+
configureStroid({ asyncAutoCreate: false });
|
|
786
|
+
installPersist();
|
|
787
|
+
installDevtools();
|
|
788
|
+
|
|
789
|
+
// stores/cart.ts
|
|
790
|
+
import { createStore, setStoreBatch, setStore } from "stroid";
|
|
22
791
|
|
|
23
|
-
|
|
24
|
-
|
|
792
|
+
createStore("cart", { items: [], total: 0 });
|
|
793
|
+
createStore("checkout", { coupon: null, deliveryType: "standard", tip: 0 });
|
|
794
|
+
|
|
795
|
+
// components/CartPanel.tsx
|
|
796
|
+
import { useSelector, useStoreField } from "stroid/react";
|
|
797
|
+
|
|
798
|
+
function CartPanel() {
|
|
799
|
+
const total = useSelector("cart", (s: any) => s?.total ?? 0);
|
|
800
|
+
const deliveryType = useStoreField("checkout", "deliveryType");
|
|
801
|
+
|
|
802
|
+
function applyPromo() {
|
|
803
|
+
setStoreBatch(() => {
|
|
804
|
+
setStore("checkout", "coupon", "SAVE20");
|
|
805
|
+
setStore("checkout", "deliveryType", "priority");
|
|
806
|
+
setStore("checkout", "tip", 50);
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
return (
|
|
811
|
+
<div>
|
|
812
|
+
<p>Total: Rs. {total}</p>
|
|
813
|
+
<p>Delivery: {deliveryType}</p>
|
|
814
|
+
<button onClick={applyPromo}>Apply promo</button>
|
|
815
|
+
</div>
|
|
816
|
+
);
|
|
817
|
+
}
|
|
25
818
|
```
|
|
26
819
|
|
|
27
|
-
|
|
820
|
+
### Atomic payment (wallet + order status)
|
|
821
|
+
|
|
822
|
+
```ts
|
|
823
|
+
import { getStore } from "stroid";
|
|
824
|
+
import { applyStorePatchesAtomic } from "stroid/psr";
|
|
825
|
+
|
|
826
|
+
function confirmPayment(amount: number) {
|
|
827
|
+
const wallet = getStore("wallet") as { balance: number } | null;
|
|
828
|
+
const nextBalance = (wallet?.balance ?? 0) - amount;
|
|
28
829
|
|
|
29
|
-
|
|
830
|
+
applyStorePatchesAtomic([
|
|
831
|
+
{
|
|
832
|
+
id: "pay-wallet",
|
|
833
|
+
store: "wallet",
|
|
834
|
+
path: ["balance"],
|
|
835
|
+
op: "set",
|
|
836
|
+
value: nextBalance,
|
|
837
|
+
meta: { timestamp: Date.now(), source: "setStore" },
|
|
838
|
+
},
|
|
839
|
+
{
|
|
840
|
+
id: "pay-order",
|
|
841
|
+
store: "order",
|
|
842
|
+
path: ["status"],
|
|
843
|
+
op: "set",
|
|
844
|
+
value: "paid",
|
|
845
|
+
meta: { timestamp: Date.now(), source: "setStore" },
|
|
846
|
+
},
|
|
847
|
+
]);
|
|
848
|
+
}
|
|
849
|
+
```
|
|
850
|
+
Applies related wallet/order updates atomically so both succeed or fail together.
|
|
30
851
|
|
|
31
852
|
---
|
|
853
|
+
### Menu with Suspense
|
|
854
|
+
|
|
855
|
+
```tsx
|
|
856
|
+
import { Suspense } from "react";
|
|
857
|
+
import { useAsyncStoreSuspense } from "stroid/react";
|
|
858
|
+
|
|
859
|
+
function MenuList() {
|
|
860
|
+
const menu = useAsyncStoreSuspense<Array<{ id: string; name: string }>>(
|
|
861
|
+
"menu",
|
|
862
|
+
"https://api.example.com/menu"
|
|
863
|
+
);
|
|
32
864
|
|
|
33
|
-
|
|
865
|
+
return <ul>{menu.map((item) => <li key={item.id}>{item.name}</li>)}</ul>;
|
|
866
|
+
}
|
|
34
867
|
|
|
868
|
+
export function MenuPage() {
|
|
869
|
+
return (
|
|
870
|
+
<Suspense fallback={<p>Loading menu...</p>}>
|
|
871
|
+
<MenuList />
|
|
872
|
+
</Suspense>
|
|
873
|
+
);
|
|
874
|
+
}
|
|
35
875
|
```
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
876
|
+
Loads menu data through Suspense-friendly async store access.
|
|
877
|
+
|
|
878
|
+
---
|
|
879
|
+
|
|
880
|
+
## ๐งฑ Layer Map
|
|
881
|
+
|
|
882
|
+
```
|
|
883
|
+
+---------------------------------------------------------+
|
|
884
|
+
| your app |
|
|
885
|
+
+---------------------------------------------------------+
|
|
886
|
+
| useStore useSelector useAsyncStore useFormStore | stroid/react
|
|
887
|
+
+---------------------------------------------------------+
|
|
888
|
+
| createStore setStore getStore setStoreBatch | stroid
|
|
889
|
+
| createComputed createSelector createEntityStore |
|
|
890
|
+
+--------------+--------------+---------------------------+
|
|
891
|
+
| stroid/persist | stroid/sync | stroid/async |
|
|
892
|
+
| installPersist | installSync | fetch + cache + retry |
|
|
893
|
+
+--------------+--------------+---------------------------+
|
|
894
|
+
| stroid/server createStoreForRequest | SSR
|
|
895
|
+
+---------------------------------------------------------+
|
|
896
|
+
| stroid/devtools stroid/testing stroid/runtime-tools |
|
|
897
|
+
+---------------------------------------------------------+
|
|
51
898
|
```
|
|
52
899
|
|
|
53
900
|
Each row is independent. Use only what you need.
|
|
54
901
|
|
|
55
|
-
`stroid/core` exports only `createStore`, `setStore`, `getStore`, `hasStore`, `resetStore`, and `deleteStore`.
|
|
902
|
+
`stroid/core` exports only `createStore`, `setStore`, `getStore`, `hasStore`, `resetStore`, and `deleteStore`.
|
|
903
|
+
Import from `stroid` for batching/hydration/computed plus runtime metrics and config.
|
|
56
904
|
|
|
57
|
-
## What Each Import Contains
|
|
905
|
+
## ๐ฆ What Each Import Contains
|
|
58
906
|
|
|
59
|
-
- `stroid`:
|
|
60
|
-
- `stroid/
|
|
61
|
-
- `stroid/
|
|
62
|
-
- `stroid/
|
|
907
|
+
- `stroid`: Core public runtime (`createStore`, `createStoreStrict`, `setStore`, `setStoreBatch`, `getStore`, `deleteStore`, `resetStore`, `hasStore`, `hydrateStores`), plus `configureStroid`, computed helpers, and health/metric helpers.
|
|
908
|
+
- `stroid/psr`: PSR contract (`getStoreSnapshot`, `getStoreSnapshotNoTrack`, `subscribeStore`, `applyStorePatch`, `applyStorePatchesAtomic`, runtime graph/timing helpers).
|
|
909
|
+
- `stroid/core`: Minimal CRUD runtime (`createStore`, `setStore`, `getStore`, `hasStore`, `resetStore`, `deleteStore`).
|
|
910
|
+
- `stroid/react`: React hooks (`useStore`, `useSelector`, `useStoreField`, `useStoreStatic`, `useAsyncStore`, `useFormStore`, `useAsyncStoreSuspense`) and `RegistryScope`.
|
|
911
|
+
- `stroid/async`: Async APIs (`fetchStore`, `refetchStore`, `enableRevalidateOnFocus`, `getAsyncMetrics`).
|
|
63
912
|
- `stroid/selectors`: `createSelector`, `subscribeWithSelector`.
|
|
64
913
|
- `stroid/computed`: `createComputed`, `invalidateComputed`, `deleteComputed`, `isComputedStore`.
|
|
65
|
-
- `stroid/persist`:
|
|
66
|
-
- `stroid/sync`:
|
|
67
|
-
- `stroid/devtools`:
|
|
68
|
-
- `stroid/server`:
|
|
69
|
-
- `stroid/helpers`:
|
|
70
|
-
- `stroid/
|
|
71
|
-
- `stroid/runtime-
|
|
72
|
-
- `stroid/
|
|
914
|
+
- `stroid/persist`: `installPersist`.
|
|
915
|
+
- `stroid/sync`: `installSync`.
|
|
916
|
+
- `stroid/devtools`: `installDevtools`, `getHistory`, `clearHistory`.
|
|
917
|
+
- `stroid/server`: `createStoreForRequest`.
|
|
918
|
+
- `stroid/helpers`: `createEntityStore`, `createListStore`, `createCounterStore`.
|
|
919
|
+
- `stroid/testing`: `createMockStore`, `resetAllStoresForTest`, `withMockedTime`, `benchmarkStoreSet`.
|
|
920
|
+
- `stroid/runtime-tools`: Store/runtime observability APIs.
|
|
921
|
+
- `stroid/runtime-admin`: `clearAllStores`, `clearStores`.
|
|
922
|
+
- `stroid/feature`: Feature registration APIs.
|
|
923
|
+
- `stroid/install`: `installPersist`, `installSync`, `installDevtools`, `installAllFeatures`.
|
|
73
924
|
|
|
74
925
|
---
|
|
75
926
|
|
|
76
|
-
## Quick API Reference
|
|
927
|
+
## ๐งพ Quick API Reference
|
|
77
928
|
|
|
78
929
|
| API | Purpose |
|
|
79
930
|
|-----|---------|
|
|
80
|
-
| `createStore(name, state, options?)` | Define a store. Returns `StoreDefinition
|
|
81
|
-
| `createStoreStrict(name, state, options?)` | Define a store;
|
|
82
|
-
| `setStore(name, update)` |
|
|
83
|
-
| `setStore(name, path, value)` | Write
|
|
84
|
-
| `setStore(name, draft => { })` |
|
|
85
|
-
| `
|
|
86
|
-
| `
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
92
|
-
| `useStore(name,
|
|
93
|
-
| `useSelector(name, fn)` |
|
|
94
|
-
| `fetchStore(name,
|
|
95
|
-
| `createComputed(name, deps, fn)` |
|
|
96
|
-
| `createStoreForRequest(fn)` |
|
|
931
|
+
| `createStore(name, state, options?)` | Define a store. Returns `StoreDefinition` or `undefined`. |
|
|
932
|
+
| `createStoreStrict(name, state, options?)` | Define a store; throws if creation fails. |
|
|
933
|
+
| `setStore(name, update)` | Merge object update into object store state. |
|
|
934
|
+
| `setStore(name, path, value)` | Write by path. |
|
|
935
|
+
| `setStore(name, draft => { ... })` | Mutator-style update. |
|
|
936
|
+
| `getStore(name, path?)` | Read current state (or nested path). |
|
|
937
|
+
| `deleteStore(name)` | Remove a store from registry. |
|
|
938
|
+
| `resetStore(name)` | Restore initial state. |
|
|
939
|
+
| `hasStore(name)` | Check if store exists. |
|
|
940
|
+
| `setStoreBatch(fn)` | Group synchronous writes into one transaction. |
|
|
941
|
+
| `hydrateStores(snapshot, options?, trust)` | Hydrate trusted snapshot into runtime. |
|
|
942
|
+
| `configureStroid(config)` | Configure global/runtime behavior. |
|
|
943
|
+
| `useStore(name, selectorOrPath?)` | React subscription hook. |
|
|
944
|
+
| `useSelector(name, fn, equality?)` | Fine-grained React selector hook. |
|
|
945
|
+
| `fetchStore(name, input, options?)` | Fetch remote data into store. |
|
|
946
|
+
| `createComputed(name, deps, fn)` | Define computed store. |
|
|
947
|
+
| `createStoreForRequest(fn)` | Build SSR request-scoped store runtime. |
|
|
97
948
|
|
|
98
949
|
---
|
|
99
950
|
|
|
100
|
-
## Module Import Map
|
|
951
|
+
## ๐งญ Module Import Map
|
|
101
952
|
|
|
102
953
|
```ts
|
|
103
954
|
// Core
|
|
104
|
-
import {
|
|
105
|
-
|
|
955
|
+
import {
|
|
956
|
+
createStore,
|
|
957
|
+
createStoreStrict,
|
|
958
|
+
setStore,
|
|
959
|
+
getStore,
|
|
960
|
+
hasStore,
|
|
961
|
+
deleteStore,
|
|
962
|
+
resetStore,
|
|
963
|
+
setStoreBatch,
|
|
964
|
+
hydrateStores,
|
|
965
|
+
configureStroid,
|
|
966
|
+
} from "stroid";
|
|
967
|
+
|
|
968
|
+
// Native PSR contract
|
|
969
|
+
import {
|
|
970
|
+
getStoreSnapshot,
|
|
971
|
+
getStoreSnapshotNoTrack,
|
|
972
|
+
subscribeStore,
|
|
973
|
+
applyStorePatch,
|
|
974
|
+
applyStorePatchesAtomic,
|
|
975
|
+
getRuntimeGraph,
|
|
976
|
+
getComputedDescriptor,
|
|
977
|
+
evaluateComputed,
|
|
978
|
+
getTimingContract,
|
|
979
|
+
} from "stroid/psr";
|
|
106
980
|
|
|
107
981
|
// Minimal core (bundle-size-sensitive)
|
|
108
|
-
import { createStore, setStore, getStore, hasStore,
|
|
109
|
-
resetStore, deleteStore } from "stroid/core"
|
|
982
|
+
import { createStore, setStore, getStore, hasStore, resetStore, deleteStore } from "stroid/core";
|
|
110
983
|
|
|
111
984
|
// React
|
|
112
|
-
import {
|
|
113
|
-
|
|
114
|
-
|
|
985
|
+
import {
|
|
986
|
+
useStore,
|
|
987
|
+
useSelector,
|
|
988
|
+
useStoreField,
|
|
989
|
+
useStoreStatic,
|
|
990
|
+
useAsyncStore,
|
|
991
|
+
useFormStore,
|
|
992
|
+
useAsyncStoreSuspense,
|
|
993
|
+
RegistryScope,
|
|
994
|
+
} from "stroid/react";
|
|
115
995
|
|
|
116
996
|
// Async
|
|
117
|
-
import { fetchStore, refetchStore, enableRevalidateOnFocus } from "stroid/async"
|
|
997
|
+
import { fetchStore, refetchStore, enableRevalidateOnFocus } from "stroid/async";
|
|
118
998
|
|
|
119
999
|
// Selectors & Computed
|
|
120
|
-
import { createSelector, subscribeWithSelector } from "stroid/selectors"
|
|
121
|
-
import { createComputed, invalidateComputed,
|
|
122
|
-
deleteComputed, isComputedStore } from "stroid/computed"
|
|
1000
|
+
import { createSelector, subscribeWithSelector } from "stroid/selectors";
|
|
1001
|
+
import { createComputed, invalidateComputed, deleteComputed, isComputedStore } from "stroid/computed";
|
|
123
1002
|
|
|
124
|
-
// Features (
|
|
125
|
-
import "stroid/persist"
|
|
126
|
-
import "stroid/sync"
|
|
127
|
-
import "stroid/devtools"
|
|
1003
|
+
// Features (explicit install - call once at app entry)
|
|
1004
|
+
import { installPersist } from "stroid/persist";
|
|
1005
|
+
import { installSync } from "stroid/sync";
|
|
1006
|
+
import { installDevtools, getHistory, clearHistory } from "stroid/devtools";
|
|
128
1007
|
|
|
129
|
-
|
|
130
|
-
|
|
1008
|
+
installPersist();
|
|
1009
|
+
installSync();
|
|
1010
|
+
installDevtools();
|
|
131
1011
|
|
|
132
1012
|
// Server / SSR
|
|
133
|
-
import { createStoreForRequest } from "stroid/server"
|
|
1013
|
+
import { createStoreForRequest } from "stroid/server";
|
|
134
1014
|
|
|
135
1015
|
// Helpers & Testing
|
|
136
|
-
import { createEntityStore, createListStore, createCounterStore } from "stroid/helpers"
|
|
137
|
-
import { createMockStore, resetAllStoresForTest,
|
|
138
|
-
withMockedTime, benchmarkStoreSet } from "stroid/testing"
|
|
139
|
-
|
|
140
|
-
// Runtime Observability
|
|
141
|
-
import { listStores, getStoreMeta, getMetrics,
|
|
142
|
-
getSubscriberCount, getStoreHealth, findColdStores,
|
|
143
|
-
getComputedGraph, getComputedDeps,
|
|
144
|
-
getPersistQueueDepth } from "stroid/runtime-tools"
|
|
145
|
-
import { clearAllStores, clearStores } from "stroid/runtime-admin"
|
|
146
|
-
|
|
147
|
-
// Devtools API (after `import "stroid/devtools"`)
|
|
148
|
-
import { getHistory, clearHistory } from "stroid/devtools"
|
|
1016
|
+
import { createEntityStore, createListStore, createCounterStore } from "stroid/helpers";
|
|
1017
|
+
import { createMockStore, resetAllStoresForTest, withMockedTime, benchmarkStoreSet } from "stroid/testing";
|
|
149
1018
|
|
|
150
|
-
//
|
|
151
|
-
import {
|
|
152
|
-
|
|
1019
|
+
// Runtime Observability + Admin
|
|
1020
|
+
import {
|
|
1021
|
+
listStores,
|
|
1022
|
+
getStoreMeta,
|
|
1023
|
+
getMetrics,
|
|
1024
|
+
getSubscriberCount,
|
|
1025
|
+
getStoreHealth,
|
|
1026
|
+
findColdStores,
|
|
1027
|
+
getComputedGraph,
|
|
1028
|
+
getComputedDeps,
|
|
1029
|
+
getPersistQueueDepth,
|
|
1030
|
+
} from "stroid/runtime-tools";
|
|
1031
|
+
import { clearAllStores, clearStores } from "stroid/runtime-admin";
|
|
153
1032
|
|
|
154
1033
|
// Feature plugin API
|
|
155
|
-
import { registerStoreFeature,
|
|
156
|
-
|
|
157
|
-
|
|
1034
|
+
import { registerStoreFeature, hasRegisteredStoreFeature, getRegisteredFeatureNames } from "stroid/feature";
|
|
1035
|
+
|
|
1036
|
+
// Optional all-in-one installer
|
|
1037
|
+
import { installAllFeatures } from "stroid/install";
|
|
158
1038
|
```
|
|
159
1039
|
|
|
160
1040
|
---
|
|
161
1041
|
|
|
162
|
-
##
|
|
1042
|
+
## ๐งท Native PSR Contract
|
|
1043
|
+
|
|
1044
|
+
`stroid/psr` is the supported public surface for native PSR-style integration.
|
|
1045
|
+
|
|
1046
|
+
- Committed reads: `getStoreSnapshot()` and `getStoreSnapshotNoTrack()`
|
|
1047
|
+
- Committed-final observation: `subscribeStore()`
|
|
1048
|
+
- Serializable writes: `applyStorePatch()` and `applyStorePatchesAtomic()` with `set`, `merge`, `delete`, and `insert`
|
|
1049
|
+
- Runtime inspection: `getRuntimeGraph()`, `getComputedDescriptor()`, `evaluateComputed()`, and `getTimingContract()`
|
|
1050
|
+
|
|
1051
|
+
See [Native PSR Contract](./docs/STROID_PSR/INDEX.md) for full details.
|
|
1052
|
+
|
|
1053
|
+
---
|
|
1054
|
+
|
|
1055
|
+
## ๐ Docs
|
|
163
1056
|
|
|
164
1057
|
Full documentation in [`/docs`](./docs/):
|
|
165
1058
|
|
|
166
|
-
- [Architecture](./docs/
|
|
167
|
-
- [Core Concepts](./docs/
|
|
168
|
-
- [React Layer](./docs/
|
|
169
|
-
- [Async Layer](./docs/
|
|
170
|
-
- [Persistence](./docs/
|
|
171
|
-
- [Cross-tab Sync](./docs/
|
|
172
|
-
- [Computed Stores](./docs/
|
|
173
|
-
- [
|
|
174
|
-
- [
|
|
175
|
-
- [
|
|
176
|
-
- [Runtime Tools](./docs/
|
|
177
|
-
- [
|
|
1059
|
+
- [Architecture](./docs/STROID_ARCHITECTURE/AECHITECTURE.md) - layers, data flow, registry model
|
|
1060
|
+
- [Core Concepts](./docs/STROID_CORE/INDEX.md) - store lifecycle, options, write modes
|
|
1061
|
+
- [React Layer](./docs/STROID_REACT/INDEX.md) - hooks, selectors, SSR
|
|
1062
|
+
- [Async Layer](./docs/STROID_ASYNC/INDEX.md) - `fetchStore`, caching, revalidation
|
|
1063
|
+
- [Persistence](./docs/STROID_PERSIST/INDEX.md) - persist options, encryption, migrations
|
|
1064
|
+
- [Cross-tab Sync](./docs/STROID_SYNC/INDEX.md) - BroadcastChannel sync behavior
|
|
1065
|
+
- [Computed Stores](./docs/STROID_COMPUTED/INDEX.md) - reactive derived values
|
|
1066
|
+
- [Native PSR Contract](./docs/STROID_PSR/INDEX.md) - patch coverage, timing/governance, graph identity
|
|
1067
|
+
- [Server & SSR](./docs/STROID_SERVER/INDEX.md) - request-scoped stores, hydration
|
|
1068
|
+
- [Testing](./docs/STROID_TESTING/INDEX.md) - mock stores, resets, benchmarks
|
|
1069
|
+
- [Runtime Tools](./docs/STROID_RUNTIME_TOOLS/INDEX.md) - observability, health checks
|
|
1070
|
+
- [TypeScript Guide](./docs/STROID_TYPESCRIPT/INDEX.md)
|
|
1071
|
+
- [Full API Reference](./docs/api/stroid.api.md)
|
|
178
1072
|
- [Project Status](./STATUS.MD)
|
|
179
1073
|
- [Contributing](./CONTRIBUTING.md)
|
|
180
1074
|
|
|
181
1075
|
---
|
|
182
1076
|
|
|
183
|
-
## Changelog & License
|
|
1077
|
+
## ๐ Changelog & License
|
|
184
1078
|
|
|
185
1079
|
- [CHANGELOG](./CHANGELOG.md)
|
|
186
1080
|
- [MIT License](./LICENSE)
|
|
187
1081
|
- [Issues](https://github.com/Himesh-Bhattarai/stroid/issues)
|
|
188
1082
|
|
|
1083
|
+
|
|
1084
|
+
<div align="center">
|
|
1085
|
+
|
|
1086
|
+
**Made with care for developers who think about state seriously.**
|
|
1087
|
+
|
|
1088
|
+
[โญ Star on GitHub](https://github.com/Himesh-Bhattarai/stroid) ยท [๐ Report a bug](https://github.com/Himesh-Bhattarai/stroid/issues) ยท [๐ฌ Discussions](https://github.com/Himesh-Bhattarai/stroid/discussions)
|
|
1089
|
+
|
|
1090
|
+
</div>
|