@zeix/cause-effect 0.14.1 → 0.14.2

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/src/signal.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { isState, state } from './state'
2
1
  import {
3
2
  type ComputedCallback,
3
+ computed,
4
4
  isComputed,
5
5
  isComputedCallback,
6
- computed,
7
6
  } from './computed'
7
+ import { isState, state } from './state'
8
8
 
9
9
  /* === Types === */
10
10
 
@@ -13,12 +13,13 @@ type Signal<T extends {}> = {
13
13
  }
14
14
  type MaybeSignal<T extends {}> = T | Signal<T> | ComputedCallback<T>
15
15
 
16
- type SignalValues<S extends Signal<{}>[]> = {
16
+ type SignalValues<S extends Signal<unknown & {}>[]> = {
17
17
  [K in keyof S]: S[K] extends Signal<infer T> ? T : never
18
18
  }
19
19
 
20
20
  /* === Constants === */
21
21
 
22
+ // biome-ignore lint/suspicious/noExplicitAny: Deliberately using any to be used as a placeholder value in any signal
22
23
  const UNSET: any = Symbol()
23
24
 
24
25
  /* === Functions === */
package/src/util.ts CHANGED
@@ -15,7 +15,7 @@ const toError = (reason: unknown): Error =>
15
15
  class CircularDependencyError extends Error {
16
16
  constructor(where: string) {
17
17
  super(`Circular dependency in ${where} detected`)
18
- return this
18
+ this.name = 'CircularDependencyError'
19
19
  }
20
20
  }
21
21
 
@@ -1,14 +1,14 @@
1
- import { describe, test, expect } from 'bun:test'
2
- import { state, computed, batch, effect } from '../'
1
+ import { describe, expect, test } from 'bun:test'
2
+ import { batch, computed, effect, state } from '../'
3
3
 
4
4
  /* === Tests === */
5
5
 
6
- describe('Batch', function () {
7
- test('should be triggered only once after repeated state change', function () {
6
+ describe('Batch', () => {
7
+ test('should be triggered only once after repeated state change', () => {
8
8
  const cause = state(0)
9
9
  let result = 0
10
10
  let count = 0
11
- effect(() => {
11
+ effect((): undefined => {
12
12
  result = cause.get()
13
13
  count++
14
14
  })
@@ -21,7 +21,7 @@ describe('Batch', function () {
21
21
  expect(count).toBe(2) // + 1 for effect initialization
22
22
  })
23
23
 
24
- test('should be triggered only once when multiple signals are set', function () {
24
+ test('should be triggered only once when multiple signals are set', () => {
25
25
  const a = state(3)
26
26
  const b = state(4)
27
27
  const c = state(5)
@@ -30,11 +30,11 @@ describe('Batch', function () {
30
30
  let count = 0
31
31
  effect({
32
32
  signals: [sum],
33
- ok: res => {
33
+ ok: (res): undefined => {
34
34
  result = res
35
35
  count++
36
36
  },
37
- err: () => {},
37
+ err: (): undefined => {},
38
38
  })
39
39
  batch(() => {
40
40
  a.set(6)
@@ -45,7 +45,7 @@ describe('Batch', function () {
45
45
  expect(count).toBe(2) // + 1 for effect initialization
46
46
  })
47
47
 
48
- test('should prove example from README works', function () {
48
+ test('should prove example from README works', () => {
49
49
  // State: define an array of Signal<number>
50
50
  const signals = [state(2), state(3), state(5)]
51
51
 
@@ -63,12 +63,12 @@ describe('Batch', function () {
63
63
  // Effect: switch cases for the result
64
64
  effect({
65
65
  signals: [sum],
66
- ok: v => {
66
+ ok: (v): undefined => {
67
67
  result = v
68
68
  okCount++
69
69
  // console.log('Sum:', v)
70
70
  },
71
- err: _error => {
71
+ err: (_error): undefined => {
72
72
  errCount++
73
73
  // console.error('Error:', error)
74
74
  },
@@ -1,10 +1,7 @@
1
- import { describe, test, expect, mock } from 'bun:test'
2
- import { state, computed, effect, batch } from '../'
3
- import { makeGraph, runGraph, Counter } from './util/dependency-graph'
4
- import {
5
- type ReactiveFramework,
6
- type Computed,
7
- } from './util/reactive-framework'
1
+ import { describe, expect, mock, test } from 'bun:test'
2
+ import { batch, computed, effect, state } from '../'
3
+ import { Counter, makeGraph, runGraph } from './util/dependency-graph'
4
+ import type { Computed, ReactiveFramework } from './util/reactive-framework'
8
5
 
9
6
  /* === Utility Functions === */
10
7
 
@@ -30,8 +27,8 @@ const framework = {
30
27
  read: () => c.get(),
31
28
  }
32
29
  },
33
- effect: (fn: () => void) => effect(fn),
34
- withBatch: (fn: () => void) => batch(fn),
30
+ effect: (fn: () => undefined) => effect(fn),
31
+ withBatch: (fn: () => undefined) => batch(fn),
35
32
  withBuild: <T>(fn: () => T) => fn(),
36
33
  }
37
34
  const testPullCounts = true
@@ -53,7 +50,7 @@ function makeConfig() {
53
50
  /** some basic tests to validate the reactive framework
54
51
  * wrapper works and can run performance tests.
55
52
  */
56
- describe('Basic test', function () {
53
+ describe('Basic test', () => {
57
54
  const name = framework.name
58
55
  test(`${name} | simple dependency executes`, () => {
59
56
  framework.withBuild(() => {
@@ -145,11 +142,11 @@ describe('Basic test', function () {
145
142
  })
146
143
 
147
144
  test(`${name} | effect`, () => {
148
- const spy = _v => {}
145
+ const spy = (_v: number) => {}
149
146
  const spyMock = mock(spy)
150
147
 
151
148
  const s = framework.signal(2)
152
- let c: any
149
+ let c: { read: () => number } = { read: () => 0 }
153
150
 
154
151
  framework.withBuild(() => {
155
152
  c = framework.computed(() => s.read() * 2)
@@ -169,18 +166,22 @@ describe('Basic test', function () {
169
166
  })
170
167
  })
171
168
 
172
- describe('Kairo tests', function () {
169
+ describe('Kairo tests', () => {
173
170
  const name = framework.name
174
171
 
175
172
  test(`${name} | avoidable propagation`, () => {
176
173
  const head = framework.signal(0)
177
174
  const computed1 = framework.computed(() => head.read())
178
- const computed2 = framework.computed(() => (computed1.read(), 0))
179
- const computed3 = framework.computed(
180
- () => (busy(), computed2.read()! + 1),
181
- ) // heavy computation
182
- const computed4 = framework.computed(() => computed3.read()! + 2)
183
- const computed5 = framework.computed(() => computed4.read()! + 3)
175
+ const computed2 = framework.computed(() => {
176
+ computed1.read()
177
+ return 0
178
+ })
179
+ const computed3 = framework.computed(() => {
180
+ busy()
181
+ return computed2.read() + 1
182
+ }) // heavy computation
183
+ const computed4 = framework.computed(() => computed3.read() + 2)
184
+ const computed5 = framework.computed(() => computed4.read() + 3)
184
185
  framework.effect(() => {
185
186
  computed5.read()
186
187
  busy() // heavy side effect
@@ -206,10 +207,10 @@ describe('Kairo tests', function () {
206
207
  const callCounter = new Counter()
207
208
  for (let i = 0; i < 50; i++) {
208
209
  const current = framework.computed(() => {
209
- return head.read()! + i
210
+ return head.read() + i
210
211
  })
211
212
  const current2 = framework.computed(() => {
212
- return current.read()! + 1
213
+ return current.read() + 1
213
214
  })
214
215
  framework.effect(() => {
215
216
  current2.read()
@@ -267,7 +268,7 @@ describe('Kairo tests', function () {
267
268
  }
268
269
  })
269
270
 
270
- test(`${name} | diamond`, function () {
271
+ test(`${name} | diamond`, () => {
271
272
  const width = 5
272
273
  const head = framework.signal(0)
273
274
  const current: { read(): number }[] = []
@@ -300,7 +301,7 @@ describe('Kairo tests', function () {
300
301
  }
301
302
  })
302
303
 
303
- test(`${name} | mux`, function () {
304
+ test(`${name} | mux`, () => {
304
305
  const heads = new Array(100).fill(null).map(_ => framework.signal(0))
305
306
  const mux = framework.computed(() => {
306
307
  return Object.fromEntries(heads.map(h => h.read()).entries())
@@ -310,7 +311,9 @@ describe('Kairo tests', function () {
310
311
  .map(x => framework.computed(() => x.read() + 1))
311
312
 
312
313
  splited.forEach(x => {
313
- framework.effect(() => x.read())
314
+ framework.effect(() => {
315
+ x.read()
316
+ })
314
317
  })
315
318
 
316
319
  return () => {
@@ -329,7 +332,7 @@ describe('Kairo tests', function () {
329
332
  }
330
333
  })
331
334
 
332
- test(`${name} | repeated observers`, function () {
335
+ test(`${name} | repeated observers`, () => {
333
336
  const size = 30
334
337
  const head = framework.signal(0)
335
338
  const current = framework.computed(() => {
@@ -362,7 +365,7 @@ describe('Kairo tests', function () {
362
365
  }
363
366
  })
364
367
 
365
- test(`${name} | triangle`, function () {
368
+ test(`${name} | triangle`, () => {
366
369
  const width = 10
367
370
  const head = framework.signal(0)
368
371
  let current = head as { read: () => number }
@@ -407,7 +410,7 @@ describe('Kairo tests', function () {
407
410
  }
408
411
  })
409
412
 
410
- test(`${name} | unstable`, function () {
413
+ test(`${name} | unstable`, () => {
411
414
  const head = framework.signal(0)
412
415
  const double = framework.computed(() => head.read() * 2)
413
416
  const inverse = framework.computed(() => -head.read())
@@ -442,10 +445,10 @@ describe('Kairo tests', function () {
442
445
  })
443
446
  })
444
447
 
445
- describe('$mol_wire tests', function () {
448
+ describe('$mol_wire tests', () => {
446
449
  const name = framework.name
447
450
 
448
- test(`${name} | $mol_wire benchmark`, function () {
451
+ test(`${name} | $mol_wire benchmark`, () => {
449
452
  const fib = (n: number) => {
450
453
  if (n < 2) return 1
451
454
  return fib(n - 1) + fib(n - 2)
@@ -454,7 +457,7 @@ describe('$mol_wire tests', function () {
454
457
  return n + fib(16)
455
458
  }
456
459
  const numbers = Array.from({ length: 5 }, (_, i) => i)
457
- const res: (() => any)[] = []
460
+ const res: (() => unknown)[] = []
458
461
  framework.withBuild(() => {
459
462
  const A = framework.signal(0)
460
463
  const B = framework.signal(0)
@@ -475,12 +478,24 @@ describe('$mol_wire tests', function () {
475
478
  D.read()[4].x +
476
479
  F.read(),
477
480
  )
478
- framework.effect(() => res.push(hard(G.read(), 'H')))
479
- framework.effect(() => res.push(G.read())) // I
480
- framework.effect(() => res.push(hard(F.read(), 'J')))
481
- framework.effect(() => (res[0] = hard(G.read(), 'H')))
482
- framework.effect(() => (res[1] = G.read())) // I
483
- framework.effect(() => (res[2] = hard(F.read(), 'J')))
481
+ framework.effect(() => {
482
+ res.push(hard(G.read(), 'H'))
483
+ })
484
+ framework.effect(() => {
485
+ res.push(G.read())
486
+ }) // I
487
+ framework.effect(() => {
488
+ res.push(hard(F.read(), 'J'))
489
+ })
490
+ framework.effect(() => {
491
+ res[0] = hard(G.read(), 'H')
492
+ })
493
+ framework.effect(() => {
494
+ res[1] = G.read()
495
+ }) // I
496
+ framework.effect(() => {
497
+ res[2] = hard(F.read(), 'J')
498
+ })
484
499
 
485
500
  return (i: number) => {
486
501
  res.length = 0
@@ -499,10 +514,10 @@ describe('$mol_wire tests', function () {
499
514
  })
500
515
  })
501
516
 
502
- describe('CellX tests', function () {
517
+ describe('CellX tests', () => {
503
518
  const name = framework.name
504
519
 
505
- test(`${name} | CellX benchmark`, function () {
520
+ test(`${name} | CellX benchmark`, () => {
506
521
  const expected = {
507
522
  10: [
508
523
  [3, 6, 2, -2],
@@ -540,10 +555,31 @@ describe('CellX tests', function () {
540
555
  prop4: framework.computed(() => m.prop3.read()),
541
556
  }
542
557
 
543
- framework.effect(() => s.prop1.read())
544
- framework.effect(() => s.prop2.read())
545
- framework.effect(() => s.prop3.read())
546
- framework.effect(() => s.prop4.read())
558
+ framework.effect(() => {
559
+ s.prop1.read()
560
+ })
561
+ framework.effect(() => {
562
+ s.prop2.read()
563
+ })
564
+ framework.effect(() => {
565
+ s.prop3.read()
566
+ })
567
+ framework.effect(() => {
568
+ s.prop4.read()
569
+ })
570
+
571
+ framework.effect(() => {
572
+ s.prop1.read()
573
+ })
574
+ framework.effect(() => {
575
+ s.prop2.read()
576
+ })
577
+ framework.effect(() => {
578
+ s.prop3.read()
579
+ })
580
+ framework.effect(() => {
581
+ s.prop4.read()
582
+ })
547
583
 
548
584
  s.prop1.read()
549
585
  s.prop2.read()
@@ -1,11 +1,11 @@
1
- import { describe, test, expect } from 'bun:test'
1
+ import { describe, expect, test } from 'bun:test'
2
2
  import {
3
- state,
4
3
  computed,
5
- UNSET,
4
+ effect,
6
5
  isComputed,
7
6
  isState,
8
- effect,
7
+ state,
8
+ UNSET,
9
9
  } from '../index.ts'
10
10
 
11
11
  /* === Utility Functions === */
@@ -15,7 +15,7 @@ const increment = (n: number) => (Number.isFinite(n) ? n + 1 : UNSET)
15
15
 
16
16
  /* === Tests === */
17
17
 
18
- describe('Computed', function () {
18
+ describe('Computed', () => {
19
19
  test('should identify computed signals with isComputed()', () => {
20
20
  const count = state(42)
21
21
  const doubled = computed(() => count.get() * 2)
@@ -23,25 +23,25 @@ describe('Computed', function () {
23
23
  expect(isState(doubled)).toBe(false)
24
24
  })
25
25
 
26
- test('should compute a function', function () {
26
+ test('should compute a function', () => {
27
27
  const derived = computed(() => 1 + 2)
28
28
  expect(derived.get()).toBe(3)
29
29
  })
30
30
 
31
- test('should compute function dependent on a signal', function () {
31
+ test('should compute function dependent on a signal', () => {
32
32
  const cause = state(42)
33
33
  const derived = computed(() => cause.get() + 1)
34
34
  expect(derived.get()).toBe(43)
35
35
  })
36
36
 
37
- test('should compute function dependent on an updated signal', function () {
37
+ test('should compute function dependent on an updated signal', () => {
38
38
  const cause = state(42)
39
39
  const derived = computed(() => cause.get() + 1)
40
40
  cause.set(24)
41
41
  expect(derived.get()).toBe(25)
42
42
  })
43
43
 
44
- test('should compute function dependent on an async signal', async function () {
44
+ test('should compute function dependent on an async signal', async () => {
45
45
  const status = state('pending')
46
46
  const promised = computed(async () => {
47
47
  await wait(100)
@@ -56,7 +56,7 @@ describe('Computed', function () {
56
56
  expect(status.get()).toBe('success')
57
57
  })
58
58
 
59
- test('should handle errors from an async signal gracefully', async function () {
59
+ test('should handle errors from an async signal gracefully', async () => {
60
60
  const status = state('pending')
61
61
  const error = state('')
62
62
  const promised = computed(async () => {
@@ -73,7 +73,7 @@ describe('Computed', function () {
73
73
  expect(status.get()).toBe('error')
74
74
  })
75
75
 
76
- test('should compute task signals in parallel without waterfalls', async function () {
76
+ test('should compute task signals in parallel without waterfalls', async () => {
77
77
  const a = computed(async () => {
78
78
  await wait(100)
79
79
  return 10
@@ -94,7 +94,7 @@ describe('Computed', function () {
94
94
  expect(c.get()).toBe(30)
95
95
  })
96
96
 
97
- test('should compute function dependent on a chain of computed states dependent on a signal', function () {
97
+ test('should compute function dependent on a chain of computed states dependent on a signal', () => {
98
98
  const x = state(42)
99
99
  const a = computed(() => x.get() + 1)
100
100
  const b = computed(() => a.get() * 2)
@@ -102,7 +102,7 @@ describe('Computed', function () {
102
102
  expect(c.get()).toBe(87)
103
103
  })
104
104
 
105
- test('should compute function dependent on a chain of computed states dependent on an updated signal', function () {
105
+ test('should compute function dependent on a chain of computed states dependent on an updated signal', () => {
106
106
  const x = state(42)
107
107
  const a = computed(() => x.get() + 1)
108
108
  const b = computed(() => a.get() * 2)
@@ -111,14 +111,14 @@ describe('Computed', function () {
111
111
  expect(c.get()).toBe(51)
112
112
  })
113
113
 
114
- test('should drop X->B->X updates', function () {
114
+ test('should drop X->B->X updates', () => {
115
115
  let count = 0
116
116
  const x = state(2)
117
117
  const a = computed(() => x.get() - 1)
118
118
  const b = computed(() => x.get() + a.get())
119
119
  const c = computed(() => {
120
120
  count++
121
- return 'c: ' + b.get()
121
+ return `c: ${b.get()}`
122
122
  })
123
123
  expect(c.get()).toBe('c: 3')
124
124
  expect(count).toBe(1)
@@ -127,7 +127,7 @@ describe('Computed', function () {
127
127
  expect(count).toBe(2)
128
128
  })
129
129
 
130
- test('should only update every signal once (diamond graph)', function () {
130
+ test('should only update every signal once (diamond graph)', () => {
131
131
  let count = 0
132
132
  const x = state('a')
133
133
  const a = computed(() => x.get())
@@ -144,12 +144,12 @@ describe('Computed', function () {
144
144
  expect(count).toBe(2)
145
145
  })
146
146
 
147
- test('should only update every signal once (diamond graph + tail)', function () {
147
+ test('should only update every signal once (diamond graph + tail)', () => {
148
148
  let count = 0
149
149
  const x = state('a')
150
150
  const a = computed(() => x.get())
151
151
  const b = computed(() => x.get())
152
- const c = computed(() => a.get() + ' ' + b.get())
152
+ const c = computed(() => `${a.get()} ${b.get()}`)
153
153
  const d = computed(() => {
154
154
  count++
155
155
  return c.get()
@@ -161,7 +161,7 @@ describe('Computed', function () {
161
161
  expect(count).toBe(2)
162
162
  })
163
163
 
164
- test('should update multiple times after multiple state changes', function () {
164
+ test('should update multiple times after multiple state changes', () => {
165
165
  const a = state(3)
166
166
  const b = state(4)
167
167
  let count = 0
@@ -185,7 +185,7 @@ describe('Computed', function () {
185
185
  * one-time performance cost that allows for efficient memoization and
186
186
  * error handling in most cases.
187
187
  */
188
- test('should bail out if result is the same', function () {
188
+ test('should bail out if result is the same', () => {
189
189
  let count = 0
190
190
  const x = state('a')
191
191
  const a = computed(() => {
@@ -205,7 +205,7 @@ describe('Computed', function () {
205
205
  expect(count).toBe(2)
206
206
  })
207
207
 
208
- test('should block if result remains unchanged', function () {
208
+ test('should block if result remains unchanged', () => {
209
209
  let count = 0
210
210
  const x = state(42)
211
211
  const a = computed(() => x.get() % 2)
@@ -223,7 +223,7 @@ describe('Computed', function () {
223
223
  expect(count).toBe(2)
224
224
  })
225
225
 
226
- test('should detect and throw error for circular dependencies', function () {
226
+ test('should detect and throw error for circular dependencies', () => {
227
227
  const a = state(1)
228
228
  const b = computed(() => c.get() + 1)
229
229
  const c = computed(() => b.get() + a.get())
@@ -233,7 +233,7 @@ describe('Computed', function () {
233
233
  expect(a.get()).toBe(1)
234
234
  })
235
235
 
236
- test('should propagate error if an error occurred', function () {
236
+ test('should propagate error if an error occurred', () => {
237
237
  let okCount = 0
238
238
  let errCount = 0
239
239
  const x = state(0)
@@ -273,7 +273,7 @@ describe('Computed', function () {
273
273
  }
274
274
  })
275
275
 
276
- test('should create an effect that reacts on async computed changes', async function () {
276
+ test('should create an effect that reacts on async computed changes', async () => {
277
277
  const cause = state(42)
278
278
  const derived = computed(async () => {
279
279
  await wait(100)
@@ -284,11 +284,11 @@ describe('Computed', function () {
284
284
  let result: number = 0
285
285
  effect({
286
286
  signals: [derived],
287
- ok: v => {
287
+ ok: (v): undefined => {
288
288
  result = v
289
289
  okCount++
290
290
  },
291
- nil: () => {
291
+ nil: (): undefined => {
292
292
  nilCount++
293
293
  },
294
294
  })
@@ -303,7 +303,7 @@ describe('Computed', function () {
303
303
  expect(result).toBe(44)
304
304
  })
305
305
 
306
- test('should handle complex computed signal with error and async dependencies', async function () {
306
+ test('should handle complex computed signal with error and async dependencies', async () => {
307
307
  const toggleState = state(true)
308
308
  const errorProne = computed(() => {
309
309
  if (toggleState.get()) throw new Error('Intentional error')
@@ -350,7 +350,7 @@ describe('Computed', function () {
350
350
  expect(errCount).toBeGreaterThan(0)
351
351
  })
352
352
 
353
- test('should handle signal changes during async computation', async function () {
353
+ test('should handle signal changes during async computation', async () => {
354
354
  const source = state(1)
355
355
  let computationCount = 0
356
356
  const derived = computed(async abort => {
@@ -371,7 +371,7 @@ describe('Computed', function () {
371
371
  expect(computationCount).toBe(1)
372
372
  })
373
373
 
374
- test('should handle multiple rapid changes during async computation', async function () {
374
+ test('should handle multiple rapid changes during async computation', async () => {
375
375
  const source = state(1)
376
376
  let computationCount = 0
377
377
  const derived = computed(async abort => {
@@ -396,7 +396,7 @@ describe('Computed', function () {
396
396
  expect(computationCount).toBe(1)
397
397
  })
398
398
 
399
- test('should handle errors in aborted computations', async function () {
399
+ test('should handle errors in aborted computations', async () => {
400
400
  const source = state(1)
401
401
  const derived = computed(async () => {
402
402
  await wait(100)