atomirx 0.0.7 → 0.1.0

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 (138) hide show
  1. package/README.md +198 -2234
  2. package/bin/cli.js +90 -0
  3. package/dist/core/derived.d.ts +2 -2
  4. package/dist/core/effect.d.ts +3 -2
  5. package/dist/core/onCreateHook.d.ts +15 -2
  6. package/dist/core/onErrorHook.d.ts +4 -1
  7. package/dist/core/pool.d.ts +78 -0
  8. package/dist/core/pool.test.d.ts +1 -0
  9. package/dist/core/select-boolean.test.d.ts +1 -0
  10. package/dist/core/select-pool.test.d.ts +1 -0
  11. package/dist/core/select.d.ts +278 -86
  12. package/dist/core/types.d.ts +233 -1
  13. package/dist/core/withAbort.d.ts +95 -0
  14. package/dist/core/withReady.d.ts +3 -3
  15. package/dist/devtools/constants.d.ts +41 -0
  16. package/dist/devtools/index.cjs +1 -0
  17. package/dist/devtools/index.d.ts +29 -0
  18. package/dist/devtools/index.js +429 -0
  19. package/dist/devtools/registry.d.ts +98 -0
  20. package/dist/devtools/registry.test.d.ts +1 -0
  21. package/dist/devtools/setup.d.ts +61 -0
  22. package/dist/devtools/types.d.ts +311 -0
  23. package/dist/index-BZEnfIcB.cjs +1 -0
  24. package/dist/index-BbPZhsDl.js +1653 -0
  25. package/dist/index.cjs +1 -1
  26. package/dist/index.d.ts +4 -3
  27. package/dist/index.js +18 -14
  28. package/dist/onDispatchHook-C8yLzr-o.cjs +1 -0
  29. package/dist/onDispatchHook-SKbiIUaJ.js +5 -0
  30. package/dist/onErrorHook-BGGy3tqK.js +38 -0
  31. package/dist/onErrorHook-DHBASmYw.cjs +1 -0
  32. package/dist/react/index.cjs +1 -30
  33. package/dist/react/index.js +206 -791
  34. package/dist/react/onDispatchHook.d.ts +106 -0
  35. package/dist/react/useAction.d.ts +4 -1
  36. package/dist/react-devtools/DevToolsPanel.d.ts +93 -0
  37. package/dist/react-devtools/EntityDetails.d.ts +10 -0
  38. package/dist/react-devtools/EntityList.d.ts +15 -0
  39. package/dist/react-devtools/LogList.d.ts +12 -0
  40. package/dist/react-devtools/hooks.d.ts +50 -0
  41. package/dist/react-devtools/index.cjs +1 -0
  42. package/dist/react-devtools/index.d.ts +31 -0
  43. package/dist/react-devtools/index.js +1589 -0
  44. package/dist/react-devtools/styles.d.ts +148 -0
  45. package/package.json +26 -2
  46. package/skills/atomirx/SKILL.md +456 -0
  47. package/skills/atomirx/references/async-patterns.md +188 -0
  48. package/skills/atomirx/references/atom-patterns.md +238 -0
  49. package/skills/atomirx/references/deferred-loading.md +191 -0
  50. package/skills/atomirx/references/derived-patterns.md +428 -0
  51. package/skills/atomirx/references/effect-patterns.md +426 -0
  52. package/skills/atomirx/references/error-handling.md +140 -0
  53. package/skills/atomirx/references/hooks.md +322 -0
  54. package/skills/atomirx/references/pool-patterns.md +229 -0
  55. package/skills/atomirx/references/react-integration.md +411 -0
  56. package/skills/atomirx/references/rules.md +407 -0
  57. package/skills/atomirx/references/select-context.md +309 -0
  58. package/skills/atomirx/references/service-template.md +172 -0
  59. package/skills/atomirx/references/store-template.md +205 -0
  60. package/skills/atomirx/references/testing-patterns.md +431 -0
  61. package/coverage/base.css +0 -224
  62. package/coverage/block-navigation.js +0 -87
  63. package/coverage/clover.xml +0 -1440
  64. package/coverage/coverage-final.json +0 -14
  65. package/coverage/favicon.png +0 -0
  66. package/coverage/index.html +0 -131
  67. package/coverage/prettify.css +0 -1
  68. package/coverage/prettify.js +0 -2
  69. package/coverage/sort-arrow-sprite.png +0 -0
  70. package/coverage/sorter.js +0 -210
  71. package/coverage/src/core/atom.ts.html +0 -889
  72. package/coverage/src/core/batch.ts.html +0 -223
  73. package/coverage/src/core/define.ts.html +0 -805
  74. package/coverage/src/core/emitter.ts.html +0 -919
  75. package/coverage/src/core/equality.ts.html +0 -631
  76. package/coverage/src/core/hook.ts.html +0 -460
  77. package/coverage/src/core/index.html +0 -281
  78. package/coverage/src/core/isAtom.ts.html +0 -100
  79. package/coverage/src/core/isPromiseLike.ts.html +0 -133
  80. package/coverage/src/core/onCreateHook.ts.html +0 -138
  81. package/coverage/src/core/scheduleNotifyHook.ts.html +0 -94
  82. package/coverage/src/core/types.ts.html +0 -523
  83. package/coverage/src/core/withUse.ts.html +0 -253
  84. package/coverage/src/index.html +0 -116
  85. package/coverage/src/index.ts.html +0 -106
  86. package/dist/index-CBVj1kSj.js +0 -1350
  87. package/dist/index-Cxk9v0um.cjs +0 -1
  88. package/scripts/publish.js +0 -198
  89. package/src/core/atom.test.ts +0 -633
  90. package/src/core/atom.ts +0 -311
  91. package/src/core/atomState.test.ts +0 -342
  92. package/src/core/atomState.ts +0 -256
  93. package/src/core/batch.test.ts +0 -257
  94. package/src/core/batch.ts +0 -172
  95. package/src/core/define.test.ts +0 -343
  96. package/src/core/define.ts +0 -243
  97. package/src/core/derived.test.ts +0 -1215
  98. package/src/core/derived.ts +0 -450
  99. package/src/core/effect.test.ts +0 -802
  100. package/src/core/effect.ts +0 -188
  101. package/src/core/emitter.test.ts +0 -364
  102. package/src/core/emitter.ts +0 -392
  103. package/src/core/equality.test.ts +0 -392
  104. package/src/core/equality.ts +0 -182
  105. package/src/core/getAtomState.ts +0 -69
  106. package/src/core/hook.test.ts +0 -227
  107. package/src/core/hook.ts +0 -177
  108. package/src/core/isAtom.ts +0 -27
  109. package/src/core/isPromiseLike.test.ts +0 -72
  110. package/src/core/isPromiseLike.ts +0 -16
  111. package/src/core/onCreateHook.ts +0 -107
  112. package/src/core/onErrorHook.test.ts +0 -350
  113. package/src/core/onErrorHook.ts +0 -52
  114. package/src/core/promiseCache.test.ts +0 -241
  115. package/src/core/promiseCache.ts +0 -284
  116. package/src/core/scheduleNotifyHook.ts +0 -53
  117. package/src/core/select.ts +0 -729
  118. package/src/core/selector.test.ts +0 -799
  119. package/src/core/types.ts +0 -389
  120. package/src/core/withReady.test.ts +0 -534
  121. package/src/core/withReady.ts +0 -191
  122. package/src/core/withUse.test.ts +0 -249
  123. package/src/core/withUse.ts +0 -56
  124. package/src/index.test.ts +0 -80
  125. package/src/index.ts +0 -65
  126. package/src/react/index.ts +0 -21
  127. package/src/react/rx.test.tsx +0 -571
  128. package/src/react/rx.tsx +0 -531
  129. package/src/react/strictModeTest.tsx +0 -71
  130. package/src/react/useAction.test.ts +0 -987
  131. package/src/react/useAction.ts +0 -607
  132. package/src/react/useSelector.test.ts +0 -182
  133. package/src/react/useSelector.ts +0 -292
  134. package/src/react/useStable.test.ts +0 -553
  135. package/src/react/useStable.ts +0 -288
  136. package/tsconfig.json +0 -9
  137. package/v2.md +0 -725
  138. package/vite.config.ts +0 -39
@@ -0,0 +1,322 @@
1
+ # Hooks — Creation Tracking & Error Handling
2
+
3
+ ## Overview
4
+
5
+ | Hook | Purpose | Fires When |
6
+ | -------------- | -------------------------------- | ---------------------- |
7
+ | `onCreateHook` | Track atom/derived/effect/module | Primitive created |
8
+ | `onErrorHook` | Global error handling | Error in derived/effect|
9
+ | `hook()` | Create custom hooks | N/A (factory) |
10
+
11
+ ## CRITICAL: MUST Use .override()
12
+
13
+ **NEVER** assign `.current` directly — breaks hook chain.
14
+
15
+ ```typescript
16
+ // ❌ FORBIDDEN
17
+ onCreateHook.current = (info) => { ... };
18
+
19
+ // ✅ REQUIRED
20
+ onCreateHook.override((prev) => (info) => {
21
+ prev?.(info);
22
+ // your code
23
+ });
24
+
25
+ onErrorHook.override((prev) => (info) => {
26
+ prev?.(info);
27
+ // your code
28
+ });
29
+ ```
30
+
31
+ ## Hook API
32
+
33
+ | Method | Signature | Description |
34
+ | ------------ | ----------------------- | ---------------------- |
35
+ | `.current` | `T \| undefined` | Read-only value |
36
+ | `.override()`| `(reducer) => void` | Set via reducer |
37
+ | `.reset()` | `() => void` | Reset to initial |
38
+ | `hook.use()` | `(setups[], fn) => T` | Temporary hooks in fn |
39
+
40
+ ## onCreateHook
41
+
42
+ Fires on atom, derived, effect, module creation.
43
+
44
+ ### CreateInfo Types
45
+
46
+ ```typescript
47
+ interface MutableCreateInfo {
48
+ type: "mutable";
49
+ key: string | undefined;
50
+ meta: MutableAtomMeta | undefined;
51
+ atom: MutableAtom<unknown>;
52
+ }
53
+
54
+ interface DerivedCreateInfo {
55
+ type: "derived";
56
+ key: string | undefined;
57
+ meta: DerivedAtomMeta | undefined;
58
+ atom: DerivedAtom<unknown, boolean>;
59
+ }
60
+
61
+ interface EffectCreateInfo {
62
+ type: "effect";
63
+ key: string | undefined;
64
+ meta: EffectMeta | undefined;
65
+ effect: Effect;
66
+ }
67
+
68
+ interface ModuleCreateInfo {
69
+ type: "module";
70
+ key: string | undefined;
71
+ meta: ModuleMeta | undefined;
72
+ module: unknown;
73
+ }
74
+ ```
75
+
76
+ ### Use Cases
77
+
78
+ #### DevTools Registry
79
+
80
+ ```typescript
81
+ const registry = {
82
+ atoms: new Map(),
83
+ derived: new Map(),
84
+ effects: new Map(),
85
+ modules: new Map(),
86
+ };
87
+
88
+ onCreateHook.override((prev) => (info) => {
89
+ prev?.(info);
90
+ const key = info.key ?? `anon-${Date.now()}`;
91
+ switch (info.type) {
92
+ case "mutable": registry.atoms.set(key, info.atom); break;
93
+ case "derived": registry.derived.set(key, info.atom); break;
94
+ case "effect": registry.effects.set(key, info.effect); break;
95
+ case "module": registry.modules.set(key, info.module); break;
96
+ }
97
+ });
98
+
99
+ window.__ATOMIRX_DEVTOOLS__ = registry;
100
+ ```
101
+
102
+ #### Persistence
103
+
104
+ ```typescript
105
+ declare module "atomirx" {
106
+ interface MutableAtomMeta { persisted?: boolean; }
107
+ }
108
+
109
+ onCreateHook.override((prev) => (info) => {
110
+ prev?.(info);
111
+ if (info.type !== "mutable" || !info.meta?.persisted || !info.key) return;
112
+
113
+ const storageKey = `app:${info.key}`;
114
+
115
+ if (!info.atom.dirty()) {
116
+ const stored = localStorage.getItem(storageKey);
117
+ if (stored) try { info.atom.set(JSON.parse(stored)); } catch {}
118
+ }
119
+
120
+ info.atom.on(() => localStorage.setItem(storageKey, JSON.stringify(info.atom.get())));
121
+ });
122
+
123
+ // Usage
124
+ const settings$ = atom({ theme: "dark" }, { meta: { key: "user.settings", persisted: true } });
125
+ ```
126
+
127
+ #### Validation
128
+
129
+ ```typescript
130
+ declare module "atomirx" {
131
+ interface MutableAtomMeta { validate?: (v: unknown) => boolean; }
132
+ }
133
+
134
+ onCreateHook.override((prev) => (info) => {
135
+ prev?.(info);
136
+ if (info.type !== "mutable" || !info.meta?.validate) return;
137
+
138
+ const validate = info.meta.validate;
139
+ const originalSet = info.atom.set.bind(info.atom);
140
+
141
+ info.atom.set = (valueOrReducer) => {
142
+ const next = typeof valueOrReducer === "function"
143
+ ? (valueOrReducer as Function)(info.atom.get())
144
+ : valueOrReducer;
145
+
146
+ if (!validate(next)) {
147
+ console.warn(`Validation failed for ${info.key}:`, next);
148
+ return;
149
+ }
150
+ originalSet(valueOrReducer);
151
+ };
152
+ });
153
+ ```
154
+
155
+ #### Debug Logging
156
+
157
+ ```typescript
158
+ if (process.env.NODE_ENV === "development") {
159
+ onCreateHook.override((prev) => (info) => {
160
+ prev?.(info);
161
+ console.log(`[atomirx] Created ${info.type}: ${info.key ?? "anon"}`);
162
+ });
163
+ }
164
+ ```
165
+
166
+ ## onErrorHook
167
+
168
+ Fires on derived/effect errors.
169
+
170
+ ```typescript
171
+ interface ErrorInfo {
172
+ source: CreateInfo;
173
+ error: unknown;
174
+ }
175
+ ```
176
+
177
+ ### Use Cases
178
+
179
+ #### Sentry
180
+
181
+ ```typescript
182
+ onErrorHook.override((prev) => (info) => {
183
+ prev?.(info);
184
+ Sentry.captureException(info.error, {
185
+ tags: { source_type: info.source.type, source_key: info.source.key ?? "anon" },
186
+ extra: { meta: info.source.meta },
187
+ });
188
+ });
189
+ ```
190
+
191
+ #### Console
192
+
193
+ ```typescript
194
+ onErrorHook.override((prev) => (info) => {
195
+ prev?.(info);
196
+ console.error(`[atomirx] Error in ${info.source.type}: ${info.source.key ?? "anon"}`, info.error);
197
+ });
198
+ ```
199
+
200
+ #### Toast
201
+
202
+ ```typescript
203
+ onErrorHook.override((prev) => (info) => {
204
+ prev?.(info);
205
+ if (info.error instanceof UserFacingError) toast.error(info.error.message);
206
+ });
207
+ ```
208
+
209
+ ## Custom Hooks
210
+
211
+ ```typescript
212
+ const debugHook = hook(false);
213
+ debugHook.current; // false
214
+ debugHook.override(() => true);
215
+ debugHook.current; // true
216
+ debugHook.reset();
217
+ debugHook.current; // false
218
+ ```
219
+
220
+ ### Temporary Hooks
221
+
222
+ ```typescript
223
+ const loggerHook = hook<(msg: string) => void>();
224
+
225
+ const result = hook.use(
226
+ [loggerHook(() => (msg) => console.log("[TEST]", msg))],
227
+ () => {
228
+ loggerHook.current?.("Inside hook.use()");
229
+ return "result";
230
+ }
231
+ );
232
+
233
+ loggerHook.current?.("Outside"); // Does nothing
234
+ ```
235
+
236
+ ## Testing (IMPORTANT)
237
+
238
+ ### Isolate Tests
239
+
240
+ ```typescript
241
+ describe("MyStore", () => {
242
+ beforeEach(() => {
243
+ onCreateHook.reset();
244
+ onErrorHook.reset();
245
+ });
246
+
247
+ afterEach(() => {
248
+ onCreateHook.reset();
249
+ onErrorHook.reset();
250
+ });
251
+
252
+ it("should track created atoms", () => {
253
+ const created: string[] = [];
254
+ onCreateHook.override((prev) => (info) => {
255
+ prev?.(info);
256
+ if (info.key) created.push(info.key);
257
+ });
258
+
259
+ myStore();
260
+ expect(created).toContain("myStore.counter");
261
+ });
262
+ });
263
+ ```
264
+
265
+ ### Verify Error Hook
266
+
267
+ ```typescript
268
+ it("should call error hook", async () => {
269
+ const errors: ErrorInfo[] = [];
270
+ onErrorHook.override((prev) => (info) => { prev?.(info); errors.push(info); });
271
+
272
+ const buggy$ = derived(({ read }) => { throw new Error("test"); }, { meta: { key: "buggy" } });
273
+
274
+ try { await buggy$.get(); } catch {}
275
+
276
+ expect(errors).toHaveLength(1);
277
+ expect(errors[0].source.key).toBe("buggy");
278
+ });
279
+ ```
280
+
281
+ ## Initialization Order (CRITICAL)
282
+
283
+ **MUST** set up hooks BEFORE atoms are created:
284
+
285
+ ```typescript
286
+ // src/app/init.ts
287
+ import { onCreateHook, onErrorHook } from "atomirx";
288
+
289
+ // 1. DevTools
290
+ onCreateHook.override((prev) => (info) => {
291
+ prev?.(info);
292
+ window.__ATOMIRX_REGISTRY__?.add(info);
293
+ });
294
+
295
+ // 2. Error monitoring
296
+ onErrorHook.override((prev) => (info) => {
297
+ prev?.(info);
298
+ Sentry.captureException(info.error);
299
+ });
300
+
301
+ // 3. Persistence
302
+ onCreateHook.override((prev) => (info) => {
303
+ prev?.(info);
304
+ if (info.type === "mutable" && info.meta?.persisted) setupPersistence(info);
305
+ });
306
+
307
+ // src/app/main.tsx
308
+ import "./init"; // Run first
309
+ import { App } from "./App";
310
+ ```
311
+
312
+ ## Summary
313
+
314
+ | Task | Hook | Pattern |
315
+ | --------------------- | -------------- | ----------------------------------- |
316
+ | Track creation | `onCreateHook` | `.override((prev) => (info) => {})` |
317
+ | Global error logging | `onErrorHook` | `.override((prev) => (info) => {})` |
318
+ | Persistence | `onCreateHook` | Check `info.meta?.persisted` |
319
+ | Validation | `onCreateHook` | Wrap `info.atom.set()` |
320
+ | DevTools | `onCreateHook` | Register in global registry |
321
+ | Reset all | Both | `.reset()` in tests |
322
+ | Temporary (tests) | `hook.use()` | `hook.use([setup], fn)` |
@@ -0,0 +1,229 @@
1
+ # Pool Patterns
2
+
3
+ Pool = collection of atoms indexed by params with automatic GC. Like `atomFamily` but with built-in GC and ScopedAtom safety.
4
+
5
+ ## Features
6
+
7
+ | Feature | Description |
8
+ | --------------- | ------------------------------------ |
9
+ | Auto GC | Removed after `gcTime` of inactivity |
10
+ | Promise-aware | GC pauses while Promise pending |
11
+ | ScopedAtom | Prevents stale reference leaks |
12
+ | Value API | Public API uses values, not atoms |
13
+ | Reactive API | `from(pool, params)` in selectors |
14
+
15
+ ## Creating
16
+
17
+ ```typescript
18
+ // Object params
19
+ const userPool = pool((params: { id: string }) => fetchUser(params.id), {
20
+ gcTime: 60_000,
21
+ meta: { key: "users" },
22
+ });
23
+
24
+ // Primitive params
25
+ const articlePool = pool((id: string) => fetchArticle(id), {
26
+ gcTime: 300_000,
27
+ meta: { key: "articles" },
28
+ });
29
+
30
+ // With context
31
+ const dataPool = pool((params: { id: string }, ctx) => {
32
+ const controller = new AbortController();
33
+ ctx.onCleanup(() => controller.abort());
34
+ return fetchData(params.id, { signal: controller.signal });
35
+ }, { gcTime: 60_000 });
36
+ ```
37
+
38
+ ## Options
39
+
40
+ ```typescript
41
+ interface PoolOptions<P> {
42
+ gcTime: number; // Required
43
+ equals?: Equality<P>; // Default: "shallow"
44
+ meta?: { key?: string };
45
+ }
46
+ ```
47
+
48
+ ## Public API (Value-based)
49
+
50
+ ```typescript
51
+ const userPool = pool((id: string) => ({ name: "", email: "" }), { gcTime: 60_000 });
52
+
53
+ userPool.get("user-1"); // Get/create
54
+ userPool.set("user-1", { name: "John", email: "j@e.com" });
55
+ userPool.set("user-1", (p) => ({ ...p, name: "Jane" })); // Reducer
56
+ userPool.has("user-1"); // Check existence
57
+ userPool.remove("user-1"); // Remove
58
+ userPool.clear(); // Clear all
59
+ userPool.forEach((val, params) => console.log(params, val));
60
+
61
+ // Subscribe
62
+ const unsub = userPool.onChange((params, value) => console.log("Changed:", params));
63
+ const unsub2 = userPool.onRemove((params, value) => console.log("Removed:", params));
64
+ ```
65
+
66
+ ## Reactive API (from())
67
+
68
+ In `derived`, `effect`, `useSelector`:
69
+
70
+ ```typescript
71
+ // derived
72
+ const userPosts$ = derived(({ read, from }) => {
73
+ const user$ = from(userPool, "user-1");
74
+ return read(user$).posts;
75
+ });
76
+
77
+ // effect
78
+ effect(({ read, from }) => {
79
+ const user$ = from(userPool, currentUserId);
80
+ console.log("User:", read(user$));
81
+ });
82
+
83
+ // useSelector
84
+ const user = useSelector(({ read, from }) => {
85
+ const user$ = from(userPool, "user-1");
86
+ return read(user$);
87
+ });
88
+ ```
89
+
90
+ ## ScopedAtom (CRITICAL)
91
+
92
+ ScopedAtom is a temporary wrapper:
93
+ - **ONLY** exists during select context
94
+ - **THROWS** if accessed outside
95
+ - **MUST** use with `read()`, **NEVER** with `.get()`
96
+
97
+ ```typescript
98
+ // ❌ FORBIDDEN
99
+ derived(({ from }) => {
100
+ const user$ = from(userPool, "user-1");
101
+ return user$.get(); // THROWS
102
+ });
103
+
104
+ // ❌ FORBIDDEN
105
+ let cached: ScopedAtom<User>;
106
+ derived(({ from }) => { cached = from(userPool, "user-1"); });
107
+ cached._getAtom(); // THROWS after context ends
108
+
109
+ // ✅ REQUIRED
110
+ derived(({ read, from }) => {
111
+ const user$ = from(userPool, "user-1");
112
+ return read(user$);
113
+ });
114
+ ```
115
+
116
+ ## GC Behavior
117
+
118
+ Timer resets on: creation, value change, access.
119
+
120
+ GC pauses while Promise pending:
121
+
122
+ ```typescript
123
+ const asyncPool = pool((id: string) => fetchData(id), { gcTime: 5000 });
124
+ asyncPool.get("1"); // Timer starts AFTER Promise resolves
125
+ ```
126
+
127
+ ## Params Equality
128
+
129
+ Default `"shallow"` — order doesn't matter:
130
+
131
+ ```typescript
132
+ const pool1 = pool((p: { a: number; b: number }) => p.a + p.b, { gcTime: 60_000 });
133
+ pool1.get({ a: 1, b: 2 }); // Creates
134
+ pool1.get({ b: 2, a: 1 }); // Same entry
135
+
136
+ // Custom
137
+ const pool2 = pool(
138
+ (p: { id: string; version?: number }) => fetchData(p.id, p.version),
139
+ { gcTime: 60_000, equals: (a, b) => a.id === b.id }
140
+ );
141
+ ```
142
+
143
+ ## Common Patterns
144
+
145
+ ### Entity Cache
146
+
147
+ ```typescript
148
+ const userCache = pool(
149
+ async (id: string) => (await fetch(`/api/users/${id}`)).json(),
150
+ { gcTime: 300_000, meta: { key: "userCache" } }
151
+ );
152
+
153
+ const user = useSelector(({ read, from }) => read(from(userCache, userId)));
154
+ ```
155
+
156
+ ### Form State per Entity
157
+
158
+ ```typescript
159
+ const formPool = pool(
160
+ (entityId: string): FormState => ({ values: {}, errors: {}, dirty: false }),
161
+ { gcTime: 600_000, meta: { key: "forms" } }
162
+ );
163
+
164
+ formPool.set(entityId, (p) => ({
165
+ ...p,
166
+ values: { ...p.values, [field]: value },
167
+ dirty: true,
168
+ }));
169
+ ```
170
+
171
+ ### Optimistic Updates
172
+
173
+ ```typescript
174
+ async function updateUserName(id: string, name: string) {
175
+ userPool.set(id, (p) => ({ ...p, name })); // Optimistic
176
+ try {
177
+ await api.updateUser(id, { name });
178
+ } catch {
179
+ userPool.set(id, await fetchUser(id)); // Rollback
180
+ throw error;
181
+ }
182
+ }
183
+ ```
184
+
185
+ ### Derived from Pool
186
+
187
+ ```typescript
188
+ const currentUser$ = derived(({ read, ready, from }) => {
189
+ const userId = ready(currentUserId$);
190
+ const user$ = from(userPool, userId);
191
+ return read(user$);
192
+ });
193
+ ```
194
+
195
+ ### Multiple Pools
196
+
197
+ ```typescript
198
+ const userDashboard$ = derived(({ read, from, all }) => {
199
+ const userId = "user-1";
200
+ const user$ = from(userPool, userId);
201
+ const posts$ = from(postsPool, userId);
202
+ const [user, posts] = all([user$, posts$]);
203
+ return { user, posts };
204
+ });
205
+ ```
206
+
207
+ ## Pool vs Manual Map
208
+
209
+ | Feature | pool() | Manual Map |
210
+ | -------------- | ------------------------ | ----------------------- |
211
+ | GC | Auto with `gcTime` | Manual cleanup |
212
+ | Memory safety | ScopedAtom prevents leaks| Easy to leak |
213
+ | Promise-aware | GC waits for pending | Manual handling |
214
+ | Events | `onChange`, `onRemove` | Implement manually |
215
+ | Reactive | Works with `from()` | Manual subscriptions |
216
+ | Testing | Easy mock via `define()` | Harder isolation |
217
+
218
+ ## When to Use
219
+
220
+ ✅ **Use pool:**
221
+ - Parameterized state (users, articles, forms)
222
+ - Entries with natural TTL (cache, session)
223
+ - Reactive subscriptions per entry
224
+ - Memory management matters
225
+
226
+ ❌ **NEVER use pool:**
227
+ - Single global atom → use `atom`
228
+ - State doesn't vary by key
229
+ - Entries should NEVER be GC'd → use Map