@zeix/cause-effect 0.15.2 → 0.16.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.
@@ -1,13 +1,13 @@
1
1
  import { describe, expect, mock, test } from 'bun:test'
2
2
  import {
3
- computed,
4
- effect,
3
+ createComputed,
4
+ createEffect,
5
+ createState,
5
6
  isAbortError,
6
7
  match,
7
8
  resolve,
8
- state,
9
9
  UNSET,
10
- } from '../'
10
+ } from '..'
11
11
 
12
12
  /* === Utility Functions === */
13
13
 
@@ -17,9 +17,9 @@ const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
17
17
 
18
18
  describe('Effect', () => {
19
19
  test('should be triggered after a state change', () => {
20
- const cause = state('foo')
20
+ const cause = createState('foo')
21
21
  let count = 0
22
- effect(() => {
22
+ createEffect(() => {
23
23
  cause.get()
24
24
  count++
25
25
  })
@@ -29,17 +29,17 @@ describe('Effect', () => {
29
29
  })
30
30
 
31
31
  test('should be triggered after computed async signals resolve without waterfalls', async () => {
32
- const a = computed(async () => {
32
+ const a = createComputed(async () => {
33
33
  await wait(100)
34
34
  return 10
35
35
  })
36
- const b = computed(async () => {
36
+ const b = createComputed(async () => {
37
37
  await wait(100)
38
38
  return 20
39
39
  })
40
40
  let result = 0
41
41
  let count = 0
42
- effect(() => {
42
+ createEffect(() => {
43
43
  const resolved = resolve({ a, b })
44
44
  match(resolved, {
45
45
  ok: ({ a: aValue, b: bValue }) => {
@@ -56,10 +56,10 @@ describe('Effect', () => {
56
56
  })
57
57
 
58
58
  test('should be triggered repeatedly after repeated state change', async () => {
59
- const cause = state(0)
59
+ const cause = createState(0)
60
60
  let result = 0
61
61
  let count = 0
62
- effect(() => {
62
+ createEffect(() => {
63
63
  result = cause.get()
64
64
  count++
65
65
  })
@@ -71,15 +71,15 @@ describe('Effect', () => {
71
71
  })
72
72
 
73
73
  test('should handle errors in effects with resolve handlers', () => {
74
- const a = state(1)
75
- const b = computed(() => {
74
+ const a = createState(1)
75
+ const b = createComputed(() => {
76
76
  const v = a.get()
77
77
  if (v > 5) throw new Error('Value too high')
78
78
  return v * 2
79
79
  })
80
80
  let normalCallCount = 0
81
81
  let errorCallCount = 0
82
- effect(() => {
82
+ createEffect(() => {
83
83
  const resolved = resolve({ b })
84
84
  match(resolved, {
85
85
  ok: () => {
@@ -109,15 +109,15 @@ describe('Effect', () => {
109
109
  })
110
110
 
111
111
  test('should handle errors in effects with resolve result', () => {
112
- const a = state(1)
113
- const b = computed(() => {
112
+ const a = createState(1)
113
+ const b = createComputed(() => {
114
114
  const v = a.get()
115
115
  if (v > 5) throw new Error('Value too high')
116
116
  return v * 2
117
117
  })
118
118
  let normalCallCount = 0
119
119
  let errorCallCount = 0
120
- effect(() => {
120
+ createEffect(() => {
121
121
  const result = resolve({ b })
122
122
  if (result.ok) {
123
123
  normalCallCount++
@@ -144,13 +144,13 @@ describe('Effect', () => {
144
144
  })
145
145
 
146
146
  test('should handle UNSET values in effects with resolve handlers', async () => {
147
- const a = computed(async () => {
147
+ const a = createComputed(async () => {
148
148
  await wait(100)
149
149
  return 42
150
150
  })
151
151
  let normalCallCount = 0
152
152
  let nilCount = 0
153
- effect(() => {
153
+ createEffect(() => {
154
154
  const resolved = resolve({ a })
155
155
  match(resolved, {
156
156
  ok: values => {
@@ -173,13 +173,13 @@ describe('Effect', () => {
173
173
  })
174
174
 
175
175
  test('should handle UNSET values in effects with resolve result', async () => {
176
- const a = computed(async () => {
176
+ const a = createComputed(async () => {
177
177
  await wait(100)
178
178
  return 42
179
179
  })
180
180
  let normalCallCount = 0
181
181
  let nilCount = 0
182
- effect(() => {
182
+ createEffect(() => {
183
183
  const result = resolve({ a })
184
184
  if (result.ok) {
185
185
  normalCallCount++
@@ -205,15 +205,15 @@ describe('Effect', () => {
205
205
  console.error = mockConsoleError
206
206
 
207
207
  try {
208
- const a = state(1)
209
- const b = computed(() => {
208
+ const a = createState(1)
209
+ const b = createComputed(() => {
210
210
  const v = a.get()
211
211
  if (v > 5) throw new Error('Value too high')
212
212
  return v * 2
213
213
  })
214
214
 
215
215
  // Create an effect without explicit error handling
216
- effect(() => {
216
+ createEffect(() => {
217
217
  b.get()
218
218
  })
219
219
 
@@ -232,10 +232,10 @@ describe('Effect', () => {
232
232
  })
233
233
 
234
234
  test('should clean up subscriptions when disposed', () => {
235
- const count = state(42)
235
+ const count = createState(42)
236
236
  let received = 0
237
237
 
238
- const cleanup = effect(() => {
238
+ const cleanup = createEffect(() => {
239
239
  received = count.get()
240
240
  })
241
241
 
@@ -250,9 +250,9 @@ describe('Effect', () => {
250
250
  test('should detect and throw error for circular dependencies in effects', () => {
251
251
  let okCount = 0
252
252
  let errCount = 0
253
- const count = state(0)
253
+ const count = createState(0)
254
254
 
255
- effect(() => {
255
+ createEffect(() => {
256
256
  const resolved = resolve({ count })
257
257
  match(resolved, {
258
258
  ok: () => {
@@ -282,7 +282,7 @@ describe('Effect - Async with AbortSignal', () => {
282
282
  let abortSignalReceived = false
283
283
  let effectCompleted = false
284
284
 
285
- effect(async (abort: AbortSignal) => {
285
+ createEffect(async (abort: AbortSignal) => {
286
286
  expect(abort).toBeInstanceOf(AbortSignal)
287
287
  expect(abort.aborted).toBe(false)
288
288
  abortSignalReceived = true
@@ -298,12 +298,12 @@ describe('Effect - Async with AbortSignal', () => {
298
298
  })
299
299
 
300
300
  test('should abort async operations when signal changes', async () => {
301
- const testSignal = state(1)
301
+ const testSignal = createState(1)
302
302
  let operationAborted = false
303
303
  let operationCompleted = false
304
304
  let abortReason: DOMException | undefined
305
305
 
306
- effect(async abort => {
306
+ createEffect(async abort => {
307
307
  const result = resolve({ testSignal })
308
308
  if (!result.ok) return
309
309
 
@@ -340,7 +340,7 @@ describe('Effect - Async with AbortSignal', () => {
340
340
  let operationAborted = false
341
341
  let abortReason: DOMException | undefined
342
342
 
343
- const cleanup = effect(async abort => {
343
+ const cleanup = createEffect(async abort => {
344
344
  abort.addEventListener('abort', () => {
345
345
  operationAborted = true
346
346
  abortReason = abort.reason
@@ -364,9 +364,9 @@ describe('Effect - Async with AbortSignal', () => {
364
364
  console.error = mockConsoleError
365
365
 
366
366
  try {
367
- const testSignal = state(1)
367
+ const testSignal = createState(1)
368
368
 
369
- effect(async abort => {
369
+ createEffect(async abort => {
370
370
  const result = resolve({ testSignal })
371
371
  if (!result.ok) return
372
372
 
@@ -408,9 +408,9 @@ describe('Effect - Async with AbortSignal', () => {
408
408
  test('should handle async effects that return cleanup functions', async () => {
409
409
  let asyncEffectCompleted = false
410
410
  let cleanupRegistered = false
411
- const testSignal = state('initial')
411
+ const testSignal = createState('initial')
412
412
 
413
- const cleanup = effect(async () => {
413
+ const cleanup = createEffect(async () => {
414
414
  const result = resolve({ testSignal })
415
415
  if (!result.ok) return
416
416
 
@@ -431,11 +431,11 @@ describe('Effect - Async with AbortSignal', () => {
431
431
  })
432
432
 
433
433
  test('should handle rapid signal changes with concurrent async operations', async () => {
434
- const testSignal = state(0)
434
+ const testSignal = createState(0)
435
435
  let completedOperations = 0
436
436
  let abortedOperations = 0
437
437
 
438
- effect(async abort => {
438
+ createEffect(async abort => {
439
439
  const result = resolve({ testSignal })
440
440
  if (!result.ok) return
441
441
 
@@ -477,15 +477,15 @@ describe('Effect - Async with AbortSignal', () => {
477
477
  console.error = mockConsoleError
478
478
 
479
479
  try {
480
- const testSignal = state(1)
480
+ const testSignal = createState(1)
481
481
 
482
- const errorThrower = computed(() => {
482
+ const errorThrower = createComputed(() => {
483
483
  const value = testSignal.get()
484
484
  if (value > 5) throw new Error('Value too high')
485
485
  return value
486
486
  })
487
487
 
488
- effect(async () => {
488
+ createEffect(async () => {
489
489
  const result = resolve({ errorThrower })
490
490
  if (result.ok) {
491
491
  // Normal operation
@@ -517,9 +517,9 @@ describe('Effect - Async with AbortSignal', () => {
517
517
  test('should handle promise-based async effects', async () => {
518
518
  let promiseResolved = false
519
519
  let effectValue = ''
520
- const testSignal = state('test-value')
520
+ const testSignal = createState('test-value')
521
521
 
522
- effect(async abort => {
522
+ createEffect(async abort => {
523
523
  const result = resolve({ testSignal })
524
524
  if (!result.ok) return
525
525
 
@@ -548,7 +548,7 @@ describe('Effect - Async with AbortSignal', () => {
548
548
  })
549
549
 
550
550
  test('should not create AbortController for sync functions', () => {
551
- const testSignal = state('test')
551
+ const testSignal = createState('test')
552
552
  let syncCallCount = 0
553
553
 
554
554
  // Mock AbortController constructor to detect if it's called
@@ -563,7 +563,7 @@ describe('Effect - Async with AbortSignal', () => {
563
563
  }
564
564
 
565
565
  try {
566
- effect(() => {
566
+ createEffect(() => {
567
567
  const result = resolve({ testSignal })
568
568
  if (result.ok) {
569
569
  syncCallCount++
@@ -579,11 +579,11 @@ describe('Effect - Async with AbortSignal', () => {
579
579
  })
580
580
 
581
581
  test('should handle concurrent async operations with abort', async () => {
582
- const testSignal = state(1)
582
+ const testSignal = createState(1)
583
583
  let operation1Completed = false
584
584
  let operation1Aborted = false
585
585
 
586
- effect(async abort => {
586
+ createEffect(async abort => {
587
587
  const result = resolve({ testSignal })
588
588
  if (!result.ok) return
589
589
 
@@ -629,11 +629,11 @@ describe('Effect - Async with AbortSignal', () => {
629
629
 
630
630
  describe('Effect + Resolve Integration', () => {
631
631
  test('should work with resolve discriminated union', () => {
632
- const a = state(10)
633
- const b = state('hello')
632
+ const a = createState(10)
633
+ const b = createState('hello')
634
634
  let effectRan = false
635
635
 
636
- effect(() => {
636
+ createEffect(() => {
637
637
  const result = resolve({ a, b })
638
638
 
639
639
  if (result.ok) {
@@ -647,10 +647,10 @@ describe('Effect + Resolve Integration', () => {
647
647
  })
648
648
 
649
649
  test('should work with match function', () => {
650
- const a = state(42)
650
+ const a = createState(42)
651
651
  let matchedValue = 0
652
652
 
653
- effect(() => {
653
+ createEffect(() => {
654
654
  const result = resolve({ a })
655
655
  match(result, {
656
656
  ok: values => {
@@ -666,12 +666,12 @@ describe('Effect + Resolve Integration', () => {
666
666
  describe('Effect - Race Conditions and Consistency', () => {
667
667
  test('should handle race conditions between abort and cleanup properly', async () => {
668
668
  // This test explores potential race conditions in effect cleanup
669
- const testSignal = state(0)
669
+ const testSignal = createState(0)
670
670
  let cleanupCallCount = 0
671
671
  let abortCallCount = 0
672
672
  let operationCount = 0
673
673
 
674
- effect(async abort => {
674
+ createEffect(async abort => {
675
675
  testSignal.get()
676
676
  ++operationCount
677
677
 
@@ -706,19 +706,19 @@ describe('Effect - Race Conditions and Consistency', () => {
706
706
 
707
707
  test('should demonstrate difference in abort handling between computed and effect', async () => {
708
708
  // This test shows why computed needs an abort listener but effect might not
709
- const source = state(1)
709
+ const source = createState(1)
710
710
  let computedRetries = 0
711
711
  let effectRuns = 0
712
712
 
713
713
  // Computed with abort listener (current implementation)
714
- const comp = computed(async () => {
714
+ const comp = createComputed(async () => {
715
715
  computedRetries++
716
716
  await wait(30)
717
717
  return source.get() * 2
718
718
  })
719
719
 
720
720
  // Effect without abort listener (current implementation)
721
- effect(async () => {
721
+ createEffect(async () => {
722
722
  effectRuns++
723
723
  // Must access the source to make effect reactive
724
724
  source.get()
@@ -741,12 +741,12 @@ describe('Effect - Race Conditions and Consistency', () => {
741
741
 
742
742
  test('should prevent stale cleanup registration with generation counter approach', async () => {
743
743
  // This test verifies that the currentController check prevents stale cleanups
744
- const testSignal = state(0)
744
+ const testSignal = createState(0)
745
745
  let cleanupCallCount = 0
746
746
  let effectRunCount = 0
747
747
  let staleCleanupAttempts = 0
748
748
 
749
- effect(async () => {
749
+ createEffect(async () => {
750
750
  effectRunCount++
751
751
  const currentRun = effectRunCount
752
752
  testSignal.get() // Make reactive
@@ -782,11 +782,11 @@ describe('Effect - Race Conditions and Consistency', () => {
782
782
 
783
783
  test('should demonstrate why computed needs immediate retry via abort listener', async () => {
784
784
  // This test shows the performance benefit of immediate retry in computed
785
- const source = state(1)
785
+ const source = createState(1)
786
786
  let computeAttempts = 0
787
787
  let finalValue: number = 0
788
788
 
789
- const comp = computed(async () => {
789
+ const comp = createComputed(async () => {
790
790
  computeAttempts++
791
791
  await wait(30)
792
792
  return source.get() * 2
@@ -1,12 +1,12 @@
1
1
  import { describe, expect, test } from 'bun:test'
2
- import { computed, match, resolve, state, UNSET } from '../'
2
+ import { createComputed, createState, match, resolve, UNSET } from '..'
3
3
 
4
4
  /* === Tests === */
5
5
 
6
6
  describe('Match Function', () => {
7
7
  test('should call ok handler for successful resolution', () => {
8
- const a = state(10)
9
- const b = state('hello')
8
+ const a = createState(10)
9
+ const b = createState('hello')
10
10
  let okCalled = false
11
11
  let okValues: { a: number; b: string } | null = null
12
12
 
@@ -32,8 +32,8 @@ describe('Match Function', () => {
32
32
  })
33
33
 
34
34
  test('should call nil handler for pending signals', () => {
35
- const a = state(10)
36
- const b = state(UNSET)
35
+ const a = createState(10)
36
+ const b = createState(UNSET)
37
37
  let nilCalled = false
38
38
 
39
39
  match(resolve({ a, b }), {
@@ -52,8 +52,8 @@ describe('Match Function', () => {
52
52
  })
53
53
 
54
54
  test('should call error handler for error signals', () => {
55
- const a = state(10)
56
- const b = computed(() => {
55
+ const a = createState(10)
56
+ const b = createComputed(() => {
57
57
  throw new Error('Test error')
58
58
  })
59
59
  let errCalled = false
@@ -78,7 +78,7 @@ describe('Match Function', () => {
78
78
  })
79
79
 
80
80
  test('should handle missing optional handlers gracefully', () => {
81
- const a = state(10)
81
+ const a = createState(10)
82
82
  const result = resolve({ a })
83
83
 
84
84
  // Should not throw even with only required ok handler (err and nil are optional)
@@ -92,7 +92,7 @@ describe('Match Function', () => {
92
92
  })
93
93
 
94
94
  test('should return void always', () => {
95
- const a = state(42)
95
+ const a = createState(42)
96
96
 
97
97
  const returnValue = match(resolve({ a }), {
98
98
  ok: () => {
@@ -105,7 +105,7 @@ describe('Match Function', () => {
105
105
  })
106
106
 
107
107
  test('should handle handler errors by calling error handler', () => {
108
- const a = state(10)
108
+ const a = createState(10)
109
109
  let handlerErrorCalled = false
110
110
  let handlerError: Error | null = null
111
111
 
@@ -125,7 +125,7 @@ describe('Match Function', () => {
125
125
  })
126
126
 
127
127
  test('should rethrow handler errors if no error handler available', () => {
128
- const a = state(10)
128
+ const a = createState(10)
129
129
 
130
130
  expect(() => {
131
131
  match(resolve({ a }), {
@@ -137,7 +137,7 @@ describe('Match Function', () => {
137
137
  })
138
138
 
139
139
  test('should combine existing errors with handler errors', () => {
140
- const a = computed(() => {
140
+ const a = createComputed(() => {
141
141
  throw new Error('Signal error')
142
142
  })
143
143
  let allErrors: readonly Error[] | null = null
@@ -167,9 +167,9 @@ describe('Match Function', () => {
167
167
  })
168
168
 
169
169
  test('should work with complex type inference', () => {
170
- const user = state({ id: 1, name: 'Alice' })
171
- const posts = state([{ id: 1, title: 'Hello' }])
172
- const settings = state({ theme: 'dark' })
170
+ const user = createState({ id: 1, name: 'Alice' })
171
+ const posts = createState([{ id: 1, title: 'Hello' }])
172
+ const settings = createState({ theme: 'dark' })
173
173
 
174
174
  let typeTestPassed = false
175
175
 
@@ -194,8 +194,8 @@ describe('Match Function', () => {
194
194
  })
195
195
 
196
196
  test('should handle side effects only pattern', () => {
197
- const count = state(5)
198
- const name = state('test')
197
+ const count = createState(5)
198
+ const name = createState('test')
199
199
  let sideEffectExecuted = false
200
200
  let capturedData = ''
201
201
 
@@ -214,10 +214,10 @@ describe('Match Function', () => {
214
214
  })
215
215
 
216
216
  test('should handle multiple error types correctly', () => {
217
- const error1 = computed(() => {
217
+ const error1 = createComputed(() => {
218
218
  throw new Error('First error')
219
219
  })
220
- const error2 = computed(() => {
220
+ const error2 = createComputed(() => {
221
221
  throw new Error('Second error')
222
222
  })
223
223
  let errorMessages: string[] = []
@@ -240,7 +240,7 @@ describe('Match Function', () => {
240
240
  const wait = (ms: number) =>
241
241
  new Promise(resolve => setTimeout(resolve, ms))
242
242
 
243
- const asyncSignal = computed(async () => {
243
+ const asyncSignal = createComputed(async () => {
244
244
  await wait(10)
245
245
  return 'async result'
246
246
  })
@@ -280,7 +280,7 @@ describe('Match Function', () => {
280
280
  })
281
281
 
282
282
  test('should maintain referential transparency', () => {
283
- const a = state(42)
283
+ const a = createState(42)
284
284
  const result = resolve({ a })
285
285
  let callCount = 0
286
286
 
@@ -303,7 +303,7 @@ describe('Match Function', () => {
303
303
 
304
304
  describe('Match Function Integration', () => {
305
305
  test('should work seamlessly with resolve', () => {
306
- const data = state({ id: 1, value: 'test' })
306
+ const data = createState({ id: 1, value: 'test' })
307
307
  let processed = false
308
308
  let processedValue = ''
309
309
 
@@ -322,12 +322,12 @@ describe('Match Function Integration', () => {
322
322
  const wait = (ms: number) =>
323
323
  new Promise(resolve => setTimeout(resolve, ms))
324
324
 
325
- const syncData = state('available')
326
- const asyncData = computed(async () => {
325
+ const syncData = createState('available')
326
+ const asyncData = createComputed(async () => {
327
327
  await wait(10)
328
328
  return 'loaded'
329
329
  })
330
- const errorData = computed(() => {
330
+ const errorData = createComputed(() => {
331
331
  throw new Error('Failed to load')
332
332
  })
333
333
 
@@ -1,12 +1,12 @@
1
1
  import { describe, expect, test } from 'bun:test'
2
- import { computed, resolve, state, UNSET } from '../'
2
+ import { createComputed, createState, resolve, UNSET } from '..'
3
3
 
4
4
  /* === Tests === */
5
5
 
6
6
  describe('Resolve Function', () => {
7
7
  test('should return discriminated union for successful resolution', () => {
8
- const a = state(10)
9
- const b = state('hello')
8
+ const a = createState(10)
9
+ const b = createState('hello')
10
10
 
11
11
  const result = resolve({ a, b })
12
12
 
@@ -20,8 +20,8 @@ describe('Resolve Function', () => {
20
20
  })
21
21
 
22
22
  test('should return discriminated union for pending signals', () => {
23
- const a = state(10)
24
- const b = state(UNSET)
23
+ const a = createState(10)
24
+ const b = createState(UNSET)
25
25
 
26
26
  const result = resolve({ a, b })
27
27
 
@@ -32,8 +32,8 @@ describe('Resolve Function', () => {
32
32
  })
33
33
 
34
34
  test('should return discriminated union for error signals', () => {
35
- const a = state(10)
36
- const b = computed(() => {
35
+ const a = createState(10)
36
+ const b = createComputed(() => {
37
37
  throw new Error('Test error')
38
38
  })
39
39
 
@@ -49,11 +49,11 @@ describe('Resolve Function', () => {
49
49
  })
50
50
 
51
51
  test('should handle mixed error and valid signals', () => {
52
- const valid = state('valid')
53
- const error1 = computed(() => {
52
+ const valid = createState('valid')
53
+ const error1 = createComputed(() => {
54
54
  throw new Error('Error 1')
55
55
  })
56
- const error2 = computed(() => {
56
+ const error2 = createComputed(() => {
57
57
  throw new Error('Error 2')
58
58
  })
59
59
 
@@ -69,8 +69,8 @@ describe('Resolve Function', () => {
69
69
  })
70
70
 
71
71
  test('should prioritize pending over errors', () => {
72
- const pending = state(UNSET)
73
- const error = computed(() => {
72
+ const pending = createState(UNSET)
73
+ const error = createComputed(() => {
74
74
  throw new Error('Test error')
75
75
  })
76
76
 
@@ -91,8 +91,8 @@ describe('Resolve Function', () => {
91
91
  })
92
92
 
93
93
  test('should handle complex nested object signals', () => {
94
- const user = state({ name: 'Alice', age: 25 })
95
- const settings = state({ theme: 'dark', lang: 'en' })
94
+ const user = createState({ name: 'Alice', age: 25 })
95
+ const settings = createState({ theme: 'dark', lang: 'en' })
96
96
 
97
97
  const result = resolve({ user, settings })
98
98
 
@@ -109,7 +109,7 @@ describe('Resolve Function', () => {
109
109
  const wait = (ms: number) =>
110
110
  new Promise(resolve => setTimeout(resolve, ms))
111
111
 
112
- const asyncSignal = computed(async () => {
112
+ const asyncSignal = createComputed(async () => {
113
113
  await wait(10)
114
114
  return 'async result'
115
115
  })
@@ -133,7 +133,7 @@ describe('Resolve Function', () => {
133
133
  const wait = (ms: number) =>
134
134
  new Promise(resolve => setTimeout(resolve, ms))
135
135
 
136
- const asyncError = computed(async () => {
136
+ const asyncError = createComputed(async () => {
137
137
  await wait(10)
138
138
  throw new Error('Async error')
139
139
  })