@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.
- package/.ai-context.md +85 -21
- package/.cursorrules +11 -5
- package/.github/copilot-instructions.md +64 -13
- package/CLAUDE.md +143 -163
- package/LICENSE +1 -1
- package/README.md +248 -333
- package/archive/benchmark.ts +688 -0
- package/archive/collection.ts +312 -0
- package/{src → archive}/computed.ts +21 -21
- package/archive/list.ts +551 -0
- package/archive/memo.ts +139 -0
- package/{src → archive}/state.ts +13 -11
- package/archive/store.ts +368 -0
- package/archive/task.ts +194 -0
- package/eslint.config.js +1 -0
- package/index.dev.js +938 -509
- package/index.js +1 -1
- package/index.ts +50 -23
- package/package.json +1 -1
- package/src/classes/collection.ts +282 -0
- package/src/classes/composite.ts +176 -0
- package/src/classes/computed.ts +333 -0
- package/src/classes/list.ts +305 -0
- package/src/classes/ref.ts +68 -0
- package/src/classes/state.ts +98 -0
- package/src/classes/store.ts +210 -0
- package/src/diff.ts +26 -53
- package/src/effect.ts +9 -9
- package/src/errors.ts +71 -25
- package/src/match.ts +5 -12
- package/src/resolve.ts +3 -2
- package/src/signal.ts +58 -41
- package/src/system.ts +79 -42
- package/src/util.ts +16 -34
- package/test/batch.test.ts +15 -17
- package/test/benchmark.test.ts +4 -4
- package/test/collection.test.ts +853 -0
- package/test/computed.test.ts +138 -130
- package/test/diff.test.ts +2 -2
- package/test/effect.test.ts +36 -35
- package/test/list.test.ts +754 -0
- package/test/match.test.ts +25 -25
- package/test/ref.test.ts +227 -0
- package/test/resolve.test.ts +17 -19
- package/test/signal.test.ts +70 -119
- package/test/state.test.ts +44 -44
- package/test/store.test.ts +253 -929
- package/types/index.d.ts +12 -9
- package/types/src/classes/collection.d.ts +46 -0
- package/types/src/classes/composite.d.ts +15 -0
- package/types/src/classes/computed.d.ts +97 -0
- package/types/src/classes/list.d.ts +41 -0
- package/types/src/classes/ref.d.ts +39 -0
- package/types/src/classes/state.d.ts +52 -0
- package/types/src/classes/store.d.ts +51 -0
- package/types/src/diff.d.ts +8 -12
- package/types/src/errors.d.ts +17 -11
- package/types/src/signal.d.ts +27 -14
- package/types/src/system.d.ts +41 -20
- package/types/src/util.d.ts +6 -4
- package/src/store.ts +0 -474
- package/types/src/collection.d.ts +0 -26
- package/types/src/computed.d.ts +0 -33
- package/types/src/scheduler.d.ts +0 -55
- package/types/src/state.d.ts +0 -24
- package/types/src/store.d.ts +0 -65
package/test/computed.test.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test'
|
|
2
2
|
import {
|
|
3
|
-
createComputed,
|
|
4
3
|
createEffect,
|
|
5
|
-
createState,
|
|
6
4
|
isComputed,
|
|
7
5
|
isState,
|
|
6
|
+
Memo,
|
|
8
7
|
match,
|
|
9
8
|
resolve,
|
|
9
|
+
State,
|
|
10
|
+
Task,
|
|
10
11
|
UNSET,
|
|
11
|
-
} from '
|
|
12
|
+
} from '../index.ts'
|
|
12
13
|
|
|
13
14
|
/* === Utility Functions === */
|
|
14
15
|
|
|
@@ -19,38 +20,38 @@ const increment = (n: number) => (Number.isFinite(n) ? n + 1 : UNSET)
|
|
|
19
20
|
|
|
20
21
|
describe('Computed', () => {
|
|
21
22
|
test('should identify computed signals with isComputed()', () => {
|
|
22
|
-
const count =
|
|
23
|
-
const doubled =
|
|
23
|
+
const count = new State(42)
|
|
24
|
+
const doubled = new Memo(() => count.get() * 2)
|
|
24
25
|
expect(isComputed(doubled)).toBe(true)
|
|
25
26
|
expect(isState(doubled)).toBe(false)
|
|
26
27
|
})
|
|
27
28
|
|
|
28
29
|
test('should compute a function', () => {
|
|
29
|
-
const derived =
|
|
30
|
+
const derived = new Memo(() => 1 + 2)
|
|
30
31
|
expect(derived.get()).toBe(3)
|
|
31
32
|
})
|
|
32
33
|
|
|
33
34
|
test('should compute function dependent on a signal', () => {
|
|
34
|
-
const cause =
|
|
35
|
-
const derived =
|
|
35
|
+
const cause = new State(42)
|
|
36
|
+
const derived = new Memo(() => cause.get() + 1)
|
|
36
37
|
expect(derived.get()).toBe(43)
|
|
37
38
|
})
|
|
38
39
|
|
|
39
40
|
test('should compute function dependent on an updated signal', () => {
|
|
40
|
-
const cause =
|
|
41
|
-
const derived =
|
|
41
|
+
const cause = new State(42)
|
|
42
|
+
const derived = new Memo(() => cause.get() + 1)
|
|
42
43
|
cause.set(24)
|
|
43
44
|
expect(derived.get()).toBe(25)
|
|
44
45
|
})
|
|
45
46
|
|
|
46
47
|
test('should compute function dependent on an async signal', async () => {
|
|
47
|
-
const status =
|
|
48
|
-
const promised =
|
|
48
|
+
const status = new State('pending')
|
|
49
|
+
const promised = new Task(async () => {
|
|
49
50
|
await wait(100)
|
|
50
51
|
status.set('success')
|
|
51
52
|
return 42
|
|
52
53
|
})
|
|
53
|
-
const derived =
|
|
54
|
+
const derived = new Memo(() => increment(promised.get()))
|
|
54
55
|
expect(derived.get()).toBe(UNSET)
|
|
55
56
|
expect(status.get()).toBe('pending')
|
|
56
57
|
await wait(110)
|
|
@@ -59,15 +60,15 @@ describe('Computed', () => {
|
|
|
59
60
|
})
|
|
60
61
|
|
|
61
62
|
test('should handle errors from an async signal gracefully', async () => {
|
|
62
|
-
const status =
|
|
63
|
-
const error =
|
|
64
|
-
const promised =
|
|
63
|
+
const status = new State('pending')
|
|
64
|
+
const error = new State('')
|
|
65
|
+
const promised = new Task(async () => {
|
|
65
66
|
await wait(100)
|
|
66
67
|
status.set('error')
|
|
67
68
|
error.set('error occurred')
|
|
68
69
|
return 0
|
|
69
70
|
})
|
|
70
|
-
const derived =
|
|
71
|
+
const derived = new Memo(() => increment(promised.get()))
|
|
71
72
|
expect(derived.get()).toBe(UNSET)
|
|
72
73
|
expect(status.get()).toBe('pending')
|
|
73
74
|
await wait(110)
|
|
@@ -76,15 +77,15 @@ describe('Computed', () => {
|
|
|
76
77
|
})
|
|
77
78
|
|
|
78
79
|
test('should compute task signals in parallel without waterfalls', async () => {
|
|
79
|
-
const a =
|
|
80
|
+
const a = new Task(async () => {
|
|
80
81
|
await wait(100)
|
|
81
82
|
return 10
|
|
82
83
|
})
|
|
83
|
-
const b =
|
|
84
|
+
const b = new Task(async () => {
|
|
84
85
|
await wait(100)
|
|
85
86
|
return 20
|
|
86
87
|
})
|
|
87
|
-
const c =
|
|
88
|
+
const c = new Memo(() => {
|
|
88
89
|
const aValue = a.get()
|
|
89
90
|
const bValue = b.get()
|
|
90
91
|
return aValue === UNSET || bValue === UNSET
|
|
@@ -97,28 +98,28 @@ describe('Computed', () => {
|
|
|
97
98
|
})
|
|
98
99
|
|
|
99
100
|
test('should compute function dependent on a chain of computed states dependent on a signal', () => {
|
|
100
|
-
const x =
|
|
101
|
-
const a =
|
|
102
|
-
const b =
|
|
103
|
-
const c =
|
|
101
|
+
const x = new State(42)
|
|
102
|
+
const a = new Memo(() => x.get() + 1)
|
|
103
|
+
const b = new Memo(() => a.get() * 2)
|
|
104
|
+
const c = new Memo(() => b.get() + 1)
|
|
104
105
|
expect(c.get()).toBe(87)
|
|
105
106
|
})
|
|
106
107
|
|
|
107
108
|
test('should compute function dependent on a chain of computed states dependent on an updated signal', () => {
|
|
108
|
-
const x =
|
|
109
|
-
const a =
|
|
110
|
-
const b =
|
|
111
|
-
const c =
|
|
109
|
+
const x = new State(42)
|
|
110
|
+
const a = new Memo(() => x.get() + 1)
|
|
111
|
+
const b = new Memo(() => a.get() * 2)
|
|
112
|
+
const c = new Memo(() => b.get() + 1)
|
|
112
113
|
x.set(24)
|
|
113
114
|
expect(c.get()).toBe(51)
|
|
114
115
|
})
|
|
115
116
|
|
|
116
117
|
test('should drop X->B->X updates', () => {
|
|
117
118
|
let count = 0
|
|
118
|
-
const x =
|
|
119
|
-
const a =
|
|
120
|
-
const b =
|
|
121
|
-
const c =
|
|
119
|
+
const x = new State(2)
|
|
120
|
+
const a = new Memo(() => x.get() - 1)
|
|
121
|
+
const b = new Memo(() => x.get() + a.get())
|
|
122
|
+
const c = new Memo(() => {
|
|
122
123
|
count++
|
|
123
124
|
return `c: ${b.get()}`
|
|
124
125
|
})
|
|
@@ -131,10 +132,10 @@ describe('Computed', () => {
|
|
|
131
132
|
|
|
132
133
|
test('should only update every signal once (diamond graph)', () => {
|
|
133
134
|
let count = 0
|
|
134
|
-
const x =
|
|
135
|
-
const a =
|
|
136
|
-
const b =
|
|
137
|
-
const c =
|
|
135
|
+
const x = new State('a')
|
|
136
|
+
const a = new Memo(() => x.get())
|
|
137
|
+
const b = new Memo(() => x.get())
|
|
138
|
+
const c = new Memo(() => {
|
|
138
139
|
count++
|
|
139
140
|
return `${a.get()} ${b.get()}`
|
|
140
141
|
})
|
|
@@ -148,11 +149,11 @@ describe('Computed', () => {
|
|
|
148
149
|
|
|
149
150
|
test('should only update every signal once (diamond graph + tail)', () => {
|
|
150
151
|
let count = 0
|
|
151
|
-
const x =
|
|
152
|
-
const a =
|
|
153
|
-
const b =
|
|
154
|
-
const c =
|
|
155
|
-
const d =
|
|
152
|
+
const x = new State('a')
|
|
153
|
+
const a = new Memo(() => x.get())
|
|
154
|
+
const b = new Memo(() => x.get())
|
|
155
|
+
const c = new Memo(() => `${a.get()} ${b.get()}`)
|
|
156
|
+
const d = new Memo(() => {
|
|
156
157
|
count++
|
|
157
158
|
return c.get()
|
|
158
159
|
})
|
|
@@ -164,10 +165,10 @@ describe('Computed', () => {
|
|
|
164
165
|
})
|
|
165
166
|
|
|
166
167
|
test('should update multiple times after multiple state changes', () => {
|
|
167
|
-
const a =
|
|
168
|
-
const b =
|
|
168
|
+
const a = new State(3)
|
|
169
|
+
const b = new State(4)
|
|
169
170
|
let count = 0
|
|
170
|
-
const sum =
|
|
171
|
+
const sum = new Memo(() => {
|
|
171
172
|
count++
|
|
172
173
|
return a.get() + b.get()
|
|
173
174
|
})
|
|
@@ -189,12 +190,12 @@ describe('Computed', () => {
|
|
|
189
190
|
*/
|
|
190
191
|
test('should bail out if result is the same', () => {
|
|
191
192
|
let count = 0
|
|
192
|
-
const x =
|
|
193
|
-
const a =
|
|
193
|
+
const x = new State('a')
|
|
194
|
+
const a = new Memo(() => {
|
|
194
195
|
x.get()
|
|
195
196
|
return 'foo'
|
|
196
197
|
})
|
|
197
|
-
const b =
|
|
198
|
+
const b = new Memo(() => {
|
|
198
199
|
count++
|
|
199
200
|
return a.get()
|
|
200
201
|
})
|
|
@@ -209,10 +210,10 @@ describe('Computed', () => {
|
|
|
209
210
|
|
|
210
211
|
test('should block if result remains unchanged', () => {
|
|
211
212
|
let count = 0
|
|
212
|
-
const x =
|
|
213
|
-
const a =
|
|
214
|
-
const b =
|
|
215
|
-
const c =
|
|
213
|
+
const x = new State(42)
|
|
214
|
+
const a = new Memo(() => x.get() % 2)
|
|
215
|
+
const b = new Memo(() => (a.get() ? 'odd' : 'even'))
|
|
216
|
+
const c = new Memo(() => {
|
|
216
217
|
count++
|
|
217
218
|
return `c: ${b.get()}`
|
|
218
219
|
})
|
|
@@ -226,26 +227,26 @@ describe('Computed', () => {
|
|
|
226
227
|
})
|
|
227
228
|
|
|
228
229
|
test('should detect and throw error for circular dependencies', () => {
|
|
229
|
-
const a =
|
|
230
|
-
const b =
|
|
231
|
-
const c =
|
|
230
|
+
const a = new State(1)
|
|
231
|
+
const b = new Memo(() => c.get() + 1)
|
|
232
|
+
const c = new Memo(() => b.get() + a.get())
|
|
232
233
|
expect(() => {
|
|
233
234
|
b.get() // This should trigger the circular dependency
|
|
234
|
-
}).toThrow('Circular dependency detected in
|
|
235
|
+
}).toThrow('Circular dependency detected in memo')
|
|
235
236
|
expect(a.get()).toBe(1)
|
|
236
237
|
})
|
|
237
238
|
|
|
238
239
|
test('should propagate error if an error occurred', () => {
|
|
239
240
|
let okCount = 0
|
|
240
241
|
let errCount = 0
|
|
241
|
-
const x =
|
|
242
|
-
const a =
|
|
242
|
+
const x = new State(0)
|
|
243
|
+
const a = new Memo(() => {
|
|
243
244
|
if (x.get() === 1) throw new Error('Calculation error')
|
|
244
245
|
return 1
|
|
245
246
|
})
|
|
246
247
|
|
|
247
248
|
// Replace matcher with try/catch in a computed
|
|
248
|
-
const b =
|
|
249
|
+
const b = new Memo(() => {
|
|
249
250
|
try {
|
|
250
251
|
a.get() // just check if it works
|
|
251
252
|
return `c: success`
|
|
@@ -254,7 +255,7 @@ describe('Computed', () => {
|
|
|
254
255
|
return `c: recovered`
|
|
255
256
|
}
|
|
256
257
|
})
|
|
257
|
-
const c =
|
|
258
|
+
const c = new Memo(() => {
|
|
258
259
|
okCount++
|
|
259
260
|
return b.get()
|
|
260
261
|
})
|
|
@@ -276,8 +277,8 @@ describe('Computed', () => {
|
|
|
276
277
|
})
|
|
277
278
|
|
|
278
279
|
test('should create an effect that reacts on async computed changes', async () => {
|
|
279
|
-
const cause =
|
|
280
|
-
const derived =
|
|
280
|
+
const cause = new State(42)
|
|
281
|
+
const derived = new Task(async () => {
|
|
281
282
|
await wait(100)
|
|
282
283
|
return cause.get() + 1
|
|
283
284
|
})
|
|
@@ -308,12 +309,12 @@ describe('Computed', () => {
|
|
|
308
309
|
})
|
|
309
310
|
|
|
310
311
|
test('should handle complex computed signal with error and async dependencies', async () => {
|
|
311
|
-
const toggleState =
|
|
312
|
-
const errorProne =
|
|
312
|
+
const toggleState = new State(true)
|
|
313
|
+
const errorProne = new Memo(() => {
|
|
313
314
|
if (toggleState.get()) throw new Error('Intentional error')
|
|
314
315
|
return 42
|
|
315
316
|
})
|
|
316
|
-
const asyncValue =
|
|
317
|
+
const asyncValue = new Task(async () => {
|
|
317
318
|
await wait(50)
|
|
318
319
|
return 10
|
|
319
320
|
})
|
|
@@ -322,7 +323,7 @@ describe('Computed', () => {
|
|
|
322
323
|
let errCount = 0
|
|
323
324
|
// let _result: number = 0
|
|
324
325
|
|
|
325
|
-
const complexComputed =
|
|
326
|
+
const complexComputed = new Memo(() => {
|
|
326
327
|
try {
|
|
327
328
|
const x = errorProne.get()
|
|
328
329
|
const y = asyncValue.get()
|
|
@@ -355,9 +356,9 @@ describe('Computed', () => {
|
|
|
355
356
|
})
|
|
356
357
|
|
|
357
358
|
test('should handle signal changes during async computation', async () => {
|
|
358
|
-
const source =
|
|
359
|
+
const source = new State(1)
|
|
359
360
|
let computationCount = 0
|
|
360
|
-
const derived =
|
|
361
|
+
const derived = new Task(async (_, abort) => {
|
|
361
362
|
computationCount++
|
|
362
363
|
expect(abort?.aborted).toBe(false)
|
|
363
364
|
await wait(100)
|
|
@@ -376,9 +377,9 @@ describe('Computed', () => {
|
|
|
376
377
|
})
|
|
377
378
|
|
|
378
379
|
test('should handle multiple rapid changes during async computation', async () => {
|
|
379
|
-
const source =
|
|
380
|
+
const source = new State(1)
|
|
380
381
|
let computationCount = 0
|
|
381
|
-
const derived =
|
|
382
|
+
const derived = new Task(async (_, abort) => {
|
|
382
383
|
computationCount++
|
|
383
384
|
expect(abort?.aborted).toBe(false)
|
|
384
385
|
await wait(100)
|
|
@@ -401,8 +402,8 @@ describe('Computed', () => {
|
|
|
401
402
|
})
|
|
402
403
|
|
|
403
404
|
test('should handle errors in aborted computations', async () => {
|
|
404
|
-
const source =
|
|
405
|
-
const derived =
|
|
405
|
+
const source = new State(1)
|
|
406
|
+
const derived = new Task(async () => {
|
|
406
407
|
await wait(100)
|
|
407
408
|
const value = source.get()
|
|
408
409
|
if (value === 2) throw new Error('Intentional error')
|
|
@@ -427,62 +428,72 @@ describe('Computed', () => {
|
|
|
427
428
|
test('should throw InvalidCallbackError when callback is not a function', () => {
|
|
428
429
|
expect(() => {
|
|
429
430
|
// @ts-expect-error - Testing invalid input
|
|
430
|
-
|
|
431
|
-
}).toThrow('Invalid
|
|
431
|
+
new Memo(null)
|
|
432
|
+
}).toThrow('Invalid memo callback null')
|
|
432
433
|
|
|
433
434
|
expect(() => {
|
|
434
435
|
// @ts-expect-error - Testing invalid input
|
|
435
|
-
|
|
436
|
-
}).toThrow('Invalid
|
|
436
|
+
new Memo(undefined)
|
|
437
|
+
}).toThrow('Invalid memo callback undefined')
|
|
437
438
|
|
|
438
439
|
expect(() => {
|
|
439
440
|
// @ts-expect-error - Testing invalid input
|
|
440
|
-
|
|
441
|
-
}).toThrow('Invalid
|
|
441
|
+
new Memo(42)
|
|
442
|
+
}).toThrow('Invalid memo callback 42')
|
|
442
443
|
|
|
443
444
|
expect(() => {
|
|
444
445
|
// @ts-expect-error - Testing invalid input
|
|
445
|
-
|
|
446
|
-
}).toThrow('Invalid
|
|
446
|
+
new Memo('not a function')
|
|
447
|
+
}).toThrow('Invalid memo callback "not a function"')
|
|
447
448
|
|
|
448
449
|
expect(() => {
|
|
449
450
|
// @ts-expect-error - Testing invalid input
|
|
450
|
-
|
|
451
|
-
}).toThrow('Invalid
|
|
451
|
+
new Memo({ not: 'a function' })
|
|
452
|
+
}).toThrow('Invalid memo callback {"not":"a function"}')
|
|
452
453
|
|
|
453
454
|
expect(() => {
|
|
454
455
|
// @ts-expect-error - Testing invalid input
|
|
455
|
-
|
|
456
|
-
}).toThrow('Invalid
|
|
456
|
+
new Memo((_a: unknown, _b: unknown, _c: unknown) => 42)
|
|
457
|
+
}).toThrow('Invalid memo callback (_a, _b, _c) => 42')
|
|
458
|
+
|
|
459
|
+
expect(() => {
|
|
460
|
+
// @ts-expect-error - Testing invalid input
|
|
461
|
+
new Memo(async (_a: unknown, _b: unknown) => 42)
|
|
462
|
+
}).toThrow('Invalid memo callback async (_a, _b) => 42')
|
|
463
|
+
|
|
464
|
+
expect(() => {
|
|
465
|
+
// @ts-expect-error - Testing invalid input
|
|
466
|
+
new Task((_a: unknown) => 42)
|
|
467
|
+
}).toThrow('Invalid task callback (_a) => 42')
|
|
457
468
|
})
|
|
458
469
|
|
|
459
470
|
test('should throw NullishSignalValueError when initialValue is null', () => {
|
|
460
471
|
expect(() => {
|
|
461
472
|
// @ts-expect-error - Testing invalid input
|
|
462
|
-
|
|
463
|
-
}).toThrow('Nullish signal values are not allowed in
|
|
473
|
+
new Memo(() => 42, null)
|
|
474
|
+
}).toThrow('Nullish signal values are not allowed in memo')
|
|
464
475
|
})
|
|
465
476
|
|
|
466
477
|
test('should throw specific error types for invalid inputs', () => {
|
|
467
478
|
try {
|
|
468
479
|
// @ts-expect-error - Testing invalid input
|
|
469
|
-
|
|
480
|
+
new Memo(null)
|
|
470
481
|
expect(true).toBe(false) // Should not reach here
|
|
471
482
|
} catch (error) {
|
|
472
483
|
expect(error).toBeInstanceOf(TypeError)
|
|
473
484
|
expect(error.name).toBe('InvalidCallbackError')
|
|
474
|
-
expect(error.message).toBe('Invalid
|
|
485
|
+
expect(error.message).toBe('Invalid memo callback null')
|
|
475
486
|
}
|
|
476
487
|
|
|
477
488
|
try {
|
|
478
489
|
// @ts-expect-error - Testing invalid input
|
|
479
|
-
|
|
490
|
+
new Memo(() => 42, null)
|
|
480
491
|
expect(true).toBe(false) // Should not reach here
|
|
481
492
|
} catch (error) {
|
|
482
493
|
expect(error).toBeInstanceOf(TypeError)
|
|
483
494
|
expect(error.name).toBe('NullishSignalValueError')
|
|
484
495
|
expect(error.message).toBe(
|
|
485
|
-
'Nullish signal values are not allowed in
|
|
496
|
+
'Nullish signal values are not allowed in memo',
|
|
486
497
|
)
|
|
487
498
|
}
|
|
488
499
|
})
|
|
@@ -490,40 +501,37 @@ describe('Computed', () => {
|
|
|
490
501
|
test('should allow valid callbacks and non-nullish initialValues', () => {
|
|
491
502
|
// These should not throw
|
|
492
503
|
expect(() => {
|
|
493
|
-
|
|
504
|
+
new Memo(() => 42)
|
|
494
505
|
}).not.toThrow()
|
|
495
506
|
|
|
496
507
|
expect(() => {
|
|
497
|
-
|
|
508
|
+
new Memo(() => 42, 0)
|
|
498
509
|
}).not.toThrow()
|
|
499
510
|
|
|
500
511
|
expect(() => {
|
|
501
|
-
|
|
512
|
+
new Memo(() => 'foo', '')
|
|
502
513
|
}).not.toThrow()
|
|
503
514
|
|
|
504
515
|
expect(() => {
|
|
505
|
-
|
|
516
|
+
new Memo(() => true, false)
|
|
506
517
|
}).not.toThrow()
|
|
507
518
|
|
|
508
519
|
expect(() => {
|
|
509
|
-
|
|
520
|
+
new Task(async () => ({ id: 42, name: 'John' }), UNSET)
|
|
510
521
|
}).not.toThrow()
|
|
511
522
|
})
|
|
512
523
|
})
|
|
513
524
|
|
|
514
525
|
describe('Initial Value and Old Value', () => {
|
|
515
526
|
test('should use initialValue when provided', () => {
|
|
516
|
-
const computed =
|
|
517
|
-
(oldValue: number) => oldValue + 1,
|
|
518
|
-
10,
|
|
519
|
-
)
|
|
527
|
+
const computed = new Memo((oldValue: number) => oldValue + 1, 10)
|
|
520
528
|
expect(computed.get()).toBe(11)
|
|
521
529
|
})
|
|
522
530
|
|
|
523
531
|
test('should pass current value as oldValue to callback', () => {
|
|
524
|
-
const state =
|
|
532
|
+
const state = new State(5)
|
|
525
533
|
let receivedOldValue: number | undefined
|
|
526
|
-
const computed =
|
|
534
|
+
const computed = new Memo((oldValue: number) => {
|
|
527
535
|
receivedOldValue = oldValue
|
|
528
536
|
return state.get() * 2
|
|
529
537
|
}, 0)
|
|
@@ -537,8 +545,8 @@ describe('Computed', () => {
|
|
|
537
545
|
})
|
|
538
546
|
|
|
539
547
|
test('should work as reducer function with oldValue', () => {
|
|
540
|
-
const increment =
|
|
541
|
-
const sum =
|
|
548
|
+
const increment = new State(0)
|
|
549
|
+
const sum = new Memo((oldValue: number) => {
|
|
542
550
|
const inc = increment.get()
|
|
543
551
|
return inc === 0 ? oldValue : oldValue + inc
|
|
544
552
|
}, 0)
|
|
@@ -556,8 +564,8 @@ describe('Computed', () => {
|
|
|
556
564
|
})
|
|
557
565
|
|
|
558
566
|
test('should handle array accumulation with oldValue', () => {
|
|
559
|
-
const item =
|
|
560
|
-
const items =
|
|
567
|
+
const item = new State('')
|
|
568
|
+
const items = new Memo((oldValue: string[]) => {
|
|
561
569
|
const newItem = item.get()
|
|
562
570
|
return newItem === '' ? oldValue : [...oldValue, newItem]
|
|
563
571
|
}, [] as string[])
|
|
@@ -575,9 +583,9 @@ describe('Computed', () => {
|
|
|
575
583
|
})
|
|
576
584
|
|
|
577
585
|
test('should handle counter with oldValue and multiple dependencies', () => {
|
|
578
|
-
const reset =
|
|
579
|
-
const add =
|
|
580
|
-
const counter =
|
|
586
|
+
const reset = new State(false)
|
|
587
|
+
const add = new State(0)
|
|
588
|
+
const counter = new Memo((oldValue: number) => {
|
|
581
589
|
if (reset.get()) return 0
|
|
582
590
|
const increment = add.get()
|
|
583
591
|
return increment === 0 ? oldValue : oldValue + increment
|
|
@@ -601,8 +609,8 @@ describe('Computed', () => {
|
|
|
601
609
|
|
|
602
610
|
test('should pass UNSET as oldValue when no initialValue provided', () => {
|
|
603
611
|
let receivedOldValue: number | undefined
|
|
604
|
-
const state =
|
|
605
|
-
const computed =
|
|
612
|
+
const state = new State(42)
|
|
613
|
+
const computed = new Memo((oldValue: number) => {
|
|
606
614
|
receivedOldValue = oldValue
|
|
607
615
|
return state.get()
|
|
608
616
|
})
|
|
@@ -614,7 +622,7 @@ describe('Computed', () => {
|
|
|
614
622
|
test('should work with async computation and oldValue', async () => {
|
|
615
623
|
let receivedOldValue: number | undefined
|
|
616
624
|
|
|
617
|
-
const asyncComputed =
|
|
625
|
+
const asyncComputed = new Task(async (oldValue: number) => {
|
|
618
626
|
receivedOldValue = oldValue
|
|
619
627
|
await wait(50)
|
|
620
628
|
return oldValue + 5
|
|
@@ -630,9 +638,9 @@ describe('Computed', () => {
|
|
|
630
638
|
})
|
|
631
639
|
|
|
632
640
|
test('should handle object updates with oldValue', () => {
|
|
633
|
-
const key =
|
|
634
|
-
const value =
|
|
635
|
-
const obj =
|
|
641
|
+
const key = new State('')
|
|
642
|
+
const value = new State('')
|
|
643
|
+
const obj = new Memo(
|
|
636
644
|
(oldValue: Record<string, string>) => {
|
|
637
645
|
const k = key.get()
|
|
638
646
|
const v = value.get()
|
|
@@ -654,11 +662,11 @@ describe('Computed', () => {
|
|
|
654
662
|
})
|
|
655
663
|
|
|
656
664
|
test('should handle async computation with AbortSignal and oldValue', async () => {
|
|
657
|
-
const source =
|
|
665
|
+
const source = new State(1)
|
|
658
666
|
let computationCount = 0
|
|
659
667
|
const receivedOldValues: number[] = []
|
|
660
668
|
|
|
661
|
-
const asyncComputed =
|
|
669
|
+
const asyncComputed = new Task(
|
|
662
670
|
async (oldValue: number, abort: AbortSignal) => {
|
|
663
671
|
computationCount++
|
|
664
672
|
receivedOldValues.push(oldValue)
|
|
@@ -692,10 +700,10 @@ describe('Computed', () => {
|
|
|
692
700
|
})
|
|
693
701
|
|
|
694
702
|
test('should work with error handling and oldValue', () => {
|
|
695
|
-
const shouldError =
|
|
696
|
-
const counter =
|
|
703
|
+
const shouldError = new State(false)
|
|
704
|
+
const counter = new State(1)
|
|
697
705
|
|
|
698
|
-
const computed =
|
|
706
|
+
const computed = new Memo((oldValue: number) => {
|
|
699
707
|
if (shouldError.get()) {
|
|
700
708
|
throw new Error('Computation failed')
|
|
701
709
|
}
|
|
@@ -722,12 +730,12 @@ describe('Computed', () => {
|
|
|
722
730
|
})
|
|
723
731
|
|
|
724
732
|
test('should work with complex state transitions using oldValue', () => {
|
|
725
|
-
const action =
|
|
733
|
+
const action = new State<
|
|
726
734
|
'increment' | 'decrement' | 'reset' | 'multiply'
|
|
727
735
|
>('increment')
|
|
728
|
-
const amount =
|
|
736
|
+
const amount = new State(1)
|
|
729
737
|
|
|
730
|
-
const calculator =
|
|
738
|
+
const calculator = new Memo((oldValue: number) => {
|
|
731
739
|
const act = action.get()
|
|
732
740
|
const amt = amount.get()
|
|
733
741
|
|
|
@@ -764,7 +772,7 @@ describe('Computed', () => {
|
|
|
764
772
|
|
|
765
773
|
test('should handle edge cases with initialValue and oldValue', () => {
|
|
766
774
|
// Test with null/undefined-like values
|
|
767
|
-
const nullishComputed =
|
|
775
|
+
const nullishComputed = new Memo((oldValue: string) => {
|
|
768
776
|
return `${oldValue} updated`
|
|
769
777
|
}, '')
|
|
770
778
|
|
|
@@ -778,7 +786,7 @@ describe('Computed', () => {
|
|
|
778
786
|
}
|
|
779
787
|
|
|
780
788
|
const now = new Date()
|
|
781
|
-
const objectComputed =
|
|
789
|
+
const objectComputed = new Memo(
|
|
782
790
|
(oldValue: StateObject) => ({
|
|
783
791
|
...oldValue,
|
|
784
792
|
count: oldValue.count + 1,
|
|
@@ -799,14 +807,14 @@ describe('Computed', () => {
|
|
|
799
807
|
|
|
800
808
|
test('should preserve initialValue type consistency', () => {
|
|
801
809
|
// Test that oldValue type is consistent with initialValue
|
|
802
|
-
const stringComputed =
|
|
810
|
+
const stringComputed = new Memo((oldValue: string) => {
|
|
803
811
|
expect(typeof oldValue).toBe('string')
|
|
804
812
|
return oldValue.toUpperCase()
|
|
805
813
|
}, 'hello')
|
|
806
814
|
|
|
807
815
|
expect(stringComputed.get()).toBe('HELLO')
|
|
808
816
|
|
|
809
|
-
const numberComputed =
|
|
817
|
+
const numberComputed = new Memo((oldValue: number) => {
|
|
810
818
|
expect(typeof oldValue).toBe('number')
|
|
811
819
|
expect(Number.isFinite(oldValue)).toBe(true)
|
|
812
820
|
return oldValue * 2
|
|
@@ -816,14 +824,14 @@ describe('Computed', () => {
|
|
|
816
824
|
})
|
|
817
825
|
|
|
818
826
|
test('should work with chained computed using oldValue', () => {
|
|
819
|
-
const source =
|
|
827
|
+
const source = new State(1)
|
|
820
828
|
|
|
821
|
-
const first =
|
|
829
|
+
const first = new Memo(
|
|
822
830
|
(oldValue: number) => oldValue + source.get(),
|
|
823
831
|
10,
|
|
824
832
|
)
|
|
825
833
|
|
|
826
|
-
const second =
|
|
834
|
+
const second = new Memo(
|
|
827
835
|
(oldValue: number) => oldValue + first.get(),
|
|
828
836
|
20,
|
|
829
837
|
)
|
|
@@ -837,10 +845,10 @@ describe('Computed', () => {
|
|
|
837
845
|
})
|
|
838
846
|
|
|
839
847
|
test('should handle frequent updates with oldValue correctly', () => {
|
|
840
|
-
const trigger =
|
|
848
|
+
const trigger = new State(0)
|
|
841
849
|
let computationCount = 0
|
|
842
850
|
|
|
843
|
-
const accumulator =
|
|
851
|
+
const accumulator = new Memo((oldValue: number) => {
|
|
844
852
|
computationCount++
|
|
845
853
|
return oldValue + trigger.get()
|
|
846
854
|
}, 100)
|
package/test/diff.test.ts
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
isEqual,
|
|
6
6
|
UNSET,
|
|
7
7
|
type UnknownRecord,
|
|
8
|
-
} from '
|
|
8
|
+
} from '../index.ts'
|
|
9
9
|
|
|
10
10
|
describe('diff', () => {
|
|
11
11
|
describe('basic object diffing', () => {
|
|
@@ -379,7 +379,7 @@ describe('diff', () => {
|
|
|
379
379
|
type OptionalKeysType = {
|
|
380
380
|
required: string
|
|
381
381
|
optional?: number
|
|
382
|
-
maybeUndefined?: string
|
|
382
|
+
maybeUndefined?: string
|
|
383
383
|
}
|
|
384
384
|
|
|
385
385
|
test('should handle optional keys correctly', () => {
|