atomirx 0.0.8 → 0.1.1

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 -1
  33. package/dist/react/index.js +191 -151
  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 -42
package/bin/cli.js ADDED
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync, mkdirSync, cpSync, readdirSync, statSync } from "fs";
4
+ import { join, dirname } from "path";
5
+ import { fileURLToPath } from "url";
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+
10
+ const COMMANDS = {
11
+ "add-skill": addSkill,
12
+ help: showHelp,
13
+ };
14
+
15
+ function showHelp() {
16
+ console.log(`
17
+ atomirx CLI
18
+
19
+ Usage:
20
+ npx atomirx <command>
21
+
22
+ Commands:
23
+ add-skill Install atomirx Cursor skills to your project
24
+ help Show this help message
25
+
26
+ Examples:
27
+ npx atomirx add-skill
28
+ `);
29
+ }
30
+
31
+ function addSkill() {
32
+ const sourceDir = join(__dirname, "..", "skills", "atomirx");
33
+ const targetDir = join(process.cwd(), ".cursor", "skills", "atomirx");
34
+
35
+ // Check if source skills exist
36
+ if (!existsSync(sourceDir)) {
37
+ console.error("❌ Skills not found in package. This may be a packaging issue.");
38
+ process.exit(1);
39
+ }
40
+
41
+ // Check if target already exists
42
+ if (existsSync(targetDir)) {
43
+ console.log("⚠️ Skills already exist at .cursor/skills/atomirx");
44
+ console.log(" To reinstall, remove the directory first and run again.");
45
+ return;
46
+ }
47
+
48
+ // Create target directory
49
+ mkdirSync(targetDir, { recursive: true });
50
+
51
+ // Copy skills recursively
52
+ copyRecursive(sourceDir, targetDir);
53
+
54
+ console.log("✅ atomirx skills installed to .cursor/skills/atomirx");
55
+ console.log("");
56
+ console.log("The skill includes:");
57
+ console.log(" - SKILL.md - Main skill guide");
58
+ console.log(" - references/ - Detailed pattern documentation");
59
+ console.log("");
60
+ console.log("Your AI assistant will now have access to atomirx best practices!");
61
+ }
62
+
63
+ function copyRecursive(source, target) {
64
+ const entries = readdirSync(source);
65
+
66
+ for (const entry of entries) {
67
+ const sourcePath = join(source, entry);
68
+ const targetPath = join(target, entry);
69
+ const stat = statSync(sourcePath);
70
+
71
+ if (stat.isDirectory()) {
72
+ mkdirSync(targetPath, { recursive: true });
73
+ copyRecursive(sourcePath, targetPath);
74
+ } else {
75
+ cpSync(sourcePath, targetPath);
76
+ }
77
+ }
78
+ }
79
+
80
+ // Main
81
+ const command = process.argv[2] || "help";
82
+ const handler = COMMANDS[command];
83
+
84
+ if (handler) {
85
+ handler();
86
+ } else {
87
+ console.error(`Unknown command: ${command}`);
88
+ showHelp();
89
+ process.exit(1);
90
+ }
@@ -1,7 +1,7 @@
1
1
  import { CreateInfo } from './onCreateHook';
2
2
  import { ReactiveSelector, SelectContext } from './select';
3
3
  import { DerivedAtom, DerivedOptions } from './types';
4
- import { WithReadySelectContext } from './withReady';
4
+ import { WithReadyContext } from './withReady';
5
5
  /**
6
6
  * Internal options for derived atoms.
7
7
  * These are not part of the public API.
@@ -21,7 +21,7 @@ export interface DerivedInternalOptions {
21
21
  * Currently identical to `SelectContext`, but defined separately to allow
22
22
  * future derived-specific extensions without breaking changes.
23
23
  */
24
- export interface DerivedContext extends SelectContext, WithReadySelectContext {
24
+ export interface DerivedContext extends SelectContext, WithReadyContext {
25
25
  }
26
26
  /**
27
27
  * Creates a derived (computed) atom from source atom(s).
@@ -1,11 +1,12 @@
1
1
  import { ReactiveSelector, SelectContext } from './select';
2
2
  import { EffectMeta, EffectOptions } from './types';
3
- import { WithReadySelectContext } from './withReady';
3
+ import { WithAbortContext } from './withAbort';
4
+ import { WithReadyContext } from './withReady';
4
5
  /**
5
6
  * Context object passed to effect functions.
6
7
  * Extends `SelectContext` with cleanup utilities.
7
8
  */
8
- export interface EffectContext extends SelectContext, WithReadySelectContext {
9
+ export interface EffectContext extends SelectContext, WithReadyContext, WithAbortContext {
9
10
  /**
10
11
  * Register a cleanup function that runs before the next execution or on dispose.
11
12
  * Multiple cleanup functions can be registered; they run in FIFO order.
@@ -1,5 +1,5 @@
1
1
  import { Effect } from './effect';
2
- import { MutableAtomMeta, DerivedAtomMeta, MutableAtom, DerivedAtom, ModuleMeta, EffectMeta } from './types';
2
+ import { MutableAtomMeta, DerivedAtomMeta, MutableAtom, DerivedAtom, ModuleMeta, EffectMeta, PoolMeta, Pool } from './types';
3
3
  /**
4
4
  * Information provided when a mutable atom is created.
5
5
  */
@@ -42,7 +42,7 @@ export interface EffectInfo {
42
42
  /**
43
43
  * Union type for atom/derived/effect creation info.
44
44
  */
45
- export type CreateInfo = MutableInfo | DerivedInfo | EffectInfo;
45
+ export type CreateInfo = MutableInfo | DerivedInfo | EffectInfo | PoolInfo;
46
46
  /**
47
47
  * Information provided when a module (via define()) is created.
48
48
  */
@@ -56,6 +56,19 @@ export interface ModuleInfo {
56
56
  /** The created module instance */
57
57
  instance: unknown;
58
58
  }
59
+ /**
60
+ * Information provided when a pool is created.
61
+ */
62
+ export interface PoolInfo {
63
+ /** Discriminator for pools */
64
+ type: "pool";
65
+ /** Optional key from pool options (for debugging/devtools) */
66
+ key: string | undefined;
67
+ /** Optional metadata from pool options */
68
+ meta: PoolMeta | undefined;
69
+ /** The created pool instance */
70
+ instance: Pool<any, any>;
71
+ }
59
72
  /**
60
73
  * Global hook that fires whenever an atom or module is created.
61
74
  *
@@ -4,7 +4,10 @@ import { CreateInfo } from './onCreateHook';
4
4
  */
5
5
  export interface ErrorInfo {
6
6
  /** The source that produced the error (atom, derived, or effect) */
7
- source: CreateInfo;
7
+ source: CreateInfo | {
8
+ type: string;
9
+ key?: string;
10
+ };
8
11
  /** The error that was thrown */
9
12
  error: unknown;
10
13
  }
@@ -0,0 +1,78 @@
1
+ import { AtomContext } from './atom';
2
+ import { Pool, PoolOptions } from './types';
3
+ /**
4
+ * Creates a pool - a collection of atoms indexed by params with automatic GC.
5
+ *
6
+ * A pool is similar to atomFamily in Jotai/Recoil, but with:
7
+ * - Automatic garbage collection based on `gcTime`
8
+ * - ScopedAtom pattern to prevent memory leaks from stale references
9
+ * - Promise-aware GC (never GC while value is a pending Promise)
10
+ * - GC timer resets on create, value change, and access
11
+ *
12
+ * ## Public API (Value-based)
13
+ *
14
+ * The public API works with values, not atoms:
15
+ * - `get(params)` - Get current value
16
+ * - `set(params, value)` - Set value
17
+ * - `has(params)` - Check if entry exists
18
+ * - `remove(params)` - Remove entry
19
+ * - `clear()` - Remove all entries
20
+ * - `forEach(cb)` - Iterate entries
21
+ * - `on(cb)` - Subscribe to pool events (create, change, remove)
22
+ *
23
+ * ## Reactive Context (via SelectContext.from())
24
+ *
25
+ * In derived/effect/useSelector, use `from(pool, params)` to get a ScopedAtom:
26
+ * ```ts
27
+ * derived(({ read, from }) => {
28
+ * const user$ = from(userPool, "user-1");
29
+ * return read(user$);
30
+ * });
31
+ * ```
32
+ *
33
+ * The ScopedAtom is automatically disposed after the computation,
34
+ * preventing memory leaks from stale atom references.
35
+ *
36
+ * @template P - The type of params used to index entries
37
+ * @template T - The type of value stored in each entry
38
+ * @param init - Factory function that creates initial value for params
39
+ * @param options - Pool configuration
40
+ * @returns A Pool instance
41
+ *
42
+ * @example Basic usage
43
+ * ```ts
44
+ * const userPool = pool(
45
+ * (id: string) => ({ name: "", email: "" }),
46
+ * { gcTime: 60_000 }
47
+ * );
48
+ *
49
+ * // Get/set values
50
+ * userPool.set("user-1", { name: "John", email: "john@example.com" });
51
+ * const user = userPool.get("user-1");
52
+ *
53
+ * // In reactive context
54
+ * derived(({ read, from }) => {
55
+ * const user$ = from(userPool, "user-1");
56
+ * return read(user$);
57
+ * });
58
+ * ```
59
+ *
60
+ * @example Async values
61
+ * ```ts
62
+ * const dataPool = pool(
63
+ * (id: string) => fetchData(id), // Returns Promise
64
+ * { gcTime: 300_000 }
65
+ * );
66
+ *
67
+ * // GC is paused while Promise is pending
68
+ * // After resolve/reject, GC timer starts
69
+ * ```
70
+ */
71
+ export declare function pool<T, P = unknown>(init: (() => T) | ((params: P, context: AtomContext) => T), options: PoolOptions<P>): Pool<P, T>;
72
+ /**
73
+ * Type guard to check if a value is a Pool.
74
+ *
75
+ * @param value - The value to check
76
+ * @returns true if value is a Pool instance
77
+ */
78
+ export declare function isPool<P = unknown, T = unknown>(value: unknown): value is Pool<P, T>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -1,4 +1,4 @@
1
- import { Atom, AtomValue, KeyedResult, Pipeable, SelectStateResult, SettledResult } from './types';
1
+ import { Atom, AtomValue, KeyedResult, Pipeable, Pool, ScopedAtom, SelectStateResult, SettledResult } from './types';
2
2
  /**
3
3
  * Result of a select computation.
4
4
  *
@@ -11,14 +11,36 @@ export interface SelectResult<T> {
11
11
  error: unknown;
12
12
  /** Promise thrown during computation - indicates loading state */
13
13
  promise: PromiseLike<unknown> | undefined;
14
- /** Set of atoms that were accessed during computation */
14
+ /**
15
+ * Set of atoms that were accessed during computation.
16
+ * @deprecated Use _atomDeps instead. Kept for backward compatibility.
17
+ */
15
18
  dependencies: Set<Atom<unknown>>;
19
+ /**
20
+ * Set of atoms that were accessed during computation.
21
+ * @internal
22
+ */
23
+ _atomDeps: Set<Atom<unknown>>;
24
+ /**
25
+ * Map of pools to their accessed params.
26
+ * @internal
27
+ */
28
+ _poolDeps: Map<Pool<any, any>, Set<any>>;
16
29
  }
17
30
  /**
18
31
  * Result type for safe() - error-first tuple.
19
32
  * Either [undefined, T] for success or [unknown, undefined] for error.
20
33
  */
21
34
  export type SafeResult<T> = [error: undefined, result: T] | [error: unknown, result: undefined];
35
+ /**
36
+ * Condition input type for and()/or() operators.
37
+ *
38
+ * Supports three forms:
39
+ * - `boolean` - Static value, no subscription
40
+ * - `Atom<unknown>` - Always read and subscribed
41
+ * - `() => boolean | Atom<unknown>` - Lazy evaluation, only called if needed
42
+ */
43
+ export type Condition = boolean | Atom<unknown> | (() => boolean | Atom<unknown>);
22
44
  /**
23
45
  * Context object passed to selector functions.
24
46
  * Provides utilities for reading atoms and handling async operations.
@@ -37,6 +59,33 @@ export interface SelectContext extends Pipeable {
37
59
  * @returns The atom's current value (Awaited<T>)
38
60
  */
39
61
  read<T>(atom: Atom<T>): Awaited<T>;
62
+ /**
63
+ * Get a ScopedAtom from a pool for the given params.
64
+ * The ScopedAtom is only valid during this select() execution.
65
+ *
66
+ * When the pool entry is removed (GC or manual), the computation
67
+ * will automatically re-run to get the new atom.
68
+ *
69
+ * @param pool - The pool to get atom from
70
+ * @param params - The params to look up
71
+ * @returns A ScopedAtom wrapping the pool entry's atom
72
+ *
73
+ * @example
74
+ * ```ts
75
+ * derived(({ read, from }) => {
76
+ * const user$ = from(userPool, "user-1");
77
+ * return read(user$);
78
+ * });
79
+ * ```
80
+ */
81
+ from<P, T>(pool: Pool<P, T>, params: P): ScopedAtom<T>;
82
+ /**
83
+ * Track an atom as a dependency without reading its value.
84
+ * Useful when you need to subscribe to changes but already have the value.
85
+ *
86
+ * @param atom - The atom to track (can be ScopedAtom)
87
+ */
88
+ track(atom: Atom<unknown>): void;
40
89
  /**
41
90
  * Wait for all atoms to resolve (like Promise.all).
42
91
  * Array-based - pass atoms as an array.
@@ -205,11 +254,195 @@ export interface SelectContext extends Pipeable {
205
254
  * ```
206
255
  */
207
256
  state<T>(selector: () => T): SelectStateResult<T>;
257
+ /**
258
+ * Logical AND - returns true if ALL conditions are truthy.
259
+ * Short-circuits on first falsy value (lazy conditions after that are not evaluated).
260
+ *
261
+ * ## Condition Types
262
+ *
263
+ * - `boolean` - Static value, no subscription
264
+ * - `Atom<T>` - Always read and subscribed
265
+ * - `() => boolean | Atom<T>` - Lazy, only called if previous conditions are truthy
266
+ *
267
+ * ## Evaluation Flow
268
+ *
269
+ * ```
270
+ * and([a, b, () => c, () => d])
271
+ * │
272
+ * ▼
273
+ * a truthy? ─No──→ return false
274
+ * │Yes
275
+ * ▼
276
+ * b truthy? ─No──→ return false
277
+ * │Yes
278
+ * ▼
279
+ * call () => c
280
+ * c truthy? ─No──→ return false
281
+ * │Yes
282
+ * ▼
283
+ * call () => d
284
+ * d truthy? ─No──→ return false
285
+ * │Yes
286
+ * ▼
287
+ * return true
288
+ * ```
289
+ *
290
+ * @param conditions - Array of conditions (booleans, atoms, or lazy functions)
291
+ * @returns true if all conditions are truthy, false otherwise
292
+ *
293
+ * @example
294
+ * ```ts
295
+ * // Simple: all atoms must be truthy
296
+ * const canAccess = and([isLoggedIn$, hasPermission$]);
297
+ *
298
+ * // With static value
299
+ * const canEdit = and([FEATURE_ENABLED, isLoggedIn$, hasEditRole$]);
300
+ *
301
+ * // With lazy evaluation (only check permission if logged in)
302
+ * const canDelete = and([
303
+ * isLoggedIn$,
304
+ * () => hasDeletePermission$, // Only read if logged in
305
+ * ]);
306
+ *
307
+ * // Nested: (A && B) || C
308
+ * const result = or([and([a$, b$]), c$]);
309
+ * ```
310
+ */
311
+ and(conditions: Condition[]): boolean;
312
+ /**
313
+ * Logical OR - returns true if ANY condition is truthy.
314
+ * Short-circuits on first truthy value (lazy conditions after that are not evaluated).
315
+ *
316
+ * ## Condition Types
317
+ *
318
+ * - `boolean` - Static value, no subscription
319
+ * - `Atom<T>` - Always read and subscribed
320
+ * - `() => boolean | Atom<T>` - Lazy, only called if previous conditions are falsy
321
+ *
322
+ * ## Evaluation Flow
323
+ *
324
+ * ```
325
+ * or([a, b, () => c, () => d])
326
+ * │
327
+ * ▼
328
+ * a truthy? ─Yes─→ return true
329
+ * │No
330
+ * ▼
331
+ * b truthy? ─Yes─→ return true
332
+ * │No
333
+ * ▼
334
+ * call () => c
335
+ * c truthy? ─Yes─→ return true
336
+ * │No
337
+ * ▼
338
+ * call () => d
339
+ * d truthy? ─Yes─→ return true
340
+ * │No
341
+ * ▼
342
+ * return false
343
+ * ```
344
+ *
345
+ * @param conditions - Array of conditions (booleans, atoms, or lazy functions)
346
+ * @returns true if any condition is truthy, false otherwise
347
+ *
348
+ * @example
349
+ * ```ts
350
+ * // Simple: any atom truthy
351
+ * const hasData = or([cacheData$, apiData$]);
352
+ *
353
+ * // With lazy fallback chain
354
+ * const data = or([
355
+ * () => primaryData$, // Try primary first
356
+ * () => fallbackData$, // Only if primary is falsy
357
+ * ]);
358
+ *
359
+ * // Nested: A || (B && C)
360
+ * const result = or([a$, and([b$, c$])]);
361
+ * ```
362
+ */
363
+ or(conditions: Condition[]): boolean;
364
+ /**
365
+ * Read atoms or execute functions without tracking dependencies.
366
+ * Useful when you need to read a value but don't want to re-compute when it changes.
367
+ *
368
+ * Supports two forms:
369
+ * - `untrack(atom$)` - Read atom value without tracking
370
+ * - `untrack(() => expr)` - Execute function without tracking any reads inside
371
+ *
372
+ * ## Flow Diagram
373
+ *
374
+ * ```
375
+ * untrack(input)
376
+ * │
377
+ * ▼
378
+ * Is input an Atom?
379
+ * │
380
+ * ┌───┴───┐
381
+ * │Yes │No (function)
382
+ * ▼ ▼
383
+ * Read Set isUntracking = true
384
+ * without │
385
+ * tracking ▼
386
+ * │ Execute fn()
387
+ * │ │
388
+ * │ ▼
389
+ * │ Set isUntracking = false
390
+ * │ │
391
+ * └───────┴──→ Return value
392
+ * ```
393
+ *
394
+ * @param atom - Atom to read without tracking
395
+ * @returns The atom's current value
396
+ *
397
+ * @example
398
+ * ```ts
399
+ * const combined$ = derived(({ read, untrack }) => {
400
+ * const count = read(count$); // Tracks count$
401
+ * const doubled = untrack(doubled$); // Does NOT track doubled$
402
+ * return count + doubled;
403
+ * });
404
+ * ```
405
+ */
406
+ untrack<T>(atom: Atom<T>): Awaited<T>;
407
+ /**
408
+ * Execute a function without tracking any atom reads inside.
409
+ *
410
+ * @param fn - Function to execute without tracking
411
+ * @returns The function's return value
412
+ *
413
+ * @example
414
+ * ```ts
415
+ * const combined$ = derived(({ read, untrack }) => {
416
+ * const count = read(count$); // Tracks count$
417
+ * const tripled = untrack(() => {
418
+ * // None of these reads are tracked
419
+ * return read(a$) + read(b$) + read(c$);
420
+ * });
421
+ * return count + tripled;
422
+ * });
423
+ * ```
424
+ */
425
+ untrack<T>(fn: () => T): T;
208
426
  }
209
427
  /**
210
428
  * Selector function type for context-based API.
211
429
  */
212
430
  export type ReactiveSelector<T, C extends SelectContext = SelectContext> = (context: C) => T;
431
+ /**
432
+ * Output of select() - includes result and startTracking function.
433
+ */
434
+ export interface SelectOutput<T> {
435
+ /** The computation result */
436
+ result: SelectResult<T>;
437
+ /**
438
+ * Start tracking dependencies and subscribe to changes.
439
+ * Call this after computation to set up subscriptions.
440
+ *
441
+ * @param onNotify - Callback to invoke when any dependency changes
442
+ * @returns Cleanup function to unsubscribe all
443
+ */
444
+ startTracking(onNotify: VoidFunction): VoidFunction;
445
+ }
213
446
  /**
214
447
  * Custom error for when all atoms in `any()` are rejected.
215
448
  */
@@ -217,114 +450,73 @@ export declare class AllAtomsRejectedError extends Error {
217
450
  readonly errors: unknown[];
218
451
  constructor(errors: unknown[], message?: string);
219
452
  }
453
+ /**
454
+ * Type guard to check if a value is a ScopedAtom.
455
+ */
456
+ export declare function isScopedAtom<T = unknown>(value: unknown): value is ScopedAtom<T>;
457
+ /**
458
+ * Type guard to check if a value is a Pool.
459
+ */
460
+ export declare function isPool<P = unknown, T = unknown>(value: unknown): value is Pool<P, T>;
220
461
  /**
221
462
  * Selects/computes a value from atom(s) with dependency tracking.
222
463
  *
223
464
  * This is the core computation logic used by `derived()`. It:
224
- * 1. Creates a context with `read`, `all`, `any`, `race`, `settled`, `safe` utilities
225
- * 2. Tracks which atoms are accessed during computation
226
- * 3. Returns a result with value/error/promise and dependencies
465
+ * 1. Creates a context with `read`, `from`, `all`, `any`, `race`, `settled`, `safe` utilities
466
+ * 2. Tracks which atoms and pools are accessed during computation
467
+ * 3. Returns a result with value/error/promise and a startTracking function
227
468
  *
228
- * All context methods use `getAtomState()` internally.
229
- *
230
- * ## IMPORTANT: Selector Must Return Synchronous Value
231
- *
232
- * **The selector function MUST NOT return a Promise or PromiseLike value.**
233
- *
234
- * If your selector returns a Promise, it will throw an error. This is because:
235
- * - `select()` is designed for synchronous derivation from atoms
236
- * - Async atoms should be created using `atom(Promise)` directly
237
- * - Use `read()` to read async atoms - it handles Suspense-style loading
469
+ * ## New API
238
470
  *
239
471
  * ```ts
240
- * // WRONG - Don't return a Promise from selector
241
- * select(({ get }) => fetch('/api/data'));
242
- *
243
- * // ✅ CORRECT - Create async atom and read with read()
244
- * const data$ = atom(fetch('/api/data').then(r => r.json()));
245
- * select(({ read }) => read(data$)); // Suspends until resolved
472
+ * const { result, startTracking } = select(fn, prevResult);
473
+ * const cleanup = startTracking(onNotify);
246
474
  * ```
247
475
  *
248
- * ## IMPORTANT: Do NOT Use try/catch - Use safe() Instead
476
+ * The `startTracking` function sets up subscriptions to all dependencies
477
+ * (atoms and pool entries) and returns a cleanup function.
249
478
  *
250
- * **Never wrap `read()` calls in try/catch blocks.** The `read()` function throws
251
- * Promises when atoms are loading (Suspense pattern). A try/catch will catch
252
- * these Promises and break the Suspense mechanism.
479
+ * ## Pool Support via from()
253
480
  *
481
+ * Use `from(pool, params)` to get a ScopedAtom from a pool:
254
482
  * ```ts
255
- * // WRONG - Catches Suspense Promise, breaks loading state
256
- * select(({ read }) => {
257
- * try {
258
- * return read(asyncAtom$);
259
- * } catch (e) {
260
- * return 'fallback'; // This catches BOTH errors AND loading promises!
261
- * }
262
- * });
263
- *
264
- * // ✅ CORRECT - Use safe() to catch errors but preserve Suspense
265
- * select(({ read, safe }) => {
266
- * const [err, data] = safe(() => {
267
- * const raw = read(asyncAtom$); // Can throw Promise (Suspense)
268
- * return JSON.parse(raw); // Can throw Error
269
- * });
270
- *
271
- * if (err) return { error: err.message };
272
- * return { data };
483
+ * const { result } = select(({ read, from }) => {
484
+ * const user$ = from(userPool, "user-1");
485
+ * return read(user$);
273
486
  * });
274
487
  * ```
275
488
  *
276
- * The `safe()` utility:
277
- * - **Catches errors** and returns `[error, undefined]`
278
- * - **Re-throws Promises** to preserve Suspense behavior
279
- * - Returns `[undefined, result]` on success
280
- *
281
- * ## IMPORTANT: SelectContext Methods Are Synchronous Only
489
+ * ScopedAtoms are automatically disposed after select() completes,
490
+ * preventing memory leaks from stale atom references.
282
491
  *
283
- * **All context methods (`read`, `all`, `race`, `any`, `settled`, `safe`) must be
284
- * called synchronously during selector execution.** They cannot be used in async
285
- * callbacks like `setTimeout`, `Promise.then`, or event handlers.
492
+ * ## IMPORTANT: Selector Must Return Synchronous Value
286
493
  *
287
- * ```ts
288
- * // ❌ WRONG - Calling read() in async callback
289
- * select(({ read }) => {
290
- * setTimeout(() => {
291
- * read(atom$); // Error: called outside selection context
292
- * }, 100);
293
- * return 'value';
294
- * });
494
+ * **The selector function MUST NOT return a Promise or PromiseLike value.**
295
495
  *
296
- * // WRONG - Storing read() for later use
297
- * let savedRead;
298
- * select(({ read }) => {
299
- * savedRead = read; // Don't do this!
300
- * return read(atom$);
301
- * });
302
- * savedRead(atom$); // Error: called outside selection context
496
+ * ## IMPORTANT: Do NOT Use try/catch - Use safe() Instead
303
497
  *
304
- * // CORRECT - For async access, use atom.get() directly
305
- * effect(({ read }) => {
306
- * const config = read(config$);
307
- * setTimeout(async () => {
308
- * // Use atom.get() for async access
309
- * const data = await asyncAtom$.get();
310
- * console.log(data);
311
- * }, 100);
312
- * });
313
- * ```
498
+ * **Never wrap `read()` calls in try/catch blocks.** Use `safe()` instead.
314
499
  *
315
500
  * @template T - The type of the computed value
316
501
  * @param fn - Context-based selector function (must return sync value)
317
- * @returns SelectResult with value, error, promise, and dependencies
318
- * @throws Error if selector returns a Promise or PromiseLike
319
- * @throws Error if context methods are called outside selection context
502
+ * @param prevResult - Previous result for diff-based subscription updates
503
+ * @returns SelectOutput with result and startTracking function
320
504
  *
321
505
  * @example
322
506
  * ```ts
323
- * select(({ read, all }) => {
324
- * const user = read(user$);
325
- * const [posts, comments] = all([posts$, comments$]);
326
- * return { user, posts, comments };
327
- * });
507
+ * // Basic usage
508
+ * const { result, startTracking } = select(({ read }) => {
509
+ * return read(count$) * 2;
510
+ * }, null);
511
+ *
512
+ * // With pool
513
+ * const { result, startTracking } = select(({ read, from }) => {
514
+ * const user$ = from(userPool, userId);
515
+ * return read(user$);
516
+ * }, prevResult);
517
+ *
518
+ * // Start tracking dependencies
519
+ * const cleanup = startTracking(() => recompute());
328
520
  * ```
329
521
  */
330
- export declare function select<T>(fn: ReactiveSelector<T>): SelectResult<T>;
522
+ export declare function select<T>(fn: ReactiveSelector<T>, _prevResult?: SelectResult<T> | null): SelectOutput<T>;