stroid 0.1.1 → 0.1.2

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.
Files changed (180) hide show
  1. package/CHANGELOG.md +210 -119
  2. package/README.md +104 -431
  3. package/dist/async.d.ts +42 -9
  4. package/dist/async.js +26 -26
  5. package/dist/async.js.map +1 -1
  6. package/dist/cache.d.ts +12 -0
  7. package/dist/computed.d.ts +40 -7
  8. package/dist/computed.js +11 -11
  9. package/dist/computed.js.map +1 -1
  10. package/dist/core.d.ts +5 -15
  11. package/dist/core.js +14 -15
  12. package/dist/core.js.map +1 -1
  13. package/dist/devtools.d.ts +30 -5
  14. package/dist/devtools.js +1 -1
  15. package/dist/devtools.js.map +1 -1
  16. package/dist/feature.d.ts +92 -14
  17. package/dist/feature.js +1 -1
  18. package/dist/feature.js.map +1 -1
  19. package/dist/helpers.d.ts +37 -3
  20. package/dist/helpers.js +14 -15
  21. package/dist/helpers.js.map +1 -1
  22. package/dist/index-internal.d.ts +44 -0
  23. package/dist/index.d.cts +169 -33
  24. package/dist/index.d.ts +169 -33
  25. package/dist/index.js +24 -23
  26. package/dist/index.js.map +1 -1
  27. package/dist/install.d.ts +6 -4
  28. package/dist/install.js +1 -1
  29. package/dist/install.js.map +1 -1
  30. package/dist/options.d.ts +295 -0
  31. package/dist/persist.d.ts +1 -1
  32. package/dist/persist.js +1 -1
  33. package/dist/persist.js.map +1 -1
  34. package/dist/react/index.d.ts +70 -0
  35. package/dist/react/index.js +38 -0
  36. package/dist/react/index.js.map +1 -0
  37. package/dist/registry.d.ts +117 -0
  38. package/dist/runtime-admin.d.ts +4 -2
  39. package/dist/runtime-admin.js +1 -1
  40. package/dist/runtime-admin.js.map +1 -1
  41. package/dist/runtime-tools.d.ts +66 -9
  42. package/dist/runtime-tools.js +2 -2
  43. package/dist/runtime-tools.js.map +1 -1
  44. package/dist/selectors.d.ts +4 -2
  45. package/dist/selectors.js +1 -1
  46. package/dist/selectors.js.map +1 -1
  47. package/dist/server.d.ts +30 -2
  48. package/dist/server.js +11 -10
  49. package/dist/server.js.map +1 -1
  50. package/dist/store-registry.d.ts +80 -0
  51. package/dist/sync.d.ts +1 -1
  52. package/dist/sync.js +1 -1
  53. package/dist/sync.js.map +1 -1
  54. package/dist/testing.d.ts +16 -4
  55. package/dist/testing.js +14 -15
  56. package/dist/testing.js.map +1 -1
  57. package/dist/tsdoc-metadata.json +11 -0
  58. package/dist/types/adapters/options.d.ts +335 -0
  59. package/dist/types/async/cache.d.ts +39 -0
  60. package/dist/types/async/clone.d.ts +10 -0
  61. package/dist/types/async/errors.d.ts +3 -0
  62. package/dist/types/async/fetch.d.ts +37 -0
  63. package/dist/types/async/inflight.d.ts +13 -0
  64. package/dist/types/async/rate.d.ts +5 -0
  65. package/dist/types/async/registry.d.ts +116 -0
  66. package/dist/types/async/request.d.ts +11 -0
  67. package/dist/types/async/retry.d.ts +10 -0
  68. package/dist/types/async.d.ts +10 -0
  69. package/dist/types/computed/computed-graph.d.ts +29 -0
  70. package/dist/types/computed/index.d.ts +16 -0
  71. package/dist/types/config.d.ts +10 -0
  72. package/dist/types/core/index.d.ts +11 -0
  73. package/dist/types/core/lifecycle-hooks.d.ts +16 -0
  74. package/dist/types/core/store-admin-impl.d.ts +9 -0
  75. package/dist/types/core/store-admin.d.ts +9 -0
  76. package/dist/types/core/store-core.d.ts +13 -0
  77. package/dist/types/core/store-create.d.ts +16 -0
  78. package/dist/types/core/store-hydrate-impl.d.ts +35 -0
  79. package/dist/types/core/store-hydrate.d.ts +9 -0
  80. package/dist/types/core/store-lifecycle/hooks.d.ts +19 -0
  81. package/dist/types/core/store-lifecycle/identity.d.ts +23 -0
  82. package/dist/types/core/store-lifecycle/registry.d.ts +53 -0
  83. package/dist/types/core/store-lifecycle/types.d.ts +67 -0
  84. package/dist/types/core/store-lifecycle/validation.d.ts +53 -0
  85. package/dist/types/core/store-name.d.ts +28 -0
  86. package/dist/types/core/store-notify.d.ts +12 -0
  87. package/dist/types/core/store-read.d.ts +18 -0
  88. package/dist/types/core/store-registry.d.ts +108 -0
  89. package/dist/types/core/store-replace-impl.d.ts +11 -0
  90. package/dist/types/core/store-replace.d.ts +9 -0
  91. package/dist/types/core/store-set-impl.d.ts +13 -0
  92. package/dist/types/core/store-set.d.ts +9 -0
  93. package/dist/types/core/store-shared/core.d.ts +13 -0
  94. package/dist/types/core/store-shared/notify.d.ts +12 -0
  95. package/dist/types/core/store-transaction.d.ts +26 -0
  96. package/dist/types/core/store-write-shared.d.ts +19 -0
  97. package/dist/types/core/store-write.d.ts +13 -0
  98. package/dist/types/features/feature-registry.d.ts +91 -0
  99. package/dist/types/features/lifecycle.d.ts +40 -0
  100. package/dist/types/index.d.ts +17 -0
  101. package/dist/types/integrations/query.d.ts +8 -0
  102. package/dist/types/internals/computed-order.d.ts +3 -0
  103. package/dist/types/internals/config.d.ts +116 -0
  104. package/dist/types/internals/diagnostics.d.ts +21 -0
  105. package/dist/types/internals/reporting.d.ts +9 -0
  106. package/dist/types/internals/store-admin.d.ts +7 -0
  107. package/dist/types/internals/store-ops.d.ts +13 -0
  108. package/dist/types/internals/test-reset.d.ts +2 -0
  109. package/dist/types/internals/write-context.d.ts +15 -0
  110. package/dist/types/notification/delivery.d.ts +3 -0
  111. package/dist/types/notification/index.d.ts +10 -0
  112. package/dist/types/notification/metrics.d.ts +12 -0
  113. package/dist/types/notification/priority.d.ts +9 -0
  114. package/dist/types/notification/scheduler.d.ts +11 -0
  115. package/dist/types/notification/snapshot.d.ts +8 -0
  116. package/dist/types/runtime-admin/index.d.ts +2 -0
  117. package/dist/types/runtime-tools/index.d.ts +58 -0
  118. package/dist/types/store.d.ts +16 -0
  119. package/dist/types/types/utility.d.ts +17 -0
  120. package/dist/types/utils/clone.d.ts +4 -0
  121. package/dist/types/utils/devfreeze.d.ts +2 -0
  122. package/dist/types/utils/hash.d.ts +8 -0
  123. package/dist/types/utils/path.d.ts +5 -0
  124. package/dist/types/utils/validation.d.ts +14 -0
  125. package/dist/types/utils.d.ts +13 -0
  126. package/dist/types.d.ts +65 -0
  127. package/dist/utility.d.ts +15 -0
  128. package/package.json +26 -11
  129. package/dist/_tsup-dts-rollup.d.cts +0 -2411
  130. package/dist/_tsup-dts-rollup.d.ts +0 -2411
  131. package/dist/async.cjs +0 -34
  132. package/dist/async.cjs.map +0 -1
  133. package/dist/async.d.cts +0 -9
  134. package/dist/computed.cjs +0 -13
  135. package/dist/computed.cjs.map +0 -1
  136. package/dist/computed.d.cts +0 -7
  137. package/dist/core.cjs +0 -24
  138. package/dist/core.cjs.map +0 -1
  139. package/dist/core.d.cts +0 -15
  140. package/dist/devtools.cjs +0 -2
  141. package/dist/devtools.cjs.map +0 -1
  142. package/dist/devtools.d.cts +0 -5
  143. package/dist/feature.cjs +0 -2
  144. package/dist/feature.cjs.map +0 -1
  145. package/dist/feature.d.cts +0 -14
  146. package/dist/helpers.cjs +0 -24
  147. package/dist/helpers.cjs.map +0 -1
  148. package/dist/helpers.d.cts +0 -3
  149. package/dist/index.cjs +0 -35
  150. package/dist/index.cjs.map +0 -1
  151. package/dist/install.cjs +0 -2
  152. package/dist/install.cjs.map +0 -1
  153. package/dist/install.d.cts +0 -4
  154. package/dist/persist.cjs +0 -2
  155. package/dist/persist.cjs.map +0 -1
  156. package/dist/persist.d.cts +0 -1
  157. package/dist/react.cjs +0 -36
  158. package/dist/react.cjs.map +0 -1
  159. package/dist/react.d.cts +0 -7
  160. package/dist/react.d.ts +0 -7
  161. package/dist/react.js +0 -36
  162. package/dist/react.js.map +0 -1
  163. package/dist/runtime-admin.cjs +0 -2
  164. package/dist/runtime-admin.cjs.map +0 -1
  165. package/dist/runtime-admin.d.cts +0 -2
  166. package/dist/runtime-tools.cjs +0 -4
  167. package/dist/runtime-tools.cjs.map +0 -1
  168. package/dist/runtime-tools.d.cts +0 -9
  169. package/dist/selectors.cjs +0 -2
  170. package/dist/selectors.cjs.map +0 -1
  171. package/dist/selectors.d.cts +0 -2
  172. package/dist/server.cjs +0 -12
  173. package/dist/server.cjs.map +0 -1
  174. package/dist/server.d.cts +0 -2
  175. package/dist/sync.cjs +0 -2
  176. package/dist/sync.cjs.map +0 -1
  177. package/dist/sync.d.cts +0 -1
  178. package/dist/testing.cjs +0 -24
  179. package/dist/testing.cjs.map +0 -1
  180. package/dist/testing.d.cts +0 -4
package/README.md CHANGED
@@ -6,20 +6,31 @@
6
6
  [![license](https://img.shields.io/npm/l/stroid)](./LICENSE)
7
7
  [![CI](https://img.shields.io/github/actions/workflow/status/Himesh-Bhattarai/stroid/ci.yml)](https://github.com/Himesh-Bhattarai/stroid/actions)
8
8
 
9
- **Named-store state engine for TypeScript and React.**
10
- Every store has a name. Write to it from anywhere — hooks, utilities, server, tests. Optional layers add persistence, sync, async fetch, SSR isolation, and devtools without touching your core logic.
9
+ **Named-store state engine for TypeScript and React.**
11
10
 
12
- > 🚀 **Power in 4 lines:** Create a store, read/write it, optionally persist, sync, or hydrate for SSR.
11
+ 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 your core logic.
13
12
 
14
- ```tsx
15
- createStore("user", { name: "Ava", role: "admin" }) // define once
16
- setStore("user", "name", "Kai") // write from anywhere
17
- const name = useStore("user", s => s.name) // React hook
13
+ ```ts
14
+ createStore("user", { name: "Ava", role: "admin" }) // define once
15
+ setStore("user", "name", "Kai") // write from anywhere
16
+ const name = useStore("user", s => s.name) // React hook (stroid/react)
18
17
  ```
19
18
 
20
19
  ---
21
20
 
22
- ## Layers
21
+ ## Install
22
+
23
+ ```bash
24
+ npm install stroid
25
+ ```
26
+
27
+ **Requirements:** Node `>=18`. React `>=18` (only if using `stroid/react`).
28
+
29
+ **ESM-only:** Stroid ships ESM only. If your toolchain requires CJS, use a bundler with ESM support (Vite, webpack 5, esbuild).
30
+
31
+ ---
32
+
33
+ ## Layer Map
23
34
 
24
35
  ```
25
36
  ┌─────────────────────────────────────────────────────────┐
@@ -39,19 +50,26 @@ const name = useStore("user", s => s.name) // React hook
39
50
  └─────────────────────────────────────────────────────────┘
40
51
  ```
41
52
 
42
- Each row is independent. Use only what you need.
43
-
44
- Note: `stroid/core` exports only `createStore`, `setStore`, `getStore`, and `deleteStore`. Import from `stroid` for the full core runtime (batching, reset, hydration, and hooks).
53
+ Each row is independent. Use only what you need.
45
54
 
46
- ---
55
+ `stroid/core` exports only `createStore`, `setStore`, `getStore`, `hasStore`, `resetStore`, and `deleteStore`. Import from `stroid` for the full runtime (batching, hydration, computed). React hooks live in `stroid/react`.
47
56
 
48
- ## Install
57
+ ## What Each Import Contains
49
58
 
50
- ```bash
51
- npm install stroid
52
- ```
53
-
54
- > **Note:** `main` is locked between releases. Active development is on the `dev` branch — PRs and forks should target `dev`. Commit messages follow [STATUS.md](./STATUS.md) conventions.
59
+ - `stroid`: Full runtime (batching, hydration, computed, async metrics, runtime tools). No React hooks.
60
+ - `stroid/core`: Minimal CRUD only (`createStore`, `setStore`, `getStore`, `hasStore`, `resetStore`, `deleteStore`).
61
+ - `stroid/react`: React hooks (`useStore`, `useSelector`, `useAsyncStore`, `useFormStore`, `useAsyncStoreSuspense`) + `RegistryScope`.
62
+ - `stroid/async`: `fetchStore`, cache, retry, revalidate helpers.
63
+ - `stroid/selectors`: `createSelector`, `subscribeWithSelector`.
64
+ - `stroid/computed`: `createComputed`, `invalidateComputed`, `deleteComputed`, `isComputedStore`.
65
+ - `stroid/persist`: Side-effect registration for persistence (localStorage/sessionStorage). Not tree-shakeable.
66
+ - `stroid/sync`: Side-effect registration for BroadcastChannel sync. Not tree-shakeable.
67
+ - `stroid/devtools`: Side-effect registration for Redux DevTools bridge. Not tree-shakeable.
68
+ - `stroid/server`: SSR registry helpers (`createStoreForRequest`).
69
+ - `stroid/helpers`: Entity/list/counter store helpers.
70
+ - `stroid/runtime-tools`: Observability and diagnostics.
71
+ - `stroid/runtime-admin`: Admin utilities (clear/flush).
72
+ - `stroid/testing`: Testing utilities (mocks, reset helpers, benchmarks).
55
73
 
56
74
  ---
57
75
 
@@ -59,452 +77,106 @@ npm install stroid
59
77
 
60
78
  | API | Purpose |
61
79
  |-----|---------|
62
- | `createStore(name, state, options?)` | Define a store |
63
- | `setStore(name, path, value)` | Write a value by path |
64
- | `setStore(name, draft => { })` | Mutate with a function |
65
- | `replaceStore(name, value)` | Replace an entire store |
66
- | `getStore(name, path?)` | Read a store (or a path inside it) |
67
- | `setStoreBatch(fn)` | Atomic multi-store write, rollback on error |
68
- | `useStore(name, selector?)` | React hook subscribes to a store |
69
- | `useSelector(name, fn)` | React hook fine-grained derived value |
70
- | `fetchStore(name, url, options?)` | Async fetch wired to store state |
71
- | `createComputed(name, deps, fn)` | Reactive derived store |
72
- | `createStoreForRequest(fn)` | Per-request SSR registry |
73
- | `hydrateStores(snapshot)` | Rehydrate on client from server state |
74
-
75
- ---
76
-
77
- ## Quick Start
78
-
79
- Three levels. Start where you are.
80
+ | `createStore(name, state, options?)` | Define a store. Returns `StoreDefinition \| undefined`. |
81
+ | `createStoreStrict(name, state, options?)` | Define a store; throw synchronously on failure. |
82
+ | `setStore(name, update)` | Shallow-merge an object update into the store. |
83
+ | `setStore(name, path, value)` | Write a value by dot-path or array path. |
84
+ | `setStore(name, draft => { })` | Mutate with a function (optional Immer support). |
85
+ | `replaceStore(name, value)` | Replace the entire store value. |
86
+ | `getStore(name, path?)` | Read a store (or a nested path). |
87
+ | `deleteStore(name)` | Remove a store from the registry. |
88
+ | `resetStore(name)` | Restore a store to its initial state. |
89
+ | `hasStore(name)` | Check if a store exists. |
90
+ | `setStoreBatch(fn)` | Atomic multi-store write rolls back all writes on failure. |
91
+ | `hydrateStores(snapshot, options?, trust)` | Rehydrate on client from a server snapshot. |
92
+ | `useStore(name, selector?)` | React hook — subscribes to a store. |
93
+ | `useSelector(name, fn)` | React hook — fine-grained derived value. |
94
+ | `fetchStore(name, url, options?)` | Async fetch wired to store state. |
95
+ | `createComputed(name, deps, fn)` | Reactive derived store. |
96
+ | `createStoreForRequest(fn)` | Per-request SSR registry. |
80
97
 
81
98
  ---
82
99
 
83
- ### Level 1 — The Basics
84
-
85
- **Create a store. Read it. Write to it.**
86
-
87
- ```ts
88
- import { createStore, getStore, setStore } from "stroid"
89
-
90
- createStore("counter", { count: 0 })
91
-
92
- setStore("counter", "count", 1)
93
- console.log(getStore("counter")) // { count: 1 }
94
- ```
95
-
96
- **Use it in React.**
97
-
98
- ```tsx
99
- import { useStore } from "stroid/react"
100
-
101
- function Counter() {
102
- const count = useStore("counter", s => s.count)
103
- return (
104
- <button onClick={() => setStore("counter", "count", count + 1)}>
105
- {count}
106
- </button>
107
- )
108
- }
109
- ```
110
-
111
- **Batch multiple writes — one notification, atomic rollback.**
112
-
113
- ```ts
114
- import { setStoreBatch, setStore } from "stroid"
115
-
116
- setStoreBatch(() => {
117
- setStore("cart", { items: [{ id: 1, price: 12 }] })
118
- setStore("ui", "loading", false)
119
- setStore("user", "lastSeen", Date.now())
120
- // if any write throws → all three roll back
121
- })
122
- ```
123
-
124
- **Typed store handle — trade string keys for compile-time safety.**
125
-
126
- ```ts
127
- import { store, createStore, setStore, getStore } from "stroid"
128
-
129
- const counter = store<"counter", { count: number }>("counter")
130
-
131
- createStore("counter", { count: 0 })
132
- setStore(counter, draft => { draft.count += 1 })
133
- console.log(getStore(counter, "count")) // 1
134
- ```
135
-
136
- **Type-safe string store names (module augmentation).**
137
-
138
- If you prefer `useStore("user")` and `setStore("user", ...)` with compile-time checking,
139
- augment `StoreStateMap` or `StrictStoreMap` in a `.d.ts` file:
140
-
141
- ```ts
142
- // src/stroid.d.ts
143
- declare module "stroid" {
144
- interface StoreStateMap {
145
- user: {
146
- name: string
147
- role: "admin" | "user"
148
- }
149
- }
150
- }
151
-
152
- // Optional strict opt-in for locked store names:
153
- // declare module "stroid" { interface StrictStoreMap { user: ... } }
154
- // If you import from "stroid/core", add the same module augmentation there.
155
- ```
156
-
157
-
158
- ---
159
-
160
- ### Level 2 — Real Features
161
-
162
- **Persist to localStorage — survives page reload.**
163
-
164
- > ⚡ **Tip:** Add `import "stroid/persist"` once at your app entry (e.g. `main.tsx`) to enable persistence globally. Any store with a `persist` option will activate automatically.
165
-
166
- ```ts
167
- import { createStore } from "stroid"
168
- import "stroid/persist"
169
-
170
- createStore("settings", { theme: "dark", lang: "en" }, {
171
- persist: {
172
- key: "app-settings",
173
- allowPlaintext: true,
174
- version: 2,
175
- migrate: (old, v) => v === 1 ? { ...old, lang: "en" } : old,
176
- }
177
- })
178
- ```
179
-
180
- **Sync across browser tabs — zero wiring.**
181
-
182
- > ⚡ **Tip:** Add `import "stroid/sync"` once at app entry. Any store with `sync: true` or `sync: { channel }` will start broadcasting automatically.
183
-
184
- ```ts
185
- import { createStore } from "stroid"
186
- import "stroid/sync"
187
-
188
- createStore("presence", { online: true, cursor: null }, {
189
- sync: { channel: "presence-sync" }
190
- // Lamport clock conflict resolution built in.
191
- // Stale messages from closed tabs auto-rejected.
192
- })
193
- ```
194
-
195
- **Persist + sync together.**
196
-
197
- ```ts
198
- import { createStore } from "stroid"
199
- import "stroid/persist"
200
- import "stroid/sync"
201
-
202
- createStore("settings", { theme: "dark", lang: "en" }, {
203
- persist: { key: "app-settings", allowPlaintext: true },
204
- sync: { channel: "settings-sync" },
205
- })
206
- // Change in one tab → persisted locally + broadcast to all other tabs.
207
- ```
208
-
209
- **Async fetch — SWR-style, wired directly to store state.**
210
-
211
- > ⚡ **Tip:** `fetchStore` manages `loading`, `error`, `data`, and `status` fields automatically. No separate state machine needed — just read `useStore("user")`.
212
-
213
- ```ts
214
- import { createStore } from "stroid"
215
- import { fetchStore } from "stroid/async"
216
- import { useStore } from "stroid/react"
217
-
218
- createStore("user", { data: null, loading: false, error: null, status: "idle" })
219
-
220
- const controller = new AbortController()
221
-
222
- fetchStore("user", "/api/user", {
223
- signal: controller.signal,
224
- ttl: 30_000, // 30s cache
225
- staleWhileRevalidate: true, // show stale, revalidate in background
226
- dedupe: true, // concurrent calls share one request
227
- retry: 3, // auto-retry on failure
228
- retryDelay: 400,
229
- transform: res => res.data, // shape the response
230
- onSuccess: data => console.log("fetched", data),
231
- onError: err => Sentry.captureException(err),
232
- })
233
-
234
- function UserCard() {
235
- const user = useStore("user")
236
- if (user?.loading) return <Spinner />
237
- if (user?.error) return <Error message={user.error} />
238
- return <div>{user?.data?.name}</div>
239
- }
240
- ```
241
-
242
- **Computed stores — reactive, cached, cycle-safe.**
243
-
244
- ```ts
245
- import { createStore } from "stroid"
246
- import { createComputed } from "stroid/computed"
247
-
248
- createStore("cart", { items: [] })
249
- createStore("discount", { pct: 10 })
250
-
251
- createComputed(
252
- "cartTotal",
253
- ["cart", "discount"],
254
- (cart, discount) => {
255
- const raw = cart.items.reduce((sum, i) => sum + i.price, 0)
256
- return raw * (1 - discount.pct / 100)
257
- }
258
- )
259
-
260
- // cartTotal updates whenever cart or discount changes.
261
- // Circular dependency detected at definition time.
262
- // Flush order is topologically sorted — always correct.
263
- ```
264
-
265
- **Entity store — built-in CRUD for collections.**
266
-
267
- ```ts
268
- import { createEntityStore } from "stroid/helpers"
269
-
270
- const users = createEntityStore("users")
271
-
272
- users.upsert({ id: "1", name: "Ava", role: "admin" })
273
- users.upsert({ id: "2", name: "Kai", role: "user" })
274
-
275
- console.log(users.get("1")) // { id: "1", name: "Ava", role: "admin" }
276
- console.log(users.getAll()) // [{ id: "1" }, { id: "2" }]
277
-
278
- users.remove("2")
279
- ```
280
-
281
- ---
282
-
283
- ### Level 3 — Production Patterns
284
-
285
- **SSR with per-request isolation — no cross-request leaks.**
286
-
287
- ```ts
288
- // app/api/render/route.ts (Next.js App Router)
289
- import { createStoreForRequest } from "stroid/server"
290
- import { renderToString } from "react-dom/server"
291
-
292
- export async function GET(req: Request) {
293
- const session = await getSession(req)
294
-
295
- // Each request gets a fully isolated registry.
296
- // AsyncLocalStorage ensures concurrent requests
297
- // never share store values or subscribers.
298
- const stores = createStoreForRequest((api) => {
299
- api.create("user", { name: session.user.name, role: session.user.role })
300
- api.create("cart", { items: [] })
301
- api.create("flags", session.featureFlags)
302
- })
303
-
304
- const html = stores.hydrate(() => renderToString(<App />))
305
- const state = stores.snapshot() // plain JSON → send to client
306
-
307
- return Response.json({ html, state })
308
- }
309
-
310
- // Client: rehydrate from server snapshot
311
- hydrateStores(window.__STROID_STATE__)
312
-
313
- Tip: For typed SSR APIs, either augment `StoreStateMap` or pass a generic:
314
- `createStoreForRequest<{ user: UserState }>((api) => { ... })`.
315
- ```
316
-
317
- **Middleware — intercept, transform, or veto any write.**
318
-
319
- ```ts
320
- createStore("cart", { items: [], total: 0 }, {
321
- middleware: (ctx) => {
322
- // ctx.action = "set" | "reset" | "hydrate"
323
- // ctx.prev = previous state
324
- // ctx.next = incoming state
325
- // return MIDDLEWARE_ABORT to cancel the write
326
- if (ctx.action === "set" && ctx.next.items.length > 100) {
327
- ctx.options.onError?.("Cart limit exceeded")
328
- return MIDDLEWARE_ABORT
329
- }
330
- // log every write to your analytics
331
- analytics.track("cart.updated", { prev: ctx.prev, next: ctx.next })
332
- return ctx.next
333
- }
334
- })
335
- ```
336
-
337
- **Persist with encryption — no plaintext secrets in localStorage.**
338
-
339
- ```ts
340
- import { createStore } from "stroid"
341
- import "stroid/persist"
342
-
343
- createStore("vault", { apiKey: "", token: "" }, {
344
- persist: {
345
- key: "secure-vault",
346
- encrypt: (data) => myAES.encrypt(JSON.stringify(data)),
347
- decrypt: (raw) => JSON.parse(myAES.decrypt(raw)),
348
- // sensitiveData: true blocks persist entirely if no encrypt is provided
349
- sensitiveData: true,
350
- onStorageCleared: ({ name, reason }) => {
351
- // fires when localStorage is cleared externally (another tab, devtools, etc.)
352
- console.warn(`${name} storage cleared: ${reason}`)
353
- redirectToLogin()
354
- },
355
- }
356
- })
357
- ```
358
-
359
- **Observability — inspect any store at runtime.**
360
-
361
- > ⚡ **Tip:** Add `import "stroid/devtools"` at app entry to enable time-travel history and store inspection. Use `getMetrics(name)` in production to track notification performance per store.
362
-
363
- ```ts
364
- import { getMetrics, getSubscriberCount, getComputedGraph } from "stroid/runtime-tools"
365
-
366
- // Per-store performance metrics
367
- const m = getMetrics("cart")
368
- // { notifyCount: 42, totalNotifyMs: 8.3, lastNotifyMs: 0.2 }
369
-
370
- // How many components are subscribed right now
371
- console.log(getSubscriberCount("cart")) // 3
372
-
373
- // Full computed dependency graph
374
- console.log(getComputedGraph())
375
- // { nodes: ["cartTotal"], edges: [{ from: "cart", to: "cartTotal" }] }
376
- ```
377
-
378
- **Global flush configuration — tune for your app's load profile.**
379
-
380
- ```ts
381
- import { configureStroid } from "stroid"
382
-
383
- configureStroid({
384
- // Route internal logs to your observability platform
385
- logSink: {
386
- warn: msg => Sentry.captureMessage(msg, "warning"),
387
- critical: msg => Sentry.captureException(new Error(msg)),
388
- },
389
-
390
- // Priority stores notify subscribers first
391
- flush: {
392
- priorityStores: ["auth", "user"],
393
- },
394
-
395
- // Revalidate async stores when tab regains focus
396
- revalidateOnFocus: {
397
- debounceMs: 500,
398
- maxConcurrent: 3,
399
- staggerMs: 100,
400
- },
401
- })
402
- ```
403
-
404
- **Large store performance (recommendations).**
405
-
406
- - Split stores by domain to keep hot updates small.
407
- - For large lists, prefer `snapshot: "shallow"` per store or `configureStroid({ snapshotStrategy: "shallow" })` globally.
408
- - Prefer path updates and targeted selectors (`useSelector`, `useStoreField`) over whole-store subscriptions.
409
-
410
- **Optional structural sharing for mutator updates.**
411
-
412
- ```ts
413
- import { configureStroid } from "stroid"
414
- import { produce } from "immer"
415
-
416
- configureStroid({ mutatorProduce: produce })
417
- ```
418
-
419
- If you prefer a shorthand, set `globalThis.__STROID_IMMER_PRODUCE__ = produce` once and use `configureStroid({ mutatorProduce: "immer" })`.
420
-
421
- **Testing — deterministic, isolated, zero globals.**
422
-
423
- ```ts
424
- import { createMockStore, resetAllStoresForTest } from "stroid/testing"
425
-
426
- beforeEach(() => resetAllStoresForTest())
427
-
428
- test("cart total updates when item added", () => {
429
- const cart = createMockStore("cart", { items: [] })
430
-
431
- setStore("cart", "items", [{ id: 1, price: 50 }])
432
-
433
- expect(getStore("cart", "items")).toHaveLength(1)
434
- expect(getStore("cartTotal")).toBe(45) // with 10% discount
435
- })
436
- ```
437
-
438
- ---
439
-
440
- ## Module Imports
100
+ ## Module Import Map
441
101
 
442
102
  ```ts
443
103
  // Core
444
- import { createStore, setStore, getStore, deleteStore,
445
- resetStore, hasStore, setStoreBatch, hydrateStores } from "stroid"
104
+ import { createStore, setStore, getStore, hasStore,
105
+ deleteStore, resetStore, setStoreBatch, hydrateStores } from "stroid"
106
+
107
+ // Minimal core (bundle-size-sensitive)
108
+ import { createStore, setStore, getStore, hasStore,
109
+ resetStore, deleteStore } from "stroid/core"
446
110
 
447
111
  // React
448
- import { useStore, useSelector, useStoreField,
449
- useAsyncStore, useFormStore, useAsyncStoreSuspense } from "stroid/react"
112
+ import { useStore, useSelector, useStoreField, useStoreStatic,
113
+ useAsyncStore, useFormStore, useAsyncStoreSuspense,
114
+ RegistryScope } from "stroid/react"
450
115
 
451
116
  // Async
452
117
  import { fetchStore, refetchStore, enableRevalidateOnFocus } from "stroid/async"
453
118
 
454
119
  // Selectors & Computed
455
120
  import { createSelector, subscribeWithSelector } from "stroid/selectors"
456
- import { createComputed, deleteComputed } from "stroid/computed"
121
+ import { createComputed, invalidateComputed,
122
+ deleteComputed, isComputedStore } from "stroid/computed"
457
123
 
458
124
  // Features (side-effect imports — register once at app entry)
459
125
  import "stroid/persist"
460
126
  import "stroid/sync"
461
127
  import "stroid/devtools"
462
128
 
129
+ // Note: stroid/persist and stroid/sync are side-effect entrypoints only.
130
+ // All types live on the main "stroid" entry.
131
+
463
132
  // Server / SSR
464
133
  import { createStoreForRequest } from "stroid/server"
465
134
 
466
135
  // Helpers & Testing
467
- import { createEntityStore, createCounterStore } from "stroid/helpers"
468
- import { createMockStore, resetAllStoresForTest } from "stroid/testing"
136
+ import { createEntityStore, createListStore, createCounterStore } from "stroid/helpers"
137
+ import { createMockStore, resetAllStoresForTest,
138
+ withMockedTime, benchmarkStoreSet } from "stroid/testing"
469
139
 
470
- // Runtime
471
- import { listStores, getMetrics, getComputedGraph } from "stroid/runtime-tools"
472
- import { clearAllStores } from "stroid/runtime-admin"
473
- ```
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"
474
146
 
475
- ---
147
+ // Devtools API (after `import "stroid/devtools"`)
148
+ import { getHistory, clearHistory } from "stroid/devtools"
476
149
 
477
- ## Behavior Notes
150
+ // Config
151
+ import { configureStroid, resetConfig,
152
+ registerMutatorProduce } from "stroid"
478
153
 
479
- - **Features are explicit.** `persist`, `sync`, and `devtools` require a side-effect import. Nothing loads you didn't ask for.
480
- - **Snapshot mode defaults to deep clone.** Subscribers and selectors always receive immutable snapshots.
481
- - **`setStoreBatch` is transactional.** All writes stage first. Commit happens only if the batch completes without error. On failure, all writes roll back.
482
- - **`setStore(name, data)` merges objects.** It shallow-merges into object stores. Use `replaceStore(name, value)` to replace the whole store.
483
- - **Typed string store names are opt-in.** If you want `setStore("user", "profile.name", ...)` to be checked, augment `StoreStateMap` or use typed store handles.
484
- - **SSR stores are request-scoped by default.** Global SSR stores require `{ allowSSRGlobalStore: true }`.
485
- - **`fetchStore` deduplicates by default.** Concurrent calls with the same store name share one in-flight request.
486
- - **Computed deps can be store names or handles.** Missing deps yield `null` until the dependency store is created.
487
- - **Persist defaults to `localStorage`.** Provide a custom `driver` for `sessionStorage`, `IndexedDB`, or any storage adapter.
488
- - **Sync uses `BroadcastChannel`.** Warns and no-ops gracefully when unavailable (Safari private mode, Node).
154
+ // Feature plugin API
155
+ import { registerStoreFeature,
156
+ hasRegisteredStoreFeature,
157
+ getRegisteredFeatureNames } from "stroid/feature"
158
+ ```
489
159
 
490
160
  ---
491
161
 
492
162
  ## Docs
493
163
 
494
- Full documentation, architecture guide, and examples:
495
-
496
- - [Start Here](./docs/start-here.md)
497
- - [Core API](./docs/core.md)
498
- - [React Layer](./docs/react.md)
499
- - [Async Layer](./docs/async.md)
500
- - [Persistence](./docs/persist.md)
501
- - [Cross-tab Sync](./docs/sync.md)
502
- - [Server & SSR](./docs/server.md)
503
- - [Computed Stores](./docs/computed.md)
504
- - [Selectors](./docs/selectors.md)
505
- - [Testing](./docs/testing.md)
506
- - [Devtools](./docs/devtools.md)
507
- - [Runtime Tools](./docs/runtime.md)
164
+ Full documentation in [`/docs`](./docs/):
165
+
166
+ - [Architecture](./docs/architecture/ARCHITECTURE.md) — layers, data flow, registry model
167
+ - [Core Concepts](./docs/core-concepts/STORES.md) — store lifecycle, options, write modes
168
+ - [React Layer](./docs/guides/REACT.md) — hooks, selectors, SSR
169
+ - [Async Layer](./docs/guides/ASYNC.md) — `fetchStore`, caching, revalidation
170
+ - [Persistence](./docs/guides/PERSIST.md) — `localStorage`, encryption, migrations
171
+ - [Cross-tab Sync](./docs/guides/SYNC.md) — `BroadcastChannel`, conflict resolution
172
+ - [Computed Stores](./docs/guides/COMPUTED.md) — reactive derived values
173
+ - [Server & SSR](./docs/guides/SERVER.md) — request-scoped stores, hydration
174
+ - [Testing](./docs/guides/TESTING.md) — mock stores, resets, benchmarks
175
+ - [Devtools](./docs/guides/DEVTOOLS.md) — history, redaction
176
+ - [Runtime Tools](./docs/guides/RUNTIME_TOOLS.md) — observability, health checks
177
+ - [Full API Reference](./docs/api/API_REFERENCE.md)
178
+ - [Project Status](./STATUS.MD)
179
+ - [Contributing](./CONTRIBUTING.md)
508
180
 
509
181
  ---
510
182
 
@@ -513,3 +185,4 @@ Full documentation, architecture guide, and examples:
513
185
  - [CHANGELOG](./CHANGELOG.md)
514
186
  - [MIT License](./LICENSE)
515
187
  - [Issues](https://github.com/Himesh-Bhattarai/stroid/issues)
188
+
package/dist/async.d.ts CHANGED
@@ -1,9 +1,42 @@
1
- export { FetchOptions_alias_2 as FetchOptions } from './_tsup-dts-rollup.js';
2
- export { FetchInput_alias_1 as FetchInput } from './_tsup-dts-rollup.js';
3
- export { AsyncStateSnapshot_alias_2 as AsyncStateSnapshot } from './_tsup-dts-rollup.js';
4
- export { AsyncStateAdapter_alias_2 as AsyncStateAdapter } from './_tsup-dts-rollup.js';
5
- export { fetchStore_alias_1 as fetchStore } from './_tsup-dts-rollup.js';
6
- export { refetchStore_alias_1 as refetchStore } from './_tsup-dts-rollup.js';
7
- export { enableRevalidateOnFocus_alias_1 as enableRevalidateOnFocus } from './_tsup-dts-rollup.js';
8
- export { getAsyncMetrics_alias_1 as getAsyncMetrics } from './_tsup-dts-rollup.js';
9
- export { _resetAsyncStateForTests_alias_1 as _resetAsyncStateForTests } from './_tsup-dts-rollup.js';
1
+ import { F as FetchInput } from './cache.js';
2
+ import { a as StoreDefinition, b as StoreKey, c as StoreName } from './types.js';
3
+ import { F as FetchOptions } from './registry.js';
4
+ export { a as AsyncStateAdapter, b as AsyncStateSnapshot } from './registry.js';
5
+ import './utility.js';
6
+
7
+ declare function fetchStore<Name extends string, State>(name: StoreDefinition<Name, State>, urlOrRequest: FetchInput, options?: FetchOptions): Promise<unknown>;
8
+ declare function fetchStore<Name extends string, State>(name: StoreKey<Name, State>, urlOrRequest: FetchInput, options?: FetchOptions): Promise<unknown>;
9
+ declare function fetchStore<Name extends StoreName>(name: Name, urlOrRequest: FetchInput, options?: FetchOptions): Promise<unknown>;
10
+ declare function refetchStore<Name extends string, State>(name: StoreDefinition<Name, State>): Promise<unknown>;
11
+ declare function refetchStore<Name extends string, State>(name: StoreKey<Name, State>): Promise<unknown>;
12
+ declare function refetchStore<Name extends StoreName>(name: Name): Promise<unknown>;
13
+ declare function enableRevalidateOnFocus<Name extends string, State>(name: StoreDefinition<Name, State>, overrides?: Partial<FetchOptions> & {
14
+ debounceMs?: number;
15
+ maxConcurrent?: number;
16
+ staggerMs?: number;
17
+ priority?: "high" | "normal";
18
+ }): (() => void);
19
+ declare function enableRevalidateOnFocus<Name extends string, State>(name: StoreKey<Name, State>, overrides?: Partial<FetchOptions> & {
20
+ debounceMs?: number;
21
+ maxConcurrent?: number;
22
+ staggerMs?: number;
23
+ priority?: "high" | "normal";
24
+ }): (() => void);
25
+ declare function enableRevalidateOnFocus<Name extends StoreName>(name?: Name | "*", overrides?: Partial<FetchOptions> & {
26
+ debounceMs?: number;
27
+ maxConcurrent?: number;
28
+ staggerMs?: number;
29
+ priority?: "high" | "normal";
30
+ }): (() => void);
31
+ declare const getAsyncMetrics: () => {
32
+ cacheHits: number;
33
+ cacheMisses: number;
34
+ dedupes: number;
35
+ requests: number;
36
+ failures: number;
37
+ avgMs: number;
38
+ lastMs: number;
39
+ };
40
+ declare const _resetAsyncStateForTests: () => void;
41
+
42
+ export { FetchInput, FetchOptions, _resetAsyncStateForTests, enableRevalidateOnFocus, fetchStore, getAsyncMetrics, refetchStore };