@zeix/cause-effect 0.16.1 → 0.17.1

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 (66) hide show
  1. package/.ai-context.md +85 -21
  2. package/.cursorrules +11 -5
  3. package/.github/copilot-instructions.md +64 -13
  4. package/CLAUDE.md +143 -163
  5. package/LICENSE +1 -1
  6. package/README.md +248 -333
  7. package/archive/benchmark.ts +688 -0
  8. package/archive/collection.ts +312 -0
  9. package/{src → archive}/computed.ts +21 -21
  10. package/archive/list.ts +551 -0
  11. package/archive/memo.ts +139 -0
  12. package/{src → archive}/state.ts +13 -11
  13. package/archive/store.ts +368 -0
  14. package/archive/task.ts +194 -0
  15. package/eslint.config.js +1 -0
  16. package/index.dev.js +938 -509
  17. package/index.js +1 -1
  18. package/index.ts +50 -23
  19. package/package.json +1 -1
  20. package/src/classes/collection.ts +282 -0
  21. package/src/classes/composite.ts +176 -0
  22. package/src/classes/computed.ts +333 -0
  23. package/src/classes/list.ts +305 -0
  24. package/src/classes/ref.ts +68 -0
  25. package/src/classes/state.ts +98 -0
  26. package/src/classes/store.ts +210 -0
  27. package/src/diff.ts +26 -53
  28. package/src/effect.ts +9 -9
  29. package/src/errors.ts +71 -25
  30. package/src/match.ts +5 -12
  31. package/src/resolve.ts +3 -2
  32. package/src/signal.ts +58 -41
  33. package/src/system.ts +79 -42
  34. package/src/util.ts +16 -34
  35. package/test/batch.test.ts +15 -17
  36. package/test/benchmark.test.ts +4 -4
  37. package/test/collection.test.ts +853 -0
  38. package/test/computed.test.ts +138 -130
  39. package/test/diff.test.ts +2 -2
  40. package/test/effect.test.ts +36 -35
  41. package/test/list.test.ts +754 -0
  42. package/test/match.test.ts +25 -25
  43. package/test/ref.test.ts +227 -0
  44. package/test/resolve.test.ts +17 -19
  45. package/test/signal.test.ts +70 -119
  46. package/test/state.test.ts +44 -44
  47. package/test/store.test.ts +253 -929
  48. package/types/index.d.ts +12 -9
  49. package/types/src/classes/collection.d.ts +46 -0
  50. package/types/src/classes/composite.d.ts +15 -0
  51. package/types/src/classes/computed.d.ts +97 -0
  52. package/types/src/classes/list.d.ts +41 -0
  53. package/types/src/classes/ref.d.ts +39 -0
  54. package/types/src/classes/state.d.ts +52 -0
  55. package/types/src/classes/store.d.ts +51 -0
  56. package/types/src/diff.d.ts +8 -12
  57. package/types/src/errors.d.ts +17 -11
  58. package/types/src/signal.d.ts +27 -14
  59. package/types/src/system.d.ts +41 -20
  60. package/types/src/util.d.ts +6 -4
  61. package/src/store.ts +0 -474
  62. package/types/src/collection.d.ts +0 -26
  63. package/types/src/computed.d.ts +0 -33
  64. package/types/src/scheduler.d.ts +0 -55
  65. package/types/src/state.d.ts +0 -24
  66. package/types/src/store.d.ts +0 -65
@@ -1,13 +1,14 @@
1
1
  import { describe, expect, mock, test } from 'bun:test'
2
2
  import {
3
- createComputed,
4
3
  createEffect,
5
- createState,
6
4
  isAbortError,
5
+ Memo,
7
6
  match,
8
7
  resolve,
8
+ State,
9
+ Task,
9
10
  UNSET,
10
- } from '..'
11
+ } from '../index.ts'
11
12
 
12
13
  /* === Utility Functions === */
13
14
 
@@ -17,7 +18,7 @@ const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
17
18
 
18
19
  describe('Effect', () => {
19
20
  test('should be triggered after a state change', () => {
20
- const cause = createState('foo')
21
+ const cause = new State('foo')
21
22
  let count = 0
22
23
  createEffect(() => {
23
24
  cause.get()
@@ -29,11 +30,11 @@ describe('Effect', () => {
29
30
  })
30
31
 
31
32
  test('should be triggered after computed async signals resolve without waterfalls', async () => {
32
- const a = createComputed(async () => {
33
+ const a = new Task(async () => {
33
34
  await wait(100)
34
35
  return 10
35
36
  })
36
- const b = createComputed(async () => {
37
+ const b = new Task(async () => {
37
38
  await wait(100)
38
39
  return 20
39
40
  })
@@ -56,7 +57,7 @@ describe('Effect', () => {
56
57
  })
57
58
 
58
59
  test('should be triggered repeatedly after repeated state change', async () => {
59
- const cause = createState(0)
60
+ const cause = new State(0)
60
61
  let result = 0
61
62
  let count = 0
62
63
  createEffect(() => {
@@ -71,8 +72,8 @@ describe('Effect', () => {
71
72
  })
72
73
 
73
74
  test('should handle errors in effects with resolve handlers', () => {
74
- const a = createState(1)
75
- const b = createComputed(() => {
75
+ const a = new State(1)
76
+ const b = new Memo(() => {
76
77
  const v = a.get()
77
78
  if (v > 5) throw new Error('Value too high')
78
79
  return v * 2
@@ -109,8 +110,8 @@ describe('Effect', () => {
109
110
  })
110
111
 
111
112
  test('should handle errors in effects with resolve result', () => {
112
- const a = createState(1)
113
- const b = createComputed(() => {
113
+ const a = new State(1)
114
+ const b = new Memo(() => {
114
115
  const v = a.get()
115
116
  if (v > 5) throw new Error('Value too high')
116
117
  return v * 2
@@ -144,7 +145,7 @@ describe('Effect', () => {
144
145
  })
145
146
 
146
147
  test('should handle UNSET values in effects with resolve handlers', async () => {
147
- const a = createComputed(async () => {
148
+ const a = new Task(async () => {
148
149
  await wait(100)
149
150
  return 42
150
151
  })
@@ -173,7 +174,7 @@ describe('Effect', () => {
173
174
  })
174
175
 
175
176
  test('should handle UNSET values in effects with resolve result', async () => {
176
- const a = createComputed(async () => {
177
+ const a = new Task(async () => {
177
178
  await wait(100)
178
179
  return 42
179
180
  })
@@ -205,8 +206,8 @@ describe('Effect', () => {
205
206
  console.error = mockConsoleError
206
207
 
207
208
  try {
208
- const a = createState(1)
209
- const b = createComputed(() => {
209
+ const a = new State(1)
210
+ const b = new Memo(() => {
210
211
  const v = a.get()
211
212
  if (v > 5) throw new Error('Value too high')
212
213
  return v * 2
@@ -232,7 +233,7 @@ describe('Effect', () => {
232
233
  })
233
234
 
234
235
  test('should clean up subscriptions when disposed', () => {
235
- const count = createState(42)
236
+ const count = new State(42)
236
237
  let received = 0
237
238
 
238
239
  const cleanup = createEffect(() => {
@@ -250,7 +251,7 @@ describe('Effect', () => {
250
251
  test('should detect and throw error for circular dependencies in effects', () => {
251
252
  let okCount = 0
252
253
  let errCount = 0
253
- const count = createState(0)
254
+ const count = new State(0)
254
255
 
255
256
  createEffect(() => {
256
257
  const resolved = resolve({ count })
@@ -298,7 +299,7 @@ describe('Effect - Async with AbortSignal', () => {
298
299
  })
299
300
 
300
301
  test('should abort async operations when signal changes', async () => {
301
- const testSignal = createState(1)
302
+ const testSignal = new State(1)
302
303
  let operationAborted = false
303
304
  let operationCompleted = false
304
305
  let abortReason: DOMException | undefined
@@ -364,7 +365,7 @@ describe('Effect - Async with AbortSignal', () => {
364
365
  console.error = mockConsoleError
365
366
 
366
367
  try {
367
- const testSignal = createState(1)
368
+ const testSignal = new State(1)
368
369
 
369
370
  createEffect(async abort => {
370
371
  const result = resolve({ testSignal })
@@ -408,7 +409,7 @@ describe('Effect - Async with AbortSignal', () => {
408
409
  test('should handle async effects that return cleanup functions', async () => {
409
410
  let asyncEffectCompleted = false
410
411
  let cleanupRegistered = false
411
- const testSignal = createState('initial')
412
+ const testSignal = new State('initial')
412
413
 
413
414
  const cleanup = createEffect(async () => {
414
415
  const result = resolve({ testSignal })
@@ -431,7 +432,7 @@ describe('Effect - Async with AbortSignal', () => {
431
432
  })
432
433
 
433
434
  test('should handle rapid signal changes with concurrent async operations', async () => {
434
- const testSignal = createState(0)
435
+ const testSignal = new State(0)
435
436
  let completedOperations = 0
436
437
  let abortedOperations = 0
437
438
 
@@ -477,9 +478,9 @@ describe('Effect - Async with AbortSignal', () => {
477
478
  console.error = mockConsoleError
478
479
 
479
480
  try {
480
- const testSignal = createState(1)
481
+ const testSignal = new State(1)
481
482
 
482
- const errorThrower = createComputed(() => {
483
+ const errorThrower = new Memo(() => {
483
484
  const value = testSignal.get()
484
485
  if (value > 5) throw new Error('Value too high')
485
486
  return value
@@ -517,7 +518,7 @@ describe('Effect - Async with AbortSignal', () => {
517
518
  test('should handle promise-based async effects', async () => {
518
519
  let promiseResolved = false
519
520
  let effectValue = ''
520
- const testSignal = createState('test-value')
521
+ const testSignal = new State('test-value')
521
522
 
522
523
  createEffect(async abort => {
523
524
  const result = resolve({ testSignal })
@@ -548,7 +549,7 @@ describe('Effect - Async with AbortSignal', () => {
548
549
  })
549
550
 
550
551
  test('should not create AbortController for sync functions', () => {
551
- const testSignal = createState('test')
552
+ const testSignal = new State('test')
552
553
  let syncCallCount = 0
553
554
 
554
555
  // Mock AbortController constructor to detect if it's called
@@ -579,7 +580,7 @@ describe('Effect - Async with AbortSignal', () => {
579
580
  })
580
581
 
581
582
  test('should handle concurrent async operations with abort', async () => {
582
- const testSignal = createState(1)
583
+ const testSignal = new State(1)
583
584
  let operation1Completed = false
584
585
  let operation1Aborted = false
585
586
 
@@ -629,8 +630,8 @@ describe('Effect - Async with AbortSignal', () => {
629
630
 
630
631
  describe('Effect + Resolve Integration', () => {
631
632
  test('should work with resolve discriminated union', () => {
632
- const a = createState(10)
633
- const b = createState('hello')
633
+ const a = new State(10)
634
+ const b = new State('hello')
634
635
  let effectRan = false
635
636
 
636
637
  createEffect(() => {
@@ -647,7 +648,7 @@ describe('Effect + Resolve Integration', () => {
647
648
  })
648
649
 
649
650
  test('should work with match function', () => {
650
- const a = createState(42)
651
+ const a = new State(42)
651
652
  let matchedValue = 0
652
653
 
653
654
  createEffect(() => {
@@ -666,7 +667,7 @@ describe('Effect + Resolve Integration', () => {
666
667
  describe('Effect - Race Conditions and Consistency', () => {
667
668
  test('should handle race conditions between abort and cleanup properly', async () => {
668
669
  // This test explores potential race conditions in effect cleanup
669
- const testSignal = createState(0)
670
+ const testSignal = new State(0)
670
671
  let cleanupCallCount = 0
671
672
  let abortCallCount = 0
672
673
  let operationCount = 0
@@ -706,12 +707,12 @@ describe('Effect - Race Conditions and Consistency', () => {
706
707
 
707
708
  test('should demonstrate difference in abort handling between computed and effect', async () => {
708
709
  // This test shows why computed needs an abort listener but effect might not
709
- const source = createState(1)
710
+ const source = new State(1)
710
711
  let computedRetries = 0
711
712
  let effectRuns = 0
712
713
 
713
714
  // Computed with abort listener (current implementation)
714
- const comp = createComputed(async () => {
715
+ const comp = new Task(async () => {
715
716
  computedRetries++
716
717
  await wait(30)
717
718
  return source.get() * 2
@@ -741,7 +742,7 @@ describe('Effect - Race Conditions and Consistency', () => {
741
742
 
742
743
  test('should prevent stale cleanup registration with generation counter approach', async () => {
743
744
  // This test verifies that the currentController check prevents stale cleanups
744
- const testSignal = createState(0)
745
+ const testSignal = new State(0)
745
746
  let cleanupCallCount = 0
746
747
  let effectRunCount = 0
747
748
  let staleCleanupAttempts = 0
@@ -782,11 +783,11 @@ describe('Effect - Race Conditions and Consistency', () => {
782
783
 
783
784
  test('should demonstrate why computed needs immediate retry via abort listener', async () => {
784
785
  // This test shows the performance benefit of immediate retry in computed
785
- const source = createState(1)
786
+ const source = new State(1)
786
787
  let computeAttempts = 0
787
788
  let finalValue: number = 0
788
789
 
789
- const comp = createComputed(async () => {
790
+ const comp = new Task(async () => {
790
791
  computeAttempts++
791
792
  await wait(30)
792
793
  return source.get() * 2