@zeix/cause-effect 0.14.0 → 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.
@@ -1,9 +1,9 @@
1
- import { describe, test, expect } from 'bun:test'
1
+ import { describe, expect, test } from 'bun:test'
2
2
  import { isComputed, isState, state } from '../'
3
3
 
4
4
  /* === Tests === */
5
5
 
6
- describe('State', function () {
6
+ describe('State', () => {
7
7
  describe('State type guard', () => {
8
8
  test('isState identifies state signals', () => {
9
9
  const count = state(42)
@@ -12,108 +12,108 @@ describe('State', function () {
12
12
  })
13
13
  })
14
14
 
15
- describe('Boolean cause', function () {
16
- test('should be boolean', function () {
15
+ describe('Boolean cause', () => {
16
+ test('should be boolean', () => {
17
17
  const cause = state(false)
18
18
  expect(typeof cause.get()).toBe('boolean')
19
19
  })
20
20
 
21
- test('should set initial value to false', function () {
21
+ test('should set initial value to false', () => {
22
22
  const cause = state(false)
23
23
  expect(cause.get()).toBe(false)
24
24
  })
25
25
 
26
- test('should set initial value to true', function () {
26
+ test('should set initial value to true', () => {
27
27
  const cause = state(true)
28
28
  expect(cause.get()).toBe(true)
29
29
  })
30
30
 
31
- test('should set new value with .set(true)', function () {
31
+ test('should set new value with .set(true)', () => {
32
32
  const cause = state(false)
33
33
  cause.set(true)
34
34
  expect(cause.get()).toBe(true)
35
35
  })
36
36
 
37
- test('should toggle initial value with .set(v => !v)', function () {
37
+ test('should toggle initial value with .set(v => !v)', () => {
38
38
  const cause = state(false)
39
39
  cause.update(v => !v)
40
40
  expect(cause.get()).toBe(true)
41
41
  })
42
42
  })
43
43
 
44
- describe('Number cause', function () {
45
- test('should be number', function () {
44
+ describe('Number cause', () => {
45
+ test('should be number', () => {
46
46
  const cause = state(0)
47
47
  expect(typeof cause.get()).toBe('number')
48
48
  })
49
49
 
50
- test('should set initial value to 0', function () {
50
+ test('should set initial value to 0', () => {
51
51
  const cause = state(0)
52
52
  expect(cause.get()).toBe(0)
53
53
  })
54
54
 
55
- test('should set new value with .set(42)', function () {
55
+ test('should set new value with .set(42)', () => {
56
56
  const cause = state(0)
57
57
  cause.set(42)
58
58
  expect(cause.get()).toBe(42)
59
59
  })
60
60
 
61
- test('should increment value with .set(v => ++v)', function () {
61
+ test('should increment value with .set(v => ++v)', () => {
62
62
  const cause = state(0)
63
63
  cause.update(v => ++v)
64
64
  expect(cause.get()).toBe(1)
65
65
  })
66
66
  })
67
67
 
68
- describe('String cause', function () {
69
- test('should be string', function () {
68
+ describe('String cause', () => {
69
+ test('should be string', () => {
70
70
  const cause = state('foo')
71
71
  expect(typeof cause.get()).toBe('string')
72
72
  })
73
73
 
74
- test('should set initial value to "foo"', function () {
74
+ test('should set initial value to "foo"', () => {
75
75
  const cause = state('foo')
76
76
  expect(cause.get()).toBe('foo')
77
77
  })
78
78
 
79
- test('should set new value with .set("bar")', function () {
79
+ test('should set new value with .set("bar")', () => {
80
80
  const cause = state('foo')
81
81
  cause.set('bar')
82
82
  expect(cause.get()).toBe('bar')
83
83
  })
84
84
 
85
- test('should upper case value with .set(v => v.toUpperCase())', function () {
85
+ test('should upper case value with .set(v => v.toUpperCase())', () => {
86
86
  const cause = state('foo')
87
87
  cause.update(v => (v ? v.toUpperCase() : ''))
88
88
  expect(cause.get()).toBe('FOO')
89
89
  })
90
90
  })
91
91
 
92
- describe('Array cause', function () {
93
- test('should be array', function () {
92
+ describe('Array cause', () => {
93
+ test('should be array', () => {
94
94
  const cause = state([1, 2, 3])
95
95
  expect(Array.isArray(cause.get())).toBe(true)
96
96
  })
97
97
 
98
- test('should set initial value to [1, 2, 3]', function () {
98
+ test('should set initial value to [1, 2, 3]', () => {
99
99
  const cause = state([1, 2, 3])
100
100
  expect(cause.get()).toEqual([1, 2, 3])
101
101
  })
102
102
 
103
- test('should set new value with .set([4, 5, 6])', function () {
103
+ test('should set new value with .set([4, 5, 6])', () => {
104
104
  const cause = state([1, 2, 3])
105
105
  cause.set([4, 5, 6])
106
106
  expect(cause.get()).toEqual([4, 5, 6])
107
107
  })
108
108
 
109
- test('should reflect current value of array after modification', function () {
109
+ test('should reflect current value of array after modification', () => {
110
110
  const array = [1, 2, 3]
111
111
  const cause = state(array)
112
112
  array.push(4) // don't do this! the result will be correct, but we can't trigger effects
113
113
  expect(cause.get()).toEqual([1, 2, 3, 4])
114
114
  })
115
115
 
116
- test('should set new value with .set([...array, 4])', function () {
116
+ test('should set new value with .set([...array, 4])', () => {
117
117
  const array = [1, 2, 3]
118
118
  const cause = state(array)
119
119
  cause.set([...array, 4]) // use destructuring instead!
@@ -121,34 +121,34 @@ describe('State', function () {
121
121
  })
122
122
  })
123
123
 
124
- describe('Object cause', function () {
125
- test('should be object', function () {
124
+ describe('Object cause', () => {
125
+ test('should be object', () => {
126
126
  const cause = state({ a: 'a', b: 1 })
127
127
  expect(typeof cause.get()).toBe('object')
128
128
  })
129
129
 
130
- test('should set initial value to { a: "a", b: 1 }', function () {
130
+ test('should set initial value to { a: "a", b: 1 }', () => {
131
131
  const cause = state({ a: 'a', b: 1 })
132
132
  expect(cause.get()).toEqual({ a: 'a', b: 1 })
133
133
  })
134
134
 
135
- test('should set new value with .set({ c: true })', function () {
136
- const cause = state<Record<string, any>>({ a: 'a', b: 1 })
135
+ test('should set new value with .set({ c: true })', () => {
136
+ const cause = state<Record<string, unknown>>({ a: 'a', b: 1 })
137
137
  cause.set({ c: true })
138
138
  expect(cause.get()).toEqual({ c: true })
139
139
  })
140
140
 
141
- test('should reflect current value of object after modification', function () {
141
+ test('should reflect current value of object after modification', () => {
142
142
  const obj = { a: 'a', b: 1 }
143
- const cause = state<Record<string, any>>(obj)
143
+ const cause = state<Record<string, unknown>>(obj)
144
144
  // @ts-expect-error Property 'c' does not exist on type '{ a: string; b: number; }'. (ts 2339)
145
145
  obj.c = true // don't do this! the result will be correct, but we can't trigger effects
146
146
  expect(cause.get()).toEqual({ a: 'a', b: 1, c: true })
147
147
  })
148
148
 
149
- test('should set new value with .set({...obj, c: true})', function () {
149
+ test('should set new value with .set({...obj, c: true})', () => {
150
150
  const obj = { a: 'a', b: 1 }
151
- const cause = state<Record<string, any>>(obj)
151
+ const cause = state<Record<string, unknown>>(obj)
152
152
  cause.set({ ...obj, c: true }) // use destructuring instead!
153
153
  expect(cause.get()).toEqual({ a: 'a', b: 1, c: true })
154
154
  })
@@ -1,5 +1,5 @@
1
- import { TestResult } from './perf-tests'
2
- import { ReactiveFramework } from './reactive-framework'
1
+ import type { TestResult } from './perf-tests'
2
+ import type { ReactiveFramework } from './reactive-framework'
3
3
 
4
4
  /** Parameters for a running a performance benchmark test
5
5
  *
@@ -1,4 +1,4 @@
1
- import { FrameworkInfo, TestConfig } from './framework-types'
1
+ import type { FrameworkInfo, TestConfig } from './framework-types'
2
2
 
3
3
  export interface TestResult {
4
4
  sum: number
@@ -25,7 +25,7 @@ export function verifyBenchResult(
25
25
 
26
26
  if (expected.sum) {
27
27
  console.assert(
28
- result.sum == expected.sum,
28
+ result.sum === expected.sum,
29
29
  `sum ${framework.name} ${config.name} result:${result.sum} expected:${expected.sum}`,
30
30
  )
31
31
  }
@@ -6,7 +6,7 @@ export interface ReactiveFramework {
6
6
  name: string
7
7
  signal<T>(initialValue: T): Signal<T>
8
8
  computed<T>(fn: () => T): Computed<T>
9
- effect(fn: () => void): void
9
+ effect(fn: () => undefined): void
10
10
  withBatch<T>(fn: () => T): void
11
11
  withBuild<T>(fn: () => T): T
12
12
  }
package/src/memo.d.ts DELETED
@@ -1,13 +0,0 @@
1
- import { type Computed } from './computed';
2
- type MemoCallback<T extends {} & {
3
- then?: void;
4
- }> = () => T;
5
- /**
6
- * Create a derived signal for synchronous computations
7
- *
8
- * @since 0.14.0
9
- * @param {MemoCallback<T>} fn - synchronous computation callback
10
- * @returns {Computed<T>} - Computed signal
11
- */
12
- declare const memo: <T extends {}>(fn: MemoCallback<T>) => Computed<T>;
13
- export { type MemoCallback, memo };
package/src/memo.ts DELETED
@@ -1,91 +0,0 @@
1
- import { UNSET } from './signal'
2
- import { CircularDependencyError } from './util'
3
- import {
4
- type Cleanup,
5
- type Watcher,
6
- flush,
7
- notify,
8
- subscribe,
9
- watch,
10
- } from './scheduler'
11
- import { type Computed, TYPE_COMPUTED } from './computed'
12
-
13
- /* === Types === */
14
-
15
- type MemoCallback<T extends {} & { then?: void }> = () => T
16
-
17
- /* === Functions === */
18
-
19
- /**
20
- * Create a derived signal for synchronous computations
21
- *
22
- * @since 0.14.0
23
- * @param {MemoCallback<T>} fn - synchronous computation callback
24
- * @returns {Computed<T>} - Computed signal
25
- */
26
- const memo = <T extends {}>(fn: MemoCallback<T>): Computed<T> => {
27
- const watchers: Set<Watcher> = new Set()
28
-
29
- // Internal state - simplified for sync only
30
- let value: T = UNSET
31
- let error: Error | undefined
32
- let dirty = true
33
- let computing = false
34
-
35
- // Called when notified from sources (push)
36
- const mark = (() => {
37
- dirty = true
38
- if (watchers.size) {
39
- notify(watchers)
40
- } else {
41
- mark.cleanups.forEach(fn => fn())
42
- mark.cleanups.clear()
43
- }
44
- }) as Watcher
45
- mark.cleanups = new Set<Cleanup>()
46
-
47
- // Called when requested by dependencies (pull)
48
- const compute = () =>
49
- watch(() => {
50
- if (computing) throw new CircularDependencyError('memo')
51
- computing = true
52
- try {
53
- const result = fn()
54
- if (null == result || UNSET === result) {
55
- value = UNSET
56
- error = undefined
57
- } else {
58
- value = result
59
- dirty = false
60
- error = undefined
61
- }
62
- } catch (e) {
63
- value = UNSET
64
- error = e instanceof Error ? e : new Error(String(e))
65
- } finally {
66
- computing = false
67
- }
68
- }, mark)
69
-
70
- const c: Computed<T> = {
71
- [Symbol.toStringTag]: TYPE_COMPUTED,
72
-
73
- /**
74
- * Get the current value of the computed
75
- *
76
- * @returns {T} - current value of the computed
77
- */
78
- get: (): T => {
79
- subscribe(watchers)
80
- flush()
81
- if (dirty) compute()
82
- if (error) throw error
83
- return value
84
- },
85
- }
86
- return c
87
- }
88
-
89
- /* === Exports === */
90
-
91
- export { type MemoCallback, memo }
package/src/task.d.ts DELETED
@@ -1,17 +0,0 @@
1
- import { type Computed } from './computed';
2
- /**
3
- * Callback for async computation tasks
4
- * This explicitly returns a Promise<T> to differentiate from MemoCallback
5
- *
6
- * @since 0.14.0
7
- */
8
- type TaskCallback<T extends {}> = (abort: AbortSignal) => Promise<T>;
9
- /**
10
- * Create a derived signal that supports asynchronous computations
11
- *
12
- * @since 0.14.0
13
- * @param {TaskCallback<T>} fn - async computation callback
14
- * @returns {Computed<T>} - Computed signal
15
- */
16
- declare const task: <T extends {}>(fn: TaskCallback<T>) => Computed<T>;
17
- export { type TaskCallback, task };
package/src/task.ts DELETED
@@ -1,153 +0,0 @@
1
- import { UNSET } from './signal'
2
- import {
3
- CircularDependencyError,
4
- isAbortError,
5
- isPromise,
6
- toError,
7
- } from './util'
8
- import {
9
- type Cleanup,
10
- type Watcher,
11
- flush,
12
- notify,
13
- subscribe,
14
- watch,
15
- } from './scheduler'
16
- import {
17
- type Computed,
18
- TYPE_COMPUTED,
19
- } from './computed'
20
-
21
- /* === Types === */
22
-
23
- /**
24
- * Callback for async computation tasks
25
- * This explicitly returns a Promise<T> to differentiate from MemoCallback
26
- *
27
- * @since 0.14.0
28
- */
29
- type TaskCallback<T extends {}> = (abort: AbortSignal) => Promise<T>
30
-
31
- /* === Function === */
32
-
33
- /**
34
- * Create a derived signal that supports asynchronous computations
35
- *
36
- * @since 0.14.0
37
- * @param {TaskCallback<T>} fn - async computation callback
38
- * @returns {Computed<T>} - Computed signal
39
- */
40
- const task = <T extends {}>(fn: TaskCallback<T>): Computed<T> => {
41
- const watchers: Set<Watcher> = new Set()
42
-
43
- // Internal state
44
- let value: T = UNSET
45
- let error: Error | undefined
46
- let dirty = true
47
- let changed = false
48
- let computing = false
49
- let controller: AbortController | undefined
50
-
51
- // Functions to update internal state
52
- const ok = (v: T) => {
53
- if (!Object.is(v, value)) {
54
- value = v
55
- dirty = false
56
- error = undefined
57
- changed = true
58
- }
59
- }
60
- const nil = () => {
61
- changed = UNSET !== value
62
- value = UNSET
63
- error = undefined
64
- }
65
- const err = (e: unknown) => {
66
- const newError = toError(e)
67
- changed = !(
68
- error &&
69
- newError.name === error.name &&
70
- newError.message === error.message
71
- )
72
- value = UNSET
73
- error = newError
74
- }
75
- const resolve = (v: T) => {
76
- computing = false
77
- controller = undefined
78
- ok(v)
79
- if (changed) notify(watchers)
80
- }
81
- const reject = (e: unknown) => {
82
- computing = false
83
- controller = undefined
84
- err(e)
85
- if (changed) notify(watchers)
86
- }
87
- const abort = () => {
88
- computing = false
89
- controller = undefined
90
- compute() // retry
91
- }
92
-
93
- // Called when notified from sources (push)
94
- const mark = (() => {
95
- dirty = true
96
- controller?.abort('Aborted because source signal changed')
97
- if (watchers.size) {
98
- notify(watchers)
99
- } else {
100
- mark.cleanups.forEach(fn => fn())
101
- mark.cleanups.clear()
102
- }
103
- }) as Watcher
104
- mark.cleanups = new Set<Cleanup>()
105
-
106
- // Called when requested by dependencies (pull)
107
- const compute = () =>
108
- watch(() => {
109
- if (computing) throw new CircularDependencyError('task')
110
- changed = false
111
- controller = new AbortController()
112
- controller.signal.addEventListener('abort', abort, {
113
- once: true,
114
- })
115
-
116
- let result: T | Promise<T>
117
- computing = true
118
- try {
119
- result = fn(controller.signal)
120
- } catch (e) {
121
- if (isAbortError(e)) nil()
122
- else err(e)
123
- computing = false
124
- return
125
- }
126
- if (isPromise(result)) result.then(resolve, reject)
127
- else if (null == result || UNSET === result) nil()
128
- else ok(result)
129
- computing = false
130
- }, mark)
131
-
132
- const c: Computed<T> = {
133
- [Symbol.toStringTag]: TYPE_COMPUTED,
134
-
135
- /**
136
- * Get the current value of the computed
137
- *
138
- * @returns {T} - current value of the computed
139
- */
140
- get: (): T => {
141
- subscribe(watchers)
142
- flush()
143
- if (dirty) compute()
144
- if (error) throw error
145
- return value
146
- },
147
- }
148
- return c
149
- }
150
-
151
- /* === Exports === */
152
-
153
- export { type TaskCallback, task }