atomirx 0.0.1 → 0.0.4

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 (53) hide show
  1. package/README.md +867 -160
  2. package/dist/core/atom.d.ts +83 -6
  3. package/dist/core/batch.d.ts +3 -3
  4. package/dist/core/derived.d.ts +55 -21
  5. package/dist/core/effect.d.ts +47 -51
  6. package/dist/core/getAtomState.d.ts +29 -0
  7. package/dist/core/promiseCache.d.ts +23 -32
  8. package/dist/core/select.d.ts +208 -29
  9. package/dist/core/types.d.ts +55 -19
  10. package/dist/core/withReady.d.ts +69 -0
  11. package/dist/index-CqO6BDwj.cjs +1 -0
  12. package/dist/index-D8RDOTB_.js +1319 -0
  13. package/dist/index.cjs +1 -1
  14. package/dist/index.d.ts +9 -7
  15. package/dist/index.js +12 -10
  16. package/dist/react/index.cjs +10 -10
  17. package/dist/react/index.d.ts +2 -1
  18. package/dist/react/index.js +423 -379
  19. package/dist/react/rx.d.ts +114 -25
  20. package/dist/react/useAction.d.ts +5 -4
  21. package/dist/react/{useValue.d.ts → useSelector.d.ts} +56 -25
  22. package/dist/react/useSelector.test.d.ts +1 -0
  23. package/package.json +17 -1
  24. package/src/core/atom.test.ts +307 -43
  25. package/src/core/atom.ts +143 -21
  26. package/src/core/batch.test.ts +10 -10
  27. package/src/core/batch.ts +3 -3
  28. package/src/core/derived.test.ts +727 -72
  29. package/src/core/derived.ts +141 -73
  30. package/src/core/effect.test.ts +259 -39
  31. package/src/core/effect.ts +62 -85
  32. package/src/core/getAtomState.ts +69 -0
  33. package/src/core/promiseCache.test.ts +5 -3
  34. package/src/core/promiseCache.ts +76 -71
  35. package/src/core/select.ts +405 -130
  36. package/src/core/selector.test.ts +574 -32
  37. package/src/core/types.ts +54 -26
  38. package/src/core/withReady.test.ts +360 -0
  39. package/src/core/withReady.ts +127 -0
  40. package/src/core/withUse.ts +1 -1
  41. package/src/index.test.ts +4 -4
  42. package/src/index.ts +11 -6
  43. package/src/react/index.ts +2 -1
  44. package/src/react/rx.test.tsx +173 -18
  45. package/src/react/rx.tsx +274 -43
  46. package/src/react/useAction.test.ts +12 -14
  47. package/src/react/useAction.ts +11 -9
  48. package/src/react/{useValue.test.ts → useSelector.test.ts} +16 -16
  49. package/src/react/{useValue.ts → useSelector.ts} +64 -33
  50. package/v2.md +44 -44
  51. package/dist/index-2ok7ilik.js +0 -1217
  52. package/dist/index-B_5SFzfl.cjs +0 -1
  53. /package/dist/{react/useValue.test.d.ts → core/withReady.test.d.ts} +0 -0
@@ -1,9 +1,45 @@
1
+ import { ReactElement, ReactNode } from 'react';
1
2
  import { Atom, Equality } from '../core/types';
2
- import { ContextSelectorFn } from '../core/select';
3
+ import { ReactiveSelector } from '../core/select';
4
+ /**
5
+ * Options for rx() with inline loading/error handling and memoization control.
6
+ */
7
+ export interface RxOptions<T> {
8
+ /** Equality function for value comparison */
9
+ equals?: Equality<T>;
10
+ /** Render function for loading state */
11
+ loading?: () => ReactNode;
12
+ /** Render function for error state */
13
+ error?: (props: {
14
+ error: unknown;
15
+ }) => ReactNode;
16
+ /**
17
+ * Dependencies array for selector memoization.
18
+ *
19
+ * Controls when the selector callback is recreated:
20
+ * - **Atom shorthand** (`rx(atom$)`): Always memoized by atom reference (deps ignored)
21
+ * - **Function selector without deps**: No memoization (recreated every render)
22
+ * - **Function selector with `deps: []`**: Stable forever (never recreated)
23
+ * - **Function selector with `deps: [a, b]`**: Recreated when deps change
24
+ *
25
+ * @example
26
+ * ```tsx
27
+ * // No memoization (default for functions) - selector recreated every render
28
+ * rx(({ read }) => read(count$) * 2)
29
+ *
30
+ * // Stable selector - never recreated
31
+ * rx(({ read }) => read(count$) * 2, { deps: [] })
32
+ *
33
+ * // Recreate when multiplier changes
34
+ * rx(({ read }) => read(count$) * multiplier, { deps: [multiplier] })
35
+ * ```
36
+ */
37
+ deps?: unknown[];
38
+ }
3
39
  /**
4
40
  * Reactive inline component that renders atom values directly in JSX.
5
41
  *
6
- * `rx` is a convenience wrapper around `useValue` that returns a memoized
42
+ * `rx` is a convenience wrapper around `useSelector` that returns a memoized
7
43
  * React component instead of a value. This enables fine-grained reactivity
8
44
  * without creating separate components for each reactive value.
9
45
  *
@@ -13,25 +49,54 @@ import { ContextSelectorFn } from '../core/select';
13
49
  *
14
50
  * ```tsx
15
51
  * // ❌ WRONG - Don't use async function
16
- * rx(async ({ get }) => {
52
+ * rx(async ({ read }) => {
17
53
  * const data = await fetch('/api');
18
54
  * return data.name;
19
55
  * });
20
56
  *
21
57
  * // ❌ WRONG - Don't return a Promise
22
- * rx(({ get }) => fetch('/api').then(r => r.json()));
58
+ * rx(({ read }) => fetch('/api').then(r => r.json()));
23
59
  *
24
- * // ✅ CORRECT - Create async atom and read with get()
60
+ * // ✅ CORRECT - Create async atom and read with read()
25
61
  * const data$ = atom(fetch('/api').then(r => r.json()));
26
- * rx(({ get }) => get(data$).name); // Suspends until resolved
62
+ * rx(({ read }) => read(data$).name); // Suspends until resolved
27
63
  * ```
28
64
  *
65
+ * ## IMPORTANT: Do NOT Use try/catch - Use safe() Instead
66
+ *
67
+ * **Never wrap `read()` calls in try/catch blocks.** The `read()` function throws
68
+ * Promises when atoms are loading (Suspense pattern). A try/catch will catch
69
+ * these Promises and break the Suspense mechanism.
70
+ *
71
+ * ```tsx
72
+ * // ❌ WRONG - Catches Suspense Promise, breaks loading state
73
+ * rx(({ read }) => {
74
+ * try {
75
+ * return <span>{read(user$).name}</span>;
76
+ * } catch (e) {
77
+ * return <span>Error</span>; // Catches BOTH errors AND loading promises!
78
+ * }
79
+ * });
80
+ *
81
+ * // ✅ CORRECT - Use safe() to catch errors but preserve Suspense
82
+ * rx(({ read, safe }) => {
83
+ * const [err, user] = safe(() => read(user$));
84
+ * if (err) return <span>Error: {err.message}</span>;
85
+ * return <span>{user.name}</span>;
86
+ * });
87
+ * ```
88
+ *
89
+ * The `safe()` utility:
90
+ * - **Catches errors** and returns `[error, undefined]`
91
+ * - **Re-throws Promises** to preserve Suspense behavior
92
+ * - Returns `[undefined, result]` on success
93
+ *
29
94
  * ## Why Use `rx`?
30
95
  *
31
96
  * Without `rx`, you need a separate component to subscribe to an atom:
32
97
  * ```tsx
33
98
  * function PostsList() {
34
- * const posts = useValue(postsAtom);
99
+ * const posts = useSelector(postsAtom);
35
100
  * return posts.map((post) => <Post post={post} />);
36
101
  * }
37
102
  *
@@ -49,8 +114,8 @@ import { ContextSelectorFn } from '../core/select';
49
114
  * function Page() {
50
115
  * return (
51
116
  * <Suspense fallback={<Loading />}>
52
- * {rx(({ get }) =>
53
- * get(postsAtom).map((post) => <Post post={post} />)
117
+ * {rx(({ read }) =>
118
+ * read(postsAtom).map((post) => <Post post={post} />)
54
119
  * )}
55
120
  * </Suspense>
56
121
  * );
@@ -68,7 +133,7 @@ import { ContextSelectorFn } from '../core/select';
68
133
  *
69
134
  * ## Async Atoms (Suspense-Style API)
70
135
  *
71
- * `rx` inherits the Suspense-style API from `useValue`:
136
+ * `rx` inherits the Suspense-style API from `useSelector`:
72
137
  * - **Loading state**: The getter throws a Promise (triggers Suspense)
73
138
  * - **Error state**: The getter throws the error (triggers ErrorBoundary)
74
139
  * - **Resolved state**: The getter returns the value
@@ -79,7 +144,7 @@ import { ContextSelectorFn } from '../core/select';
79
144
  * return (
80
145
  * <ErrorBoundary fallback={<div>Error!</div>}>
81
146
  * <Suspense fallback={<div>Loading...</div>}>
82
- * {rx(({ get }) => get(userAtom).name)}
147
+ * {rx(({ read }) => read(userAtom).name)}
83
148
  * </Suspense>
84
149
  * </ErrorBoundary>
85
150
  * );
@@ -88,9 +153,9 @@ import { ContextSelectorFn } from '../core/select';
88
153
  *
89
154
  * Or catch errors in the selector to handle loading/error inline:
90
155
  * ```tsx
91
- * {rx(({ get }) => {
156
+ * {rx(({ read }) => {
92
157
  * try {
93
- * return get(userAtom).name;
158
+ * return read(userAtom).name;
94
159
  * } catch {
95
160
  * return "Loading...";
96
161
  * }
@@ -98,13 +163,37 @@ import { ContextSelectorFn } from '../core/select';
98
163
  * ```
99
164
  *
100
165
  * @template T - The type of the selected/derived value
101
- * @param selector - Context-based selector function with `{ get, all, any, race, settled }`.
166
+ * @param selector - Context-based selector function with `{ read, all, any, race, settled }`.
102
167
  * Must return sync value, not a Promise.
103
168
  * @param equals - Equality function or shorthand ("strict", "shallow", "deep").
104
169
  * Defaults to "shallow".
105
170
  * @returns A React element that renders the selected value
106
171
  * @throws Error if selector returns a Promise or PromiseLike
107
172
  *
173
+ * ## IMPORTANT: Atom Value Must Be ReactNode
174
+ *
175
+ * When using the shorthand `rx(atom)`, the atom's value must be a valid `ReactNode`
176
+ * (string, number, boolean, null, undefined, or React element). Objects and arrays
177
+ * are NOT valid ReactNode values and will cause React to throw an error.
178
+ *
179
+ * ```tsx
180
+ * // ✅ CORRECT - Atom contains ReactNode (number)
181
+ * const count$ = atom(5);
182
+ * rx(count$);
183
+ *
184
+ * // ✅ CORRECT - Atom contains ReactNode (string)
185
+ * const name$ = atom("John");
186
+ * rx(name$);
187
+ *
188
+ * // ❌ WRONG - Atom contains object (not ReactNode)
189
+ * const user$ = atom({ name: "John", age: 30 });
190
+ * rx(user$); // React error: "Objects are not valid as a React child"
191
+ *
192
+ * // ✅ CORRECT - Use selector to extract ReactNode from object
193
+ * rx(({ read }) => read(user$).name);
194
+ * rx(({ read }) => <UserCard user={read(user$)} />);
195
+ * ```
196
+ *
108
197
  * @example Shorthand - render atom value directly
109
198
  * ```tsx
110
199
  * const count = atom(5);
@@ -119,7 +208,7 @@ import { ContextSelectorFn } from '../core/select';
119
208
  * const count = atom(5);
120
209
  *
121
210
  * function DoubledCounter() {
122
- * return <div>Doubled: {rx(({ get }) => get(count) * 2)}</div>;
211
+ * return <div>Doubled: {rx(({ read }) => read(count) * 2)}</div>;
123
212
  * }
124
213
  * ```
125
214
  *
@@ -131,7 +220,7 @@ import { ContextSelectorFn } from '../core/select';
131
220
  * function FullName() {
132
221
  * return (
133
222
  * <div>
134
- * {rx(({ get }) => `${get(firstName)} ${get(lastName)}`)}
223
+ * {rx(({ read }) => `${read(firstName)} ${read(lastName)}`)}
135
224
  * </div>
136
225
  * );
137
226
  * }
@@ -158,14 +247,14 @@ import { ContextSelectorFn } from '../core/select';
158
247
  * return (
159
248
  * <div>
160
249
  * <header>
161
- * <Suspense fallback="...">{rx(({ get }) => get(userAtom).name)}</Suspense>
250
+ * <Suspense fallback="...">{rx(({ read }) => read(userAtom).name)}</Suspense>
162
251
  * </header>
163
252
  * <main>
164
253
  * <Suspense fallback="...">
165
- * {rx(({ get }) => get(postsAtom).length)} posts
254
+ * {rx(({ read }) => read(postsAtom).length)} posts
166
255
  * </Suspense>
167
256
  * <Suspense fallback="...">
168
- * {rx(({ get }) => get(notificationsAtom).length)} notifications
257
+ * {rx(({ read }) => read(notificationsAtom).length)} notifications
169
258
  * </Suspense>
170
259
  * </main>
171
260
  * </div>
@@ -182,8 +271,8 @@ import { ContextSelectorFn } from '../core/select';
182
271
  * function Info() {
183
272
  * return (
184
273
  * <div>
185
- * {rx(({ get }) =>
186
- * get(showDetails) ? get(details) : get(summary)
274
+ * {rx(({ read }) =>
275
+ * read(showDetails) ? read(details) : read(summary)
187
276
  * )}
188
277
  * </div>
189
278
  * );
@@ -198,7 +287,7 @@ import { ContextSelectorFn } from '../core/select';
198
287
  * return (
199
288
  * <div>
200
289
  * {rx(
201
- * ({ get }) => get(user).name,
290
+ * ({ read }) => read(user).name,
202
291
  * (a, b) => a === b // Only re-render if name string changes
203
292
  * )}
204
293
  * </div>
@@ -216,7 +305,7 @@ import { ContextSelectorFn } from '../core/select';
216
305
  * <Suspense fallback={<Loading />}>
217
306
  * {rx(({ all }) => {
218
307
  * // Use all() to wait for multiple atoms
219
- * const [user, posts] = all([userAtom, postsAtom]);
308
+ * const [user, posts] = all([user$, posts$]);
220
309
  * return <DashboardContent user={user} posts={posts} />;
221
310
  * })}
222
311
  * </Suspense>
@@ -246,5 +335,5 @@ import { ContextSelectorFn } from '../core/select';
246
335
  * }
247
336
  * ```
248
337
  */
249
- export declare function rx<T>(atom: Atom<T>, equals?: Equality<Awaited<T>>): Awaited<T>;
250
- export declare function rx<T>(selector: ContextSelectorFn<T>, equals?: Equality<T>): T;
338
+ export declare function rx<T extends ReactNode | PromiseLike<ReactNode>>(atom: Atom<T>, options?: Equality<T> | RxOptions<T>): ReactElement;
339
+ export declare function rx<T extends ReactNode | PromiseLike<ReactNode>>(selector: ReactiveSelector<T>, options?: Equality<T> | RxOptions<T>): ReactElement;
@@ -1,3 +1,4 @@
1
+ import { Pipeable } from '../core/types';
1
2
  /**
2
3
  * State for an action that hasn't been dispatched yet.
3
4
  */
@@ -59,7 +60,7 @@ export interface UseActionOptions {
59
60
  /**
60
61
  * Dependencies array. When lazy is false, re-executes when deps change.
61
62
  * - Regular values: compared by reference (like useEffect deps)
62
- * - Atoms: automatically tracked via useValue, re-executes when atom values change
63
+ * - Atoms: automatically tracked via useSelector, re-executes when atom values change
63
64
  * @default []
64
65
  */
65
66
  deps?: unknown[];
@@ -67,7 +68,7 @@ export interface UseActionOptions {
67
68
  /**
68
69
  * Context passed to the action function.
69
70
  */
70
- export interface ActionContext {
71
+ export interface ActionContext extends Pipeable {
71
72
  /** AbortSignal for cancellation. New signal per dispatch. */
72
73
  signal: AbortSignal;
73
74
  }
@@ -229,11 +230,11 @@ export type Action<TResult, TLazy extends boolean = true> = ActionDispatch<TResu
229
230
  *
230
231
  * function UserProfile() {
231
232
  * const fetchUser = useAction(
232
- * async ({ signal }) => fetchUserApi(userIdAtom.value, { signal }),
233
+ * async ({ signal }) => fetchUserApi(userIdAtom.get(), { signal }),
233
234
  * { lazy: false, deps: [userIdAtom] }
234
235
  * );
235
236
  * // Automatically re-fetches when userIdAtom changes
236
- * // Atoms in deps are tracked reactively via useValue
237
+ * // Atoms in deps are tracked reactively via useSelector
237
238
  * }
238
239
  * ```
239
240
  *
@@ -1,4 +1,4 @@
1
- import { ContextSelectorFn } from '../core/select';
1
+ import { ReactiveSelector } from '../core/select';
2
2
  import { Atom, Equality } from '../core/types';
3
3
  /**
4
4
  * React hook that selects/derives a value from atom(s) with automatic subscriptions.
@@ -12,19 +12,52 @@ import { Atom, Equality } from '../core/types';
12
12
  *
13
13
  * ```tsx
14
14
  * // ❌ WRONG - Don't use async function
15
- * useValue(async ({ get }) => {
15
+ * useSelector(async ({ read }) => {
16
16
  * const data = await fetch('/api');
17
17
  * return data;
18
18
  * });
19
19
  *
20
20
  * // ❌ WRONG - Don't return a Promise
21
- * useValue(({ get }) => fetch('/api').then(r => r.json()));
21
+ * useSelector(({ read }) => fetch('/api').then(r => r.json()));
22
22
  *
23
- * // ✅ CORRECT - Create async atom and read with get()
23
+ * // ✅ CORRECT - Create async atom and read with read()
24
24
  * const data$ = atom(fetch('/api').then(r => r.json()));
25
- * useValue(({ get }) => get(data$)); // Suspends until resolved
25
+ * useSelector(({ read }) => read(data$)); // Suspends until resolved
26
26
  * ```
27
27
  *
28
+ * ## IMPORTANT: Do NOT Use try/catch - Use safe() Instead
29
+ *
30
+ * **Never wrap `read()` calls in try/catch blocks.** The `read()` function throws
31
+ * Promises when atoms are loading (Suspense pattern). A try/catch will catch
32
+ * these Promises and break the Suspense mechanism.
33
+ *
34
+ * ```tsx
35
+ * // ❌ WRONG - Catches Suspense Promise, breaks loading state
36
+ * const data = useSelector(({ read }) => {
37
+ * try {
38
+ * return read(asyncAtom$);
39
+ * } catch (e) {
40
+ * return null; // This catches BOTH errors AND loading promises!
41
+ * }
42
+ * });
43
+ *
44
+ * // ✅ CORRECT - Use safe() to catch errors but preserve Suspense
45
+ * const result = useSelector(({ read, safe }) => {
46
+ * const [err, data] = safe(() => {
47
+ * const raw = read(asyncAtom$); // Can throw Promise (Suspense)
48
+ * return JSON.parse(raw); // Can throw Error
49
+ * });
50
+ *
51
+ * if (err) return { error: err.message };
52
+ * return { data };
53
+ * });
54
+ * ```
55
+ *
56
+ * The `safe()` utility:
57
+ * - **Catches errors** and returns `[error, undefined]`
58
+ * - **Re-throws Promises** to preserve Suspense behavior
59
+ * - Returns `[undefined, result]` on success
60
+ *
28
61
  * ## IMPORTANT: Suspense-Style API
29
62
  *
30
63
  * This hook uses a **Suspense-style API** for async atoms:
@@ -36,22 +69,19 @@ import { Atom, Equality } from '../core/types';
36
69
  * - **You MUST wrap components with `<Suspense>`** to handle loading states
37
70
  * - **You MUST wrap components with `<ErrorBoundary>`** to handle errors
38
71
  *
39
- * ## Alternative: Using staleValue for Non-Suspense
72
+ * ## Alternative: useAsyncState for Non-Suspense
40
73
  *
41
- * If you want to show loading states without Suspense:
74
+ * If you want to handle loading/error states imperatively without Suspense:
42
75
  *
43
76
  * ```tsx
77
+ * import { useAsyncState } from 'atomirx/react';
78
+ *
44
79
  * function MyComponent() {
45
- * // Access staleValue directly - always has a value (with fallback)
46
- * const count = myDerivedAtom$.staleValue;
47
- * const isLoading = isPending(myDerivedAtom$.value);
80
+ * const state = useAsyncState(myAtom$);
48
81
  *
49
- * return (
50
- * <div>
51
- * {isLoading && <Spinner />}
52
- * Count: {count}
53
- * </div>
54
- * );
82
+ * if (state.status === "loading") return <Spinner />;
83
+ * if (state.status === "error") return <Error error={state.error} />;
84
+ * return <div>{state.value}</div>;
55
85
  * }
56
86
  * ```
57
87
  *
@@ -59,14 +89,15 @@ import { Atom, Equality } from '../core/types';
59
89
  * @param selectorOrAtom - Atom or context-based selector function (must return sync value)
60
90
  * @param equals - Equality function or shorthand. Defaults to "shallow"
61
91
  * @returns The selected value (Awaited<T>)
62
- * @throws Error if selector returns a Promise or PromiseLike
92
+ * @throws Promise when loading (caught by Suspense)
93
+ * @throws Error when failed (caught by ErrorBoundary)
63
94
  *
64
95
  * @example Single atom (shorthand)
65
96
  * ```tsx
66
97
  * const count = atom(5);
67
98
  *
68
99
  * function Counter() {
69
- * const value = useValue(count);
100
+ * const value = useSelector(count);
70
101
  * return <div>{value}</div>;
71
102
  * }
72
103
  * ```
@@ -76,7 +107,7 @@ import { Atom, Equality } from '../core/types';
76
107
  * const count = atom(5);
77
108
  *
78
109
  * function Counter() {
79
- * const doubled = useValue(({ get }) => get(count) * 2);
110
+ * const doubled = useSelector(({ read }) => read(count) * 2);
80
111
  * return <div>{doubled}</div>;
81
112
  * }
82
113
  * ```
@@ -87,8 +118,8 @@ import { Atom, Equality } from '../core/types';
87
118
  * const lastName = atom("Doe");
88
119
  *
89
120
  * function FullName() {
90
- * const fullName = useValue(({ get }) =>
91
- * `${get(firstName)} ${get(lastName)}`
121
+ * const fullName = useSelector(({ read }) =>
122
+ * `${read(firstName)} ${read(lastName)}`
92
123
  * );
93
124
  * return <div>{fullName}</div>;
94
125
  * }
@@ -99,7 +130,7 @@ import { Atom, Equality } from '../core/types';
99
130
  * const userAtom = atom(fetchUser());
100
131
  *
101
132
  * function UserProfile() {
102
- * const user = useValue(({ get }) => get(userAtom));
133
+ * const user = useSelector(({ read }) => read(userAtom));
103
134
  * return <div>{user.name}</div>;
104
135
  * }
105
136
  *
@@ -121,7 +152,7 @@ import { Atom, Equality } from '../core/types';
121
152
  * const postsAtom = atom(fetchPosts());
122
153
  *
123
154
  * function Dashboard() {
124
- * const data = useValue(({ all }) => {
155
+ * const data = useSelector(({ all }) => {
125
156
  * const [user, posts] = all(userAtom, postsAtom);
126
157
  * return { user, posts };
127
158
  * });
@@ -130,5 +161,5 @@ import { Atom, Equality } from '../core/types';
130
161
  * }
131
162
  * ```
132
163
  */
133
- export declare function useValue<T>(atom: Atom<T>, equals?: Equality<Awaited<T>>): Awaited<T>;
134
- export declare function useValue<T>(selector: ContextSelectorFn<T>, equals?: Equality<T>): T;
164
+ export declare function useSelector<T>(atom: Atom<T>, equals?: Equality<Awaited<T>>): Awaited<T>;
165
+ export declare function useSelector<T>(selector: ReactiveSelector<T>, equals?: Equality<T>): T;
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,22 @@
1
1
  {
2
2
  "name": "atomirx",
3
- "version": "0.0.1",
3
+ "description": "Opinionated, Batteries-Included Reactive State Management",
4
+ "keywords": [
5
+ "reactive",
6
+ "state",
7
+ "management",
8
+ "atom",
9
+ "derived",
10
+ "effect"
11
+ ],
12
+ "author": "Gignuyen",
13
+ "license": "MIT",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/linq2js/atomirx"
17
+ },
18
+ "homepage": "https://github.com/linq2js/atomirx",
19
+ "version": "0.0.4",
4
20
  "type": "module",
5
21
  "main": "./dist/atomirx.umd.cjs",
6
22
  "module": "./dist/atomirx.js",