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.
- package/CHANGELOG.md +210 -119
- package/README.md +104 -431
- package/dist/async.d.ts +42 -9
- package/dist/async.js +26 -26
- package/dist/async.js.map +1 -1
- package/dist/cache.d.ts +12 -0
- package/dist/computed.d.ts +40 -7
- package/dist/computed.js +11 -11
- package/dist/computed.js.map +1 -1
- package/dist/core.d.ts +5 -15
- package/dist/core.js +14 -15
- package/dist/core.js.map +1 -1
- package/dist/devtools.d.ts +30 -5
- package/dist/devtools.js +1 -1
- package/dist/devtools.js.map +1 -1
- package/dist/feature.d.ts +92 -14
- package/dist/feature.js +1 -1
- package/dist/feature.js.map +1 -1
- package/dist/helpers.d.ts +37 -3
- package/dist/helpers.js +14 -15
- package/dist/helpers.js.map +1 -1
- package/dist/index-internal.d.ts +44 -0
- package/dist/index.d.cts +169 -33
- package/dist/index.d.ts +169 -33
- package/dist/index.js +24 -23
- package/dist/index.js.map +1 -1
- package/dist/install.d.ts +6 -4
- package/dist/install.js +1 -1
- package/dist/install.js.map +1 -1
- package/dist/options.d.ts +295 -0
- package/dist/persist.d.ts +1 -1
- package/dist/persist.js +1 -1
- package/dist/persist.js.map +1 -1
- package/dist/react/index.d.ts +70 -0
- package/dist/react/index.js +38 -0
- package/dist/react/index.js.map +1 -0
- package/dist/registry.d.ts +117 -0
- package/dist/runtime-admin.d.ts +4 -2
- package/dist/runtime-admin.js +1 -1
- package/dist/runtime-admin.js.map +1 -1
- package/dist/runtime-tools.d.ts +66 -9
- package/dist/runtime-tools.js +2 -2
- package/dist/runtime-tools.js.map +1 -1
- package/dist/selectors.d.ts +4 -2
- package/dist/selectors.js +1 -1
- package/dist/selectors.js.map +1 -1
- package/dist/server.d.ts +30 -2
- package/dist/server.js +11 -10
- package/dist/server.js.map +1 -1
- package/dist/store-registry.d.ts +80 -0
- package/dist/sync.d.ts +1 -1
- package/dist/sync.js +1 -1
- package/dist/sync.js.map +1 -1
- package/dist/testing.d.ts +16 -4
- package/dist/testing.js +14 -15
- package/dist/testing.js.map +1 -1
- package/dist/tsdoc-metadata.json +11 -0
- package/dist/types/adapters/options.d.ts +335 -0
- package/dist/types/async/cache.d.ts +39 -0
- package/dist/types/async/clone.d.ts +10 -0
- package/dist/types/async/errors.d.ts +3 -0
- package/dist/types/async/fetch.d.ts +37 -0
- package/dist/types/async/inflight.d.ts +13 -0
- package/dist/types/async/rate.d.ts +5 -0
- package/dist/types/async/registry.d.ts +116 -0
- package/dist/types/async/request.d.ts +11 -0
- package/dist/types/async/retry.d.ts +10 -0
- package/dist/types/async.d.ts +10 -0
- package/dist/types/computed/computed-graph.d.ts +29 -0
- package/dist/types/computed/index.d.ts +16 -0
- package/dist/types/config.d.ts +10 -0
- package/dist/types/core/index.d.ts +11 -0
- package/dist/types/core/lifecycle-hooks.d.ts +16 -0
- package/dist/types/core/store-admin-impl.d.ts +9 -0
- package/dist/types/core/store-admin.d.ts +9 -0
- package/dist/types/core/store-core.d.ts +13 -0
- package/dist/types/core/store-create.d.ts +16 -0
- package/dist/types/core/store-hydrate-impl.d.ts +35 -0
- package/dist/types/core/store-hydrate.d.ts +9 -0
- package/dist/types/core/store-lifecycle/hooks.d.ts +19 -0
- package/dist/types/core/store-lifecycle/identity.d.ts +23 -0
- package/dist/types/core/store-lifecycle/registry.d.ts +53 -0
- package/dist/types/core/store-lifecycle/types.d.ts +67 -0
- package/dist/types/core/store-lifecycle/validation.d.ts +53 -0
- package/dist/types/core/store-name.d.ts +28 -0
- package/dist/types/core/store-notify.d.ts +12 -0
- package/dist/types/core/store-read.d.ts +18 -0
- package/dist/types/core/store-registry.d.ts +108 -0
- package/dist/types/core/store-replace-impl.d.ts +11 -0
- package/dist/types/core/store-replace.d.ts +9 -0
- package/dist/types/core/store-set-impl.d.ts +13 -0
- package/dist/types/core/store-set.d.ts +9 -0
- package/dist/types/core/store-shared/core.d.ts +13 -0
- package/dist/types/core/store-shared/notify.d.ts +12 -0
- package/dist/types/core/store-transaction.d.ts +26 -0
- package/dist/types/core/store-write-shared.d.ts +19 -0
- package/dist/types/core/store-write.d.ts +13 -0
- package/dist/types/features/feature-registry.d.ts +91 -0
- package/dist/types/features/lifecycle.d.ts +40 -0
- package/dist/types/index.d.ts +17 -0
- package/dist/types/integrations/query.d.ts +8 -0
- package/dist/types/internals/computed-order.d.ts +3 -0
- package/dist/types/internals/config.d.ts +116 -0
- package/dist/types/internals/diagnostics.d.ts +21 -0
- package/dist/types/internals/reporting.d.ts +9 -0
- package/dist/types/internals/store-admin.d.ts +7 -0
- package/dist/types/internals/store-ops.d.ts +13 -0
- package/dist/types/internals/test-reset.d.ts +2 -0
- package/dist/types/internals/write-context.d.ts +15 -0
- package/dist/types/notification/delivery.d.ts +3 -0
- package/dist/types/notification/index.d.ts +10 -0
- package/dist/types/notification/metrics.d.ts +12 -0
- package/dist/types/notification/priority.d.ts +9 -0
- package/dist/types/notification/scheduler.d.ts +11 -0
- package/dist/types/notification/snapshot.d.ts +8 -0
- package/dist/types/runtime-admin/index.d.ts +2 -0
- package/dist/types/runtime-tools/index.d.ts +58 -0
- package/dist/types/store.d.ts +16 -0
- package/dist/types/types/utility.d.ts +17 -0
- package/dist/types/utils/clone.d.ts +4 -0
- package/dist/types/utils/devfreeze.d.ts +2 -0
- package/dist/types/utils/hash.d.ts +8 -0
- package/dist/types/utils/path.d.ts +5 -0
- package/dist/types/utils/validation.d.ts +14 -0
- package/dist/types/utils.d.ts +13 -0
- package/dist/types.d.ts +65 -0
- package/dist/utility.d.ts +15 -0
- package/package.json +26 -11
- package/dist/_tsup-dts-rollup.d.cts +0 -2411
- package/dist/_tsup-dts-rollup.d.ts +0 -2411
- package/dist/async.cjs +0 -34
- package/dist/async.cjs.map +0 -1
- package/dist/async.d.cts +0 -9
- package/dist/computed.cjs +0 -13
- package/dist/computed.cjs.map +0 -1
- package/dist/computed.d.cts +0 -7
- package/dist/core.cjs +0 -24
- package/dist/core.cjs.map +0 -1
- package/dist/core.d.cts +0 -15
- package/dist/devtools.cjs +0 -2
- package/dist/devtools.cjs.map +0 -1
- package/dist/devtools.d.cts +0 -5
- package/dist/feature.cjs +0 -2
- package/dist/feature.cjs.map +0 -1
- package/dist/feature.d.cts +0 -14
- package/dist/helpers.cjs +0 -24
- package/dist/helpers.cjs.map +0 -1
- package/dist/helpers.d.cts +0 -3
- package/dist/index.cjs +0 -35
- package/dist/index.cjs.map +0 -1
- package/dist/install.cjs +0 -2
- package/dist/install.cjs.map +0 -1
- package/dist/install.d.cts +0 -4
- package/dist/persist.cjs +0 -2
- package/dist/persist.cjs.map +0 -1
- package/dist/persist.d.cts +0 -1
- package/dist/react.cjs +0 -36
- package/dist/react.cjs.map +0 -1
- package/dist/react.d.cts +0 -7
- package/dist/react.d.ts +0 -7
- package/dist/react.js +0 -36
- package/dist/react.js.map +0 -1
- package/dist/runtime-admin.cjs +0 -2
- package/dist/runtime-admin.cjs.map +0 -1
- package/dist/runtime-admin.d.cts +0 -2
- package/dist/runtime-tools.cjs +0 -4
- package/dist/runtime-tools.cjs.map +0 -1
- package/dist/runtime-tools.d.cts +0 -9
- package/dist/selectors.cjs +0 -2
- package/dist/selectors.cjs.map +0 -1
- package/dist/selectors.d.cts +0 -2
- package/dist/server.cjs +0 -12
- package/dist/server.cjs.map +0 -1
- package/dist/server.d.cts +0 -2
- package/dist/sync.cjs +0 -2
- package/dist/sync.cjs.map +0 -1
- package/dist/sync.d.cts +0 -1
- package/dist/testing.cjs +0 -24
- package/dist/testing.cjs.map +0 -1
- package/dist/testing.d.cts +0 -4
package/README.md
CHANGED
|
@@ -6,20 +6,31 @@
|
|
|
6
6
|
[](./LICENSE)
|
|
7
7
|
[](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
|
-
|
|
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
|
-
```
|
|
15
|
-
createStore("user", { name: "Ava", role: "admin" })
|
|
16
|
-
setStore("user", "name", "Kai")
|
|
17
|
-
const name = useStore("user", s => s.name)
|
|
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
|
-
##
|
|
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
|
-
##
|
|
57
|
+
## What Each Import Contains
|
|
49
58
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
| `
|
|
64
|
-
| `setStore(name,
|
|
65
|
-
| `
|
|
66
|
-
| `
|
|
67
|
-
| `
|
|
68
|
-
| `
|
|
69
|
-
| `
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
72
|
-
| `
|
|
73
|
-
| `hydrateStores(snapshot)` | Rehydrate on client from server
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
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,
|
|
445
|
-
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
472
|
-
|
|
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
|
-
|
|
150
|
+
// Config
|
|
151
|
+
import { configureStroid, resetConfig,
|
|
152
|
+
registerMutatorProduce } from "stroid"
|
|
478
153
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
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
|
|
495
|
-
|
|
496
|
-
- [
|
|
497
|
-
- [Core
|
|
498
|
-
- [React Layer](./docs/
|
|
499
|
-
- [Async Layer](./docs/
|
|
500
|
-
- [Persistence](./docs/
|
|
501
|
-
- [Cross-tab Sync](./docs/
|
|
502
|
-
- [
|
|
503
|
-
- [
|
|
504
|
-
- [
|
|
505
|
-
- [
|
|
506
|
-
- [
|
|
507
|
-
- [
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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 };
|