dantian 0.0.2-beta.8 → 1.0.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 +55 -0
- package/README.md +299 -32
- package/dist/dantian.cjs +1 -29
- package/dist/dantian.js +217 -5301
- package/dist/event-store.d.ts +11 -4
- package/dist/types.d.ts +17 -0
- package/package.json +79 -34
- package/dist/__tests__/index.test.d.ts +0 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
|
+
|
|
5
|
+
### [1.0.2](https://github.com/JuliusKoronciCH/dantian/compare/v1.0.1-beta.0...v1.0.2) (2026-02-08)
|
|
6
|
+
|
|
7
|
+
### [1.0.1](https://github.com/JuliusKoronciCH/dantian/compare/v1.0.1-beta.0...v1.0.1) (2026-02-08)
|
|
8
|
+
|
|
9
|
+
### 1.0.1-beta.1 (2026-02-08)
|
|
10
|
+
|
|
11
|
+
### 1.0.1-beta.0 (2026-02-08)
|
|
12
|
+
|
|
13
|
+
### 0.0.2-beta.19 (2026-02-08)
|
|
14
|
+
|
|
15
|
+
### 0.0.2-beta.18 (2024-05-21)
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
- useIsHydrated race condition fix on later call of the hook ([#19](https://github.com/JuliusKoronciCH/dantian/issues/19)) ([2c7c0ff](https://github.com/JuliusKoronciCH/dantian/commit/2c7c0ffbe1ecdab03b912508ca4410fc65e1f242))
|
|
20
|
+
|
|
21
|
+
### 0.0.2-beta.17 (2024-05-07)
|
|
22
|
+
|
|
23
|
+
### 0.0.2-beta.16 (2024-05-07)
|
|
24
|
+
|
|
25
|
+
### 0.0.2-beta.15 (2024-04-25)
|
|
26
|
+
|
|
27
|
+
### Bug Fixes
|
|
28
|
+
|
|
29
|
+
- child property observable ([#16](https://github.com/JuliusKoronciCH/dantian/issues/16)) ([6d33779](https://github.com/JuliusKoronciCH/dantian/commit/6d33779d5dfe7d51cd7e2e75f0b3a20fe88bcf41))
|
|
30
|
+
|
|
31
|
+
### 0.0.2-beta.14 (2024-04-25)
|
|
32
|
+
|
|
33
|
+
### 0.0.2-beta.13 (2024-04-25)
|
|
34
|
+
|
|
35
|
+
### 0.0.2-beta.12 (2024-04-18)
|
|
36
|
+
|
|
37
|
+
### 0.0.2-beta.11 (2024-04-08)
|
|
38
|
+
|
|
39
|
+
### 0.0.2-beta.10 (2024-04-07)
|
|
40
|
+
|
|
41
|
+
### 0.0.2-beta.9 (2024-04-07)
|
|
42
|
+
|
|
43
|
+
### 0.0.2-beta.8 (2024-04-04)
|
|
44
|
+
|
|
45
|
+
### 0.0.2-beta.7 (2024-04-04)
|
|
46
|
+
|
|
47
|
+
### 0.0.2-beta.6 (2024-04-04)
|
|
48
|
+
|
|
49
|
+
### 0.0.2-beta.5 (2024-04-04)
|
|
50
|
+
|
|
51
|
+
### 0.0.2-beta.4 (2024-04-04)
|
|
52
|
+
|
|
53
|
+
### 0.0.2-beta.3 (2024-04-04)
|
|
54
|
+
|
|
55
|
+
### 0.0.2-beta.2 (2024-04-04)
|
package/README.md
CHANGED
|
@@ -1,55 +1,322 @@
|
|
|
1
|
-
|
|
1
|
+
# Dantian
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Event-based state management for React, powered by RxJS. Dantian exposes a small event store API and React hooks that subscribe to specific property paths, so only the parts of UI that care about a given event re-render.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Installation
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```bash
|
|
8
|
+
npm install dantian
|
|
9
|
+
```
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
## Compatibility
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
| Runtime | Supported versions |
|
|
14
|
+
| ------- | ------------------ |
|
|
15
|
+
| React | 18, 19 |
|
|
16
|
+
| RxJS | 7, 8 |
|
|
17
|
+
| Node | 18, 20, 22 |
|
|
12
18
|
|
|
13
|
-
|
|
19
|
+
## Use Cases & FAQ
|
|
14
20
|
|
|
15
|
-
|
|
21
|
+
### Quickstart: event store basics
|
|
16
22
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- **Optimized form integration:** Tackle large forms without performance compromises.
|
|
23
|
+
```ts
|
|
24
|
+
// store.ts
|
|
25
|
+
import { createEventStore } from 'dantian';
|
|
21
26
|
|
|
22
|
-
|
|
27
|
+
export const store = createEventStore({
|
|
28
|
+
count: 0,
|
|
29
|
+
user: { name: 'n/a' },
|
|
30
|
+
});
|
|
23
31
|
|
|
24
|
-
|
|
25
|
-
|
|
32
|
+
export const {
|
|
33
|
+
useStoreValue,
|
|
34
|
+
publish,
|
|
35
|
+
useHydrateStore,
|
|
36
|
+
useIsHydrated,
|
|
37
|
+
reset,
|
|
38
|
+
feed,
|
|
39
|
+
destroy,
|
|
40
|
+
state$,
|
|
41
|
+
systemEvents$,
|
|
42
|
+
} = store;
|
|
26
43
|
```
|
|
27
44
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
import { useEventState } from 'dantian';
|
|
45
|
+
```tsx
|
|
46
|
+
// Counter.tsx
|
|
47
|
+
import { useStoreValue } from './store';
|
|
32
48
|
|
|
33
|
-
function
|
|
34
|
-
const [count, setCount] =
|
|
35
|
-
|
|
36
|
-
const increment = () => setCount(count + 1);
|
|
49
|
+
export function Counter() {
|
|
50
|
+
const [count, setCount] = useStoreValue('count');
|
|
37
51
|
|
|
38
52
|
return (
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
</div>
|
|
53
|
+
<button type="button" onClick={() => setCount(count + 1)}>
|
|
54
|
+
Count: {count}
|
|
55
|
+
</button>
|
|
43
56
|
);
|
|
44
57
|
}
|
|
45
58
|
```
|
|
46
59
|
|
|
47
|
-
|
|
60
|
+
At a glance, `createEventStore` maintains a stream of events and derives state from them. Updates are published by property path (for example, `user.name`), and hooks subscribe to those paths.
|
|
61
|
+
|
|
62
|
+
### How do I update nested fields?
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
const [name, setName] = useStoreValue('user.name');
|
|
66
|
+
setName('Ada');
|
|
67
|
+
|
|
68
|
+
// Or outside React:
|
|
69
|
+
publish('user.name', 'Ada');
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### How do I hydrate from an async source?
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
const store = createEventStore(
|
|
76
|
+
{ user: { name: 'n/a' } },
|
|
77
|
+
{
|
|
78
|
+
hydrator: async () => {
|
|
79
|
+
const response = await fetch('/api/profile');
|
|
80
|
+
return (await response.json()) as { user: { name: string } };
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### How do I persist state?
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
const store = createEventStore(
|
|
90
|
+
{ count: 0 },
|
|
91
|
+
{
|
|
92
|
+
persist: async (state) => {
|
|
93
|
+
localStorage.setItem('dantianState', JSON.stringify(state));
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
);
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
If hydration or persistence fails, you can observe the error via system events
|
|
100
|
+
while console errors remain intact:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
systemEvents$.subscribe((event) => {
|
|
104
|
+
if (event.type === '@@HYDRATE_ERROR' || event.type === '@@PERSIST_ERROR') {
|
|
105
|
+
console.error('store error', event.payload.error);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### How do I avoid flicker from concurrent updates?
|
|
111
|
+
|
|
112
|
+
If updates are coming from multiple sources, you can disable local caching per hook:
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
const [value, setValue] = useStoreValue('profile.name', {
|
|
116
|
+
disableCache: true,
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
You can also throttle update propagation with `throttle` (milliseconds):
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
const [user] = useStoreValue('user', { throttle: 50 });
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### How do I reset or feed state?
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
reset({ count: 0, user: { name: 'n/a' } });
|
|
130
|
+
feed({ count: 5, user: { name: 'Julius' } });
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### How do I subscribe outside React?
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
const subscription = state$.subscribe((state) => {
|
|
137
|
+
console.log('state changed', state);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
subscription.unsubscribe();
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Dispose of a store
|
|
144
|
+
|
|
145
|
+
If a store is no longer needed, dispose of it to complete internal subjects and
|
|
146
|
+
prevent lingering subscriptions:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
store.destroy();
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
After `destroy()`, calls to `publish`, `reset`, `feed`, and the callback from
|
|
153
|
+
`useHydrateStore()` are no-ops.
|
|
154
|
+
|
|
155
|
+
## Task Guides
|
|
156
|
+
|
|
157
|
+
### Create a store
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
import { createEventStore } from 'dantian';
|
|
161
|
+
|
|
162
|
+
const store = createEventStore({ count: 0 }, { debug: false });
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Read and update values in components
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
const [count, setCount] = store.useStoreValue('count');
|
|
169
|
+
setCount(count + 1);
|
|
170
|
+
|
|
171
|
+
// Or publish directly
|
|
172
|
+
store.publish('count', 42);
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
`useStoreValue` options:
|
|
176
|
+
|
|
177
|
+
- `disableCache`: bypasses local caching to avoid flicker in edge cases.
|
|
178
|
+
- `throttle`: throttles updates in milliseconds.
|
|
179
|
+
- `throtle`: legacy alias for `throttle` (kept for backward compatibility).
|
|
180
|
+
|
|
181
|
+
### Hydrate and persist
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
const store = createEventStore(
|
|
185
|
+
{ count: 0 },
|
|
186
|
+
{
|
|
187
|
+
hydrator: async () => ({ count: 10 }),
|
|
188
|
+
persist: async (state) => {
|
|
189
|
+
localStorage.setItem('dantianState', JSON.stringify(state));
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
);
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
In React, you can trigger hydration manually:
|
|
196
|
+
|
|
197
|
+
```tsx
|
|
198
|
+
const hydrate = store.useHydrateStore();
|
|
199
|
+
const isHydrated = store.useIsHydrated();
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Reset and feed
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
store.reset({ count: 0 });
|
|
206
|
+
store.feed({ count: 5 });
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Destroy
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
store.destroy();
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Observe with RxJS
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
const sub = store.getPropertyObservable('count').subscribe((value) => {
|
|
219
|
+
console.log('count changed', value);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
sub.unsubscribe();
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Classic store basics
|
|
226
|
+
|
|
227
|
+
If you need a minimal store with selectors and updates, use `buildClassicStore`:
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
import { buildClassicStore } from 'dantian';
|
|
231
|
+
|
|
232
|
+
const classic = await buildClassicStore({ count: 0 });
|
|
233
|
+
const [state, update] = classic.useStore();
|
|
234
|
+
const count = classic.useSelector((s) => s.count);
|
|
235
|
+
update((prev) => ({ ...prev, count: prev.count + 1 }));
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
With hydration and persistence:
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
const classic = await buildClassicStore({
|
|
242
|
+
beforeLoadState: { count: 0 },
|
|
243
|
+
hydrator: async () => ({ count: 2 }),
|
|
244
|
+
persist: async (state) => {
|
|
245
|
+
localStorage.setItem('classicState', JSON.stringify(state));
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## API Reference
|
|
251
|
+
|
|
252
|
+
### `createEventStore<T extends object>(initialState, options?)`
|
|
253
|
+
|
|
254
|
+
Options:
|
|
255
|
+
|
|
256
|
+
- `debug?: boolean` — log events and state transitions.
|
|
257
|
+
- `hydrator?: () => Promise<T>` — async hydration source.
|
|
258
|
+
- `persist?: (state: T) => Promise<void>` — persistence callback.
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
|
|
262
|
+
- `useStoreValue<K>(path, options?)`: React hook for reading/updating a property path.
|
|
263
|
+
- `publish(path, payload)`: publish an event for a property path.
|
|
264
|
+
- `getPropertyObservable(path, throttle?)`: RxJS observable for a property path.
|
|
265
|
+
- `useHydrateStore()`: returns a function to emit `@@HYDRATED` with payload.
|
|
266
|
+
- `useIsHydrated()`: returns a boolean hydration flag.
|
|
267
|
+
- `reset(payload)`: emit `@@RESET` system event.
|
|
268
|
+
- `feed(payload)`: emit `@@FEED` system event.
|
|
269
|
+
- `state$`: `BehaviorSubject<T>` with current state.
|
|
270
|
+
- `globalEventStore$`: `BehaviorSubject` of all events.
|
|
271
|
+
- `systemEvents$`: observable of system events (event types starting with `@@`,
|
|
272
|
+
including `@@HYDRATE_ERROR` and `@@PERSIST_ERROR`).
|
|
273
|
+
- `destroy()`: dispose the store, completing internal subjects and stopping
|
|
274
|
+
further publishes.
|
|
275
|
+
|
|
276
|
+
`useStoreValue` options:
|
|
277
|
+
|
|
278
|
+
- `disableCache?: boolean`
|
|
279
|
+
- `throttle?: number`
|
|
280
|
+
- `throtle?: number` (legacy alias)
|
|
281
|
+
|
|
282
|
+
### `buildClassicStore<T>(defaultState)`
|
|
283
|
+
|
|
284
|
+
`defaultState` can be either a plain initial state or an object with:
|
|
285
|
+
|
|
286
|
+
- `beforeLoadState: T`
|
|
287
|
+
- `hydrator: () => Promise<T>`
|
|
288
|
+
- `persist?: (state: T) => Promise<void>`
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
|
|
292
|
+
- `useStore()`: React hook for `[state, update]`.
|
|
293
|
+
- `useSelector(selector)`: React hook for derived values.
|
|
294
|
+
- `update(updater)`: update function.
|
|
295
|
+
- `getValue()`: current value getter.
|
|
296
|
+
- `subject$`: `BehaviorSubject<T>`.
|
|
297
|
+
- `defaultState`: the provided default state.
|
|
298
|
+
|
|
299
|
+
### `wuji`
|
|
300
|
+
|
|
301
|
+
Alias of `createEventStore`.
|
|
302
|
+
|
|
303
|
+
## Troubleshooting
|
|
304
|
+
|
|
305
|
+
- Hydration errors: `systemEvents$` emits `@@HYDRATE_ERROR` and the console logs
|
|
306
|
+
`Failed to hydrate store`. Verify the hydrator resolves with the same shape
|
|
307
|
+
as the initial state and handle thrown errors.
|
|
308
|
+
- Persist errors: `systemEvents$` emits `@@PERSIST_ERROR` and the console logs
|
|
309
|
+
`Failed to persist store`. Ensure your persist callback returns a promise and
|
|
310
|
+
handles storage quotas or serialization failures.
|
|
311
|
+
- Disposed stores still referenced: call `destroy()` when a store is no longer
|
|
312
|
+
used, and avoid calling `publish/reset/feed` afterward.
|
|
313
|
+
|
|
314
|
+
## 1.0 Migration Notes
|
|
48
315
|
|
|
49
|
-
|
|
316
|
+
- No breaking changes are required.
|
|
317
|
+
- `destroy()` is now available to explicitly dispose of stores.
|
|
318
|
+
- `systemEvents$` now includes `@@HYDRATE_ERROR` and `@@PERSIST_ERROR` events.
|
|
50
319
|
|
|
51
|
-
|
|
52
|
-
Developer-friendly: Intuitive API and seamless form integration.
|
|
53
|
-
**License**
|
|
320
|
+
## License
|
|
54
321
|
|
|
55
322
|
MIT
|