stroid 0.1.4-beta.0 → 0.1.5
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 +427 -288
- package/README.md +1125 -118
- package/dist/async-internal.d.ts +30 -0
- package/dist/async.d.ts +7 -12
- package/dist/async.js +1 -33
- package/dist/chunk-4C666HHU.js +2 -0
- package/dist/chunk-4D6OA3DD.js +2 -0
- package/dist/chunk-6IBJ7CIK.js +14 -0
- package/dist/chunk-BWNLQKTY.js +2 -0
- package/dist/chunk-E33L4MII.js +2 -0
- package/dist/chunk-FSNVSMAV.js +2 -0
- package/dist/chunk-GZQGU64H.js +2 -0
- package/dist/chunk-LWUT37FW.js +13 -0
- package/dist/chunk-NFT6AZXY.js +2 -0
- package/dist/chunk-PHE2BCFG.js +2 -0
- package/dist/chunk-SF6EP56S.js +2 -0
- package/dist/chunk-WE3ZR6OG.js +2 -0
- package/dist/chunk-WXJ3IREA.js +2 -0
- package/dist/chunk-X2MKRN7O.js +14 -0
- package/dist/chunk-Y54SMROI.js +2 -0
- package/dist/chunk-YU5GMPCC.js +2 -0
- package/dist/computed.d.ts +6 -5
- package/dist/computed.js +1 -12
- package/dist/core-internal.d.ts +23 -0
- package/dist/core.d.ts +3 -2
- package/dist/core.js +1 -22
- package/dist/devtools-internal.d.ts +26 -0
- package/dist/devtools.d.ts +4 -25
- package/dist/devtools.js +1 -1
- package/dist/feature.d.ts +9 -2
- package/dist/feature.js +1 -1
- package/dist/fetch-4RH6MPY3.js +2 -0
- package/dist/graph-D28.d.ts +20 -0
- package/dist/helpers.d.ts +2 -1
- package/dist/helpers.js +1 -22
- package/dist/index-internal.d.ts +19 -19
- package/dist/index.d.cts +40 -22
- package/dist/index.d.ts +40 -22
- package/dist/index.js +1 -35
- package/dist/install.d.ts +16 -4
- package/dist/install.js +1 -1
- package/dist/metrics.d.ts +13 -0
- package/dist/persist.d.ts +3 -1
- package/dist/persist.js +1 -1
- package/dist/psr.d.ts +20 -11
- package/dist/psr.js +1 -18
- package/dist/query.d.ts +17 -0
- package/dist/query.js +2 -0
- package/dist/react/index.d.ts +10 -3
- package/dist/react/index.js +5 -36
- package/dist/registry.d.ts +23 -14
- package/dist/runtime-admin.js +1 -1
- package/dist/runtime-tools.d.ts +38 -65
- package/dist/runtime-tools.js +1 -3
- package/dist/selectors.d.ts +1 -1
- package/dist/selectors.js +1 -1
- package/dist/server/portable.d.ts +27 -0
- package/dist/server/portable.js +2 -0
- package/dist/server.d.ts +9 -19
- package/dist/server.js +1 -12
- package/dist/shared.d.ts +36 -0
- package/dist/store-registry.d.ts +5 -1
- package/dist/sync.d.ts +10 -1
- package/dist/sync.js +1 -1
- package/dist/testing.d.ts +1 -0
- package/dist/testing.js +1 -22
- package/dist/tsdoc-metadata.json +1 -1
- package/dist/types-internal-2.d.ts +168 -0
- package/dist/{computed-types.d.ts → types-internal-3.d.ts} +1 -1
- package/dist/{options.d.ts → types-internal.d.ts} +26 -10
- package/dist/types.d.ts +12 -2
- package/package.json +111 -44
- package/dist/computed-types.js +0 -2
package/README.md
CHANGED
|
@@ -1,190 +1,1197 @@
|
|
|
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/badge/tree--shakeable-subpaths-0F766E?style=flat-square" alt="tree-shakeable via subpaths" />
|
|
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" />
|
|
8
15
|
|
|
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 />
|
|
20
|
+
|
|
21
|
+
**Stroid is the only state management library with a theoretical correctness argument, a matching implementation, and a certified benchmark suite proving it holds under production-grade concurrent SSR conditions.**
|
|
22
|
+
<br /><br />
|
|
23
|
+
|
|
24
|
+
# 🟣 Stroid - State Engine for TypeScript and React
|
|
9
25
|
**Named-store state engine for TypeScript and React.**
|
|
10
26
|
|
|
11
|
-
Every store has a name. Write to it from anywhere
|
|
27
|
+
Every store has a name. Write to it from anywhere: hooks, utilities, server, tests. Optional layers add persistence, sync, async fetch, SSR isolation, post-hydration consistency controls, and devtools without coupling to core logic.
|
|
28
|
+
<br />
|
|
29
|
+
[**Get Started**](#30-second-quickstart) | [**Why Stroid**](#why-stroid) | [**API Reference**](#full-api-reference) | [**PSR**](#psr---write-governance) | [**DevTools**](#devtools) | [**Examples**](#real-world-examples)
|
|
30
|
+
<br /><br />
|
|
31
|
+
|
|
32
|
+
**Certified benchmark suite (latest rerun: `2026-04-02`)**
|
|
33
|
+
<br />
|
|
34
|
+
`0` SSR correctness violations across `2 x 1,024` burst requests, `8,192` sustained requests, and `256` concurrent React streaming SSR requests, plus `0` detached leaks in warm-container/provider-model runs and `0` React concurrency invariant violations under `useTransition` and `useDeferredValue`.
|
|
35
|
+
<br />
|
|
36
|
+
[**Benchmark Report**](./docs/STROID/BENCHMARK.md) | Run: `npm run benchmark:guarantees`
|
|
37
|
+
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
> [!IMPORTANT]
|
|
42
|
+
> ## 🧠 What Is Stroid?
|
|
43
|
+
>
|
|
44
|
+
> A structured state management system focused on predictability, SSR safety, and debugging clarity.
|
|
45
|
+
>
|
|
46
|
+
> - Core store runtime (`createStore`, `setStore`, `getStore`)
|
|
47
|
+
> - React hooks (`useStore`, `useSelector`)
|
|
48
|
+
> - Async fetch/cache/revalidate
|
|
49
|
+
> - Optional features
|
|
50
|
+
> - SSR request isolation
|
|
51
|
+
> - Native PSR contract
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
<a id="30-second-quickstart"></a>
|
|
55
|
+
## ⚡ 30-Second Quickstart
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
>[!NOTE]
|
|
60
|
+
>```bash
|
|
61
|
+
>npm install stroid
|
|
62
|
+
>```
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
>[!NOTE]
|
|
66
|
+
>```ts
|
|
67
|
+
>import { createStore, setStore, getStore, configureStroid } from "stroid";
|
|
68
|
+
>import { installPersist } from "stroid/persist";
|
|
69
|
+
>import { installSync } from "stroid/sync";
|
|
70
|
+
>
|
|
71
|
+
>configureStroid({
|
|
72
|
+
> asyncAutoCreate: false,
|
|
73
|
+
> defaultSnapshotMode: "deep",
|
|
74
|
+
>});
|
|
75
|
+
>
|
|
76
|
+
>installPersist();
|
|
77
|
+
>installSync();
|
|
78
|
+
>//create store
|
|
79
|
+
>createStore("auth", { user: null, token: null });
|
|
80
|
+
>//create store with persist
|
|
81
|
+
>createStore("settings", { theme: "dark" }, { persist: true });
|
|
82
|
+
>//create store with sync and persist.
|
|
83
|
+
>createStore("session", { active: true }, { persist: true, sync: true });
|
|
84
|
+
>
|
|
85
|
+
>setStore("auth", "user", { id: "u1", name: "Asha" });
|
|
86
|
+
>const auth = getStore("auth");
|
|
87
|
+
>
|
|
88
|
+
>```
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Operational Notes
|
|
92
|
+
|
|
93
|
+
- Store names are runtime-validated. Avoid spaces and reserved keys like `__proto__`, `constructor`, and `prototype`.
|
|
94
|
+
- `useStore("name")` without a path or selector subscribes to the full store. Prefer `useSelector(...)` or path reads in hot React components.
|
|
95
|
+
- Hook string names are only strongly typed after `StoreStateMap` augmentation. Without it, `useStore("name")` reads are intentionally loose and typically resolve to `unknown`.
|
|
96
|
+
- Selector-heavy dev flows that read frozen state deep-clone by default for safe dependency tracking. If that overhead matters more than the extra safety, tune `selectorCloneFrozen`.
|
|
97
|
+
- `fetchStore(name, promise, ...)` accepts a direct Promise, but direct Promise inputs cannot use retries or replayable `refetchStore()` semantics. Use a URL string or factory when you need retry/backoff behavior.
|
|
98
|
+
- `asyncAutoCreate` is a development convenience, not a production safety feature. Leave it off in production to avoid typo-created phantom stores.
|
|
99
|
+
- `stroid/sync` uses same-origin `BroadcastChannel` transport. Stroid requests a fresh snapshot on startup, focus, and reconnect, but listener registration can still race under load, `policy: "insecure"` is an explicit opt-out, and open channels may reduce BFCache restores.
|
|
100
|
+
- `stroid/persist` relies on browser storage. `checksum: "hash"` is non-cryptographic, and Safari/WebKit can evict script-writable storage after roughly 7 days of inactivity, so persisted auth, carts, and drafts should have a server-backed recovery path.
|
|
101
|
+
- `hydrateStores(snapshot, options, trust, consistency?)` can add a bounded post-hydration consistency window. Stroid can defer early client writes, emit structured drift events, and reconcile per store with `server_wins`, `client_wins`, `merge`, or `invalidate_and_refetch`.
|
|
102
|
+
- React hooks are built on `useSyncExternalStore`; local concurrency certification now covers no-tearing invariants under `useTransition` and `useDeferredValue`.
|
|
103
|
+
- `stroid/server` is Node-only today because it depends on `node:async_hooks`. Edge runtimes and Workers need a different adapter.
|
|
104
|
+
- `stroid/server/portable` is the explicit request-scope boundary for serverless hand-offs, worker-style runtimes, and Server Actions. It does not rely on implicit async context; use the bound scope API it returns.
|
|
105
|
+
- Local provider-model certification now covers warm AWS Lambda-style Node handlers, Vercel render-to-action hand-off, and Cloudflare Workers-style explicit scopes, but you should still validate against your deployed provider before claiming production certification.
|
|
106
|
+
- Next.js Server Actions are a separate execution boundary. They do not inherit the original request carrier automatically; capture state on render and resume it with `stroid/server/portable`. The render-to-action hand-off is covered by `benchmark:next-server-actions` and [examples/next-app-router-server-actions.ts](./examples/next-app-router-server-actions.ts).
|
|
107
|
+
- Stroid can only guarantee request isolation for state written through Stroid APIs. Third-party singleton stores remain outside that guarantee.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
### Stroid PSR
|
|
112
|
+
|
|
113
|
+
Stroid ships a native PSR contract in `stroid/psr`.
|
|
114
|
+
It exposes committed snapshots, patch application APIs, and runtime graph/timing data used for governance flows.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## 🗺️ Ecosystem Map
|
|
119
|
+
|
|
120
|
+
Stroid is organized into focused sub-packages. Import only what you need.
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
stroid <- core public runtime
|
|
124
|
+
|- stroid/react <- React hooks
|
|
125
|
+
|- stroid/core <- minimal core surface
|
|
126
|
+
|- stroid/psr <- native PSR contract
|
|
127
|
+
|- stroid/async <- fetch/cache/revalidate
|
|
128
|
+
|- stroid/query <- reactQueryKey(), swrKey()
|
|
129
|
+
|- stroid/selectors <- selector helpers
|
|
130
|
+
|- stroid/computed <- computed stores
|
|
131
|
+
|- stroid/persist <- installPersist()
|
|
132
|
+
|- stroid/sync <- installSync()
|
|
133
|
+
|- stroid/devtools <- installDevtools(), history API
|
|
134
|
+
|- stroid/server <- SSR request-scoped registry
|
|
135
|
+
|- stroid/server/portable <- explicit request-scope bridge for serverless / workers / server actions
|
|
136
|
+
|- stroid/helpers <- entity/list/counter helpers
|
|
137
|
+
|- stroid/testing <- test helpers
|
|
138
|
+
|- stroid/runtime-tools <- observability APIs
|
|
139
|
+
|- stroid/runtime-admin <- clear helpers
|
|
140
|
+
|- stroid/feature <- feature plugin API
|
|
141
|
+
|- stroid/install <- installAllFeatures()
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Import note:
|
|
145
|
+
- Prefer subpath imports and avoid defaulting to the full `stroid` root import unless you need its broader compatibility surface.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
<a id="why-stroid"></a>
|
|
150
|
+
## 🤔 Why Stroid?
|
|
151
|
+
|
|
152
|
+
### Honest comparison
|
|
153
|
+
|
|
154
|
+
| Feature | **Stroid** | Redux Toolkit | Zustand | Jotai | Valtio |
|
|
155
|
+
|---|:---:|:---:|:---:|:---:|:---:|
|
|
156
|
+
| Write without reducers | ✅ | ❌ | ✅ | ✅ | ✅ |
|
|
157
|
+
| Named global stores | ✅ | ✅ | ⚠️ manual | ❌ | ❌ |
|
|
158
|
+
| Write governance (PSR) | ✅ | ❌ | ❌ | ❌ | ❌ |
|
|
159
|
+
| Built-in DevTools extension | ✅ | ✅ | ⚠️ limited | ❌ | ❌ |
|
|
160
|
+
| Computed / derived state | ✅ | ✅ | ⚠️ manual | ✅ | ✅ |
|
|
161
|
+
| Async data built-in | ✅ | ✅ RTK Query | ❌ | ⚠️ | ❌ |
|
|
162
|
+
| SSR / request isolation | ✅ | ⚠️ | ⚠️ | ✅ | ⚠️ |
|
|
163
|
+
| Atomic rollback guarantee | ✅ | ❌ | ❌ | ❌ | ❌ |
|
|
164
|
+
| Race resistance proof | ✅ | ❌ | ❌ | ❌ | ❌ |
|
|
165
|
+
| Determinism replay | ✅ | ❌ | ❌ | ❌ | ❌ |
|
|
166
|
+
| Ring-buffer event timeline | ✅ | ❌ | ❌ | ❌ | ❌ |
|
|
167
|
+
| TypeScript-first | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
168
|
+
|
|
169
|
+
> ⚠️ = possible with extra setup · ❌ = not supported natively
|
|
170
|
+
|
|
171
|
+
Stroid exposes governance-oriented write flows through `stroid/psr`, including committed snapshot reads, patch application APIs, runtime graph inspection, and timing contracts.
|
|
172
|
+
Benchmark report: [docs/STROID/BENCHMARK.md](./docs/STROID/BENCHMARK.md).
|
|
173
|
+
Benchmark script layout is categorized by domain under `scripts/core`, `scripts/ssr`, `scripts/hydration`, `scripts/react`, `scripts/guarantees`, and `scripts/comparison`.
|
|
174
|
+
|
|
175
|
+
Stroid is a fit when you need these together:
|
|
176
|
+
- Named global stores with direct writes
|
|
177
|
+
- Optional feature installs instead of mandatory side effects
|
|
178
|
+
- Strict hydration trust gate (`hydrateStores(..., ..., { allowTrusted: true })`)
|
|
179
|
+
- Governed post-hydration drift handling with per-store consistency policies
|
|
180
|
+
- Request-scoped SSR runtime (`createStoreForRequest`) with server guards
|
|
181
|
+
- PSR-style patch application and runtime graph inspection (`stroid/psr`)
|
|
182
|
+
|
|
183
|
+
If you only need ultra-minimal local state, `stroid/core` exists for a smaller surface.
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
<a id="full-api-reference"></a>
|
|
188
|
+
## 📚 Full API Reference
|
|
189
|
+
|
|
190
|
+
All examples use real APIs from this repository's current source.
|
|
191
|
+
|
|
192
|
+
## ⚙️ Core - `stroid`
|
|
193
|
+
|
|
194
|
+
### `createStore`
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
import { createStore } from "stroid";
|
|
198
|
+
|
|
199
|
+
createStore("cart", {
|
|
200
|
+
items: [],
|
|
201
|
+
total: 0,
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Creates a named store and registers its initial state.
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
### `setStore`
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
import { setStore } from "stroid";
|
|
213
|
+
|
|
214
|
+
setStore("cart", "total", 499);
|
|
215
|
+
setStore("cart", { currency: "NPR" });
|
|
216
|
+
setStore("cart", (draft: any) => {
|
|
217
|
+
draft.items.push({ id: "pizza", qty: 1 });
|
|
218
|
+
});
|
|
219
|
+
```
|
|
220
|
+
Updates existing store state by path, partial object merge, or mutator function.
|
|
221
|
+
The public root API intentionally does not export `replaceStore`; explicit full-store replacement is kept on the internal runtime/PSR side to reduce accidental overwrite mistakes.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
### `getStore`
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
import { getStore } from "stroid";
|
|
229
|
+
|
|
230
|
+
const cart = getStore("cart");
|
|
231
|
+
const total = getStore("cart", "total");
|
|
232
|
+
```
|
|
233
|
+
Reads current store state, optionally at a nested path.
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
### `hasStore`
|
|
12
238
|
|
|
13
239
|
```ts
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
240
|
+
import { hasStore, createStore } from "stroid";
|
|
241
|
+
|
|
242
|
+
if (!hasStore("cart")) {
|
|
243
|
+
createStore("cart", { items: [], total: 0 });
|
|
244
|
+
}
|
|
17
245
|
```
|
|
246
|
+
Checks whether a store is already registered.
|
|
18
247
|
|
|
19
248
|
---
|
|
20
249
|
|
|
21
|
-
|
|
250
|
+
### `resetStore`
|
|
22
251
|
|
|
23
|
-
```
|
|
24
|
-
|
|
252
|
+
```ts
|
|
253
|
+
import { resetStore } from "stroid";
|
|
254
|
+
|
|
255
|
+
resetStore("cart");
|
|
25
256
|
```
|
|
257
|
+
Resets a store back to its original initial state.
|
|
26
258
|
|
|
27
|
-
|
|
259
|
+
Reset clone behavior is configurable:
|
|
28
260
|
|
|
29
|
-
|
|
261
|
+
- Per store: `createStore("cart", initial, { resetClone: "deep" | "shallow" | "none" })`
|
|
262
|
+
- Global default: `configureStroid({ resetCloneMode: "deep" | "shallow" | "none" })`
|
|
30
263
|
|
|
31
264
|
---
|
|
32
265
|
|
|
33
|
-
|
|
266
|
+
### `deleteStore`
|
|
34
267
|
|
|
268
|
+
```ts
|
|
269
|
+
import { deleteStore } from "stroid";
|
|
270
|
+
|
|
271
|
+
deleteStore("cart");
|
|
35
272
|
```
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
273
|
+
Removes a store and its runtime metadata/subscriptions.
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
### `setStoreBatch`
|
|
278
|
+
|
|
279
|
+
```ts
|
|
280
|
+
import { setStoreBatch, setStore } from "stroid";
|
|
281
|
+
|
|
282
|
+
setStoreBatch(() => {
|
|
283
|
+
setStore("checkout", "coupon", "SAVE20");
|
|
284
|
+
setStore("checkout", "deliveryType", "priority");
|
|
285
|
+
setStore("checkout", "tip", 50);
|
|
286
|
+
});
|
|
287
|
+
```
|
|
288
|
+
`setStoreBatch` accepts only synchronous callbacks.
|
|
289
|
+
Runs multiple synchronous writes in one transaction-style batch.
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
### `hydrateStores`
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
import { hydrateStores } from "stroid";
|
|
297
|
+
|
|
298
|
+
const hydration = hydrateStores(
|
|
299
|
+
{
|
|
300
|
+
cart: { items: [{ id: "pizza", qty: 1 }], total: 499 },
|
|
301
|
+
profile: { name: "Asha" },
|
|
302
|
+
},
|
|
303
|
+
{},
|
|
304
|
+
{ allowTrusted: true },
|
|
305
|
+
{
|
|
306
|
+
contract: {
|
|
307
|
+
snapshotVersion: 3,
|
|
308
|
+
timestamp: Date.now(),
|
|
309
|
+
stores: {
|
|
310
|
+
cart: { authority: "server-authoritative" },
|
|
311
|
+
profile: { authority: "client-authoritative" },
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
bootWindow: {
|
|
315
|
+
mode: "manual",
|
|
316
|
+
fallbackMs: 3000,
|
|
317
|
+
},
|
|
318
|
+
policyMap: {
|
|
319
|
+
cart: "server_wins",
|
|
320
|
+
profile: "client_wins",
|
|
321
|
+
},
|
|
322
|
+
}
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
hydration.bootWindow?.close();
|
|
326
|
+
```
|
|
327
|
+
Hydrates many stores from a trusted snapshot payload. The optional fourth argument adds post-hydration drift controls, write deferral during the boot window, and structured drift diagnostics. Manual mode returns `hydration.bootWindow`, so your app can close the gate when its critical hydration boundary is ready.
|
|
328
|
+
|
|
329
|
+
Recommended rollout defaults:
|
|
330
|
+
- use `bootWindow: { mode: "manual", fallbackMs: 3000 }` when you need certification-grade control
|
|
331
|
+
- keep `bootWindowMs` or `bootWindow: { mode: "timer", ms: ... }` only as a compatibility fallback when you cannot close manually yet
|
|
332
|
+
- keep auth/session stores `server_wins`
|
|
333
|
+
- keep drafts/forms `client_wins`
|
|
334
|
+
- use `merge` for filters or preference bags and `invalidate_and_refetch` for replayable async caches
|
|
335
|
+
|
|
336
|
+
The full adoption guide, policy defaults, and runtime-tools workflow live in [Post-Hydration Consistency](./docs/STROID_SERVER/POST_HYDRATION_CONSISTENCY.md).
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
### `configureStroid`
|
|
341
|
+
|
|
342
|
+
```ts
|
|
343
|
+
import { configureStroid } from "stroid";
|
|
344
|
+
|
|
345
|
+
configureStroid({
|
|
346
|
+
asyncAutoCreate: false,
|
|
347
|
+
strictMutatorReturns: true,
|
|
348
|
+
defaultSnapshotMode: "deep",
|
|
349
|
+
});
|
|
350
|
+
```
|
|
351
|
+
Sets global runtime behavior such as async and snapshot defaults.
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
>[!TIP]
|
|
355
|
+
>
|
|
356
|
+
>Stroid is NOT for:
|
|
357
|
+
>- small apps
|
|
358
|
+
>- simple UI state
|
|
359
|
+
>- beginners learning React
|
|
360
|
+
>
|
|
361
|
+
>Stroid is for:
|
|
362
|
+
>- complex apps
|
|
363
|
+
>- SSR-heavy systems
|
|
364
|
+
>- multi-source async data
|
|
365
|
+
>- teams that need debugging + guarantees
|
|
366
|
+
> *If Still want to learn, then:
|
|
367
|
+
> - **Beginners:** If you are building a personal portfolio or a small app, you likely only need `createStore`, `getStore`, and the basic React hooks like `useStore`. I don't want you to read whole README.
|
|
368
|
+
> - **Intermediate:** We recommend reading the full README to understand features like batching, persistence,SSR Isolation,Sync,and async fetching. Don't take overhead about PSR FOR NOW, THINK THAT NOT EXIST AT ALL. UNTIL, I MAKE PROPER EXPLANATION VIDEO.
|
|
369
|
+
> - **Advanced:** Explore the `/docs` directory for deep dives into architecture, SSR isolation, and the PSR contract, DevTools.
|
|
370
|
+
---
|
|
371
|
+
## ⚛️ React Hooks - `stroid/react`
|
|
372
|
+
|
|
373
|
+
### `useStore`
|
|
374
|
+
|
|
375
|
+
```tsx
|
|
376
|
+
import { useStore } from "stroid/react";
|
|
377
|
+
|
|
378
|
+
function CartPanel() {
|
|
379
|
+
const cart = useStore("cart");
|
|
380
|
+
return <div>{cart ? `${cart.items.length} items` : "Cart empty"}</div>;
|
|
381
|
+
}
|
|
382
|
+
```
|
|
383
|
+
Subscribes React components to a store value (full store, path, or selector form).
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
### `useSelector`
|
|
388
|
+
|
|
389
|
+
```tsx
|
|
390
|
+
import { useSelector } from "stroid/react";
|
|
391
|
+
|
|
392
|
+
function CartTotal() {
|
|
393
|
+
const total = useSelector("cart", (s: any) => s?.total ?? 0);
|
|
394
|
+
return <strong>Rs. {total}</strong>;
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
Subscribes to a derived slice and re-renders only when selected output changes.
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
### `useStoreField`
|
|
402
|
+
|
|
403
|
+
```tsx
|
|
404
|
+
import { useStoreField } from "stroid/react";
|
|
405
|
+
|
|
406
|
+
function DeliveryTypeChip() {
|
|
407
|
+
const deliveryType = useStoreField("checkout", "deliveryType");
|
|
408
|
+
return <span>{deliveryType ?? "standard"}</span>;
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
Subscribes directly to one field/path inside a store.
|
|
412
|
+
|
|
413
|
+
---
|
|
414
|
+
|
|
415
|
+
### `useStoreStatic`
|
|
416
|
+
|
|
417
|
+
```tsx
|
|
418
|
+
import { useStoreStatic } from "stroid/react";
|
|
419
|
+
|
|
420
|
+
function DebugPanel() {
|
|
421
|
+
const snapshot = useStoreStatic("cart");
|
|
422
|
+
return <pre>{JSON.stringify(snapshot, null, 2)}</pre>;
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
Reads a snapshot once without live subscription updates.
|
|
426
|
+
|
|
427
|
+
---
|
|
428
|
+
|
|
429
|
+
### `useAsyncStore`
|
|
430
|
+
|
|
431
|
+
```tsx
|
|
432
|
+
import { useEffect } from "react";
|
|
433
|
+
import { useAsyncStore } from "stroid/react";
|
|
434
|
+
import { fetchStore } from "stroid/async";
|
|
435
|
+
|
|
436
|
+
function Menu() {
|
|
437
|
+
useEffect(() => {
|
|
438
|
+
void fetchStore("menu", "https://api.example.com/menu", { autoCreate: true });
|
|
439
|
+
}, []);
|
|
440
|
+
|
|
441
|
+
const { loading, error, data } = useAsyncStore("menu");
|
|
442
|
+
|
|
443
|
+
if (loading) return <p>Loading menu...</p>;
|
|
444
|
+
if (error) return <p>Failed to load menu</p>;
|
|
445
|
+
return <MenuList items={data ?? []} />;
|
|
446
|
+
}
|
|
447
|
+
```
|
|
448
|
+
Reads async store shape (`data/loading/error/status`) from an existing store.
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
### `useAsyncStoreSuspense`
|
|
453
|
+
|
|
454
|
+
```tsx
|
|
455
|
+
import { useAsyncStoreSuspense } from "stroid/react";
|
|
456
|
+
|
|
457
|
+
function MenuSuspense() {
|
|
458
|
+
const menu = useAsyncStoreSuspense<Array<{ id: string; name: string }>>(
|
|
459
|
+
"menu",
|
|
460
|
+
"https://api.example.com/menu",
|
|
461
|
+
{ autoCreate: true }
|
|
462
|
+
);
|
|
463
|
+
|
|
464
|
+
return <MenuList items={menu} />;
|
|
465
|
+
}
|
|
466
|
+
```
|
|
467
|
+
Integrates async store reads with React Suspense by throwing pending work.
|
|
468
|
+
|
|
469
|
+
---
|
|
470
|
+
|
|
471
|
+
### `useFormStore`
|
|
472
|
+
|
|
473
|
+
```tsx
|
|
474
|
+
import { createStore } from "stroid";
|
|
475
|
+
import { useFormStore } from "stroid/react";
|
|
476
|
+
|
|
477
|
+
createStore("loginForm", { email: "", password: "" });
|
|
478
|
+
|
|
479
|
+
function LoginForm() {
|
|
480
|
+
const email = useFormStore("loginForm", "email");
|
|
481
|
+
const password = useFormStore("loginForm", "password");
|
|
482
|
+
|
|
483
|
+
return (
|
|
484
|
+
<form>
|
|
485
|
+
<input value={email.value ?? ""} onChange={email.onChange} />
|
|
486
|
+
<input value={password.value ?? ""} onChange={password.onChange} type="password" />
|
|
487
|
+
<button disabled={!email.value || !password.value}>Sign in</button>
|
|
488
|
+
</form>
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
```
|
|
492
|
+
Binds a store field to form-style `value` and `onChange` helpers.
|
|
493
|
+
|
|
494
|
+
---
|
|
495
|
+
|
|
496
|
+
### `RegistryScope`
|
|
497
|
+
|
|
498
|
+
```tsx
|
|
499
|
+
import { RegistryScope } from "stroid/react";
|
|
500
|
+
|
|
501
|
+
function App({ registry }: { registry: any }) {
|
|
502
|
+
return (
|
|
503
|
+
<RegistryScope value={registry}>
|
|
504
|
+
<CartPanel />
|
|
505
|
+
<ProfilePanel />
|
|
506
|
+
</RegistryScope>
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
```
|
|
510
|
+
Scopes a React subtree to a specific store registry context.
|
|
511
|
+
|
|
512
|
+
---
|
|
513
|
+
|
|
514
|
+
## 𛲝Se Selectors & Computed
|
|
515
|
+
|
|
516
|
+
### `createSelector` - `stroid/selectors`
|
|
517
|
+
|
|
518
|
+
```ts
|
|
519
|
+
import { createSelector } from "stroid/selectors";
|
|
520
|
+
|
|
521
|
+
const selectItemCount = createSelector("cart", (s: any) => s?.items?.length ?? 0);
|
|
522
|
+
const count = selectItemCount();
|
|
523
|
+
```
|
|
524
|
+
Builds a memoized selector function for derived store reads.
|
|
525
|
+
|
|
526
|
+
---
|
|
527
|
+
|
|
528
|
+
### `subscribeWithSelector` - `stroid/selectors`
|
|
529
|
+
|
|
530
|
+
```ts
|
|
531
|
+
import { subscribeWithSelector } from "stroid/selectors";
|
|
532
|
+
|
|
533
|
+
const stop = subscribeWithSelector(
|
|
534
|
+
"cart",
|
|
535
|
+
(s: any) => s?.total,
|
|
536
|
+
Object.is,
|
|
537
|
+
(next, prev) => {
|
|
538
|
+
if (typeof prev === "number" && next > prev) {
|
|
539
|
+
// run side effect
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
);
|
|
543
|
+
|
|
544
|
+
stop();
|
|
545
|
+
```
|
|
546
|
+
Runs a listener only when the selected value changes by equality check.
|
|
547
|
+
|
|
548
|
+
---
|
|
549
|
+
|
|
550
|
+
### `createComputed` - `stroid/computed`
|
|
551
|
+
|
|
552
|
+
```ts
|
|
553
|
+
import { createComputed } from "stroid/computed";
|
|
554
|
+
import { getStore } from "stroid";
|
|
555
|
+
|
|
556
|
+
createComputed("deliveryFee", ["cart"], (cart: any) => (cart?.total ?? 0) > 1000 ? 0 : 60);
|
|
557
|
+
|
|
558
|
+
const fee = getStore("deliveryFee");
|
|
559
|
+
```
|
|
560
|
+
Creates a computed store derived from one or more dependency stores.
|
|
561
|
+
|
|
562
|
+
---
|
|
563
|
+
|
|
564
|
+
### `invalidateComputed` / `deleteComputed` / `isComputedStore`
|
|
565
|
+
|
|
566
|
+
```ts
|
|
567
|
+
import { invalidateComputed, deleteComputed, isComputedStore } from "stroid/computed";
|
|
568
|
+
|
|
569
|
+
invalidateComputed("deliveryFee");
|
|
570
|
+
deleteComputed("deliveryFee");
|
|
571
|
+
isComputedStore("deliveryFee");
|
|
572
|
+
```
|
|
573
|
+
Invalidates, removes, or checks computed-store registrations.
|
|
574
|
+
|
|
575
|
+
---
|
|
576
|
+
|
|
577
|
+
## ⏱️ Async - `stroid/async`
|
|
578
|
+
|
|
579
|
+
```ts
|
|
580
|
+
import { fetchStore, refetchStore, enableRevalidateOnFocus } from "stroid/async";
|
|
581
|
+
|
|
582
|
+
await fetchStore("menu", "https://api.example.com/menu");
|
|
583
|
+
await refetchStore("menu");
|
|
584
|
+
const stopFocusRevalidate = enableRevalidateOnFocus("menu");
|
|
585
|
+
|
|
586
|
+
stopFocusRevalidate();
|
|
587
|
+
```
|
|
588
|
+
Fetches/refetches remote data into stores with cache, dedupe, retries, and focus revalidation.
|
|
589
|
+
|
|
590
|
+
---
|
|
591
|
+
|
|
592
|
+
## PSR - Write Governance
|
|
593
|
+
|
|
594
|
+
```ts
|
|
595
|
+
import { applyStorePatch, applyStorePatchesAtomic } from "stroid/psr";
|
|
596
|
+
|
|
597
|
+
applyStorePatch({
|
|
598
|
+
id: "cart-total-set",
|
|
599
|
+
store: "cart",
|
|
600
|
+
path: ["total"],
|
|
601
|
+
op: "set",
|
|
602
|
+
value: 549,
|
|
603
|
+
meta: { timestamp: Date.now(), source: "setStore" },
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
applyStorePatchesAtomic([
|
|
607
|
+
{
|
|
608
|
+
id: "wallet-set",
|
|
609
|
+
store: "wallet",
|
|
610
|
+
path: ["balance"],
|
|
611
|
+
op: "set",
|
|
612
|
+
value: 900,
|
|
613
|
+
meta: { timestamp: Date.now(), source: "setStore" },
|
|
614
|
+
},
|
|
615
|
+
{
|
|
616
|
+
id: "order-set",
|
|
617
|
+
store: "order",
|
|
618
|
+
path: ["status"],
|
|
619
|
+
op: "set",
|
|
620
|
+
value: "paid",
|
|
621
|
+
meta: { timestamp: Date.now(), source: "setStore" },
|
|
622
|
+
},
|
|
623
|
+
]);
|
|
624
|
+
```
|
|
625
|
+
Applies patch-based governed writes with atomic multi-patch support.
|
|
626
|
+
|
|
627
|
+
---
|
|
628
|
+
|
|
629
|
+
## 🛡️ Features - `stroid/feature`
|
|
630
|
+
|
|
631
|
+
### Install - Opt-In Capabilities
|
|
632
|
+
|
|
633
|
+
```ts
|
|
634
|
+
import { installPersist } from "stroid/persist";
|
|
635
|
+
import { installSync } from "stroid/sync";
|
|
636
|
+
import { installDevtools } from "stroid/devtools";
|
|
637
|
+
|
|
638
|
+
installPersist();
|
|
639
|
+
installSync();
|
|
640
|
+
installDevtools();
|
|
641
|
+
```
|
|
642
|
+
Installs optional persist/sync/devtools features explicitly at app entry.
|
|
643
|
+
|
|
644
|
+
---
|
|
645
|
+
|
|
646
|
+
## 🌐 SSR - `stroid/server`
|
|
647
|
+
|
|
648
|
+
```ts
|
|
649
|
+
import { createStoreForRequest } from "stroid/server";
|
|
650
|
+
|
|
651
|
+
const requestScope = createStoreForRequest((api) => {
|
|
652
|
+
api.create("session", { userId: "u-1" });
|
|
653
|
+
api.create("cart", { items: [], total: 0 });
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
const html = requestScope.hydrate(() => renderToString(<App />));
|
|
657
|
+
const snapshot = requestScope.snapshot();
|
|
658
|
+
```
|
|
659
|
+
Creates per-request store scopes for SSR-safe hydrate/snapshot flows.
|
|
660
|
+
|
|
661
|
+
---
|
|
662
|
+
|
|
663
|
+
## 🔢 Helpers - `stroid/helpers`
|
|
664
|
+
|
|
665
|
+
```ts
|
|
666
|
+
import { createEntityStore, createListStore, createCounterStore } from "stroid/helpers";
|
|
667
|
+
|
|
668
|
+
const users = createEntityStore<{ id?: string; name: string }>("users");
|
|
669
|
+
users.upsert({ id: "u1", name: "Asha" });
|
|
670
|
+
users.remove("u1");
|
|
671
|
+
|
|
672
|
+
const tasks = createListStore("tasks", [] as string[]);
|
|
673
|
+
tasks.push("pick up order");
|
|
674
|
+
tasks.removeAt(0);
|
|
675
|
+
tasks.clear();
|
|
676
|
+
|
|
677
|
+
const retries = createCounterStore("retries", 0);
|
|
678
|
+
retries.inc();
|
|
679
|
+
retries.dec();
|
|
680
|
+
retries.set(5);
|
|
681
|
+
const retryValue = retries.get();
|
|
682
|
+
```
|
|
683
|
+
Provides ready-made entity, list, and counter store helpers.
|
|
684
|
+
|
|
685
|
+
---
|
|
686
|
+
|
|
687
|
+
## 🧪 Testing - `stroid/testing`
|
|
688
|
+
|
|
689
|
+
```ts
|
|
690
|
+
import { store } from "stroid";
|
|
691
|
+
import {
|
|
692
|
+
createMockStore,
|
|
693
|
+
resetAllStoresForTest,
|
|
694
|
+
withMockedTime,
|
|
695
|
+
benchmarkStoreSet,
|
|
696
|
+
} from "stroid/testing";
|
|
697
|
+
|
|
698
|
+
const mockOrder = createMockStore("order", { status: "draft" });
|
|
699
|
+
mockOrder.set({ status: "confirmed" });
|
|
700
|
+
|
|
701
|
+
withMockedTime(1700000000000, () => {
|
|
702
|
+
// Date.now() is fixed in this callback
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
const result = benchmarkStoreSet(store("cart"), 300);
|
|
706
|
+
const avgMs = result.avgMs;
|
|
707
|
+
|
|
708
|
+
resetAllStoresForTest();
|
|
709
|
+
```
|
|
710
|
+
Provides test helpers for mock stores, time control, reset, and micro-benchmarks.
|
|
711
|
+
|
|
712
|
+
---
|
|
713
|
+
|
|
714
|
+
## 📈 Runtime Observability - `stroid/runtime-tools`
|
|
715
|
+
|
|
716
|
+
```ts
|
|
717
|
+
import {
|
|
718
|
+
listStores,
|
|
719
|
+
getStoreMeta,
|
|
720
|
+
getMetrics,
|
|
721
|
+
getSubscriberCount,
|
|
722
|
+
getStoreHealth,
|
|
723
|
+
findColdStores,
|
|
724
|
+
getComputedGraph,
|
|
725
|
+
getComputedDeps,
|
|
726
|
+
getPersistQueueDepth,
|
|
727
|
+
} from "stroid/runtime-tools";
|
|
728
|
+
|
|
729
|
+
const stores = listStores();
|
|
730
|
+
const meta = getStoreMeta("cart");
|
|
731
|
+
const metrics = getMetrics("cart");
|
|
732
|
+
const subscribers = getSubscriberCount("cart");
|
|
733
|
+
const health = getStoreHealth();
|
|
734
|
+
const cold = findColdStores();
|
|
735
|
+
const graph = getComputedGraph();
|
|
736
|
+
const deps = getComputedDeps("deliveryFee");
|
|
737
|
+
const persistDepth = getPersistQueueDepth("cart");
|
|
738
|
+
```
|
|
739
|
+
Exposes runtime diagnostics for stores, metrics, health, and computed graph state.
|
|
740
|
+
Import only the functions you need. The internal helpers are grouped more narrowly now, but the published multi-entry build still shares runtime chunks, so the biggest remaining wins are still deeper than this surface split.
|
|
741
|
+
|
|
742
|
+
---
|
|
743
|
+
|
|
744
|
+
## 🗝️ Query Keys - `stroid/query`
|
|
745
|
+
|
|
746
|
+
```ts
|
|
747
|
+
import { reactQueryKey, swrKey } from "stroid/query";
|
|
748
|
+
|
|
749
|
+
const tanstackKey = reactQueryKey("cart");
|
|
750
|
+
const swrCacheKey = swrKey("cart", "summary");
|
|
751
|
+
```
|
|
752
|
+
Use `stroid/query` when you only need stable cache keys for TanStack Query or SWR.
|
|
753
|
+
The root `queryIntegrations` namespace still exists for compatibility, but `stroid/query` is the leaner path.
|
|
754
|
+
|
|
755
|
+
---
|
|
756
|
+
|
|
757
|
+
## 🔧 Runtime Admin - `stroid/runtime-admin`
|
|
758
|
+
|
|
759
|
+
```ts
|
|
760
|
+
import { clearAllStores, clearStores } from "stroid/runtime-admin";
|
|
761
|
+
|
|
762
|
+
clearStores("cart*");
|
|
763
|
+
clearAllStores();
|
|
764
|
+
```
|
|
765
|
+
Clears stores in bulk by pattern or globally.
|
|
766
|
+
|
|
767
|
+
---
|
|
768
|
+
|
|
769
|
+
## 🌉 DevTools Bridge - `stroid/devtools`
|
|
770
|
+
|
|
771
|
+
```ts
|
|
772
|
+
import { installDevtools, getHistory, clearHistory } from "stroid/devtools";
|
|
773
|
+
|
|
774
|
+
installDevtools();
|
|
775
|
+
|
|
776
|
+
const cartHistory = getHistory("cart");
|
|
777
|
+
// Array<HistoryEntry> where entry has: ts, action, prev, next, diff
|
|
778
|
+
|
|
779
|
+
clearHistory("cart");
|
|
780
|
+
```
|
|
781
|
+
Connects to devtools runtime and exposes local history read/clear APIs.
|
|
782
|
+
|
|
783
|
+
---
|
|
784
|
+
|
|
785
|
+
## 🔌 Feature Plugin API - `stroid/feature`
|
|
786
|
+
|
|
787
|
+
```ts
|
|
788
|
+
import {
|
|
789
|
+
registerStoreFeature,
|
|
790
|
+
hasRegisteredStoreFeature,
|
|
791
|
+
getRegisteredFeatureNames,
|
|
792
|
+
} from "stroid/feature";
|
|
793
|
+
|
|
794
|
+
registerStoreFeature("auditFeature", () => ({
|
|
795
|
+
onStoreCreate(ctx) {
|
|
796
|
+
// fires when a store is created
|
|
797
|
+
},
|
|
798
|
+
onStoreWrite(ctx) {
|
|
799
|
+
// fires on store write
|
|
800
|
+
},
|
|
801
|
+
}));
|
|
802
|
+
|
|
803
|
+
const hasAudit = hasRegisteredStoreFeature("auditFeature");
|
|
804
|
+
const featureNames = getRegisteredFeatureNames();
|
|
805
|
+
```
|
|
806
|
+
Registers custom feature runtimes with lifecycle hooks.
|
|
807
|
+
|
|
808
|
+
---
|
|
809
|
+
|
|
810
|
+
<a id="psr---write-governance"></a>
|
|
811
|
+
## 🛡️ PSR - Write Governance - `stroid/psr`
|
|
812
|
+
|
|
813
|
+
PSR (`stroid/psr`) is the public contract for:
|
|
814
|
+
- Committed snapshots
|
|
815
|
+
- Post-commit subscriptions
|
|
816
|
+
- Serializable patch application
|
|
817
|
+
- Runtime graph and timing contract inspection
|
|
818
|
+
|
|
819
|
+
### PSR API
|
|
820
|
+
|
|
821
|
+
```ts
|
|
822
|
+
import {
|
|
823
|
+
getStoreSnapshot,
|
|
824
|
+
getStoreSnapshotNoTrack,
|
|
825
|
+
subscribeStore,
|
|
826
|
+
applyStorePatch,
|
|
827
|
+
applyStorePatchesAtomic,
|
|
828
|
+
getRuntimeGraph,
|
|
829
|
+
getComputedDescriptor,
|
|
830
|
+
evaluateComputed,
|
|
831
|
+
getTimingContract,
|
|
832
|
+
} from "stroid/psr";
|
|
833
|
+
|
|
834
|
+
const committed = getStoreSnapshot("cart");
|
|
835
|
+
const committedNoTrack = getStoreSnapshotNoTrack("cart");
|
|
836
|
+
|
|
837
|
+
const unsubscribe = subscribeStore("cart", (next) => {
|
|
838
|
+
// handle committed updates
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
const graph = getRuntimeGraph();
|
|
842
|
+
const checkoutNode = graph.nodes.find(
|
|
843
|
+
(n) => n.storeId === "checkoutTotal" && (n.type === "computed" || n.type === "async-boundary")
|
|
844
|
+
);
|
|
845
|
+
|
|
846
|
+
if (checkoutNode) {
|
|
847
|
+
const descriptor = getComputedDescriptor(checkoutNode.id);
|
|
848
|
+
const preview = evaluateComputed(checkoutNode.id, {
|
|
849
|
+
cart: { items: [{ id: "pizza", qty: 2 }], total: 998 },
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
const timing = getTimingContract("cart");
|
|
854
|
+
```
|
|
855
|
+
Exposes committed snapshots, subscriptions, patch APIs, runtime graph, and timing contract.
|
|
856
|
+
---
|
|
857
|
+
|
|
858
|
+
<a id="devtools"></a>
|
|
859
|
+
## 🔬 DevTools - `stroid/devtools`
|
|
860
|
+
|
|
861
|
+
`stroid/devtools` integrates with the Redux DevTools browser extension and also keeps in-memory history per store.
|
|
862
|
+
|
|
863
|
+
What you get from code:
|
|
864
|
+
- `installDevtools()` to enable the devtools feature runtime
|
|
865
|
+
- `getHistory(name, limit?)` to read recorded store history
|
|
866
|
+
- `clearHistory(name?)` to clear one store or all stores
|
|
867
|
+
|
|
868
|
+
Setup:
|
|
869
|
+
1. Install Redux DevTools extension in your browser.
|
|
870
|
+
2. Call `installDevtools()` once in app entry.
|
|
871
|
+
3. Open browser DevTools and inspect the connected `stroid` session.
|
|
872
|
+
|
|
873
|
+
---
|
|
874
|
+
|
|
875
|
+
<a id="real-world-examples"></a>
|
|
876
|
+
## 🍕 Real-World Examples
|
|
877
|
+
|
|
878
|
+
### Food delivery cart (full flow)
|
|
879
|
+
|
|
880
|
+
```tsx
|
|
881
|
+
// app/entry.ts
|
|
882
|
+
import { configureStroid } from "stroid";
|
|
883
|
+
import { installPersist } from "stroid/persist";
|
|
884
|
+
import { installDevtools } from "stroid/devtools";
|
|
885
|
+
|
|
886
|
+
configureStroid({ asyncAutoCreate: false });
|
|
887
|
+
installPersist();
|
|
888
|
+
installDevtools();
|
|
889
|
+
|
|
890
|
+
// stores/cart.ts
|
|
891
|
+
import { createStore, setStoreBatch, setStore } from "stroid";
|
|
892
|
+
|
|
893
|
+
createStore("cart", { items: [], total: 0 });
|
|
894
|
+
createStore("checkout", { coupon: null, deliveryType: "standard", tip: 0 });
|
|
895
|
+
|
|
896
|
+
// components/CartPanel.tsx
|
|
897
|
+
import { useSelector, useStoreField } from "stroid/react";
|
|
898
|
+
|
|
899
|
+
function CartPanel() {
|
|
900
|
+
const total = useSelector("cart", (s: any) => s?.total ?? 0);
|
|
901
|
+
const deliveryType = useStoreField("checkout", "deliveryType");
|
|
902
|
+
|
|
903
|
+
function applyPromo() {
|
|
904
|
+
setStoreBatch(() => {
|
|
905
|
+
setStore("checkout", "coupon", "SAVE20");
|
|
906
|
+
setStore("checkout", "deliveryType", "priority");
|
|
907
|
+
setStore("checkout", "tip", 50);
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
return (
|
|
912
|
+
<div>
|
|
913
|
+
<p>Total: Rs. {total}</p>
|
|
914
|
+
<p>Delivery: {deliveryType}</p>
|
|
915
|
+
<button onClick={applyPromo}>Apply promo</button>
|
|
916
|
+
</div>
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
```
|
|
920
|
+
|
|
921
|
+
### Atomic payment (wallet + order status)
|
|
922
|
+
|
|
923
|
+
```ts
|
|
924
|
+
import { getStore } from "stroid";
|
|
925
|
+
import { applyStorePatchesAtomic } from "stroid/psr";
|
|
926
|
+
|
|
927
|
+
function confirmPayment(amount: number) {
|
|
928
|
+
const wallet = getStore("wallet") as { balance: number } | null;
|
|
929
|
+
const nextBalance = (wallet?.balance ?? 0) - amount;
|
|
930
|
+
|
|
931
|
+
applyStorePatchesAtomic([
|
|
932
|
+
{
|
|
933
|
+
id: "pay-wallet",
|
|
934
|
+
store: "wallet",
|
|
935
|
+
path: ["balance"],
|
|
936
|
+
op: "set",
|
|
937
|
+
value: nextBalance,
|
|
938
|
+
meta: { timestamp: Date.now(), source: "setStore" },
|
|
939
|
+
},
|
|
940
|
+
{
|
|
941
|
+
id: "pay-order",
|
|
942
|
+
store: "order",
|
|
943
|
+
path: ["status"],
|
|
944
|
+
op: "set",
|
|
945
|
+
value: "paid",
|
|
946
|
+
meta: { timestamp: Date.now(), source: "setStore" },
|
|
947
|
+
},
|
|
948
|
+
]);
|
|
949
|
+
}
|
|
950
|
+
```
|
|
951
|
+
Applies related wallet/order updates atomically so both succeed or fail together.
|
|
952
|
+
|
|
953
|
+
---
|
|
954
|
+
### Menu with Suspense
|
|
955
|
+
|
|
956
|
+
```tsx
|
|
957
|
+
import { Suspense } from "react";
|
|
958
|
+
import { useAsyncStoreSuspense } from "stroid/react";
|
|
959
|
+
|
|
960
|
+
function MenuList() {
|
|
961
|
+
const menu = useAsyncStoreSuspense<Array<{ id: string; name: string }>>(
|
|
962
|
+
"menu",
|
|
963
|
+
"https://api.example.com/menu",
|
|
964
|
+
{ autoCreate: true }
|
|
965
|
+
);
|
|
966
|
+
|
|
967
|
+
return <ul>{menu.map((item) => <li key={item.id}>{item.name}</li>)}</ul>;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
export function MenuPage() {
|
|
971
|
+
return (
|
|
972
|
+
<Suspense fallback={<p>Loading menu...</p>}>
|
|
973
|
+
<MenuList />
|
|
974
|
+
</Suspense>
|
|
975
|
+
);
|
|
976
|
+
}
|
|
977
|
+
```
|
|
978
|
+
Loads menu data through Suspense-friendly async store access.
|
|
979
|
+
|
|
980
|
+
---
|
|
981
|
+
|
|
982
|
+
## 🧱 Layer Map
|
|
983
|
+
|
|
984
|
+
```
|
|
985
|
+
+---------------------------------------------------------+
|
|
986
|
+
| your app |
|
|
987
|
+
+---------------------------------------------------------+
|
|
988
|
+
| useStore useSelector useAsyncStore useFormStore | stroid/react
|
|
989
|
+
+---------------------------------------------------------+
|
|
990
|
+
| createStore setStore getStore setStoreBatch | stroid
|
|
991
|
+
| createComputed createSelector createEntityStore |
|
|
992
|
+
+--------------+--------------+---------------------------+
|
|
993
|
+
| stroid/persist | stroid/sync | stroid/async |
|
|
994
|
+
| installPersist | installSync | fetch + cache + retry |
|
|
995
|
+
+--------------+--------------+---------------------------+
|
|
996
|
+
| stroid/server createStoreForRequest | SSR
|
|
997
|
+
+---------------------------------------------------------+
|
|
998
|
+
| stroid/devtools stroid/testing stroid/runtime-tools |
|
|
999
|
+
+---------------------------------------------------------+
|
|
51
1000
|
```
|
|
52
1001
|
|
|
53
1002
|
Each row is independent. Use only what you need.
|
|
54
1003
|
|
|
55
|
-
`stroid/core` exports only `createStore`, `setStore`, `getStore`, `hasStore`, `resetStore`, and `deleteStore`.
|
|
1004
|
+
`stroid/core` exports only `createStore`, `setStore`, `getStore`, `hasStore`, `resetStore`, and `deleteStore`.
|
|
1005
|
+
Import from `stroid` for batching/hydration/computed plus runtime metrics and config.
|
|
56
1006
|
|
|
57
|
-
## What Each Import Contains
|
|
1007
|
+
## 📦 What Each Import Contains
|
|
58
1008
|
|
|
59
|
-
- `stroid`:
|
|
60
|
-
- `stroid/
|
|
61
|
-
- `stroid/
|
|
62
|
-
- `stroid/
|
|
1009
|
+
- `stroid`: Core public runtime (`createStore`, `createStoreStrict`, `setStore`, `setStoreBatch`, `getStore`, `deleteStore`, `resetStore`, `hasStore`, `hydrateStores`), plus `configureStroid`, computed helpers, and health/metric helpers.
|
|
1010
|
+
- `stroid/psr`: PSR contract (`getStoreSnapshot`, `getStoreSnapshotNoTrack`, `subscribeStore`, `applyStorePatch`, `applyStorePatchesAtomic`, runtime graph/timing helpers).
|
|
1011
|
+
- `stroid/core`: Minimal CRUD runtime (`createStore`, `setStore`, `getStore`, `hasStore`, `resetStore`, `deleteStore`).
|
|
1012
|
+
- `stroid/react`: React hooks (`useStore`, `useSelector`, `useStoreField`, `useStoreStatic`, `useAsyncStore`, `useFormStore`, `useAsyncStoreSuspense`) and `RegistryScope`.
|
|
1013
|
+
- `stroid/async`: Async APIs (`fetchStore`, `refetchStore`, `enableRevalidateOnFocus`, `getAsyncMetrics`).
|
|
1014
|
+
- `stroid/query`: cache-key helpers (`reactQueryKey`, `swrKey`) without the fetcher helpers.
|
|
63
1015
|
- `stroid/selectors`: `createSelector`, `subscribeWithSelector`.
|
|
64
1016
|
- `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/
|
|
1017
|
+
- `stroid/persist`: `installPersist`.
|
|
1018
|
+
- `stroid/sync`: `installSync`.
|
|
1019
|
+
- `stroid/devtools`: `installDevtools`, `getHistory`, `clearHistory`.
|
|
1020
|
+
- `stroid/server`: `createStoreForRequest`.
|
|
1021
|
+
- `stroid/helpers`: `createEntityStore`, `createListStore`, `createCounterStore`.
|
|
1022
|
+
- `stroid/testing`: `createMockStore`, `resetAllStoresForTest`, `withMockedTime`, `benchmarkStoreSet`.
|
|
1023
|
+
- `stroid/runtime-tools`: Store/runtime observability APIs, including hydration drift reports and counters.
|
|
1024
|
+
- `stroid/runtime-admin`: `clearAllStores`, `clearStores`.
|
|
1025
|
+
- `stroid/feature`: Feature registration APIs.
|
|
1026
|
+
- `stroid/install`: `installPersist`, `installSync`, `installDevtools`, `installAllFeatures`.
|
|
73
1027
|
|
|
74
1028
|
---
|
|
75
1029
|
|
|
76
|
-
## Quick API Reference
|
|
1030
|
+
## 🧾 Quick API Reference
|
|
77
1031
|
|
|
78
1032
|
| API | Purpose |
|
|
79
1033
|
|-----|---------|
|
|
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
|
-
| `
|
|
96
|
-
| `
|
|
1034
|
+
| `createStore(name, state, options?)` | Define a store. Returns `StoreDefinition` or `undefined`. |
|
|
1035
|
+
| `createStoreStrict(name, state, options?)` | Define a store; throws if creation fails. |
|
|
1036
|
+
| `setStore(name, update)` | Merge object update into object store state. |
|
|
1037
|
+
| `setStore(name, path, value)` | Write by path. |
|
|
1038
|
+
| `setStore(name, draft => { ... })` | Mutator-style update. |
|
|
1039
|
+
| `getStore(name, path?)` | Read current state (or nested path). |
|
|
1040
|
+
| `deleteStore(name)` | Remove a store from registry. |
|
|
1041
|
+
| `resetStore(name)` | Restore initial state. |
|
|
1042
|
+
| `hasStore(name)` | Check if store exists. |
|
|
1043
|
+
| `setStoreBatch(fn)` | Group synchronous writes into one transaction. |
|
|
1044
|
+
| `hydrateStores(snapshot, options?, trust, consistency?)` | Hydrate trusted snapshot into runtime, optionally governing post-hydration drift. |
|
|
1045
|
+
| `configureStroid(config)` | Configure global/runtime behavior. |
|
|
1046
|
+
| `useStore(name, selectorOrPath?)` | React subscription hook. |
|
|
1047
|
+
| `useSelector(name, fn, equality?)` | Fine-grained React selector hook. |
|
|
1048
|
+
| `fetchStore(name, input, options?)` | Fetch remote data into store. |
|
|
1049
|
+
| `getAsyncMetrics(name?)` | Read global or per-store async counters. |
|
|
1050
|
+
| `createComputed(name, deps, fn)` | Define computed store. |
|
|
1051
|
+
| `createStoreForRequest(fn)` | Build SSR request-scoped store runtime. |
|
|
97
1052
|
|
|
98
1053
|
---
|
|
99
1054
|
|
|
100
|
-
## Module Import Map
|
|
1055
|
+
## 🧭 Module Import Map
|
|
101
1056
|
|
|
102
1057
|
```ts
|
|
103
1058
|
// Core
|
|
104
|
-
import {
|
|
105
|
-
|
|
1059
|
+
import {
|
|
1060
|
+
createStore,
|
|
1061
|
+
createStoreStrict,
|
|
1062
|
+
setStore,
|
|
1063
|
+
getStore,
|
|
1064
|
+
hasStore,
|
|
1065
|
+
deleteStore,
|
|
1066
|
+
resetStore,
|
|
1067
|
+
setStoreBatch,
|
|
1068
|
+
hydrateStores,
|
|
1069
|
+
configureStroid,
|
|
1070
|
+
} from "stroid";
|
|
1071
|
+
|
|
1072
|
+
// Native PSR contract
|
|
1073
|
+
import {
|
|
1074
|
+
getStoreSnapshot,
|
|
1075
|
+
getStoreSnapshotNoTrack,
|
|
1076
|
+
subscribeStore,
|
|
1077
|
+
applyStorePatch,
|
|
1078
|
+
applyStorePatchesAtomic,
|
|
1079
|
+
getRuntimeGraph,
|
|
1080
|
+
getComputedDescriptor,
|
|
1081
|
+
evaluateComputed,
|
|
1082
|
+
getTimingContract,
|
|
1083
|
+
} from "stroid/psr";
|
|
106
1084
|
|
|
107
|
-
// Minimal core (
|
|
108
|
-
import { createStore, setStore, getStore, hasStore,
|
|
109
|
-
resetStore, deleteStore } from "stroid/core"
|
|
1085
|
+
// Minimal core (subpath import)
|
|
1086
|
+
import { createStore, setStore, getStore, hasStore, resetStore, deleteStore } from "stroid/core";
|
|
110
1087
|
|
|
111
1088
|
// React
|
|
112
|
-
import {
|
|
113
|
-
|
|
114
|
-
|
|
1089
|
+
import {
|
|
1090
|
+
useStore,
|
|
1091
|
+
useSelector,
|
|
1092
|
+
useStoreField,
|
|
1093
|
+
useStoreStatic,
|
|
1094
|
+
useAsyncStore,
|
|
1095
|
+
useFormStore,
|
|
1096
|
+
useAsyncStoreSuspense,
|
|
1097
|
+
RegistryScope,
|
|
1098
|
+
} from "stroid/react";
|
|
115
1099
|
|
|
116
1100
|
// Async
|
|
117
|
-
import { fetchStore, refetchStore, enableRevalidateOnFocus } from "stroid/async"
|
|
1101
|
+
import { fetchStore, refetchStore, enableRevalidateOnFocus } from "stroid/async";
|
|
118
1102
|
|
|
119
|
-
//
|
|
120
|
-
import {
|
|
121
|
-
import { createComputed, invalidateComputed,
|
|
122
|
-
deleteComputed, isComputedStore } from "stroid/computed"
|
|
1103
|
+
// Query keys only
|
|
1104
|
+
import { reactQueryKey, swrKey } from "stroid/query";
|
|
123
1105
|
|
|
124
|
-
//
|
|
125
|
-
import {
|
|
126
|
-
import {
|
|
127
|
-
import { installDevtools } from "stroid/devtools"
|
|
1106
|
+
// Selectors & Computed
|
|
1107
|
+
import { createSelector, subscribeWithSelector } from "stroid/selectors";
|
|
1108
|
+
import { createComputed, invalidateComputed, deleteComputed, isComputedStore } from "stroid/computed";
|
|
128
1109
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
1110
|
+
// Features (explicit install - call once at app entry)
|
|
1111
|
+
import { installPersist } from "stroid/persist";
|
|
1112
|
+
import { installSync } from "stroid/sync";
|
|
1113
|
+
import { installDevtools, getHistory, clearHistory } from "stroid/devtools";
|
|
132
1114
|
|
|
1115
|
+
installPersist();
|
|
1116
|
+
installSync();
|
|
1117
|
+
installDevtools();
|
|
133
1118
|
|
|
134
1119
|
// Server / SSR
|
|
135
|
-
import { createStoreForRequest } from "stroid/server"
|
|
1120
|
+
import { createStoreForRequest } from "stroid/server";
|
|
136
1121
|
|
|
137
1122
|
// Helpers & Testing
|
|
138
|
-
import { createEntityStore, createListStore, createCounterStore } from "stroid/helpers"
|
|
139
|
-
import { createMockStore, resetAllStoresForTest,
|
|
140
|
-
withMockedTime, benchmarkStoreSet } from "stroid/testing"
|
|
141
|
-
|
|
142
|
-
// Runtime Observability
|
|
143
|
-
import { listStores, getStoreMeta, getMetrics,
|
|
144
|
-
getSubscriberCount, getStoreHealth, findColdStores,
|
|
145
|
-
getComputedGraph, getComputedDeps,
|
|
146
|
-
getPersistQueueDepth } from "stroid/runtime-tools"
|
|
147
|
-
import { clearAllStores, clearStores } from "stroid/runtime-admin"
|
|
1123
|
+
import { createEntityStore, createListStore, createCounterStore } from "stroid/helpers";
|
|
1124
|
+
import { createMockStore, resetAllStoresForTest, withMockedTime, benchmarkStoreSet } from "stroid/testing";
|
|
148
1125
|
|
|
149
|
-
//
|
|
150
|
-
import {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
1126
|
+
// Runtime Observability + Admin
|
|
1127
|
+
import {
|
|
1128
|
+
listStores,
|
|
1129
|
+
getStoreMeta,
|
|
1130
|
+
getMetrics,
|
|
1131
|
+
getSubscriberCount,
|
|
1132
|
+
getStoreHealth,
|
|
1133
|
+
findColdStores,
|
|
1134
|
+
getComputedGraph,
|
|
1135
|
+
getComputedDeps,
|
|
1136
|
+
getPersistQueueDepth,
|
|
1137
|
+
} from "stroid/runtime-tools";
|
|
1138
|
+
import { clearAllStores, clearStores } from "stroid/runtime-admin";
|
|
155
1139
|
|
|
156
1140
|
// Feature plugin API
|
|
157
|
-
import { registerStoreFeature,
|
|
158
|
-
|
|
159
|
-
|
|
1141
|
+
import { registerStoreFeature, hasRegisteredStoreFeature, getRegisteredFeatureNames } from "stroid/feature";
|
|
1142
|
+
|
|
1143
|
+
// Optional all-in-one installer
|
|
1144
|
+
import { installAllFeatures } from "stroid/install";
|
|
160
1145
|
```
|
|
161
1146
|
|
|
162
1147
|
---
|
|
163
1148
|
|
|
164
|
-
##
|
|
1149
|
+
## 🧷 Native PSR Contract
|
|
1150
|
+
|
|
1151
|
+
`stroid/psr` is the supported public surface for native PSR-style integration.
|
|
1152
|
+
|
|
1153
|
+
- Committed reads: `getStoreSnapshot()` and `getStoreSnapshotNoTrack()`
|
|
1154
|
+
- Committed-final observation: `subscribeStore()`
|
|
1155
|
+
- Serializable writes: `applyStorePatch()` and `applyStorePatchesAtomic()` with `set`, `merge`, `delete`, and `insert`
|
|
1156
|
+
- Runtime inspection: `getRuntimeGraph()`, `getComputedDescriptor()`, `evaluateComputed()`, and `getTimingContract()`
|
|
1157
|
+
|
|
1158
|
+
See [Native PSR Contract](./docs/STROID_PSR/INDEX.md) for full details.
|
|
1159
|
+
|
|
1160
|
+
---
|
|
1161
|
+
|
|
1162
|
+
## 📘 Docs
|
|
165
1163
|
|
|
166
1164
|
Full documentation in [`/docs`](./docs/):
|
|
167
1165
|
|
|
168
|
-
- [Architecture](./docs/
|
|
169
|
-
- [Core Concepts](./docs/
|
|
170
|
-
- [React Layer](./docs/
|
|
171
|
-
- [Async Layer](./docs/
|
|
172
|
-
- [Persistence](./docs/
|
|
173
|
-
- [Cross-tab Sync](./docs/
|
|
174
|
-
- [Computed Stores](./docs/
|
|
175
|
-
- [
|
|
176
|
-
- [
|
|
177
|
-
- [
|
|
178
|
-
- [Runtime Tools](./docs/
|
|
179
|
-
- [
|
|
1166
|
+
- [Architecture](./docs/STROID_ARCHITECTURE/AECHITECTURE.md) - layers, data flow, registry model
|
|
1167
|
+
- [Core Concepts](./docs/STROID_CORE/INDEX.md) - store lifecycle, options, write modes
|
|
1168
|
+
- [React Layer](./docs/STROID_REACT/INDEX.md) - hooks, selectors, SSR
|
|
1169
|
+
- [Async Layer](./docs/STROID_ASYNC/INDEX.md) - `fetchStore`, caching, revalidation
|
|
1170
|
+
- [Persistence](./docs/STROID_PERSIST/INDEX.md) - persist options, encryption, migrations
|
|
1171
|
+
- [Cross-tab Sync](./docs/STROID_SYNC/INDEX.md) - BroadcastChannel sync behavior
|
|
1172
|
+
- [Computed Stores](./docs/STROID_COMPUTED/INDEX.md) - reactive derived values
|
|
1173
|
+
- [Native PSR Contract](./docs/STROID_PSR/INDEX.md) - patch coverage, timing/governance, graph identity
|
|
1174
|
+
- [Server & SSR](./docs/STROID_SERVER/INDEX.md) - request-scoped stores, hydration
|
|
1175
|
+
- [Testing](./docs/STROID_TESTING/INDEX.md) - mock stores, resets, benchmarks
|
|
1176
|
+
- [Runtime Tools](./docs/STROID_RUNTIME_TOOLS/INDEX.md) - observability, health checks
|
|
1177
|
+
- [TypeScript Guide](./docs/STROID_TYPESCRIPT/INDEX.md)
|
|
1178
|
+
- [Full API Reference](./docs/api/stroid.api.md)
|
|
180
1179
|
- [Project Status](./STATUS.MD)
|
|
181
1180
|
- [Contributing](./CONTRIBUTING.md)
|
|
182
1181
|
|
|
183
1182
|
---
|
|
184
1183
|
|
|
185
|
-
## Changelog & License
|
|
1184
|
+
## 📝 Changelog & License
|
|
186
1185
|
|
|
187
1186
|
- [CHANGELOG](./CHANGELOG.md)
|
|
188
1187
|
- [MIT License](./LICENSE)
|
|
189
1188
|
- [Issues](https://github.com/Himesh-Bhattarai/stroid/issues)
|
|
190
1189
|
|
|
1190
|
+
|
|
1191
|
+
<div align="center">
|
|
1192
|
+
|
|
1193
|
+
**Made with care for developers who think about state seriously.**
|
|
1194
|
+
|
|
1195
|
+
[⭐ 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)
|
|
1196
|
+
|
|
1197
|
+
</div>
|