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.
Files changed (53) hide show
  1. package/README.md +866 -159
  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 +1 -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
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";
@@ -1,8 +1,9 @@
1
- export { useValue } from "./useValue";
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,
@@ -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(<div data-testid="result">{rx(({ get }) => get(count) * 2)}</div>);
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(({ get }) => `${get(firstName)} ${get(lastName)}`)}
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(({ get }) => get(a) + get(b) + get(c))}
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(<div data-testid="result">{rx(({ get }) => get(count) * 2)}</div>);
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(({ get }) => get(a) + get(b))}</div>
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(({ get }: SelectContext) =>
146
- get(flag) ? get(a) : get(b)
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(({ get }) => (get(flag) ? get(a) : get(b)))}
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(({ get }) => JSON.stringify(get(user)))}
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 = ({ get }: SelectContext) => {
230
+ const selector = ({ read }: SelectContext) => {
227
231
  selectorCallCount.current++;
228
- return JSON.stringify(get(user));
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
- ({ get }) => JSON.stringify(get(user)),
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(({ get }) => get(count) * 2)}</div>
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(({ get }) => String(get(flag)))}</div>
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(({ get }) => {
359
+ {rx(({ read }) => {
356
360
  try {
357
- return get(asyncAtom) * 2;
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(({ get }) => get(sourceAtom) * 2)}</div>
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
  });