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,188 @@
1
+ # Async Patterns
2
+
3
+ ## Summary
4
+
5
+ | Utility | Input | Output | Behavior |
6
+ | ----------- | --------------- | ---------------------- | -------------------------- |
7
+ | `all()` | Array of atoms | Array of values | Waits for all |
8
+ | `any()` | Record of atoms | `{ key, value }` | First to resolve |
9
+ | `race()` | Record of atoms | `{ key, value }` | First to settle |
10
+ | `settled()` | Array of atoms | Array of SettledResult | Waits for all settled |
11
+ | `and()` | Array of conds | boolean | AND with short-circuit |
12
+ | `or()` | Array of conds | boolean | OR with short-circuit |
13
+
14
+ ## all() — Promise.all
15
+
16
+ Waits for ALL atoms. Returns array.
17
+
18
+ ```typescript
19
+ const dashboard$ = derived(({ all }) => {
20
+ const [user, posts, comments] = all([user$, posts$, comments$]);
21
+ return { user, posts, comments };
22
+ });
23
+ ```
24
+
25
+ **Use when:** Need all data before rendering.
26
+
27
+ ## any() — Promise.any
28
+
29
+ Returns first resolved. Uses object for key identification.
30
+
31
+ ```typescript
32
+ const data$ = derived(({ any }) => {
33
+ const result = any({ primary: primaryApi$, fallback: fallbackApi$ });
34
+ // result: { key: "primary" | "fallback", value: T }
35
+ return result.value;
36
+ });
37
+ ```
38
+
39
+ **Use when:** Multiple redundant sources, want fastest.
40
+
41
+ ## race() — Promise.race
42
+
43
+ Returns first settled (ready OR error). Uses object.
44
+
45
+ ```typescript
46
+ const data$ = derived(({ race }) => {
47
+ const result = race({ cache: cache$, api: api$ });
48
+ return result.value;
49
+ });
50
+ ```
51
+
52
+ **Use when:** Show whatever resolves first.
53
+
54
+ ## settled() — Promise.allSettled
55
+
56
+ Returns status for each. Waits until all settled.
57
+
58
+ ```typescript
59
+ const results$ = derived(({ settled }) => {
60
+ const [userResult, postsResult] = settled([user$, posts$]);
61
+ return {
62
+ user: userResult.status === "ready" ? userResult.value : null,
63
+ posts: postsResult.status === "ready" ? postsResult.value : [],
64
+ hasErrors: userResult.status === "error" || postsResult.status === "error",
65
+ };
66
+ });
67
+ ```
68
+
69
+ **Use when:** Handle partial failures gracefully.
70
+
71
+ ### SettledResult Type
72
+
73
+ ```typescript
74
+ type SettledResult<T> =
75
+ | { status: "ready"; value: T }
76
+ | { status: "error"; error: unknown };
77
+ ```
78
+
79
+ ## state() — No Throwing
80
+
81
+ Get state without Suspense.
82
+
83
+ ```typescript
84
+ const data$ = derived(({ state }) => {
85
+ const userState = state(user$);
86
+ if (userState.status === "loading") return { loading: true };
87
+ if (userState.status === "error") return { error: userState.error };
88
+ return { user: userState.value };
89
+ });
90
+ ```
91
+
92
+ ### AtomState Type
93
+
94
+ ```typescript
95
+ type AtomState<T> =
96
+ | { status: "ready"; value: T }
97
+ | { status: "error"; error: unknown }
98
+ | { status: "loading"; promise: Promise<T> };
99
+ ```
100
+
101
+ ## Combining Patterns
102
+
103
+ ### Graceful Degradation
104
+
105
+ ```typescript
106
+ const dashboard$ = derived(({ read, settled }) => {
107
+ const user = read(user$); // Required
108
+
109
+ const [analyticsResult, notificationsResult] = settled([analytics$, notifications$]);
110
+
111
+ return {
112
+ user,
113
+ analytics: analyticsResult.status === "ready" ? analyticsResult.value : null,
114
+ notifications: notificationsResult.status === "ready" ? notificationsResult.value : [],
115
+ warnings: [analyticsResult, notificationsResult]
116
+ .filter((r) => r.status === "error")
117
+ .map((r) => r.error),
118
+ };
119
+ });
120
+ ```
121
+
122
+ ### Cache-First
123
+
124
+ ```typescript
125
+ const article$ = derived(({ race }) => {
126
+ const result = race({ cache: cachedArticle$, network: fetchedArticle$ });
127
+ console.log(`From: ${result.key}`);
128
+ return result.value;
129
+ });
130
+ ```
131
+
132
+ ### Parallel Loading
133
+
134
+ ```typescript
135
+ const [user, posts, comments] = useSelector(({ all }) =>
136
+ all([user$, posts$, comments$])
137
+ );
138
+ ```
139
+
140
+ ## and() — Logical AND
141
+
142
+ Short-circuit. Returns true if ALL truthy.
143
+
144
+ ```typescript
145
+ const canEdit$ = derived(({ and }) => and([isLoggedIn$, hasPermission$]));
146
+
147
+ // Lazy evaluation
148
+ const canDelete$ = derived(({ and }) => and([
149
+ isLoggedIn$,
150
+ () => hasDeletePermission$, // Only if logged in
151
+ ]));
152
+ ```
153
+
154
+ ### Condition Types
155
+
156
+ ```typescript
157
+ type Condition =
158
+ | boolean // Static
159
+ | Atom<unknown> // Always read
160
+ | (() => boolean | Atom<unknown>); // Lazy
161
+ ```
162
+
163
+ ## or() — Logical OR
164
+
165
+ Short-circuit. Returns true if ANY truthy.
166
+
167
+ ```typescript
168
+ const hasData$ = derived(({ or }) => or([cacheData$, apiData$]));
169
+
170
+ // Lazy fallback
171
+ const data$ = derived(({ or }) => or([
172
+ () => primaryData$,
173
+ () => fallbackData$,
174
+ ]));
175
+ ```
176
+
177
+ ## Boolean + Async
178
+
179
+ ```typescript
180
+ // (A && B) || C
181
+ const result$ = derived(({ or, and }) => or([and([a$, b$]), c$]));
182
+
183
+ // Guard expensive ops
184
+ const data$ = derived(({ and, read }) => {
185
+ if (!and([isLoggedIn$, hasPermission$])) return null;
186
+ return read(expensiveData$);
187
+ });
188
+ ```
@@ -0,0 +1,238 @@
1
+ # Atom Patterns
2
+
3
+ ## Overview
4
+
5
+ ```typescript
6
+ const count$ = atom(0);
7
+ const user$ = atom<User | null>(null, { meta: { key: "auth.user" } });
8
+ const data$ = atom(() => expensiveInit());
9
+ ```
10
+
11
+ ## Core API
12
+
13
+ | Method | Signature | Description |
14
+ | ------------ | -------------------------- | ------------------------ |
15
+ | `get()` | `() => T` | Get current value |
16
+ | `set()` | `(value \| reducer)` | Update value |
17
+ | `reset()` | `() => void` | Reset to initial |
18
+ | `dirty()` | `() => boolean` | Changed since init/reset |
19
+ | `on()` | `(listener) => unsub` | Subscribe to changes |
20
+ | `_dispose()` | `() => void` | Cleanup (used by pool) |
21
+
22
+ ## Lazy Initialization
23
+
24
+ ```typescript
25
+ const config$ = atom(() => parseExpensiveConfig());
26
+
27
+ // reset() re-runs initializer
28
+ const timestamp$ = atom(() => Date.now());
29
+ timestamp$.reset(); // New timestamp
30
+
31
+ // Store function as value
32
+ const callback$ = atom(() => () => console.log("hello"));
33
+ ```
34
+
35
+ ## Dirty Tracking
36
+
37
+ ```typescript
38
+ const form$ = atom({ name: "", email: "" }, { meta: { key: "form" } });
39
+
40
+ form$.dirty(); // false
41
+ form$.set({ name: "John", email: "" });
42
+ form$.dirty(); // true
43
+ form$.reset();
44
+ form$.dirty(); // false
45
+ ```
46
+
47
+ ```tsx
48
+ function FormButtons() {
49
+ const isDirty = useSelector(() => form$.dirty());
50
+ return (
51
+ <div>
52
+ <button disabled={!isDirty}>Save</button>
53
+ <button onClick={() => form$.reset()}>Reset</button>
54
+ </div>
55
+ );
56
+ }
57
+ ```
58
+
59
+ ## AtomContext — Signal & Cleanup
60
+
61
+ Lazy initializer receives context:
62
+
63
+ ```typescript
64
+ interface AtomContext {
65
+ signal: AbortSignal; // Aborted on set()/reset()
66
+ onCleanup(fn: VoidFunction): void; // Runs on value change
67
+ }
68
+ ```
69
+
70
+ ### Abort Signal
71
+
72
+ ```typescript
73
+ const data$ = atom((ctx) => {
74
+ const controller = new AbortController();
75
+ ctx.signal.addEventListener("abort", () => controller.abort());
76
+ return fetch("/api/data", { signal: controller.signal });
77
+ });
78
+
79
+ data$.set(fetch("/api/data/new")); // Previous fetch aborted
80
+ ```
81
+
82
+ ### Cleanup
83
+
84
+ ```typescript
85
+ const subscription$ = atom((ctx) => {
86
+ const sub = websocket.subscribe("channel");
87
+ ctx.onCleanup(() => sub.unsubscribe());
88
+ return sub;
89
+ });
90
+
91
+ subscription$.reset(); // Unsubscribes, creates new
92
+ ```
93
+
94
+ ### Combined
95
+
96
+ ```typescript
97
+ const realtime$ = atom((ctx) => {
98
+ const socket = new WebSocket("wss://api.example.com");
99
+ ctx.onCleanup(() => socket.close());
100
+ fetchInitialData({ signal: ctx.signal });
101
+ return socket;
102
+ });
103
+ ```
104
+
105
+ ## Equality Options
106
+
107
+ ```typescript
108
+ // Default: strict (Object.is)
109
+ const count$ = atom(0);
110
+
111
+ // Shallow
112
+ const user$ = atom({ name: "", email: "" }, { equals: "shallow" });
113
+ user$.set((prev) => ({ ...prev })); // No notification
114
+
115
+ // Deep
116
+ const config$ = atom({ nested: { value: 1 } }, { equals: "deep" });
117
+
118
+ // Custom
119
+ const data$ = atom(
120
+ { id: 1, timestamp: Date.now() },
121
+ { equals: (a, b) => a.id === b.id }
122
+ );
123
+ ```
124
+
125
+ | Shorthand | Description |
126
+ | ------------ | ---------------------------- |
127
+ | `"strict"` | Object.is (default, fastest) |
128
+ | `"shallow"` | Compare keys with Object.is |
129
+ | `"shallow2"` | 2 levels deep |
130
+ | `"shallow3"` | 3 levels deep |
131
+ | `"deep"` | Full recursive (slowest) |
132
+
133
+ ## readonly() (REQUIRED)
134
+
135
+ **MUST** expose atoms as read-only:
136
+
137
+ ```typescript
138
+ const myModule = define(() => {
139
+ const count$ = atom(0, { meta: { key: "counter" } });
140
+
141
+ return {
142
+ count$: readonly(count$), // Consumers can't set()
143
+ increment: () => count$.set((p) => p + 1),
144
+ };
145
+ });
146
+
147
+ // Usage
148
+ const { count$, increment } = myModule();
149
+ count$.get(); // ✅ OK
150
+ count$.set(5); // ❌ TypeScript error
151
+ increment(); // ✅ Use action
152
+ ```
153
+
154
+ Multiple atoms:
155
+
156
+ ```typescript
157
+ return { ...readonly({ count$, name$ }), setName: (n) => name$.set(n) };
158
+ ```
159
+
160
+ ## Async Values
161
+
162
+ Atom stores Promises as-is:
163
+
164
+ ```typescript
165
+ const posts$ = atom(fetchPosts());
166
+ posts$.get(); // Promise<Post[]>
167
+ posts$.set(fetchPosts()); // Store new Promise
168
+
169
+ // With lazy init
170
+ const lazyPosts$ = atom(() => fetchPosts());
171
+ lazyPosts$.reset(); // Refetches
172
+ ```
173
+
174
+ **Note:** Use `derived()` with `read()` for automatic Promise unwrapping and Suspense.
175
+
176
+ ## Plugin System (.use())
177
+
178
+ ```typescript
179
+ const count$ = atom(0)
180
+ .use((src) => ({ ...src, double: () => src.get() * 2 }))
181
+ .use((src) => ({ ...src, triple: () => src.get() * 3 }));
182
+
183
+ count$.double(); // 0
184
+ count$.triple(); // 0
185
+ ```
186
+
187
+ ## Common Patterns
188
+
189
+ ### Form State
190
+
191
+ ```typescript
192
+ const form$ = atom<FormState>(
193
+ { values: {}, errors: {}, touched: {} },
194
+ { meta: { key: "contactForm" }, equals: "shallow" }
195
+ );
196
+
197
+ const setField = (name: string, value: string) => {
198
+ form$.set((p) => ({
199
+ ...p,
200
+ values: { ...p.values, [name]: value },
201
+ touched: { ...p.touched, [name]: true },
202
+ }));
203
+ };
204
+ ```
205
+
206
+ ### Cache with Expiration
207
+
208
+ ```typescript
209
+ const cache$ = atom((ctx) => {
210
+ const data = new Map<string, unknown>();
211
+ const timeout = setTimeout(() => data.clear(), 300_000);
212
+ ctx.onCleanup(() => clearTimeout(timeout));
213
+ return data;
214
+ });
215
+ ```
216
+
217
+ ### WebSocket
218
+
219
+ ```typescript
220
+ const ws$ = atom((ctx) => {
221
+ const socket = new WebSocket("wss://api.example.com");
222
+ socket.onopen = () => console.log("Connected");
223
+ ctx.onCleanup(() => socket.close());
224
+ return socket;
225
+ });
226
+
227
+ ws$.reset(); // Closes old, creates new
228
+ ```
229
+
230
+ ## When to Use
231
+
232
+ | Use Case | MutableAtom | Derived |
233
+ | ----------------------- | ----------- | ------- |
234
+ | User input/form state | ✅ | ❌ |
235
+ | API response storage | ✅ | ❌ |
236
+ | Computed from atoms | ❌ | ✅ |
237
+ | Transformed async data | ❌ | ✅ |
238
+ | Cached calculations | ❌ | ✅ |
@@ -0,0 +1,191 @@
1
+ # Deferred Loading with ready()
2
+
3
+ ## The Problem
4
+
5
+ When atom value is `undefined`/`null` during initialization:
6
+
7
+ ```typescript
8
+ // ❌ Problem: id starts undefined
9
+ const currentUserId$ = atom<string | undefined>(undefined);
10
+
11
+ const userProfile$ = derived(({ read, from }) => {
12
+ const userId = read(currentUserId$); // undefined initially
13
+ const user$ = from(userPool, userId); // Error: can't create entry
14
+ return read(user$);
15
+ });
16
+ ```
17
+
18
+ ## Solution: ready()
19
+
20
+ `ready()` returns `never` when value is `undefined`/`null`, blocking computation until value exists:
21
+
22
+ ```typescript
23
+ // ✅ Correct
24
+ const userProfile$ = derived(({ read, ready, from }) => {
25
+ const userId = ready(currentUserId$); // Blocks until truthy
26
+ const user$ = from(userPool, userId);
27
+ return read(user$);
28
+ });
29
+ ```
30
+
31
+ ## How It Works
32
+
33
+ | Input | Output |
34
+ | ----------------------------- | --------------- |
35
+ | `ready(atom<T \| undefined>)` | `T` when truthy |
36
+ | Value is `undefined`/`null` | Returns `never` |
37
+ | Value is truthy | Returns `T` |
38
+
39
+ ```typescript
40
+ const id$ = atom<string | undefined>(undefined);
41
+
42
+ derived(({ ready }) => {
43
+ const id = ready(id$);
44
+ // ^? string (not string | undefined)
45
+ return fetchUser(id);
46
+ });
47
+ ```
48
+
49
+ ## Behavior
50
+
51
+ ```typescript
52
+ const userId$ = atom<string | undefined>(undefined);
53
+
54
+ const profile$ = derived(({ read, ready, from }) => {
55
+ const userId = ready(userId$);
56
+ const user$ = from(userPool, userId);
57
+ return read(user$);
58
+ });
59
+
60
+ // Initially:
61
+ profile$.get(); // Promise never resolves (blocked)
62
+ profile$.staleValue; // fallback if set, else undefined
63
+
64
+ // After:
65
+ userId$.set("user-123");
66
+ await profile$.get(); // { name: "John", ... }
67
+ ```
68
+
69
+ ## Use Cases
70
+
71
+ ### Pool with Optional Params
72
+
73
+ ```typescript
74
+ const currentEntityId$ = atom<string | undefined>(undefined);
75
+
76
+ const entityDetails$ = derived(({ read, ready, from }) => {
77
+ const id = ready(currentEntityId$);
78
+ const entity$ = from(entityPool, id);
79
+ return read(entity$);
80
+ }, { fallback: null });
81
+ ```
82
+
83
+ ### Dependent Computation
84
+
85
+ ```typescript
86
+ const config$ = atom<Config | null>(null);
87
+ const apiUrl$ = atom<string | undefined>(undefined);
88
+
89
+ const data$ = derived(({ read, ready }) => {
90
+ const config = ready(config$);
91
+ const url = ready(apiUrl$);
92
+ return read(atom(() => fetch(`${url}/data?version=${config.version}`)));
93
+ });
94
+ ```
95
+
96
+ ### Conditional UI
97
+
98
+ ```typescript
99
+ function UserDashboard() {
100
+ const user = useSelector(({ read, ready }) => {
101
+ return ready(currentUser$);
102
+ });
103
+
104
+ return <Dashboard user={user} />;
105
+ }
106
+
107
+ // With state() fallback
108
+ function UserCard() {
109
+ const result = useSelector(({ state }) => state(currentUser$));
110
+
111
+ if (result.status === "loading") return <Skeleton />;
112
+ if (result.status === "error") return <Error />;
113
+ if (!result.value) return <LoginPrompt />;
114
+ return <Card user={result.value} />;
115
+ }
116
+ ```
117
+
118
+ ### Effect with Guard
119
+
120
+ ```typescript
121
+ effect(({ read, ready }) => {
122
+ const userId = ready(currentUserId$);
123
+ analytics.identify(userId);
124
+ }, { meta: { key: "analytics.identify" } });
125
+ ```
126
+
127
+ ### Multi-ready
128
+
129
+ ```typescript
130
+ const report$ = derived(({ read, ready, from }) => {
131
+ const userId = ready(currentUserId$);
132
+ const projectId = ready(currentProjectId$);
133
+ const teamId = ready(currentTeamId$);
134
+
135
+ const user$ = from(userPool, userId);
136
+ const project$ = from(projectPool, projectId);
137
+ const team$ = from(teamPool, teamId);
138
+
139
+ const [user, project, team] = [read(user$), read(project$), read(team$)];
140
+ return { user, project, team };
141
+ });
142
+ ```
143
+
144
+ ## ready() vs Manual Check
145
+
146
+ ```typescript
147
+ // ❌ Verbose, TypeScript still sees string | undefined
148
+ const profile$ = derived(({ read }) => {
149
+ const id = read(currentUserId$);
150
+ if (!id) return null;
151
+ return read(from(userPool, id)); // Type: still string | undefined
152
+ });
153
+
154
+ // ✅ Concise, TypeScript narrows to string
155
+ const profile$ = derived(({ ready, read, from }) => {
156
+ const id = ready(currentUserId$);
157
+ // ^? string
158
+ return read(from(userPool, id));
159
+ });
160
+ ```
161
+
162
+ ## With Suspense
163
+
164
+ ```tsx
165
+ function EntityPage() {
166
+ return (
167
+ <Suspense fallback={<Skeleton />}>
168
+ <EntityDetails />
169
+ </Suspense>
170
+ );
171
+ }
172
+
173
+ function EntityDetails() {
174
+ const entity = useSelector(({ read, ready, from }) => {
175
+ const id = ready(currentEntityId$); // Suspends until ID set
176
+ return read(from(entityPool, id));
177
+ });
178
+
179
+ return <Details entity={entity} />;
180
+ }
181
+ ```
182
+
183
+ ## Summary
184
+
185
+ | Scenario | Solution |
186
+ | ------------------------------- | --------------------------- |
187
+ | Pool with optional params | `ready(params$)` + `from()` |
188
+ | Dependent chains | `ready()` per dependency |
189
+ | Wait for auth state | `ready(currentUser$)` |
190
+ | Multi-value gate | Multiple `ready()` calls |
191
+ | Type narrowing | `ready()` removes `null` |