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.
- package/README.md +868 -161
- package/coverage/src/core/onCreateHook.ts.html +72 -70
- package/dist/core/atom.d.ts +83 -6
- package/dist/core/batch.d.ts +3 -3
- package/dist/core/derived.d.ts +69 -22
- package/dist/core/effect.d.ts +52 -52
- package/dist/core/getAtomState.d.ts +29 -0
- package/dist/core/hook.d.ts +1 -1
- package/dist/core/onCreateHook.d.ts +37 -23
- package/dist/core/onErrorHook.d.ts +49 -0
- package/dist/core/promiseCache.d.ts +23 -32
- package/dist/core/select.d.ts +208 -29
- package/dist/core/types.d.ts +107 -22
- package/dist/core/withReady.d.ts +115 -0
- package/dist/core/withReady.test.d.ts +1 -0
- package/dist/index-CBVj1kSj.js +1350 -0
- package/dist/index-Cxk9v0um.cjs +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +12 -8
- package/dist/index.js +18 -15
- package/dist/react/index.cjs +10 -10
- package/dist/react/index.d.ts +2 -1
- package/dist/react/index.js +422 -377
- package/dist/react/rx.d.ts +114 -25
- package/dist/react/useAction.d.ts +5 -4
- package/dist/react/{useValue.d.ts → useSelector.d.ts} +56 -25
- package/dist/react/useSelector.test.d.ts +1 -0
- package/package.json +1 -1
- package/src/core/atom.test.ts +307 -43
- package/src/core/atom.ts +144 -22
- package/src/core/batch.test.ts +10 -10
- package/src/core/batch.ts +3 -3
- package/src/core/define.test.ts +12 -11
- package/src/core/define.ts +1 -1
- package/src/core/derived.test.ts +906 -72
- package/src/core/derived.ts +192 -81
- package/src/core/effect.test.ts +651 -45
- package/src/core/effect.ts +102 -98
- package/src/core/getAtomState.ts +69 -0
- package/src/core/hook.test.ts +5 -5
- package/src/core/hook.ts +1 -1
- package/src/core/onCreateHook.ts +38 -23
- package/src/core/onErrorHook.test.ts +350 -0
- package/src/core/onErrorHook.ts +52 -0
- package/src/core/promiseCache.test.ts +5 -3
- package/src/core/promiseCache.ts +76 -71
- package/src/core/select.ts +405 -130
- package/src/core/selector.test.ts +574 -32
- package/src/core/types.ts +107 -29
- package/src/core/withReady.test.ts +534 -0
- package/src/core/withReady.ts +191 -0
- package/src/core/withUse.ts +1 -1
- package/src/index.test.ts +4 -4
- package/src/index.ts +21 -7
- package/src/react/index.ts +2 -1
- package/src/react/rx.test.tsx +173 -18
- package/src/react/rx.tsx +274 -43
- package/src/react/useAction.test.ts +12 -14
- package/src/react/useAction.ts +11 -9
- package/src/react/{useValue.test.ts → useSelector.test.ts} +16 -16
- package/src/react/{useValue.ts → useSelector.ts} +64 -33
- package/v2.md +44 -44
- package/dist/index-2ok7ilik.js +0 -1217
- package/dist/index-B_5SFzfl.cjs +0 -1
- /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>>;
|
package/dist/core/hook.d.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
-
|
|
14
|
+
instance: MutableAtom<unknown>;
|
|
14
15
|
}
|
|
15
16
|
/**
|
|
16
17
|
* Information provided when a derived atom is created.
|
|
17
18
|
*/
|
|
18
|
-
export interface
|
|
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
|
-
|
|
27
|
+
instance: DerivedAtom<unknown, boolean>;
|
|
27
28
|
}
|
|
28
29
|
/**
|
|
29
|
-
*
|
|
30
|
+
* Information provided when an effect is created.
|
|
30
31
|
*/
|
|
31
|
-
export
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
63
|
-
* const modules = new Map();
|
|
80
|
+
* const registry = new Map();
|
|
64
81
|
*
|
|
65
|
-
* onCreateHook.
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
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
|
|
88
|
+
* @example Reset to default (disable all handlers)
|
|
75
89
|
* ```ts
|
|
76
|
-
* onCreateHook.
|
|
90
|
+
* onCreateHook.reset();
|
|
77
91
|
* ```
|
|
78
92
|
*/
|
|
79
|
-
export declare const onCreateHook: import('./hook').Hook<((info:
|
|
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 {
|
|
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.
|
package/dist/core/select.d.ts
CHANGED
|
@@ -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
|
-
|
|
39
|
+
read<T>(atom: Atom<T>): Awaited<T>;
|
|
35
40
|
/**
|
|
36
41
|
* Wait for all atoms to resolve (like Promise.all).
|
|
37
|
-
*
|
|
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 -
|
|
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>[]>(
|
|
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
|
-
*
|
|
62
|
+
* Object-based - pass atoms as a record with keys.
|
|
58
63
|
*
|
|
59
|
-
* - If any atom is ready → returns first ready
|
|
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 -
|
|
66
|
-
* @returns
|
|
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<
|
|
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
|
-
*
|
|
85
|
+
* Object-based - pass atoms as a record with keys.
|
|
72
86
|
*
|
|
73
|
-
* - If any atom is ready → returns first ready
|
|
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 -
|
|
80
|
-
* @returns
|
|
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<
|
|
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
|
-
*
|
|
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 -
|
|
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>[]>(
|
|
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
|
|
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 `
|
|
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 `
|
|
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
|
|
243
|
+
* // ✅ CORRECT - Create async atom and read with read()
|
|
133
244
|
* const data$ = atom(fetch('/api/data').then(r => r.json()));
|
|
134
|
-
* select(({
|
|
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(({
|
|
145
|
-
* const 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:
|
|
330
|
+
export declare function select<T>(fn: ReactiveSelector<T>): SelectResult<T>;
|