@zeix/cause-effect 0.15.2 → 0.16.0
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/.ai-context.md +254 -0
- package/.cursorrules +54 -0
- package/.github/copilot-instructions.md +132 -0
- package/CLAUDE.md +319 -0
- package/README.md +136 -166
- package/eslint.config.js +1 -1
- package/index.dev.js +125 -129
- package/index.js +1 -1
- package/index.ts +22 -22
- package/package.json +1 -1
- package/src/computed.ts +40 -29
- package/src/effect.ts +15 -12
- package/src/errors.ts +8 -0
- package/src/signal.ts +6 -6
- package/src/state.ts +27 -20
- package/src/store.ts +99 -121
- package/src/system.ts +122 -0
- package/src/util.ts +1 -6
- package/test/batch.test.ts +18 -11
- package/test/benchmark.test.ts +4 -4
- package/test/computed.test.ts +507 -71
- package/test/effect.test.ts +60 -60
- package/test/match.test.ts +25 -25
- package/test/resolve.test.ts +16 -16
- package/test/signal.test.ts +7 -7
- package/test/state.test.ts +212 -25
- package/test/store.test.ts +476 -183
- package/test/util/dependency-graph.ts +1 -1
- package/types/index.d.ts +8 -8
- package/types/src/collection.d.ts +26 -0
- package/types/src/computed.d.ts +9 -9
- package/types/src/effect.d.ts +3 -3
- package/types/src/errors.d.ts +4 -1
- package/types/src/state.d.ts +5 -5
- package/types/src/store.d.ts +27 -41
- package/types/src/system.d.ts +44 -0
- package/types/src/util.d.ts +1 -2
- package/src/scheduler.ts +0 -172
package/test/effect.test.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { describe, expect, mock, test } from 'bun:test'
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
createComputed,
|
|
4
|
+
createEffect,
|
|
5
|
+
createState,
|
|
5
6
|
isAbortError,
|
|
6
7
|
match,
|
|
7
8
|
resolve,
|
|
8
|
-
state,
|
|
9
9
|
UNSET,
|
|
10
10
|
} from '../'
|
|
11
11
|
|
|
@@ -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 =
|
|
20
|
+
const cause = createState('foo')
|
|
21
21
|
let count = 0
|
|
22
|
-
|
|
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 =
|
|
32
|
+
const a = createComputed(async () => {
|
|
33
33
|
await wait(100)
|
|
34
34
|
return 10
|
|
35
35
|
})
|
|
36
|
-
const b =
|
|
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
|
-
|
|
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 =
|
|
59
|
+
const cause = createState(0)
|
|
60
60
|
let result = 0
|
|
61
61
|
let count = 0
|
|
62
|
-
|
|
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 =
|
|
75
|
-
const b =
|
|
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
|
-
|
|
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 =
|
|
113
|
-
const b =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
209
|
-
const b =
|
|
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
|
-
|
|
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 =
|
|
235
|
+
const count = createState(42)
|
|
236
236
|
let received = 0
|
|
237
237
|
|
|
238
|
-
const cleanup =
|
|
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 =
|
|
253
|
+
const count = createState(0)
|
|
254
254
|
|
|
255
|
-
|
|
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
|
-
|
|
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 =
|
|
301
|
+
const testSignal = createState(1)
|
|
302
302
|
let operationAborted = false
|
|
303
303
|
let operationCompleted = false
|
|
304
304
|
let abortReason: DOMException | undefined
|
|
305
305
|
|
|
306
|
-
|
|
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 =
|
|
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 =
|
|
367
|
+
const testSignal = createState(1)
|
|
368
368
|
|
|
369
|
-
|
|
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 =
|
|
411
|
+
const testSignal = createState('initial')
|
|
412
412
|
|
|
413
|
-
const cleanup =
|
|
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 =
|
|
434
|
+
const testSignal = createState(0)
|
|
435
435
|
let completedOperations = 0
|
|
436
436
|
let abortedOperations = 0
|
|
437
437
|
|
|
438
|
-
|
|
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 =
|
|
480
|
+
const testSignal = createState(1)
|
|
481
481
|
|
|
482
|
-
const errorThrower =
|
|
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
|
-
|
|
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 =
|
|
520
|
+
const testSignal = createState('test-value')
|
|
521
521
|
|
|
522
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
582
|
+
const testSignal = createState(1)
|
|
583
583
|
let operation1Completed = false
|
|
584
584
|
let operation1Aborted = false
|
|
585
585
|
|
|
586
|
-
|
|
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 =
|
|
633
|
-
const b =
|
|
632
|
+
const a = createState(10)
|
|
633
|
+
const b = createState('hello')
|
|
634
634
|
let effectRan = false
|
|
635
635
|
|
|
636
|
-
|
|
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 =
|
|
650
|
+
const a = createState(42)
|
|
651
651
|
let matchedValue = 0
|
|
652
652
|
|
|
653
|
-
|
|
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 =
|
|
669
|
+
const testSignal = createState(0)
|
|
670
670
|
let cleanupCallCount = 0
|
|
671
671
|
let abortCallCount = 0
|
|
672
672
|
let operationCount = 0
|
|
673
673
|
|
|
674
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
744
|
+
const testSignal = createState(0)
|
|
745
745
|
let cleanupCallCount = 0
|
|
746
746
|
let effectRunCount = 0
|
|
747
747
|
let staleCleanupAttempts = 0
|
|
748
748
|
|
|
749
|
-
|
|
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 =
|
|
785
|
+
const source = createState(1)
|
|
786
786
|
let computeAttempts = 0
|
|
787
787
|
let finalValue: number = 0
|
|
788
788
|
|
|
789
|
-
const comp =
|
|
789
|
+
const comp = createComputed(async () => {
|
|
790
790
|
computeAttempts++
|
|
791
791
|
await wait(30)
|
|
792
792
|
return source.get() * 2
|
package/test/match.test.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test'
|
|
2
|
-
import {
|
|
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 =
|
|
9
|
-
const b =
|
|
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 =
|
|
36
|
-
const b =
|
|
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 =
|
|
56
|
-
const b =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
171
|
-
const posts =
|
|
172
|
-
const settings =
|
|
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 =
|
|
198
|
-
const name =
|
|
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 =
|
|
217
|
+
const error1 = createComputed(() => {
|
|
218
218
|
throw new Error('First error')
|
|
219
219
|
})
|
|
220
|
-
const error2 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
326
|
-
const asyncData =
|
|
325
|
+
const syncData = createState('available')
|
|
326
|
+
const asyncData = createComputed(async () => {
|
|
327
327
|
await wait(10)
|
|
328
328
|
return 'loaded'
|
|
329
329
|
})
|
|
330
|
-
const errorData =
|
|
330
|
+
const errorData = createComputed(() => {
|
|
331
331
|
throw new Error('Failed to load')
|
|
332
332
|
})
|
|
333
333
|
|
package/test/resolve.test.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test'
|
|
2
|
-
import {
|
|
2
|
+
import { createComputed, resolve, createState, 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 =
|
|
9
|
-
const b =
|
|
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 =
|
|
24
|
-
const b =
|
|
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 =
|
|
36
|
-
const b =
|
|
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 =
|
|
53
|
-
const error1 =
|
|
52
|
+
const valid = createState('valid')
|
|
53
|
+
const error1 = createComputed(() => {
|
|
54
54
|
throw new Error('Error 1')
|
|
55
55
|
})
|
|
56
|
-
const error2 =
|
|
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 =
|
|
73
|
-
const error =
|
|
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 =
|
|
95
|
-
const settings =
|
|
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 =
|
|
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 =
|
|
136
|
+
const asyncError = createComputed(async () => {
|
|
137
137
|
await wait(10)
|
|
138
138
|
throw new Error('Async error')
|
|
139
139
|
})
|
package/test/signal.test.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test'
|
|
2
2
|
import {
|
|
3
3
|
type Computed,
|
|
4
|
-
|
|
4
|
+
createComputed,
|
|
5
|
+
createState,
|
|
6
|
+
createStore,
|
|
5
7
|
isComputed,
|
|
6
8
|
isState,
|
|
7
9
|
isStore,
|
|
8
10
|
type Signal,
|
|
9
11
|
type State,
|
|
10
12
|
type Store,
|
|
11
|
-
state,
|
|
12
|
-
store,
|
|
13
13
|
toSignal,
|
|
14
14
|
type UnknownRecord,
|
|
15
15
|
} from '..'
|
|
@@ -58,7 +58,7 @@ describe('toSignal', () => {
|
|
|
58
58
|
})
|
|
59
59
|
|
|
60
60
|
test('passes through existing Store unchanged', () => {
|
|
61
|
-
const originalStore =
|
|
61
|
+
const originalStore = createStore({ count: 5 })
|
|
62
62
|
const result = toSignal(originalStore)
|
|
63
63
|
|
|
64
64
|
// Runtime behavior
|
|
@@ -72,7 +72,7 @@ describe('toSignal', () => {
|
|
|
72
72
|
})
|
|
73
73
|
|
|
74
74
|
test('passes through existing State unchanged', () => {
|
|
75
|
-
const originalState =
|
|
75
|
+
const originalState = createState(42)
|
|
76
76
|
const result = toSignal(originalState)
|
|
77
77
|
|
|
78
78
|
// Runtime behavior
|
|
@@ -86,7 +86,7 @@ describe('toSignal', () => {
|
|
|
86
86
|
})
|
|
87
87
|
|
|
88
88
|
test('passes through existing Computed unchanged', () => {
|
|
89
|
-
const originalComputed =
|
|
89
|
+
const originalComputed = createComputed(() => 'hello world')
|
|
90
90
|
const result = toSignal(originalComputed)
|
|
91
91
|
|
|
92
92
|
// Runtime behavior
|
|
@@ -185,7 +185,7 @@ describe('Signal compatibility', () => {
|
|
|
185
185
|
const recordSignal = toSignal({ a: 1, b: 2 })
|
|
186
186
|
const primitiveSignal = toSignal(42)
|
|
187
187
|
const functionSignal = toSignal(() => 'hello')
|
|
188
|
-
const stateSignal = toSignal(
|
|
188
|
+
const stateSignal = toSignal(createState(true))
|
|
189
189
|
|
|
190
190
|
// All should have get() method
|
|
191
191
|
expect(typeof arraySignal.get).toBe('function')
|