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
package/dist/core/types.d.ts
CHANGED
|
@@ -25,8 +25,8 @@ export declare const SYMBOL_DERIVED: unique symbol;
|
|
|
25
25
|
* @example
|
|
26
26
|
* ```ts
|
|
27
27
|
* const enhanced = atom(0)
|
|
28
|
-
* .use(source => ({ ...source, double: () => source.
|
|
29
|
-
* .use(source => ({ ...source, triple: () => source.
|
|
28
|
+
* .use(source => ({ ...source, double: () => source.get() * 2 }))
|
|
29
|
+
* .use(source => ({ ...source, triple: () => source.get() * 3 }));
|
|
30
30
|
* ```
|
|
31
31
|
*/
|
|
32
32
|
export interface Pipeable {
|
|
@@ -37,9 +37,8 @@ export interface Pipeable {
|
|
|
37
37
|
/**
|
|
38
38
|
* Optional metadata for atoms.
|
|
39
39
|
*/
|
|
40
|
-
export interface AtomMeta {
|
|
40
|
+
export interface AtomMeta extends AtomirxMeta {
|
|
41
41
|
key?: string;
|
|
42
|
-
[key: string]: unknown;
|
|
43
42
|
}
|
|
44
43
|
/**
|
|
45
44
|
* Base interface for all atoms.
|
|
@@ -50,10 +49,10 @@ export interface AtomMeta {
|
|
|
50
49
|
export interface Atom<T> {
|
|
51
50
|
/** Symbol marker to identify atom instances */
|
|
52
51
|
readonly [SYMBOL_ATOM]: true;
|
|
53
|
-
/** The current value */
|
|
54
|
-
readonly value: T;
|
|
55
52
|
/** Optional metadata for the atom */
|
|
56
53
|
readonly meta?: AtomMeta;
|
|
54
|
+
/** Get the current value */
|
|
55
|
+
get(): T;
|
|
57
56
|
/**
|
|
58
57
|
* Subscribe to value changes.
|
|
59
58
|
* @param listener - Callback invoked when value changes
|
|
@@ -79,7 +78,7 @@ export interface Atom<T> {
|
|
|
79
78
|
*
|
|
80
79
|
* // Async value (stores Promise as-is)
|
|
81
80
|
* const posts = atom(fetchPosts());
|
|
82
|
-
* posts.
|
|
81
|
+
* posts.get(); // Promise<Post[]>
|
|
83
82
|
* posts.set(fetchPosts()); // Store new Promise
|
|
84
83
|
* ```
|
|
85
84
|
*/
|
|
@@ -119,7 +118,7 @@ export interface MutableAtom<T> extends Atom<T>, Pipeable {
|
|
|
119
118
|
* A derived (computed) atom that always returns Promise<T> for its value.
|
|
120
119
|
*
|
|
121
120
|
* DerivedAtom computes its value from other atoms. The computation is
|
|
122
|
-
* re-run whenever dependencies change. The `.
|
|
121
|
+
* re-run whenever dependencies change. The `.get()` always returns a Promise,
|
|
123
122
|
* even for synchronous computations.
|
|
124
123
|
*
|
|
125
124
|
* @template T - The resolved type of the computed value
|
|
@@ -128,13 +127,13 @@ export interface MutableAtom<T> extends Atom<T>, Pipeable {
|
|
|
128
127
|
* @example
|
|
129
128
|
* ```ts
|
|
130
129
|
* // Without fallback
|
|
131
|
-
* const double$ = derived(({
|
|
132
|
-
* await double$.
|
|
130
|
+
* const double$ = derived(({ read }) => read(count$) * 2);
|
|
131
|
+
* await double$.get(); // number
|
|
133
132
|
* double$.staleValue; // number | undefined
|
|
134
133
|
* double$.state(); // { status: "ready", value: 10 }
|
|
135
134
|
*
|
|
136
135
|
* // With fallback - during loading
|
|
137
|
-
* const double$ = derived(({
|
|
136
|
+
* const double$ = derived(({ read }) => read(count$) * 2, { fallback: 0 });
|
|
138
137
|
* double$.staleValue; // number (guaranteed)
|
|
139
138
|
* double$.state(); // { status: "loading", promise } during loading
|
|
140
139
|
* ```
|
|
@@ -178,13 +177,59 @@ export type AtomValue<A> = A extends DerivedAtom<infer V, boolean> ? V : A exten
|
|
|
178
177
|
export type AtomState<T> = {
|
|
179
178
|
status: "ready";
|
|
180
179
|
value: T;
|
|
180
|
+
error?: undefined;
|
|
181
|
+
promise?: undefined;
|
|
181
182
|
} | {
|
|
182
183
|
status: "error";
|
|
183
184
|
error: unknown;
|
|
185
|
+
value?: undefined;
|
|
186
|
+
promise?: undefined;
|
|
184
187
|
} | {
|
|
185
188
|
status: "loading";
|
|
186
189
|
promise: Promise<T>;
|
|
190
|
+
value?: undefined;
|
|
191
|
+
error?: undefined;
|
|
187
192
|
};
|
|
193
|
+
/**
|
|
194
|
+
* Result type for SelectContext.state() - simplified AtomState without promise.
|
|
195
|
+
*
|
|
196
|
+
* All properties (`status`, `value`, `error`) are always present:
|
|
197
|
+
* - `value` is `T` when ready, `undefined` otherwise
|
|
198
|
+
* - `error` is the error when errored, `undefined` otherwise
|
|
199
|
+
*
|
|
200
|
+
* This enables easy destructuring without type narrowing:
|
|
201
|
+
* ```ts
|
|
202
|
+
* const { status, value, error } = state(atom$);
|
|
203
|
+
* ```
|
|
204
|
+
*
|
|
205
|
+
* Equality comparisons work correctly (no promise reference issues).
|
|
206
|
+
*/
|
|
207
|
+
export type SelectStateResult<T> = {
|
|
208
|
+
status: "ready";
|
|
209
|
+
value: T;
|
|
210
|
+
error: undefined;
|
|
211
|
+
} | {
|
|
212
|
+
status: "error";
|
|
213
|
+
value: undefined;
|
|
214
|
+
error: unknown;
|
|
215
|
+
} | {
|
|
216
|
+
status: "loading";
|
|
217
|
+
value: undefined;
|
|
218
|
+
error: undefined;
|
|
219
|
+
};
|
|
220
|
+
/**
|
|
221
|
+
* Result type for race() and any() - includes winning key.
|
|
222
|
+
*
|
|
223
|
+
* @template K - The key type (string literal union)
|
|
224
|
+
* @template V - The value type
|
|
225
|
+
*/
|
|
226
|
+
export type KeyedResult<K extends string, V> = {
|
|
227
|
+
/** The key that won the race/any */
|
|
228
|
+
key: K;
|
|
229
|
+
/** The resolved value */
|
|
230
|
+
value: V;
|
|
231
|
+
};
|
|
232
|
+
export type AtomPlugin = <T extends Atom<any>>(atom: T) => T | void;
|
|
188
233
|
/**
|
|
189
234
|
* Result type for settled operations.
|
|
190
235
|
*/
|
|
@@ -220,15 +265,63 @@ export interface DerivedOptions<T> {
|
|
|
220
265
|
meta?: DerivedAtomMeta;
|
|
221
266
|
/** Equality strategy for change detection (default: "strict") */
|
|
222
267
|
equals?: Equality<T>;
|
|
268
|
+
/**
|
|
269
|
+
* Callback invoked when the derived computation throws an error.
|
|
270
|
+
* This is called for actual errors, NOT for Promise throws (Suspense).
|
|
271
|
+
*
|
|
272
|
+
* @param error - The error thrown during computation
|
|
273
|
+
*
|
|
274
|
+
* @example
|
|
275
|
+
* ```ts
|
|
276
|
+
* const data$ = derived(
|
|
277
|
+
* ({ read }) => {
|
|
278
|
+
* const raw = read(source$);
|
|
279
|
+
* return JSON.parse(raw); // May throw SyntaxError
|
|
280
|
+
* },
|
|
281
|
+
* {
|
|
282
|
+
* onError: (error) => {
|
|
283
|
+
* console.error('Derived computation failed:', error);
|
|
284
|
+
* reportToSentry(error);
|
|
285
|
+
* }
|
|
286
|
+
* }
|
|
287
|
+
* );
|
|
288
|
+
* ```
|
|
289
|
+
*/
|
|
290
|
+
onError?: (error: unknown) => void;
|
|
223
291
|
}
|
|
224
292
|
/**
|
|
225
293
|
* Configuration options for effects.
|
|
226
294
|
*/
|
|
227
295
|
export interface EffectOptions {
|
|
228
|
-
|
|
296
|
+
meta?: EffectMeta;
|
|
297
|
+
/**
|
|
298
|
+
* Callback invoked when the effect computation throws an error.
|
|
299
|
+
* This is called for actual errors, NOT for Promise throws (Suspense).
|
|
300
|
+
*
|
|
301
|
+
* @param error - The error thrown during effect execution
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
* ```ts
|
|
305
|
+
* effect(
|
|
306
|
+
* ({ read }) => {
|
|
307
|
+
* const data = read(source$);
|
|
308
|
+
* riskyOperation(data); // May throw
|
|
309
|
+
* },
|
|
310
|
+
* {
|
|
311
|
+
* onError: (error) => {
|
|
312
|
+
* console.error('Effect failed:', error);
|
|
313
|
+
* showErrorNotification(error);
|
|
314
|
+
* }
|
|
315
|
+
* }
|
|
316
|
+
* );
|
|
317
|
+
* ```
|
|
318
|
+
*/
|
|
319
|
+
onError?: (error: unknown) => void;
|
|
320
|
+
}
|
|
321
|
+
export interface AtomirxMeta {
|
|
322
|
+
}
|
|
323
|
+
export interface EffectMeta extends AtomirxMeta {
|
|
229
324
|
key?: string;
|
|
230
|
-
/** Error handler for uncaught errors in the effect */
|
|
231
|
-
onError?: (error: Error) => void;
|
|
232
325
|
}
|
|
233
326
|
/**
|
|
234
327
|
* A function that returns a value when called.
|
|
@@ -269,11 +362,3 @@ export interface ModuleMeta {
|
|
|
269
362
|
}
|
|
270
363
|
export type Listener<T> = (value: T) => void;
|
|
271
364
|
export type SingleOrMultipleListeners<T> = Listener<T> | Listener<T>[];
|
|
272
|
-
/**
|
|
273
|
-
* Type guard to check if a value is an Atom.
|
|
274
|
-
*/
|
|
275
|
-
export declare function isAtom<T>(value: unknown): value is Atom<T>;
|
|
276
|
-
/**
|
|
277
|
-
* Type guard to check if a value is a DerivedAtom.
|
|
278
|
-
*/
|
|
279
|
-
export declare function isDerived<T>(value: unknown): value is DerivedAtom<T, boolean>;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { SelectContext } from './select';
|
|
2
|
+
import { Atom } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Extension interface that adds `ready()` method to SelectContext.
|
|
5
|
+
* Used in derived atoms and effects to wait for non-null values.
|
|
6
|
+
*/
|
|
7
|
+
export interface WithReadySelectContext {
|
|
8
|
+
/**
|
|
9
|
+
* Wait for an atom to have a non-null/non-undefined value.
|
|
10
|
+
*
|
|
11
|
+
* If the value is null/undefined, the computation suspends until the atom
|
|
12
|
+
* changes to a non-null value, then automatically resumes.
|
|
13
|
+
*
|
|
14
|
+
* **IMPORTANT: Only use in `derived()` or `effect()` context**
|
|
15
|
+
*
|
|
16
|
+
* @param atom - The atom to read and wait for
|
|
17
|
+
* @returns The non-null value (type excludes null | undefined)
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* // Wait for currentArticleId to be set before computing
|
|
22
|
+
* const currentArticle$ = derived(({ ready, read }) => {
|
|
23
|
+
* const id = ready(currentArticleId$); // Suspends if null
|
|
24
|
+
* const cache = read(articleCache$);
|
|
25
|
+
* return cache[id];
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
ready<T>(atom: Atom<T>): T extends PromiseLike<any> ? never : Exclude<T, null | undefined>;
|
|
30
|
+
/**
|
|
31
|
+
* Wait for a selected value from an atom to be non-null/non-undefined.
|
|
32
|
+
*
|
|
33
|
+
* If the selected value is null/undefined, the computation suspends until the
|
|
34
|
+
* selected value changes to a non-null value, then automatically resumes.
|
|
35
|
+
*
|
|
36
|
+
* **IMPORTANT: Only use in `derived()` or `effect()` context**
|
|
37
|
+
*
|
|
38
|
+
* @param atom - The atom to read
|
|
39
|
+
* @param selector - Function to extract/transform the value
|
|
40
|
+
* @returns The non-null selected value
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* // Wait for user's email to be set
|
|
45
|
+
* const emailDerived$ = derived(({ ready }) => {
|
|
46
|
+
* const email = ready(user$, u => u.email); // Suspends if email is null
|
|
47
|
+
* return `Contact: ${email}`;
|
|
48
|
+
* });
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
ready<T, R>(atom: Atom<T>, selector: (current: Awaited<T>) => R): R extends PromiseLike<any> ? never : Exclude<R, null | undefined>;
|
|
52
|
+
/**
|
|
53
|
+
* Execute a function and wait for its result to be non-null/non-undefined.
|
|
54
|
+
*
|
|
55
|
+
* If the function returns null/undefined, the computation suspends until
|
|
56
|
+
* re-executed with a non-null result.
|
|
57
|
+
*
|
|
58
|
+
* **IMPORTANT: Only use in `derived()` or `effect()` context**
|
|
59
|
+
*
|
|
60
|
+
* **NOTE:** This overload is designed for use with async combinators like
|
|
61
|
+
* `all()`, `race()`, `any()`, `settled()` where promises come from stable
|
|
62
|
+
* atom sources. It does NOT support dynamic promise creation (returning a
|
|
63
|
+
* new Promise from the callback). For async selectors that return promises,
|
|
64
|
+
* use `ready(atom$, selector?)` instead.
|
|
65
|
+
*
|
|
66
|
+
* @param fn - Synchronous function to execute and wait for
|
|
67
|
+
* @returns The non-null result (excludes null | undefined)
|
|
68
|
+
* @throws {Error} If the callback returns a Promise
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```ts
|
|
72
|
+
* // Wait for a computed value to be ready
|
|
73
|
+
* const result$ = derived(({ ready, read }) => {
|
|
74
|
+
* const value = ready(() => computeExpensiveValue(read(input$)));
|
|
75
|
+
* return `Result: ${value}`;
|
|
76
|
+
* });
|
|
77
|
+
* ```
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```ts
|
|
81
|
+
* // Use with async combinators (all, race, any, settled)
|
|
82
|
+
* const combined$ = derived(({ ready, all }) => {
|
|
83
|
+
* const [user, posts] = ready(() => all(user$, posts$));
|
|
84
|
+
* return { user, posts };
|
|
85
|
+
* });
|
|
86
|
+
* ```
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* // For async selectors, use ready(atom$, selector?) instead:
|
|
91
|
+
* const data$ = derived(({ ready }) => {
|
|
92
|
+
* const data = ready(source$, (val) => fetchData(val.id));
|
|
93
|
+
* return data;
|
|
94
|
+
* });
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
ready<T>(fn: () => T): T extends PromiseLike<any> ? never : Exclude<Awaited<T>, null | undefined>;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Plugin that adds `ready()` method to a SelectContext.
|
|
101
|
+
*
|
|
102
|
+
* `ready()` enables a "reactive suspension" pattern where derived atoms
|
|
103
|
+
* wait for required values before computing. This is useful for:
|
|
104
|
+
*
|
|
105
|
+
* - Route-based entity loading (`/article/:id` - wait for ID to be set)
|
|
106
|
+
* - Authentication-gated content (wait for user to be logged in)
|
|
107
|
+
* - Conditional data dependencies (wait for prerequisite data)
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```ts
|
|
111
|
+
* // Used internally by derived() - you don't need to call this directly
|
|
112
|
+
* const result = select((context) => fn(context.use(withReady())));
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
export declare function withReady(): <TContext extends SelectContext>(context: TContext) => TContext & WithReadySelectContext;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|