atomirx 0.0.2 → 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.
- package/README.md +866 -159
- package/dist/core/atom.d.ts +83 -6
- package/dist/core/batch.d.ts +3 -3
- package/dist/core/derived.d.ts +55 -21
- package/dist/core/effect.d.ts +47 -51
- package/dist/core/getAtomState.d.ts +29 -0
- package/dist/core/promiseCache.d.ts +23 -32
- package/dist/core/select.d.ts +208 -29
- package/dist/core/types.d.ts +55 -19
- package/dist/core/withReady.d.ts +69 -0
- package/dist/index-CqO6BDwj.cjs +1 -0
- package/dist/index-D8RDOTB_.js +1319 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +9 -7
- package/dist/index.js +12 -10
- package/dist/react/index.cjs +10 -10
- package/dist/react/index.d.ts +2 -1
- package/dist/react/index.js +423 -379
- 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 +143 -21
- package/src/core/batch.test.ts +10 -10
- package/src/core/batch.ts +3 -3
- package/src/core/derived.test.ts +727 -72
- package/src/core/derived.ts +141 -73
- package/src/core/effect.test.ts +259 -39
- package/src/core/effect.ts +62 -85
- package/src/core/getAtomState.ts +69 -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 +54 -26
- package/src/core/withReady.test.ts +360 -0
- package/src/core/withReady.ts +127 -0
- package/src/core/withUse.ts +1 -1
- package/src/index.test.ts +4 -4
- package/src/index.ts +11 -6
- 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/withReady.test.d.ts} +0 -0
package/src/index.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
// Core
|
|
2
|
-
export { atom } from "./core/atom";
|
|
2
|
+
export { atom, readonly } from "./core/atom";
|
|
3
3
|
export { batch } from "./core/batch";
|
|
4
4
|
export { define } from "./core/define";
|
|
5
|
-
export { derived } from "./core/derived";
|
|
6
|
-
export { effect } from "./core/effect";
|
|
5
|
+
export { derived, type DerivedContext } from "./core/derived";
|
|
6
|
+
export { effect, type EffectContext } from "./core/effect";
|
|
7
7
|
export { emitter } from "./core/emitter";
|
|
8
8
|
export { isAtom, isDerived } from "./core/isAtom";
|
|
9
9
|
export { select, AllAtomsRejectedError } from "./core/select";
|
|
10
10
|
|
|
11
11
|
// Promise utilities
|
|
12
|
+
export { getAtomState } from "./core/getAtomState";
|
|
12
13
|
export {
|
|
13
|
-
getAtomState,
|
|
14
14
|
isPending,
|
|
15
15
|
isFulfilled,
|
|
16
16
|
isRejected,
|
|
@@ -33,9 +33,11 @@ export type {
|
|
|
33
33
|
Equality,
|
|
34
34
|
EqualityShorthand,
|
|
35
35
|
Getter,
|
|
36
|
+
KeyedResult,
|
|
36
37
|
MutableAtom,
|
|
37
38
|
MutableAtomMeta,
|
|
38
39
|
Pipeable,
|
|
40
|
+
SelectStateResult,
|
|
39
41
|
SettledResult,
|
|
40
42
|
} from "./core/types";
|
|
41
43
|
|
|
@@ -45,7 +47,10 @@ export type { AtomCreateInfo, ModuleCreateInfo } from "./core/onCreateHook";
|
|
|
45
47
|
export type {
|
|
46
48
|
SelectContext,
|
|
47
49
|
SelectResult,
|
|
48
|
-
ContextSelectorFn,
|
|
50
|
+
ReactiveSelector as ContextSelectorFn,
|
|
51
|
+
SafeResult,
|
|
49
52
|
} from "./core/select";
|
|
50
53
|
|
|
51
|
-
export type { PromiseState } from "./core/promiseCache";
|
|
54
|
+
export type { PromiseState, CombinedPromiseMeta } from "./core/promiseCache";
|
|
55
|
+
|
|
56
|
+
export { promisesEqual } from "./core/promiseCache";
|
package/src/react/index.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { useSelector } from "./useSelector";
|
|
2
2
|
export { useStable } from "./useStable";
|
|
3
3
|
export type { UseStableResult } from "./useStable";
|
|
4
4
|
export { useAction } from "./useAction";
|
|
5
5
|
export { rx } from "./rx";
|
|
6
|
+
export type { RxOptions } from "./rx";
|
|
6
7
|
|
|
7
8
|
export type {
|
|
8
9
|
ActionState,
|
package/src/react/rx.test.tsx
CHANGED
|
@@ -24,7 +24,9 @@ describe.each(wrappers)("rx - $mode", ({ render }) => {
|
|
|
24
24
|
it("should render derived value with context selector", () => {
|
|
25
25
|
const count = atom(5);
|
|
26
26
|
|
|
27
|
-
render(
|
|
27
|
+
render(
|
|
28
|
+
<div data-testid="result">{rx(({ read }) => read(count) * 2)}</div>
|
|
29
|
+
);
|
|
28
30
|
|
|
29
31
|
expect(screen.getByTestId("result").textContent).toBe("10");
|
|
30
32
|
});
|
|
@@ -61,7 +63,7 @@ describe.each(wrappers)("rx - $mode", ({ render }) => {
|
|
|
61
63
|
|
|
62
64
|
render(
|
|
63
65
|
<div data-testid="result">
|
|
64
|
-
{rx(({
|
|
66
|
+
{rx(({ read }) => `${read(firstName)} ${read(lastName)}`)}
|
|
65
67
|
</div>
|
|
66
68
|
);
|
|
67
69
|
|
|
@@ -75,7 +77,7 @@ describe.each(wrappers)("rx - $mode", ({ render }) => {
|
|
|
75
77
|
|
|
76
78
|
render(
|
|
77
79
|
<div data-testid="result">
|
|
78
|
-
{rx(({
|
|
80
|
+
{rx(({ read }) => read(a) + read(b) + read(c))}
|
|
79
81
|
</div>
|
|
80
82
|
);
|
|
81
83
|
|
|
@@ -101,7 +103,9 @@ describe.each(wrappers)("rx - $mode", ({ render }) => {
|
|
|
101
103
|
it("should update when source atom changes (context selector)", () => {
|
|
102
104
|
const count = atom(5);
|
|
103
105
|
|
|
104
|
-
render(
|
|
106
|
+
render(
|
|
107
|
+
<div data-testid="result">{rx(({ read }) => read(count) * 2)}</div>
|
|
108
|
+
);
|
|
105
109
|
|
|
106
110
|
expect(screen.getByTestId("result").textContent).toBe("10");
|
|
107
111
|
|
|
@@ -117,7 +121,7 @@ describe.each(wrappers)("rx - $mode", ({ render }) => {
|
|
|
117
121
|
const b = atom(2);
|
|
118
122
|
|
|
119
123
|
render(
|
|
120
|
-
<div data-testid="result">{rx(({
|
|
124
|
+
<div data-testid="result">{rx(({ read }) => read(a) + read(b))}</div>
|
|
121
125
|
);
|
|
122
126
|
|
|
123
127
|
expect(screen.getByTestId("result").textContent).toBe("3");
|
|
@@ -142,8 +146,8 @@ describe.each(wrappers)("rx - $mode", ({ render }) => {
|
|
|
142
146
|
const a = atom(1);
|
|
143
147
|
const b = atom(2);
|
|
144
148
|
|
|
145
|
-
const selectorFn = vi.fn(({
|
|
146
|
-
|
|
149
|
+
const selectorFn = vi.fn(({ read }: SelectContext) =>
|
|
150
|
+
read(flag) ? read(a) : read(b)
|
|
147
151
|
);
|
|
148
152
|
|
|
149
153
|
render(<div data-testid="result">{rx(selectorFn)}</div>);
|
|
@@ -168,7 +172,7 @@ describe.each(wrappers)("rx - $mode", ({ render }) => {
|
|
|
168
172
|
|
|
169
173
|
render(
|
|
170
174
|
<div data-testid="result">
|
|
171
|
-
{rx(({
|
|
175
|
+
{rx(({ read }) => (read(flag) ? read(a) : read(b)))}
|
|
172
176
|
</div>
|
|
173
177
|
);
|
|
174
178
|
|
|
@@ -199,7 +203,7 @@ describe.each(wrappers)("rx - $mode", ({ render }) => {
|
|
|
199
203
|
renderCount.current++;
|
|
200
204
|
return (
|
|
201
205
|
<div data-testid="result">
|
|
202
|
-
{rx(({
|
|
206
|
+
{rx(({ read }) => JSON.stringify(read(user)))}
|
|
203
207
|
</div>
|
|
204
208
|
);
|
|
205
209
|
};
|
|
@@ -223,9 +227,9 @@ describe.each(wrappers)("rx - $mode", ({ render }) => {
|
|
|
223
227
|
const user = atom({ name: "John", age: 30 });
|
|
224
228
|
const selectorCallCount = { current: 0 };
|
|
225
229
|
|
|
226
|
-
const selector = ({
|
|
230
|
+
const selector = ({ read }: SelectContext) => {
|
|
227
231
|
selectorCallCount.current++;
|
|
228
|
-
return JSON.stringify(
|
|
232
|
+
return JSON.stringify(read(user));
|
|
229
233
|
};
|
|
230
234
|
|
|
231
235
|
render(<div data-testid="result">{rx(selector, "strict")}</div>);
|
|
@@ -254,7 +258,7 @@ describe.each(wrappers)("rx - $mode", ({ render }) => {
|
|
|
254
258
|
return (
|
|
255
259
|
<div data-testid="result">
|
|
256
260
|
{rx(
|
|
257
|
-
({
|
|
261
|
+
({ read }) => JSON.stringify(read(user)),
|
|
258
262
|
(a, b) => a === b // Compare stringified values
|
|
259
263
|
)}
|
|
260
264
|
</div>
|
|
@@ -285,7 +289,7 @@ describe.each(wrappers)("rx - $mode", ({ render }) => {
|
|
|
285
289
|
const Parent = () => {
|
|
286
290
|
parentRenderCount.current++;
|
|
287
291
|
return (
|
|
288
|
-
<div data-testid="result">{rx(({
|
|
292
|
+
<div data-testid="result">{rx(({ read }) => read(count) * 2)}</div>
|
|
289
293
|
);
|
|
290
294
|
};
|
|
291
295
|
|
|
@@ -317,7 +321,7 @@ describe.each(wrappers)("rx - $mode", ({ render }) => {
|
|
|
317
321
|
const flag = atom(false);
|
|
318
322
|
|
|
319
323
|
render(
|
|
320
|
-
<div data-testid="result">{rx(({
|
|
324
|
+
<div data-testid="result">{rx(({ read }) => String(read(flag)))}</div>
|
|
321
325
|
);
|
|
322
326
|
|
|
323
327
|
expect(screen.getByTestId("result").textContent).toBe("false");
|
|
@@ -352,9 +356,9 @@ describe.each(wrappers)("rx - $mode", ({ render }) => {
|
|
|
352
356
|
|
|
353
357
|
render(
|
|
354
358
|
<div data-testid="result">
|
|
355
|
-
{rx(({
|
|
359
|
+
{rx(({ read }) => {
|
|
356
360
|
try {
|
|
357
|
-
return
|
|
361
|
+
return read(asyncAtom) * 2;
|
|
358
362
|
} catch {
|
|
359
363
|
return "loading";
|
|
360
364
|
}
|
|
@@ -372,7 +376,7 @@ describe.each(wrappers)("rx - $mode", ({ render }) => {
|
|
|
372
376
|
const sourceAtom = atom(5);
|
|
373
377
|
|
|
374
378
|
render(
|
|
375
|
-
<div data-testid="result">{rx(({
|
|
379
|
+
<div data-testid="result">{rx(({ read }) => read(sourceAtom) * 2)}</div>
|
|
376
380
|
);
|
|
377
381
|
|
|
378
382
|
expect(screen.getByTestId("result").textContent).toBe("10");
|
|
@@ -396,7 +400,7 @@ describe.each(wrappers)("rx - $mode", ({ render }) => {
|
|
|
396
400
|
render(
|
|
397
401
|
<div data-testid="result">
|
|
398
402
|
{rx(({ all }) => {
|
|
399
|
-
const [valA, valB] = all(a, b);
|
|
403
|
+
const [valA, valB] = all([a, b]);
|
|
400
404
|
return valA + valB;
|
|
401
405
|
})}
|
|
402
406
|
</div>
|
|
@@ -413,4 +417,155 @@ describe.each(wrappers)("rx - $mode", ({ render }) => {
|
|
|
413
417
|
expect(screen.getByTestId("result").textContent).toBe("12");
|
|
414
418
|
});
|
|
415
419
|
});
|
|
420
|
+
|
|
421
|
+
describe("loading/error options", () => {
|
|
422
|
+
it("should render loading fallback when atom is pending", () => {
|
|
423
|
+
const asyncAtom = atom(new Promise<string>(() => {}));
|
|
424
|
+
|
|
425
|
+
render(
|
|
426
|
+
<div data-testid="result">
|
|
427
|
+
{rx(asyncAtom, { loading: () => <span>Loading...</span> })}
|
|
428
|
+
</div>
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
expect(screen.getByTestId("result").textContent).toBe("Loading...");
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
it("should render error fallback when atom has error", async () => {
|
|
435
|
+
const error = new Error("Test error");
|
|
436
|
+
const rejectedPromise = Promise.reject(error);
|
|
437
|
+
rejectedPromise.catch(() => {}); // Prevent unhandled rejection
|
|
438
|
+
const asyncAtom = atom(rejectedPromise);
|
|
439
|
+
|
|
440
|
+
// Wait for promise to be tracked
|
|
441
|
+
await act(async () => {
|
|
442
|
+
await Promise.resolve();
|
|
443
|
+
await Promise.resolve();
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
render(
|
|
447
|
+
<div data-testid="result">
|
|
448
|
+
{rx(asyncAtom, {
|
|
449
|
+
error: ({ error: e }) => <span>Error: {(e as Error).message}</span>,
|
|
450
|
+
})}
|
|
451
|
+
</div>
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
expect(screen.getByTestId("result").textContent).toBe(
|
|
455
|
+
"Error: Test error"
|
|
456
|
+
);
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it("should render value when atom resolves with loading option", async () => {
|
|
460
|
+
let resolve: (value: string) => void;
|
|
461
|
+
const promise = new Promise<string>((r) => {
|
|
462
|
+
resolve = r;
|
|
463
|
+
});
|
|
464
|
+
const asyncAtom = atom(promise);
|
|
465
|
+
|
|
466
|
+
const { rerender } = render(
|
|
467
|
+
<div data-testid="result">
|
|
468
|
+
{rx(asyncAtom, {
|
|
469
|
+
loading: () => <span>Loading...</span>,
|
|
470
|
+
})}
|
|
471
|
+
</div>
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
expect(screen.getByTestId("result").textContent).toBe("Loading...");
|
|
475
|
+
|
|
476
|
+
await act(async () => {
|
|
477
|
+
resolve!("Hello");
|
|
478
|
+
await Promise.resolve();
|
|
479
|
+
await Promise.resolve();
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
rerender(
|
|
483
|
+
<div data-testid="result">
|
|
484
|
+
{rx(asyncAtom, {
|
|
485
|
+
loading: () => <span>Loading...</span>,
|
|
486
|
+
})}
|
|
487
|
+
</div>
|
|
488
|
+
);
|
|
489
|
+
|
|
490
|
+
expect(screen.getByTestId("result").textContent).toBe("Hello");
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
it("should work with selector function and loading option", () => {
|
|
494
|
+
const asyncAtom = atom(new Promise<number>(() => {}));
|
|
495
|
+
|
|
496
|
+
render(
|
|
497
|
+
<div data-testid="result">
|
|
498
|
+
{rx(({ read }) => read(asyncAtom) * 2, {
|
|
499
|
+
loading: () => <span>Computing...</span>,
|
|
500
|
+
})}
|
|
501
|
+
</div>
|
|
502
|
+
);
|
|
503
|
+
|
|
504
|
+
expect(screen.getByTestId("result").textContent).toBe("Computing...");
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
it("should support both loading and error options", async () => {
|
|
508
|
+
const error = new Error("Failed");
|
|
509
|
+
const rejectedPromise = Promise.reject(error);
|
|
510
|
+
rejectedPromise.catch(() => {});
|
|
511
|
+
const asyncAtom = atom(rejectedPromise);
|
|
512
|
+
|
|
513
|
+
await act(async () => {
|
|
514
|
+
await Promise.resolve();
|
|
515
|
+
await Promise.resolve();
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
render(
|
|
519
|
+
<div data-testid="result">
|
|
520
|
+
{rx(asyncAtom, {
|
|
521
|
+
loading: () => <span>Loading...</span>,
|
|
522
|
+
error: ({ error: e }) => (
|
|
523
|
+
<span>Failed: {(e as Error).message}</span>
|
|
524
|
+
),
|
|
525
|
+
})}
|
|
526
|
+
</div>
|
|
527
|
+
);
|
|
528
|
+
|
|
529
|
+
expect(screen.getByTestId("result").textContent).toBe("Failed: Failed");
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
it("should pass equality in options object", () => {
|
|
533
|
+
const user = atom({ id: 1, name: "John" });
|
|
534
|
+
const renderSpy = vi.fn();
|
|
535
|
+
|
|
536
|
+
function TestComponent() {
|
|
537
|
+
renderSpy();
|
|
538
|
+
return (
|
|
539
|
+
<div data-testid="result">
|
|
540
|
+
{rx(({ read }) => read(user).name, {
|
|
541
|
+
equals: (a, b) => a === b,
|
|
542
|
+
})}
|
|
543
|
+
</div>
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
render(<TestComponent />);
|
|
548
|
+
expect(screen.getByTestId("result").textContent).toBe("John");
|
|
549
|
+
|
|
550
|
+
// Update with same name
|
|
551
|
+
act(() => {
|
|
552
|
+
user.set({ id: 2, name: "John" });
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
// Name didn't change, so rx content should be same
|
|
556
|
+
expect(screen.getByTestId("result").textContent).toBe("John");
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
it("should still work with legacy equality parameter", () => {
|
|
560
|
+
const count = atom(5);
|
|
561
|
+
|
|
562
|
+
render(
|
|
563
|
+
<div data-testid="result">
|
|
564
|
+
{rx(({ read }) => read(count) * 2, "strict")}
|
|
565
|
+
</div>
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
expect(screen.getByTestId("result").textContent).toBe("10");
|
|
569
|
+
});
|
|
570
|
+
});
|
|
416
571
|
});
|