atomirx 0.0.2 → 0.0.5

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 (65) hide show
  1. package/README.md +868 -161
  2. package/coverage/src/core/onCreateHook.ts.html +72 -70
  3. package/dist/core/atom.d.ts +83 -6
  4. package/dist/core/batch.d.ts +3 -3
  5. package/dist/core/derived.d.ts +69 -22
  6. package/dist/core/effect.d.ts +52 -52
  7. package/dist/core/getAtomState.d.ts +29 -0
  8. package/dist/core/hook.d.ts +1 -1
  9. package/dist/core/onCreateHook.d.ts +37 -23
  10. package/dist/core/onErrorHook.d.ts +49 -0
  11. package/dist/core/promiseCache.d.ts +23 -32
  12. package/dist/core/select.d.ts +208 -29
  13. package/dist/core/types.d.ts +107 -22
  14. package/dist/core/withReady.d.ts +115 -0
  15. package/dist/core/withReady.test.d.ts +1 -0
  16. package/dist/index-CBVj1kSj.js +1350 -0
  17. package/dist/index-Cxk9v0um.cjs +1 -0
  18. package/dist/index.cjs +1 -1
  19. package/dist/index.d.ts +12 -8
  20. package/dist/index.js +18 -15
  21. package/dist/react/index.cjs +10 -10
  22. package/dist/react/index.d.ts +2 -1
  23. package/dist/react/index.js +422 -377
  24. package/dist/react/rx.d.ts +114 -25
  25. package/dist/react/useAction.d.ts +5 -4
  26. package/dist/react/{useValue.d.ts → useSelector.d.ts} +56 -25
  27. package/dist/react/useSelector.test.d.ts +1 -0
  28. package/package.json +1 -1
  29. package/src/core/atom.test.ts +307 -43
  30. package/src/core/atom.ts +144 -22
  31. package/src/core/batch.test.ts +10 -10
  32. package/src/core/batch.ts +3 -3
  33. package/src/core/define.test.ts +12 -11
  34. package/src/core/define.ts +1 -1
  35. package/src/core/derived.test.ts +906 -72
  36. package/src/core/derived.ts +192 -81
  37. package/src/core/effect.test.ts +651 -45
  38. package/src/core/effect.ts +102 -98
  39. package/src/core/getAtomState.ts +69 -0
  40. package/src/core/hook.test.ts +5 -5
  41. package/src/core/hook.ts +1 -1
  42. package/src/core/onCreateHook.ts +38 -23
  43. package/src/core/onErrorHook.test.ts +350 -0
  44. package/src/core/onErrorHook.ts +52 -0
  45. package/src/core/promiseCache.test.ts +5 -3
  46. package/src/core/promiseCache.ts +76 -71
  47. package/src/core/select.ts +405 -130
  48. package/src/core/selector.test.ts +574 -32
  49. package/src/core/types.ts +107 -29
  50. package/src/core/withReady.test.ts +534 -0
  51. package/src/core/withReady.ts +191 -0
  52. package/src/core/withUse.ts +1 -1
  53. package/src/index.test.ts +4 -4
  54. package/src/index.ts +21 -7
  55. package/src/react/index.ts +2 -1
  56. package/src/react/rx.test.tsx +173 -18
  57. package/src/react/rx.tsx +274 -43
  58. package/src/react/useAction.test.ts +12 -14
  59. package/src/react/useAction.ts +11 -9
  60. package/src/react/{useValue.test.ts → useSelector.test.ts} +16 -16
  61. package/src/react/{useValue.ts → useSelector.ts} +64 -33
  62. package/v2.md +44 -44
  63. package/dist/index-2ok7ilik.js +0 -1217
  64. package/dist/index-B_5SFzfl.cjs +0 -1
  65. /package/dist/{react/useValue.test.d.ts → core/onErrorHook.test.d.ts} +0 -0
@@ -0,0 +1,29 @@
1
+ import { Atom, AtomState } from './types';
2
+ /**
3
+ * Returns the current state of an atom as a discriminated union.
4
+ *
5
+ * For any atom (mutable or derived):
6
+ * - If value is not a Promise: returns ready state
7
+ * - If value is a Promise: tracks and returns its state (ready/error/loading)
8
+ *
9
+ * @param atom - The atom to get state from
10
+ * @returns AtomState discriminated union (ready | error | loading)
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const state = getAtomState(myAtom$);
15
+ *
16
+ * switch (state.status) {
17
+ * case "ready":
18
+ * console.log(state.value); // T
19
+ * break;
20
+ * case "error":
21
+ * console.log(state.error);
22
+ * break;
23
+ * case "loading":
24
+ * console.log(state.promise);
25
+ * break;
26
+ * }
27
+ * ```
28
+ */
29
+ export declare function getAtomState<T>(atom: Atom<T>): AtomState<Awaited<T>>;
@@ -54,7 +54,7 @@ export interface Hook<T> {
54
54
  /**
55
55
  * Current value of the hook. Direct property access for fast reads.
56
56
  */
57
- current: T;
57
+ readonly current: T;
58
58
  /**
59
59
  * Override the current value using a reducer.
60
60
  * The reducer receives the previous value and returns the next value.
@@ -1,8 +1,9 @@
1
- import { MutableAtomMeta, DerivedAtomMeta, MutableAtom, DerivedAtom, ModuleMeta } from './types';
1
+ import { Effect } from './effect';
2
+ import { MutableAtomMeta, DerivedAtomMeta, MutableAtom, DerivedAtom, ModuleMeta, EffectMeta } from './types';
2
3
  /**
3
4
  * Information provided when a mutable atom is created.
4
5
  */
5
- export interface MutableAtomCreateInfo {
6
+ export interface MutableInfo {
6
7
  /** Discriminator for mutable atoms */
7
8
  type: "mutable";
8
9
  /** Optional key from atom options (for debugging/devtools) */
@@ -10,12 +11,12 @@ export interface MutableAtomCreateInfo {
10
11
  /** Optional metadata from atom options */
11
12
  meta: MutableAtomMeta | undefined;
12
13
  /** The created mutable atom instance */
13
- atom: MutableAtom<unknown>;
14
+ instance: MutableAtom<unknown>;
14
15
  }
15
16
  /**
16
17
  * Information provided when a derived atom is created.
17
18
  */
18
- export interface DerivedAtomCreateInfo {
19
+ export interface DerivedInfo {
19
20
  /** Discriminator for derived atoms */
20
21
  type: "derived";
21
22
  /** Optional key from derived options (for debugging/devtools) */
@@ -23,16 +24,29 @@ export interface DerivedAtomCreateInfo {
23
24
  /** Optional metadata from derived options */
24
25
  meta: DerivedAtomMeta | undefined;
25
26
  /** The created derived atom instance */
26
- atom: DerivedAtom<unknown, boolean>;
27
+ instance: DerivedAtom<unknown, boolean>;
27
28
  }
28
29
  /**
29
- * Union type for atom creation info (mutable or derived).
30
+ * Information provided when an effect is created.
30
31
  */
31
- export type AtomCreateInfo = MutableAtomCreateInfo | DerivedAtomCreateInfo;
32
+ export interface EffectInfo {
33
+ /** Discriminator for effects */
34
+ type: "effect";
35
+ /** Optional key from effect options (for debugging/devtools) */
36
+ key: string | undefined;
37
+ /** Optional metadata from effect options */
38
+ meta: EffectMeta | undefined;
39
+ /** The created effect instance */
40
+ instance: Effect;
41
+ }
42
+ /**
43
+ * Union type for atom/derived/effect creation info.
44
+ */
45
+ export type CreateInfo = MutableInfo | DerivedInfo | EffectInfo;
32
46
  /**
33
47
  * Information provided when a module (via define()) is created.
34
48
  */
35
- export interface ModuleCreateInfo {
49
+ export interface ModuleInfo {
36
50
  /** Discriminator for modules */
37
51
  type: "module";
38
52
  /** Optional key from define options (for debugging/devtools) */
@@ -40,7 +54,7 @@ export interface ModuleCreateInfo {
40
54
  /** Optional metadata from define options */
41
55
  meta: ModuleMeta | undefined;
42
56
  /** The created module instance */
43
- module: unknown;
57
+ instance: unknown;
44
58
  }
45
59
  /**
46
60
  * Global hook that fires whenever an atom or module is created.
@@ -50,30 +64,30 @@ export interface ModuleCreateInfo {
50
64
  * - **Debugging** - log atom creation for troubleshooting
51
65
  * - **Testing** - verify expected atoms are created
52
66
  *
67
+ * **IMPORTANT**: Always use `.override()` to preserve the hook chain.
68
+ * Direct assignment to `.current` will break existing handlers.
69
+ *
53
70
  * @example Basic logging
54
71
  * ```ts
55
- * onCreateHook.current = (info) => {
72
+ * onCreateHook.override((prev) => (info) => {
73
+ * prev?.(info); // call existing handlers first
56
74
  * console.log(`Created ${info.type}: ${info.key ?? "anonymous"}`);
57
- * };
75
+ * });
58
76
  * ```
59
77
  *
60
78
  * @example DevTools integration
61
79
  * ```ts
62
- * const atoms = new Map();
63
- * const modules = new Map();
80
+ * const registry = new Map();
64
81
  *
65
- * onCreateHook.current = (info) => {
66
- * if (info.type === "module") {
67
- * modules.set(info.key, info.module);
68
- * } else {
69
- * atoms.set(info.key, info.atom);
70
- * }
71
- * };
82
+ * onCreateHook.override((prev) => (info) => {
83
+ * prev?.(info); // preserve chain
84
+ * registry.set(info.key, info.instance);
85
+ * });
72
86
  * ```
73
87
  *
74
- * @example Cleanup (disable hook)
88
+ * @example Reset to default (disable all handlers)
75
89
  * ```ts
76
- * onCreateHook.current = undefined;
90
+ * onCreateHook.reset();
77
91
  * ```
78
92
  */
79
- export declare const onCreateHook: import('./hook').Hook<((info: AtomCreateInfo | ModuleCreateInfo) => void) | undefined>;
93
+ export declare const onCreateHook: import('./hook').Hook<((info: CreateInfo | ModuleInfo) => void) | undefined>;
@@ -0,0 +1,49 @@
1
+ import { CreateInfo } from './onCreateHook';
2
+ /**
3
+ * Information provided when an error occurs in an atom, derived, or effect.
4
+ */
5
+ export interface ErrorInfo {
6
+ /** The source that produced the error (atom, derived, or effect) */
7
+ source: CreateInfo;
8
+ /** The error that was thrown */
9
+ error: unknown;
10
+ }
11
+ /**
12
+ * Global hook that fires whenever an error occurs in a derived atom or effect.
13
+ *
14
+ * This is useful for:
15
+ * - **Global error logging** - capture all errors in one place
16
+ * - **Error monitoring** - send errors to monitoring services (Sentry, etc.)
17
+ * - **DevTools integration** - show errors in developer tools
18
+ * - **Debugging** - track which atoms/effects are failing
19
+ *
20
+ * **IMPORTANT**: Always use `.override()` to preserve the hook chain.
21
+ * Direct assignment to `.current` will break existing handlers.
22
+ *
23
+ * @example Basic logging
24
+ * ```ts
25
+ * onErrorHook.override((prev) => (info) => {
26
+ * prev?.(info); // call existing handlers first
27
+ * console.error(`Error in ${info.source.type}: ${info.source.key ?? "anonymous"}`, info.error);
28
+ * });
29
+ * ```
30
+ *
31
+ * @example Send to monitoring service
32
+ * ```ts
33
+ * onErrorHook.override((prev) => (info) => {
34
+ * prev?.(info); // preserve chain
35
+ * Sentry.captureException(info.error, {
36
+ * tags: {
37
+ * source_type: info.source.type,
38
+ * source_key: info.source.key,
39
+ * },
40
+ * });
41
+ * });
42
+ * ```
43
+ *
44
+ * @example Reset to default (disable all handlers)
45
+ * ```ts
46
+ * onErrorHook.reset();
47
+ * ```
48
+ */
49
+ export declare const onErrorHook: import('./hook').Hook<((info: ErrorInfo) => void) | undefined>;
@@ -1,4 +1,26 @@
1
- import { Atom, AtomState, DerivedAtom } from './types';
1
+ import { DerivedAtom } from './types';
2
+ /**
3
+ * Metadata attached to combined promises for comparison.
4
+ */
5
+ export interface CombinedPromiseMeta {
6
+ type: "all" | "race" | "allSettled";
7
+ promises: Promise<unknown>[];
8
+ }
9
+ /**
10
+ * Gets the metadata for a combined promise, if any.
11
+ * Used internally by promisesEqual for comparison.
12
+ */
13
+ export declare function getCombinedPromiseMetadata(promise: PromiseLike<unknown>): CombinedPromiseMeta | undefined;
14
+ /**
15
+ * Create a combined promise with metadata for comparison.
16
+ * If only one promise, returns it directly (no metadata needed).
17
+ */
18
+ export declare function createCombinedPromise(type: "all" | "race" | "allSettled", promises: Promise<unknown>[]): PromiseLike<unknown>;
19
+ /**
20
+ * Compare two promises, considering combined promise metadata.
21
+ * Returns true if promises are considered equal.
22
+ */
23
+ export declare function promisesEqual(a: PromiseLike<unknown> | undefined, b: PromiseLike<unknown> | undefined): boolean;
2
24
  /**
3
25
  * Represents the state of a tracked Promise.
4
26
  */
@@ -51,37 +73,6 @@ export declare function isTracked(promise: PromiseLike<unknown>): boolean;
51
73
  * Type guard to check if a value is a DerivedAtom.
52
74
  */
53
75
  export declare function isDerived<T>(value: unknown): value is DerivedAtom<T, boolean>;
54
- /**
55
- * Returns the current state of an atom as a discriminated union.
56
- *
57
- * For DerivedAtom:
58
- * - Returns atom.state() directly (derived atoms track their own state)
59
- *
60
- * For MutableAtom:
61
- * - If value is not a Promise: returns ready state
62
- * - If value is a Promise: tracks and returns its state (ready/error/loading)
63
- *
64
- * @param atom - The atom to get state from
65
- * @returns AtomState discriminated union (ready | error | loading)
66
- *
67
- * @example
68
- * ```ts
69
- * const state = getAtomState(myAtom$);
70
- *
71
- * switch (state.status) {
72
- * case "ready":
73
- * console.log(state.value); // T
74
- * break;
75
- * case "error":
76
- * console.log(state.error);
77
- * break;
78
- * case "loading":
79
- * console.log(state.promise);
80
- * break;
81
- * }
82
- * ```
83
- */
84
- export declare function getAtomState<T>(atom: Atom<T>): AtomState<Awaited<T>>;
85
76
  /**
86
77
  * Unwraps a value that may be a Promise.
87
78
  * - If not a Promise, returns the value directly.
@@ -1,4 +1,4 @@
1
- import { Atom, AtomValue, SettledResult } from './types';
1
+ import { Atom, AtomValue, KeyedResult, Pipeable, SelectStateResult, SettledResult } from './types';
2
2
  /**
3
3
  * Result of a select computation.
4
4
  *
@@ -14,11 +14,16 @@ export interface SelectResult<T> {
14
14
  /** Set of atoms that were accessed during computation */
15
15
  dependencies: Set<Atom<unknown>>;
16
16
  }
17
+ /**
18
+ * Result type for safe() - error-first tuple.
19
+ * Either [undefined, T] for success or [unknown, undefined] for error.
20
+ */
21
+ export type SafeResult<T> = [error: undefined, result: T] | [error: unknown, result: undefined];
17
22
  /**
18
23
  * Context object passed to selector functions.
19
24
  * Provides utilities for reading atoms and handling async operations.
20
25
  */
21
- export interface SelectContext {
26
+ export interface SelectContext extends Pipeable {
22
27
  /**
23
28
  * Read the current value of an atom.
24
29
  * Tracks the atom as a dependency.
@@ -31,74 +36,180 @@ export interface SelectContext {
31
36
  * @param atom - The atom to read
32
37
  * @returns The atom's current value (Awaited<T>)
33
38
  */
34
- get<T>(atom: Atom<T>): Awaited<T>;
39
+ read<T>(atom: Atom<T>): Awaited<T>;
35
40
  /**
36
41
  * Wait for all atoms to resolve (like Promise.all).
37
- * Variadic form - pass atoms as arguments.
42
+ * Array-based - pass atoms as an array.
38
43
  *
39
44
  * - If all atoms are ready → returns array of values
40
45
  * - If any atom has error → throws that error
41
46
  * - If any atom is loading (no fallback) → throws Promise
42
47
  * - If loading with fallback → uses staleValue
43
48
  *
44
- * @param atoms - Atoms to wait for (variadic)
49
+ * @param atoms - Array of atoms to wait for
45
50
  * @returns Array of resolved values (same order as input)
46
51
  *
47
52
  * @example
48
53
  * ```ts
49
- * const [user, posts] = all(user$, posts$);
54
+ * const [user, posts] = all([user$, posts$]);
50
55
  * ```
51
56
  */
52
- all<A extends Atom<unknown>[]>(...atoms: A): {
57
+ all<A extends Atom<unknown>[]>(atoms: A): {
53
58
  [K in keyof A]: AtomValue<A[K]>;
54
59
  };
55
60
  /**
56
61
  * Return the first settled value (like Promise.race).
57
- * Variadic form - pass atoms as arguments.
62
+ * Object-based - pass atoms as a record with keys.
58
63
  *
59
- * - If any atom is ready → returns first ready value
64
+ * - If any atom is ready → returns `{ key, value }` for first ready
60
65
  * - If any atom has error → throws first error
61
66
  * - If all atoms are loading → throws first Promise
62
67
  *
68
+ * The `key` in the result identifies which atom won the race.
69
+ *
63
70
  * Note: race() does NOT use fallback - it's meant for first "real" settled value.
64
71
  *
65
- * @param atoms - Atoms to race (variadic)
66
- * @returns First settled value
72
+ * @param atoms - Record of atoms to race
73
+ * @returns KeyedResult with winning key and value
74
+ *
75
+ * @example
76
+ * ```ts
77
+ * const result = race({ cache: cache$, api: api$ });
78
+ * console.log(result.key); // "cache" or "api"
79
+ * console.log(result.value); // The winning value
80
+ * ```
67
81
  */
68
- race<A extends Atom<unknown>[]>(...atoms: A): AtomValue<A[number]>;
82
+ race<T extends Record<string, Atom<unknown>>>(atoms: T): KeyedResult<keyof T & string, AtomValue<T[keyof T]>>;
69
83
  /**
70
84
  * Return the first ready value (like Promise.any).
71
- * Variadic form - pass atoms as arguments.
85
+ * Object-based - pass atoms as a record with keys.
72
86
  *
73
- * - If any atom is ready → returns first ready value
87
+ * - If any atom is ready → returns `{ key, value }` for first ready
74
88
  * - If all atoms have errors → throws AggregateError
75
89
  * - If any loading (not all errored) → throws Promise
76
90
  *
91
+ * The `key` in the result identifies which atom resolved first.
92
+ *
77
93
  * Note: any() does NOT use fallback - it waits for a real ready value.
78
94
  *
79
- * @param atoms - Atoms to check (variadic)
80
- * @returns First ready value
95
+ * @param atoms - Record of atoms to check
96
+ * @returns KeyedResult with winning key and value
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * const result = any({ primary: primaryApi$, fallback: fallbackApi$ });
101
+ * console.log(result.key); // "primary" or "fallback"
102
+ * console.log(result.value); // The winning value
103
+ * ```
81
104
  */
82
- any<A extends Atom<unknown>[]>(...atoms: A): AtomValue<A[number]>;
105
+ any<T extends Record<string, Atom<unknown>>>(atoms: T): KeyedResult<keyof T & string, AtomValue<T[keyof T]>>;
83
106
  /**
84
107
  * Get all atom statuses when all are settled (like Promise.allSettled).
85
- * Variadic form - pass atoms as arguments.
108
+ * Array-based - pass atoms as an array.
86
109
  *
87
110
  * - If all atoms are settled → returns array of statuses
88
111
  * - If any atom is loading (no fallback) → throws Promise
89
112
  * - If loading with fallback → { status: "ready", value: staleValue }
90
113
  *
91
- * @param atoms - Atoms to check (variadic)
114
+ * @param atoms - Array of atoms to check
92
115
  * @returns Array of settled results
116
+ *
117
+ * @example
118
+ * ```ts
119
+ * const [userResult, postsResult] = settled([user$, posts$]);
120
+ * ```
93
121
  */
94
- settled<A extends Atom<unknown>[]>(...atoms: A): {
122
+ settled<A extends Atom<unknown>[]>(atoms: A): {
95
123
  [K in keyof A]: SettledResult<AtomValue<A[K]>>;
96
124
  };
125
+ /**
126
+ * Safely execute a function, catching errors but preserving Suspense.
127
+ *
128
+ * - If function succeeds → returns [undefined, result]
129
+ * - If function throws Error → returns [error, undefined]
130
+ * - If function throws Promise → re-throws (preserves Suspense)
131
+ *
132
+ * Use this when you need error handling inside selectors without
133
+ * accidentally catching Suspense promises.
134
+ *
135
+ * @param fn - Function to execute safely
136
+ * @returns Error-first tuple: [error, result]
137
+ *
138
+ * @example
139
+ * ```ts
140
+ * const data$ = derived(({ get, safe }) => {
141
+ * const [err, data] = safe(() => {
142
+ * const raw = get(raw$);
143
+ * return JSON.parse(raw); // Can throw SyntaxError
144
+ * });
145
+ *
146
+ * if (err) {
147
+ * return { error: err.message };
148
+ * }
149
+ * return { data };
150
+ * });
151
+ * ```
152
+ */
153
+ safe<T>(fn: () => T): SafeResult<T>;
154
+ /**
155
+ * Get the async state of an atom or selector without throwing.
156
+ *
157
+ * Unlike `read()` which throws promises/errors (Suspense pattern),
158
+ * `state()` always returns a `SelectStateResult<T>` object that you can
159
+ * inspect and handle inline.
160
+ *
161
+ * All properties (`status`, `value`, `error`) are always present,
162
+ * enabling easy destructuring:
163
+ * ```ts
164
+ * const { status, value, error } = state(atom$);
165
+ * ```
166
+ *
167
+ * @param atom - The atom to get state from
168
+ * @returns SelectStateResult with status, value, error (no promise - for equality)
169
+ *
170
+ * @example
171
+ * ```ts
172
+ * // Get state of single atom
173
+ * const dashboard$ = derived(({ state }) => {
174
+ * const userState = state(user$);
175
+ * const postsState = state(posts$);
176
+ *
177
+ * return {
178
+ * user: userState.value, // undefined if not ready
179
+ * isLoading: userState.status === 'loading' || postsState.status === 'loading',
180
+ * };
181
+ * });
182
+ * ```
183
+ */
184
+ state<T>(atom: Atom<T>): SelectStateResult<Awaited<T>>;
185
+ /**
186
+ * Get the async state of a selector function without throwing.
187
+ *
188
+ * Wraps the selector in try/catch and returns the result as a
189
+ * `SelectStateResult<T>` object. Useful for getting state of combined
190
+ * operations like `all()`, `race()`, etc.
191
+ *
192
+ * @param selector - Function that may throw promises or errors
193
+ * @returns SelectStateResult with status, value, error (no promise - for equality)
194
+ *
195
+ * @example
196
+ * ```ts
197
+ * // Get state of combined operation
198
+ * const allData$ = derived(({ state, all }) => {
199
+ * const result = state(() => all(a$, b$, c$));
200
+ *
201
+ * if (result.status === 'loading') return { loading: true };
202
+ * if (result.status === 'error') return { error: result.error };
203
+ * return { data: result.value };
204
+ * });
205
+ * ```
206
+ */
207
+ state<T>(selector: () => T): SelectStateResult<T>;
97
208
  }
98
209
  /**
99
210
  * Selector function type for context-based API.
100
211
  */
101
- export type ContextSelectorFn<T> = (context: SelectContext) => T;
212
+ export type ReactiveSelector<T, C extends SelectContext = SelectContext> = (context: C) => T;
102
213
  /**
103
214
  * Custom error for when all atoms in `any()` are rejected.
104
215
  */
@@ -110,7 +221,7 @@ export declare class AllAtomsRejectedError extends Error {
110
221
  * Selects/computes a value from atom(s) with dependency tracking.
111
222
  *
112
223
  * This is the core computation logic used by `derived()`. It:
113
- * 1. Creates a context with `get`, `all`, `any`, `race`, `settled` utilities
224
+ * 1. Creates a context with `read`, `all`, `any`, `race`, `settled`, `safe` utilities
114
225
  * 2. Tracks which atoms are accessed during computation
115
226
  * 3. Returns a result with value/error/promise and dependencies
116
227
  *
@@ -123,29 +234,97 @@ export declare class AllAtomsRejectedError extends Error {
123
234
  * If your selector returns a Promise, it will throw an error. This is because:
124
235
  * - `select()` is designed for synchronous derivation from atoms
125
236
  * - Async atoms should be created using `atom(Promise)` directly
126
- * - Use `get()` to read async atoms - it handles Suspense-style loading
237
+ * - Use `read()` to read async atoms - it handles Suspense-style loading
127
238
  *
128
239
  * ```ts
129
240
  * // ❌ WRONG - Don't return a Promise from selector
130
241
  * select(({ get }) => fetch('/api/data'));
131
242
  *
132
- * // ✅ CORRECT - Create async atom and read with get()
243
+ * // ✅ CORRECT - Create async atom and read with read()
133
244
  * const data$ = atom(fetch('/api/data').then(r => r.json()));
134
- * select(({ get }) => get(data$)); // Suspends until resolved
245
+ * select(({ read }) => read(data$)); // Suspends until resolved
246
+ * ```
247
+ *
248
+ * ## IMPORTANT: Do NOT Use try/catch - Use safe() Instead
249
+ *
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.
253
+ *
254
+ * ```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 };
273
+ * });
274
+ * ```
275
+ *
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
282
+ *
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.
286
+ *
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
+ * });
295
+ *
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
303
+ *
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
+ * });
135
313
  * ```
136
314
  *
137
315
  * @template T - The type of the computed value
138
316
  * @param fn - Context-based selector function (must return sync value)
139
317
  * @returns SelectResult with value, error, promise, and dependencies
140
318
  * @throws Error if selector returns a Promise or PromiseLike
319
+ * @throws Error if context methods are called outside selection context
141
320
  *
142
321
  * @example
143
322
  * ```ts
144
- * select(({ get, all }) => {
145
- * const user = get(user$);
146
- * const [posts, comments] = all(posts$, comments$);
323
+ * select(({ read, all }) => {
324
+ * const user = read(user$);
325
+ * const [posts, comments] = all([posts$, comments$]);
147
326
  * return { user, posts, comments };
148
327
  * });
149
328
  * ```
150
329
  */
151
- export declare function select<T>(fn: ContextSelectorFn<T>): SelectResult<T>;
330
+ export declare function select<T>(fn: ReactiveSelector<T>): SelectResult<T>;