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
@@ -6,7 +6,7 @@ describe("atom", () => {
6
6
  describe("basic functionality", () => {
7
7
  it("should create an atom with initial value", () => {
8
8
  const count$ = atom(0);
9
- expect(count$.value).toBe(0);
9
+ expect(count$.get()).toBe(0);
10
10
  });
11
11
 
12
12
  it("should have SYMBOL_ATOM marker", () => {
@@ -17,51 +17,51 @@ describe("atom", () => {
17
17
  it("should set value directly", () => {
18
18
  const count$ = atom(0);
19
19
  count$.set(5);
20
- expect(count$.value).toBe(5);
20
+ expect(count$.get()).toBe(5);
21
21
  });
22
22
 
23
23
  it("should set value with reducer", () => {
24
24
  const count$ = atom(0);
25
25
  count$.set((prev) => prev + 1);
26
- expect(count$.value).toBe(1);
26
+ expect(count$.get()).toBe(1);
27
27
  count$.set((prev) => prev * 2);
28
- expect(count$.value).toBe(2);
28
+ expect(count$.get()).toBe(2);
29
29
  });
30
30
 
31
31
  it("should reset to initial value", () => {
32
32
  const count$ = atom(10);
33
33
  count$.set(42);
34
- expect(count$.value).toBe(42);
34
+ expect(count$.get()).toBe(42);
35
35
  count$.reset();
36
- expect(count$.value).toBe(10);
36
+ expect(count$.get()).toBe(10);
37
37
  });
38
38
 
39
39
  it("should store objects", () => {
40
40
  const user$ = atom({ name: "John", age: 30 });
41
- expect(user$.value).toEqual({ name: "John", age: 30 });
41
+ expect(user$.get()).toEqual({ name: "John", age: 30 });
42
42
  user$.set({ name: "Jane", age: 25 });
43
- expect(user$.value).toEqual({ name: "Jane", age: 25 });
43
+ expect(user$.get()).toEqual({ name: "Jane", age: 25 });
44
44
  });
45
45
 
46
46
  it("should store null and undefined", () => {
47
47
  const nullable$ = atom<string | null>(null);
48
- expect(nullable$.value).toBe(null);
48
+ expect(nullable$.get()).toBe(null);
49
49
  nullable$.set("hello");
50
- expect(nullable$.value).toBe("hello");
50
+ expect(nullable$.get()).toBe("hello");
51
51
  nullable$.set(null);
52
- expect(nullable$.value).toBe(null);
52
+ expect(nullable$.get()).toBe(null);
53
53
 
54
54
  const undef$ = atom<string | undefined>(undefined);
55
- expect(undef$.value).toBe(undefined);
55
+ expect(undef$.get()).toBe(undefined);
56
56
  undef$.set("world");
57
- expect(undef$.value).toBe("world");
57
+ expect(undef$.get()).toBe("world");
58
58
  });
59
59
 
60
60
  it("should store arrays", () => {
61
61
  const items$ = atom<number[]>([1, 2, 3]);
62
- expect(items$.value).toEqual([1, 2, 3]);
62
+ expect(items$.get()).toEqual([1, 2, 3]);
63
63
  items$.set((prev) => [...prev, 4]);
64
- expect(items$.value).toEqual([1, 2, 3, 4]);
64
+ expect(items$.get()).toEqual([1, 2, 3, 4]);
65
65
  });
66
66
  });
67
67
 
@@ -223,25 +223,25 @@ describe("atom", () => {
223
223
  it("should store Promise as-is", () => {
224
224
  const promise = Promise.resolve(42);
225
225
  const data$ = atom(promise);
226
- expect(data$.value).toBe(promise);
226
+ expect(data$.get()).toBe(promise);
227
227
  });
228
228
 
229
229
  it("should store a new Promise on set", () => {
230
230
  const promise1 = Promise.resolve(1);
231
231
  const promise2 = Promise.resolve(2);
232
232
  const data$ = atom(promise1);
233
- expect(data$.value).toBe(promise1);
233
+ expect(data$.get()).toBe(promise1);
234
234
  data$.set(promise2);
235
- expect(data$.value).toBe(promise2);
235
+ expect(data$.get()).toBe(promise2);
236
236
  });
237
237
 
238
238
  it("should reset to original Promise object", () => {
239
239
  const originalPromise = Promise.resolve(42);
240
240
  const data$ = atom(originalPromise);
241
241
  data$.set(Promise.resolve(100));
242
- expect(data$.value).not.toBe(originalPromise);
242
+ expect(data$.get()).not.toBe(originalPromise);
243
243
  data$.reset();
244
- expect(data$.value).toBe(originalPromise);
244
+ expect(data$.get()).toBe(originalPromise);
245
245
  });
246
246
  });
247
247
 
@@ -255,7 +255,7 @@ describe("atom", () => {
255
255
  });
256
256
  }).toThrow(error);
257
257
  // Value should remain unchanged
258
- expect(count$.value).toBe(0);
258
+ expect(count$.get()).toBe(0);
259
259
  });
260
260
  });
261
261
 
@@ -264,7 +264,7 @@ describe("atom", () => {
264
264
  const initializer = vi.fn(() => 42);
265
265
  const count$ = atom(initializer);
266
266
  expect(initializer).toHaveBeenCalledTimes(1);
267
- expect(count$.value).toBe(42);
267
+ expect(count$.get()).toBe(42);
268
268
  });
269
269
 
270
270
  it("should only call initializer once", () => {
@@ -272,8 +272,8 @@ describe("atom", () => {
272
272
  const obj$ = atom(initializer);
273
273
  expect(initializer).toHaveBeenCalledTimes(1);
274
274
  // Access value multiple times
275
- obj$.value;
276
- obj$.value;
275
+ obj$.get();
276
+ obj$.get();
277
277
  expect(initializer).toHaveBeenCalledTimes(1);
278
278
  });
279
279
 
@@ -284,33 +284,33 @@ describe("atom", () => {
284
284
  return callCount; // Returns different value each call
285
285
  };
286
286
  const count$ = atom(initializer);
287
- expect(count$.value).toBe(1); // First call returns 1
287
+ expect(count$.get()).toBe(1); // First call returns 1
288
288
  expect(callCount).toBe(1);
289
289
 
290
290
  count$.set(100);
291
- expect(count$.value).toBe(100);
291
+ expect(count$.get()).toBe(100);
292
292
 
293
293
  count$.reset();
294
- expect(count$.value).toBe(2); // Re-runs initializer, gets fresh value
294
+ expect(count$.get()).toBe(2); // Re-runs initializer, gets fresh value
295
295
  expect(callCount).toBe(2); // Initializer called again
296
296
  });
297
297
 
298
298
  it("should work with lazy initializer returning object", () => {
299
299
  const obj$ = atom(() => ({ count: 0, items: [] as number[] }));
300
- expect(obj$.value).toEqual({ count: 0, items: [] });
300
+ expect(obj$.get()).toEqual({ count: 0, items: [] });
301
301
  obj$.set((prev) => ({ ...prev, count: 1 }));
302
- expect(obj$.value).toEqual({ count: 1, items: [] });
302
+ expect(obj$.get()).toEqual({ count: 1, items: [] });
303
303
  });
304
304
 
305
305
  it("should work with lazy initializer returning Promise", () => {
306
306
  const promise = Promise.resolve(42);
307
307
  const data$ = atom(() => promise);
308
- expect(data$.value).toBe(promise);
308
+ expect(data$.get()).toBe(promise);
309
309
  });
310
310
 
311
311
  it("should still work with direct value (non-function)", () => {
312
312
  const count$ = atom(10);
313
- expect(count$.value).toBe(10);
313
+ expect(count$.get()).toBe(10);
314
314
  });
315
315
  });
316
316
 
@@ -326,44 +326,308 @@ describe("atom", () => {
326
326
  });
327
327
  });
328
328
 
329
+ describe("AtomContext (init function with context)", () => {
330
+ describe("signal", () => {
331
+ it("should pass context with signal to init function", () => {
332
+ let receivedSignal: AbortSignal | undefined;
333
+ atom((ctx) => {
334
+ receivedSignal = ctx.signal;
335
+ return 42;
336
+ });
337
+ expect(receivedSignal).toBeInstanceOf(AbortSignal);
338
+ expect(receivedSignal!.aborted).toBe(false);
339
+ });
340
+
341
+ it("should abort signal when set() is called", () => {
342
+ let receivedSignal: AbortSignal | undefined;
343
+ const count$ = atom((ctx) => {
344
+ receivedSignal = ctx.signal;
345
+ return 0;
346
+ });
347
+ expect(receivedSignal!.aborted).toBe(false);
348
+
349
+ count$.set(1);
350
+ expect(receivedSignal!.aborted).toBe(true);
351
+ });
352
+
353
+ it("should abort signal when reset() is called", () => {
354
+ const signals: AbortSignal[] = [];
355
+ const count$ = atom((ctx) => {
356
+ signals.push(ctx.signal);
357
+ return 0;
358
+ });
359
+ expect(signals[0].aborted).toBe(false);
360
+
361
+ count$.reset();
362
+ // Original signal should be aborted
363
+ expect(signals[0].aborted).toBe(true);
364
+ });
365
+
366
+ it("should provide fresh signal on reset", () => {
367
+ const signals: AbortSignal[] = [];
368
+ const count$ = atom((ctx) => {
369
+ signals.push(ctx.signal);
370
+ return signals.length;
371
+ });
372
+
373
+ expect(signals).toHaveLength(1);
374
+ expect(signals[0].aborted).toBe(false);
375
+
376
+ count$.reset();
377
+ expect(signals).toHaveLength(2);
378
+ expect(signals[0].aborted).toBe(true);
379
+ expect(signals[1].aborted).toBe(false);
380
+ });
381
+
382
+ it("should not create new signal on subsequent set() calls", () => {
383
+ let signalRef: AbortSignal | undefined;
384
+ const count$ = atom((ctx) => {
385
+ signalRef = ctx.signal;
386
+ return 0;
387
+ });
388
+
389
+ const firstSignal = signalRef;
390
+ count$.set(1);
391
+ expect(firstSignal!.aborted).toBe(true);
392
+
393
+ // After set, the signal is already aborted, no new context is created
394
+ // because set() doesn't re-run the initializer
395
+ count$.set(2);
396
+ expect(firstSignal!.aborted).toBe(true);
397
+ });
398
+
399
+ it("should abort with reason", () => {
400
+ let receivedSignal: AbortSignal | undefined;
401
+ const count$ = atom((ctx) => {
402
+ receivedSignal = ctx.signal;
403
+ return 0;
404
+ });
405
+
406
+ count$.set(1);
407
+ expect(receivedSignal!.aborted).toBe(true);
408
+ expect(receivedSignal!.reason).toBe("value changed");
409
+ });
410
+
411
+ it("should abort with 'reset' reason on reset", () => {
412
+ const signals: AbortSignal[] = [];
413
+ const count$ = atom((ctx) => {
414
+ signals.push(ctx.signal);
415
+ return 0;
416
+ });
417
+
418
+ count$.reset();
419
+ // Original signal should be aborted with 'reset' reason
420
+ expect(signals[0].aborted).toBe(true);
421
+ expect(signals[0].reason).toBe("reset");
422
+ });
423
+ });
424
+
425
+ describe("onCleanup", () => {
426
+ it("should call cleanup when set() is called", () => {
427
+ const cleanup = vi.fn();
428
+ const count$ = atom((ctx) => {
429
+ ctx.onCleanup(cleanup);
430
+ return 0;
431
+ });
432
+
433
+ expect(cleanup).not.toHaveBeenCalled();
434
+ count$.set(1);
435
+ expect(cleanup).toHaveBeenCalledTimes(1);
436
+ });
437
+
438
+ it("should call cleanup when reset() is called", () => {
439
+ const cleanup = vi.fn();
440
+ const count$ = atom((ctx) => {
441
+ ctx.onCleanup(cleanup);
442
+ return 0;
443
+ });
444
+
445
+ expect(cleanup).not.toHaveBeenCalled();
446
+ count$.reset();
447
+ expect(cleanup).toHaveBeenCalledTimes(1);
448
+ });
449
+
450
+ it("should call multiple cleanups in FIFO order", () => {
451
+ const order: number[] = [];
452
+ const count$ = atom((ctx) => {
453
+ ctx.onCleanup(() => order.push(1));
454
+ ctx.onCleanup(() => order.push(2));
455
+ ctx.onCleanup(() => order.push(3));
456
+ return 0;
457
+ });
458
+
459
+ count$.set(1);
460
+ expect(order).toEqual([1, 2, 3]);
461
+ });
462
+
463
+ it("should clear cleanups after calling them", () => {
464
+ const cleanup = vi.fn();
465
+ const count$ = atom((ctx) => {
466
+ ctx.onCleanup(cleanup);
467
+ return 0;
468
+ });
469
+
470
+ count$.set(1);
471
+ expect(cleanup).toHaveBeenCalledTimes(1);
472
+
473
+ // Second set should not call cleanup again (cleanup was from init, not from set)
474
+ count$.set(2);
475
+ expect(cleanup).toHaveBeenCalledTimes(1);
476
+ });
477
+
478
+ it("should call cleanup from reset, then register new cleanups on next reset", () => {
479
+ let cleanupCallCount = 0;
480
+ const count$ = atom((ctx) => {
481
+ ctx.onCleanup(() => cleanupCallCount++);
482
+ return cleanupCallCount;
483
+ });
484
+
485
+ expect(cleanupCallCount).toBe(0);
486
+
487
+ count$.reset();
488
+ expect(cleanupCallCount).toBe(1); // First cleanup called
489
+
490
+ count$.reset();
491
+ expect(cleanupCallCount).toBe(2); // Second cleanup called (registered in previous reset)
492
+ });
493
+
494
+ it("should work with async operations that check signal", async () => {
495
+ let cleanupCalled = false;
496
+ let operationAborted = false;
497
+
498
+ const data$ = atom((ctx) => {
499
+ ctx.onCleanup(() => {
500
+ cleanupCalled = true;
501
+ });
502
+
503
+ // Simulate an async operation that respects the signal
504
+ const timeoutId = setTimeout(() => {
505
+ if (!ctx.signal.aborted) {
506
+ // Would do something here
507
+ } else {
508
+ operationAborted = true;
509
+ }
510
+ }, 100);
511
+
512
+ ctx.onCleanup(() => clearTimeout(timeoutId));
513
+
514
+ return "initial";
515
+ });
516
+
517
+ // Trigger abort
518
+ data$.set("changed");
519
+
520
+ expect(cleanupCalled).toBe(true);
521
+
522
+ // Wait for the timeout to potentially fire
523
+ await new Promise((r) => setTimeout(r, 150));
524
+
525
+ // The operation should have been cleaned up/aborted
526
+ expect(operationAborted).toBe(false); // timeout was cleared by cleanup
527
+ });
528
+ });
529
+
530
+ describe("combined signal and cleanup", () => {
531
+ it("should abort signal and call cleanup on set", () => {
532
+ let signal: AbortSignal | undefined;
533
+ const cleanup = vi.fn();
534
+
535
+ const count$ = atom((ctx) => {
536
+ signal = ctx.signal;
537
+ ctx.onCleanup(cleanup);
538
+ return 0;
539
+ });
540
+
541
+ expect(signal!.aborted).toBe(false);
542
+ expect(cleanup).not.toHaveBeenCalled();
543
+
544
+ count$.set(1);
545
+
546
+ expect(signal!.aborted).toBe(true);
547
+ expect(cleanup).toHaveBeenCalledTimes(1);
548
+ });
549
+
550
+ it("should abort signal and call cleanup on reset, then provide fresh context", () => {
551
+ const signals: AbortSignal[] = [];
552
+ const cleanups: number[] = [];
553
+ let callCount = 0;
554
+
555
+ const count$ = atom((ctx) => {
556
+ callCount++;
557
+ signals.push(ctx.signal);
558
+ ctx.onCleanup(() => cleanups.push(callCount));
559
+ return callCount;
560
+ });
561
+
562
+ expect(signals).toHaveLength(1);
563
+ expect(cleanups).toEqual([]);
564
+
565
+ count$.reset();
566
+
567
+ expect(signals).toHaveLength(2);
568
+ expect(signals[0].aborted).toBe(true);
569
+ expect(signals[1].aborted).toBe(false);
570
+ expect(cleanups).toEqual([1]); // cleanup from first init was called
571
+ });
572
+ });
573
+
574
+ describe("non-function initializer (no context)", () => {
575
+ it("should work normally with direct value", () => {
576
+ const count$ = atom(42);
577
+ expect(count$.get()).toBe(42);
578
+
579
+ count$.set(100);
580
+ expect(count$.get()).toBe(100);
581
+
582
+ count$.reset();
583
+ expect(count$.get()).toBe(42);
584
+ });
585
+
586
+ it("should not have cleanup issues with direct value", () => {
587
+ const count$ = atom(0);
588
+ // These should not throw
589
+ count$.set(1);
590
+ count$.set(2);
591
+ count$.reset();
592
+ count$.reset();
593
+ });
594
+ });
595
+ });
596
+
329
597
  describe("plugin system (use)", () => {
330
598
  it("should support .use() for extensions", () => {
331
599
  // Note: Don't use ...source spread as it copies values, not getters
332
600
  // Instead, access source properties through the reference
333
601
  const base$ = atom(0);
334
602
  const count$ = base$.use((source) => ({
335
- get value() {
336
- return source.value;
337
- },
603
+ get: source.get,
338
604
  set: source.set,
339
605
  reset: source.reset,
340
606
  on: source.on,
341
607
  increment: () => source.set((v) => v + 1),
342
608
  }));
343
609
 
344
- expect(count$.value).toBe(0);
610
+ expect(count$.get()).toBe(0);
345
611
  count$.increment();
346
- expect(count$.value).toBe(1);
612
+ expect(count$.get()).toBe(1);
347
613
  });
348
614
 
349
615
  it("should support .use() with source reference pattern", () => {
350
616
  // Simpler pattern: just reference the original atom
351
617
  const base$ = atom(0);
352
618
  const enhanced$ = base$.use(() => ({
353
- get value() {
354
- return base$.value;
355
- },
619
+ get: () => base$.get(),
356
620
  increment: () => base$.set((v) => v + 1),
357
621
  decrement: () => base$.set((v) => v - 1),
358
622
  }));
359
623
 
360
- expect(enhanced$.value).toBe(0);
624
+ expect(enhanced$.get()).toBe(0);
361
625
  enhanced$.increment();
362
- expect(enhanced$.value).toBe(1);
626
+ expect(enhanced$.get()).toBe(1);
363
627
  enhanced$.increment();
364
- expect(enhanced$.value).toBe(2);
628
+ expect(enhanced$.get()).toBe(2);
365
629
  enhanced$.decrement();
366
- expect(enhanced$.value).toBe(1);
630
+ expect(enhanced$.get()).toBe(1);
367
631
  });
368
632
  });
369
633
  });