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,148 @@
1
+ import { CSSProperties } from 'react';
2
+ /**
3
+ * CSS variables for theming.
4
+ * Users can override these via CSS custom properties.
5
+ */
6
+ export declare const cssVariables: {
7
+ readonly "--atomirx-bg-primary": "#1a1a2e";
8
+ readonly "--atomirx-bg-secondary": "#16213e";
9
+ readonly "--atomirx-bg-tertiary": "#0f3460";
10
+ readonly "--atomirx-bg-hover": "#1f4287";
11
+ readonly "--atomirx-text-primary": "#eaeaea";
12
+ readonly "--atomirx-text-secondary": "#a0a0a0";
13
+ readonly "--atomirx-text-muted": "#666666";
14
+ readonly "--atomirx-border": "#2a2a4a";
15
+ readonly "--atomirx-accent": "#e94560";
16
+ readonly "--atomirx-accent-hover": "#ff6b6b";
17
+ readonly "--atomirx-success": "#4ade80";
18
+ readonly "--atomirx-warning": "#fbbf24";
19
+ readonly "--atomirx-error": "#ef4444";
20
+ readonly "--atomirx-info": "#3b82f6";
21
+ readonly "--atomirx-badge-mutable": "#3b82f6";
22
+ readonly "--atomirx-badge-derived": "#8b5cf6";
23
+ readonly "--atomirx-badge-effect": "#f59e0b";
24
+ readonly "--atomirx-badge-pool": "#10b981";
25
+ readonly "--atomirx-badge-module": "#ec4899";
26
+ readonly "--atomirx-radius": "6px";
27
+ readonly "--atomirx-radius-lg": "8px";
28
+ readonly "--atomirx-font-size": "11px";
29
+ readonly "--atomirx-font-size-sm": "9px";
30
+ readonly "--atomirx-font-mono": "ui-monospace, SFMono-Regular, SF Mono, Menlo, monospace";
31
+ };
32
+ /**
33
+ * Base container styles with CSS variables.
34
+ */
35
+ export declare const baseContainerStyle: CSSProperties;
36
+ /**
37
+ * Floating toggle button styles.
38
+ */
39
+ export declare const floatingButtonStyle: CSSProperties;
40
+ /**
41
+ * Panel container styles by position.
42
+ * Uses non-shorthand border properties to avoid React warnings.
43
+ */
44
+ export declare const getPanelStyle: (position: "bottom" | "right" | "left", size: number, isOpen: boolean) => CSSProperties;
45
+ /**
46
+ * Tab bar styles.
47
+ */
48
+ export declare const tabBarStyle: CSSProperties;
49
+ /**
50
+ * Tab button styles.
51
+ */
52
+ export declare const getTabStyle: (isActive: boolean) => CSSProperties;
53
+ /**
54
+ * Toolbar styles (search, filter, actions).
55
+ */
56
+ export declare const toolbarStyle: CSSProperties;
57
+ /**
58
+ * Search input styles.
59
+ */
60
+ export declare const searchInputStyle: CSSProperties;
61
+ /**
62
+ * Filter button group styles.
63
+ */
64
+ export declare const filterGroupStyle: CSSProperties;
65
+ /**
66
+ * Filter button styles.
67
+ */
68
+ export declare const getFilterButtonStyle: (isActive: boolean) => CSSProperties;
69
+ /**
70
+ * Entity list container styles.
71
+ */
72
+ export declare const entityListStyle: CSSProperties;
73
+ /**
74
+ * Entity item styles.
75
+ */
76
+ export declare const getEntityItemStyle: (isSelected: boolean) => CSSProperties;
77
+ /**
78
+ * Entity type badge styles.
79
+ */
80
+ export declare const getTypeBadgeStyle: (type: "mutable" | "derived" | "effect" | "pool" | "module") => CSSProperties;
81
+ /**
82
+ * Entity key/name styles.
83
+ */
84
+ export declare const entityKeyStyle: CSSProperties;
85
+ /**
86
+ * Entity value preview styles.
87
+ */
88
+ export declare const entityValueStyle: CSSProperties;
89
+ /**
90
+ * Details panel styles.
91
+ */
92
+ export declare const detailsPanelStyle: CSSProperties;
93
+ /**
94
+ * Details header styles.
95
+ */
96
+ export declare const detailsHeaderStyle: CSSProperties;
97
+ /**
98
+ * Details section styles.
99
+ */
100
+ export declare const detailsSectionStyle: CSSProperties;
101
+ /**
102
+ * Details label styles.
103
+ */
104
+ export declare const detailsLabelStyle: CSSProperties;
105
+ /**
106
+ * Details value styles.
107
+ */
108
+ export declare const detailsValueStyle: CSSProperties;
109
+ /**
110
+ * Code block styles.
111
+ */
112
+ export declare const codeBlockStyle: CSSProperties;
113
+ /**
114
+ * History entry styles.
115
+ */
116
+ export declare const historyEntryStyle: CSSProperties;
117
+ /**
118
+ * History timestamp styles.
119
+ */
120
+ export declare const historyTimestampStyle: CSSProperties;
121
+ /**
122
+ * Position button styles.
123
+ */
124
+ export declare const positionButtonStyle: CSSProperties;
125
+ /**
126
+ * Close button styles.
127
+ */
128
+ export declare const closeButtonStyle: CSSProperties;
129
+ /**
130
+ * Empty state styles.
131
+ */
132
+ export declare const emptyStateStyle: CSSProperties;
133
+ /**
134
+ * Status indicator styles.
135
+ */
136
+ export declare const getStatusStyle: (status: "ready" | "loading" | "error") => CSSProperties;
137
+ /**
138
+ * Resize handle styles.
139
+ */
140
+ export declare const getResizeHandleStyle: (position: "bottom" | "right" | "left") => CSSProperties;
141
+ /**
142
+ * Main content area styles.
143
+ */
144
+ export declare const mainContentStyle: CSSProperties;
145
+ /**
146
+ * Stat badge styles.
147
+ */
148
+ export declare const statBadgeStyle: CSSProperties;
package/package.json CHANGED
@@ -7,7 +7,8 @@
7
7
  "management",
8
8
  "atom",
9
9
  "derived",
10
- "effect"
10
+ "effect",
11
+ "devtools"
11
12
  ],
12
13
  "author": "Gignuyen",
13
14
  "license": "MIT",
@@ -16,7 +17,20 @@
16
17
  "url": "https://github.com/linq2js/atomirx"
17
18
  },
18
19
  "homepage": "https://github.com/linq2js/atomirx",
19
- "version": "0.0.7",
20
+ "version": "0.1.0",
21
+ "bin": {
22
+ "atomirx": "./bin/cli.js"
23
+ },
24
+ "cursor": {
25
+ "skills": [
26
+ "./skills/atomirx"
27
+ ]
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "bin",
32
+ "skills"
33
+ ],
20
34
  "type": "module",
21
35
  "main": "./dist/index.cjs",
22
36
  "module": "./dist/index.js",
@@ -31,6 +45,16 @@
31
45
  "import": "./dist/react/index.js",
32
46
  "require": "./dist/react/index.cjs",
33
47
  "types": "./dist/react/index.d.ts"
48
+ },
49
+ "./devtools": {
50
+ "import": "./dist/devtools/index.js",
51
+ "require": "./dist/devtools/index.cjs",
52
+ "types": "./dist/devtools/index.d.ts"
53
+ },
54
+ "./react-devtools": {
55
+ "import": "./dist/react-devtools/index.js",
56
+ "require": "./dist/react-devtools/index.cjs",
57
+ "types": "./dist/react-devtools/index.d.ts"
34
58
  }
35
59
  },
36
60
  "scripts": {
@@ -0,0 +1,456 @@
1
+ ---
2
+ name: atomirx
3
+ description: Guide for atomirx reactive state management. Use for atom, derived, effect, select, pool, define(), ready(), React hooks (useSelector, rx, useAction, useStable), and debugging reactive flows.
4
+ ---
5
+
6
+ # atomirx State Management
7
+
8
+ ## Philosophy: You Don't Care About Async/Sync
9
+
10
+ **Atomirx abstracts away the async/sync distinction.** In reactive contexts, you write sync code regardless of whether atoms contain sync values or Promises.
11
+
12
+ | Context | Your Code | Async Handling |
13
+ | ---------------------------------- | -------------------------------- | -------------------- |
14
+ | `useSelector`, `derived`, `effect` | **Sync** — just `read()` | Suspense handles it |
15
+ | Services | Receive values as **parameters** | Don't read atoms |
16
+ | Outside reactive context | `await .get()` | Explicit when needed |
17
+
18
+ ```typescript
19
+ // You don't care if user$ contains sync value or Promise
20
+ const userName$ = derived(({ read }) => read(user$).name);
21
+
22
+ // Same pattern works for any atom
23
+ function MyComponent() {
24
+ const name = useSelector(userName$); // Sync code, Suspense handles async
25
+ return <div>{name}</div>;
26
+ }
27
+ ```
28
+
29
+ **This is why naming doesn't use `Async$` suffix** — the abstraction makes it irrelevant.
30
+
31
+ ## Bootstrap Pattern (DevTools)
32
+
33
+ **DevTools must initialize BEFORE any atoms are created** to properly track all reactive primitives.
34
+
35
+ ```typescript
36
+ // main.tsx
37
+ async function main() {
38
+ // 1. Render devtools FIRST (before any atom imports)
39
+ if (process.env.NODE_ENV !== 'production') {
40
+ const { renderDevtools } = await import("atomirx/react-devtools");
41
+ await renderDevtools();
42
+ }
43
+
44
+ // 2. THEN import React and App (which contains atoms)
45
+ const React = await import("react");
46
+ const ReactDOM = await import("react-dom/client");
47
+ const { default: App } = await import("./App");
48
+
49
+ // 3. Render app
50
+ ReactDOM.createRoot(document.getElementById("root")!).render(
51
+ <React.StrictMode>
52
+ <App />
53
+ </React.StrictMode>
54
+ );
55
+ }
56
+
57
+ main();
58
+ ```
59
+
60
+ **Why this order matters:**
61
+
62
+ - `onCreateHook` must be set up before atoms are created
63
+ - DevTools uses `onCreateHook` to track atom/derived/effect creation
64
+ - If atoms are imported before devtools, they won't appear in the panel
65
+
66
+ **For apps with bootstrap logic:**
67
+
68
+ ```typescript
69
+ // bootstrap.ts
70
+ export async function bootstrap() {
71
+ // Initialize services, fetch config, etc.
72
+ await initializeServices();
73
+ }
74
+
75
+ // main.tsx
76
+ async function main() {
77
+ // 1. DevTools first
78
+ if (process.env.NODE_ENV !== 'production') {
79
+ const { renderDevtools } = await import("atomirx/react-devtools");
80
+ await renderDevtools();
81
+ }
82
+
83
+ // 2. Bootstrap (may create atoms)
84
+ const { bootstrap } = await import("./bootstrap");
85
+ await bootstrap();
86
+
87
+ // 3. Import and render app
88
+ const React = await import("react");
89
+ const ReactDOM = await import("react-dom/client");
90
+ const { default: App } = await import("./App");
91
+
92
+ ReactDOM.createRoot(document.getElementById("root")!).render(
93
+ <React.StrictMode>
94
+ <App />
95
+ </React.StrictMode>
96
+ );
97
+ }
98
+
99
+ main();
100
+ ```
101
+
102
+ ## Core Primitives
103
+
104
+ | Primitive | Purpose | Subscription |
105
+ | ------------------ | ------------------------------ | ------------ |
106
+ | `atom<T>(initial)` | Mutable state | No |
107
+ | `derived(fn)` | Computed value | Yes (lazy) |
108
+ | `effect(fn)` | Side effects on changes | Yes (eager) |
109
+ | `select(fn)` | One-time read, no subscription | No |
110
+ | `pool(fn, opts)` | Parameterized atoms with GC | Per-entry |
111
+ | `batch(fn)` | Group updates, single notify | No |
112
+ | `define(fn)` | Lazy singleton factory | No |
113
+ | `onCreateHook` | Track atom/effect creation | No |
114
+ | `onErrorHook` | Global error handling | No |
115
+
116
+ ## SelectContext Methods
117
+
118
+ **Works identically in `derived()`, `effect()`, `useSelector()`, `rx()`.** Learn once, use everywhere.
119
+
120
+ | Method | Signature | Behavior |
121
+ | ----------- | ------------------------- | -------------------------------- |
122
+ | `read()` | `read(atom$)` | Read + track dependency |
123
+ | `ready()` | `ready(atom$)` or with fn | Wait for non-null (suspends) |
124
+ | `from()` | `from(pool, params)` | Get ScopedAtom from pool |
125
+ | `track()` | `track(atom$)` | Track without reading |
126
+ | `untrack()` | `untrack(atom$)` or fn | Read/exec without tracking |
127
+ | `safe()` | `safe(() => expr)` | Catch errors, preserve Suspense |
128
+ | `all()` | `all([a$, b$])` | Wait for all (Promise.all) |
129
+ | `any()` | `any({ a: a$, b: b$ })` | First ready (Promise.any) |
130
+ | `race()` | `race({ a: a$, b: b$ })` | First settled (Promise.race) |
131
+ | `settled()` | `settled([a$, b$])` | All results (Promise.allSettled) |
132
+ | `state()` | `state(atom$)` | Get state without throwing |
133
+ | `and()` | `and([cond1, cond2])` | Logical AND, short-circuit |
134
+ | `or()` | `or([cond1, cond2])` | Logical OR, short-circuit |
135
+
136
+ ```typescript
137
+ // Same pattern works everywhere
138
+ const pattern = ({ read, all, safe }) => {
139
+ const [user, posts] = all([user$, posts$]);
140
+ const [err, parsed] = safe(() => JSON.parse(read(config$)));
141
+ return { user, posts, config: err ? null : parsed };
142
+ };
143
+
144
+ const combined$ = derived(pattern);
145
+ const data = useSelector(pattern);
146
+ effect(pattern);
147
+ {
148
+ rx(pattern);
149
+ }
150
+ ```
151
+
152
+ ## read() vs ready() vs state()
153
+
154
+ | Method | On null/undefined | On loading | Use Case |
155
+ | --------- | ----------------- | -------------- | -------------------- |
156
+ | `read()` | Returns null | Throws Promise | Always need value |
157
+ | `ready()` | Suspends | Throws Promise | Wait for data |
158
+ | `state()` | Returns state obj | Returns state | Manual loading/error |
159
+
160
+ ## Key Rules
161
+
162
+ 1. **MUST use define()** for all state/logic. Global classes OK, variables MUST be in `define()`.
163
+ 2. **MUST use batch()** for multiple atom updates.
164
+ 3. **MUST group useSelector calls** into single selector.
165
+ 4. **useAction deps: pass atoms, use .get() inside** for auto re-dispatch.
166
+ 5. **NEVER try/catch with read()** — breaks Suspense. Use `safe()`.
167
+ 6. **MUST co-locate mutations** in store that owns the atom.
168
+ 7. **MUST export readonly atoms** via `readonly({ atom$ })`.
169
+ 8. **SelectContext is sync only** — NEVER use in setTimeout/Promise.then.
170
+ 9. **Services vs Stores** — Services are stateless, Stores have atoms.
171
+ 10. **NEVER import service factories** — use `define()`, invoke with `()`.
172
+ 11. **Single effect, single workflow** — split multiple workflows.
173
+ 12. **MUST define meta.key** for debugging: `{ meta: { key: "store.name" } }`.
174
+ 13. **MUST use .override()** for hooks, never assign `.current` directly.
175
+ 14. **MUST use useStable()** — NEVER use React's useCallback.
176
+ 15. **Use pool** for parameterized state instead of manual Maps.
177
+
178
+ ### meta.key (REQUIRED)
179
+
180
+ ```tsx
181
+ // ✅ DO: Define meta.key
182
+ const user$ = atom<User | null>(null, { meta: { key: "auth.user" } });
183
+ const isAuth$ = derived(({ read }) => !!read(user$), { meta: { key: "auth.isAuthenticated" } });
184
+ effect(({ read }) => { ... }, { meta: { key: "auth.persistSession" } });
185
+ const userPool = pool((id: string) => fetchUser(id), { gcTime: 60_000, meta: { key: "users" } });
186
+
187
+ // ❌ DON'T: Skip meta.key
188
+ const user$ = atom<User | null>(null);
189
+ ```
190
+
191
+ ### useSelector Grouping
192
+
193
+ ```tsx
194
+ // ✅ DO: Single useSelector
195
+ const { user, posts, settings } = useSelector(({ read }) => ({
196
+ user: read(user$),
197
+ posts: read(posts$),
198
+ settings: read(settings$),
199
+ }));
200
+
201
+ // ❌ DON'T: Multiple calls
202
+ const user = useSelector(user$);
203
+ const posts = useSelector(posts$);
204
+ ```
205
+
206
+ ### useAction with Atoms
207
+
208
+ ```tsx
209
+ // ✅ DO: Pass atoms to deps, use .get() inside
210
+ const load = useAction(async () => atom1$.get() + (await atom2$.get()), {
211
+ deps: [atom1$, atom2$],
212
+ lazy: false,
213
+ });
214
+
215
+ // ❌ DON'T: useSelector values in deps
216
+ const { v1, v2 } = useSelector(({ read }) => ({
217
+ v1: read(atom1$),
218
+ v2: read(atom2$),
219
+ }));
220
+ const load = useAction(async () => v1 + v2, { deps: [v1, v2], lazy: false });
221
+ ```
222
+
223
+ ### batch() for Multiple Updates
224
+
225
+ ```tsx
226
+ // ✅ DO: Batch
227
+ batch(() => {
228
+ user$.set(newUser);
229
+ settings$.set(newSettings);
230
+ lastUpdated$.set(Date.now());
231
+ });
232
+
233
+ // ❌ DON'T: Separate updates
234
+ user$.set(newUser);
235
+ settings$.set(newSettings);
236
+ ```
237
+
238
+ ### useStable() (REQUIRED)
239
+
240
+ **MUST use `useStable()` instead of React's `useCallback`/`useMemo`.**
241
+
242
+ ```tsx
243
+ // ❌ FORBIDDEN
244
+ const handleSubmit = useCallback(
245
+ () => auth.register(username),
246
+ [auth, username]
247
+ );
248
+
249
+ // ✅ REQUIRED
250
+ const stable = useStable({
251
+ onSubmit: () => auth.register(username),
252
+ onLogin: () => auth.login(),
253
+ config: { timeout: 5000, retries: 3 },
254
+ columns: [{ key: "name", label: "Name" }],
255
+ });
256
+ ```
257
+
258
+ ### pool() for Parameterized State
259
+
260
+ ```tsx
261
+ // ✅ DO: Use pool
262
+ const userPool = pool((id: string) => fetchUser(id), {
263
+ gcTime: 60_000,
264
+ meta: { key: "users" },
265
+ });
266
+ userPool.get("user-1");
267
+ userPool.set("user-1", newUser);
268
+
269
+ // In reactive context use from()
270
+ const userPosts$ = derived(({ read, from }) => {
271
+ const user$ = from(userPool, "user-1");
272
+ return read(user$).posts;
273
+ });
274
+
275
+ // ❌ DON'T: Manual Map
276
+ const userCache = new Map<string, MutableAtom<User>>();
277
+ ```
278
+
279
+ ### and()/or() for Boolean Logic
280
+
281
+ ```tsx
282
+ // ✅ DO: Use and()/or()
283
+ const canEdit$ = derived(({ and }) => and([isLoggedIn$, hasPermission$]));
284
+ const hasData$ = derived(({ or }) => or([cacheData$, apiData$]));
285
+
286
+ // Lazy evaluation
287
+ const canDelete$ = derived(({ and }) =>
288
+ and([
289
+ isLoggedIn$,
290
+ () => hasDeletePermission$, // Only evaluated if logged in
291
+ ])
292
+ );
293
+
294
+ // ❌ DON'T: Manual logic
295
+ const canEdit$ = derived(
296
+ ({ read }) => read(isLoggedIn$) && read(hasPermission$)
297
+ );
298
+ ```
299
+
300
+ ### untrack() for Non-Reactive Reads
301
+
302
+ ```tsx
303
+ // ✅ DO: Use untrack() when you need to read without re-computing
304
+ const combined$ = derived(({ read, untrack }) => {
305
+ const count = read(count$); // Tracks count$ - re-computes on change
306
+ const config = untrack(config$); // Does NOT track - no re-compute on change
307
+ return count * config.multiplier;
308
+ });
309
+
310
+ // Also works with functions for multiple reads
311
+ const snapshot$ = derived(({ read, untrack }) => {
312
+ const liveData = read(liveData$); // Tracked
313
+ const snapshot = untrack(() => {
314
+ // None of these are tracked
315
+ return { a: read(a$), b: read(b$), c: read(c$) };
316
+ });
317
+ return { liveData, snapshot };
318
+ });
319
+ ```
320
+
321
+ ### define() for Services and Stores
322
+
323
+ ```tsx
324
+ // ✅ STORE (has atoms)
325
+ export const counterStore = define(() => {
326
+ const count$ = atom(0, { meta: { key: "counter.count" } });
327
+ return {
328
+ ...readonly({ count$ }),
329
+ increment: () => count$.set((x) => x + 1),
330
+ };
331
+ });
332
+
333
+ // ✅ SERVICE (stateless)
334
+ export const storageService = define(
335
+ (): StorageService => ({
336
+ get: (key) => localStorage.getItem(key),
337
+ set: (key, val) => localStorage.setItem(key, val),
338
+ })
339
+ );
340
+
341
+ // ❌ FORBIDDEN: Factory pattern
342
+ import { getAuthService } from "@/services/auth";
343
+ const auth = getAuthService(); // WRONG
344
+
345
+ // ✅ REQUIRED: Module invocation
346
+ import { authService } from "@/services/auth.service";
347
+ const auth = authService(); // Correct
348
+ ```
349
+
350
+ ### Hooks (.override() REQUIRED)
351
+
352
+ ```tsx
353
+ // ❌ FORBIDDEN: Direct assignment
354
+ onCreateHook.current = (info) => { ... };
355
+
356
+ // ✅ REQUIRED: Use .override()
357
+ onCreateHook.override((prev) => (info) => {
358
+ prev?.(info);
359
+ console.log(`Created ${info.type}: ${info.key}`);
360
+ });
361
+
362
+ onErrorHook.override((prev) => (info) => {
363
+ prev?.(info);
364
+ Sentry.captureException(info.error);
365
+ });
366
+ ```
367
+
368
+ ## Finding Things
369
+
370
+ | To Find | Search Pattern |
371
+ | ------------------ | ---------------------------------------------------------- |
372
+ | Atom definitions | `atom<` or `atom(` |
373
+ | Derived atoms | `derived((` |
374
+ | Effects | `effect((` |
375
+ | Pools | `pool((` |
376
+ | Stores | `define(() =>` in `*.store.ts` |
377
+ | Services | `define(() =>` in `*.service.ts` |
378
+ | Atom usages | `read(`, `ready(`, `all([`, `any({`, `race({`, `settled([` |
379
+ | Non-reactive reads | `untrack(` |
380
+ | Pool usages | `from(poolName,` |
381
+ | Mutations | Find store owner, check return statement |
382
+ | Hook setup | `onCreateHook.override`, `onErrorHook.override` |
383
+
384
+ ## Debugging "Why doesn't X update?"
385
+
386
+ 1. Find atom being set → search `.set(`
387
+ 2. Find subscribers → search `read(atomName$)`, `ready(atomName$)`
388
+ 3. Check derived is subscribed → used in `useSelector`?
389
+ 4. Check effect cleanup → doesn't prevent re-run?
390
+ 5. For pools → check if entry was GC'd, verify `from()` usage
391
+
392
+ ## Common Issues
393
+
394
+ | Symptom | Likely Cause | Fix |
395
+ | ---------------------- | ---------------------------- | --------------------------------- |
396
+ | Derived never updates | No active subscription | Use `useSelector` in component |
397
+ | Effect runs infinitely | Setting atom it reads | Use `select()` for non-reactive |
398
+ | ready() never resolves | Value never becomes non-null | Check data flow |
399
+ | Stale closure | Reading atom in callback | Use `.get()` in callbacks |
400
+ | Suspense not working | try/catch around read() | Use `safe()` instead |
401
+ | Hook not firing | Direct `.current` assign | Use `.override()` instead |
402
+ | Missing hook calls | Hook chain broken | Always call `prev?.(info)` |
403
+ | Pool entry missing | GC'd before access | Increase gcTime |
404
+ | ScopedAtom error | Used outside context | Only use from() inside derived |
405
+ | Too many re-computes | Tracking unnecessary deps | Use `untrack()` for config |
406
+ | DevTools missing atoms | Atoms created before hook | Use bootstrap pattern (see above) |
407
+
408
+ ## Naming Conventions
409
+
410
+ | Type | Variable | File | Contains |
411
+ | ----------- | ------------- | ----------------- | ----------------------- |
412
+ | **Service** | `authService` | `auth.service.ts` | Pure functions only |
413
+ | **Store** | `authStore` | `auth.store.ts` | Atoms, derived, effects |
414
+
415
+ | Type | Suffix | Example |
416
+ | -------------------- | --------- | ------------------------------ |
417
+ | Atom (sync or async) | `$` | `count$`, `user$`, `products$` |
418
+ | Derived | `$` | `doubled$`, `userName$` |
419
+ | Pool | `Pool` | `userPool`, `productPool` |
420
+ | Service | `Service` | `authService` (NO atoms) |
421
+ | Store | `Store` | `authStore` (HAS atoms) |
422
+ | Actions | verb-led | `navigateTo`, `invalidate` |
423
+
424
+ **Why no `Async$`?** Atomirx abstracts async/sync — you don't care in SelectContext (Suspense handles it).
425
+
426
+ ### File Structure
427
+
428
+ ```
429
+ src/
430
+ ├── services/ # Stateless
431
+ │ ├── auth/
432
+ │ │ └── auth.service.ts
433
+ │ └── crypto/
434
+ │ └── crypto.service.ts
435
+ └── stores/ # Stateful
436
+ ├── auth.store.ts
437
+ ├── todos.store.ts
438
+ └── sync.store.ts
439
+ ```
440
+
441
+ ## References
442
+
443
+ - [Rules & Best Practices](references/rules.md)
444
+ - [Pool Patterns](references/pool-patterns.md)
445
+ - [Select Context](references/select-context.md)
446
+ - [Deferred Loading](references/deferred-loading.md)
447
+ - [React Integration](references/react-integration.md)
448
+ - [Error Handling](references/error-handling.md)
449
+ - [Async Patterns](references/async-patterns.md)
450
+ - [Atom Patterns](references/atom-patterns.md)
451
+ - [Derived Patterns](references/derived-patterns.md)
452
+ - [Effect Patterns](references/effect-patterns.md)
453
+ - [Hooks](references/hooks.md)
454
+ - [Testing Patterns](references/testing-patterns.md)
455
+ - [Store Template](references/store-template.md)
456
+ - [Service Template](references/service-template.md)