@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.
Files changed (66) hide show
  1. package/.ai-context.md +85 -21
  2. package/.cursorrules +11 -5
  3. package/.github/copilot-instructions.md +64 -13
  4. package/CLAUDE.md +143 -163
  5. package/LICENSE +1 -1
  6. package/README.md +248 -333
  7. package/archive/benchmark.ts +688 -0
  8. package/archive/collection.ts +312 -0
  9. package/{src → archive}/computed.ts +21 -21
  10. package/archive/list.ts +551 -0
  11. package/archive/memo.ts +139 -0
  12. package/{src → archive}/state.ts +13 -11
  13. package/archive/store.ts +368 -0
  14. package/archive/task.ts +194 -0
  15. package/eslint.config.js +1 -0
  16. package/index.dev.js +938 -509
  17. package/index.js +1 -1
  18. package/index.ts +50 -23
  19. package/package.json +1 -1
  20. package/src/classes/collection.ts +282 -0
  21. package/src/classes/composite.ts +176 -0
  22. package/src/classes/computed.ts +333 -0
  23. package/src/classes/list.ts +305 -0
  24. package/src/classes/ref.ts +68 -0
  25. package/src/classes/state.ts +98 -0
  26. package/src/classes/store.ts +210 -0
  27. package/src/diff.ts +26 -53
  28. package/src/effect.ts +9 -9
  29. package/src/errors.ts +71 -25
  30. package/src/match.ts +5 -12
  31. package/src/resolve.ts +3 -2
  32. package/src/signal.ts +58 -41
  33. package/src/system.ts +79 -42
  34. package/src/util.ts +16 -34
  35. package/test/batch.test.ts +15 -17
  36. package/test/benchmark.test.ts +4 -4
  37. package/test/collection.test.ts +853 -0
  38. package/test/computed.test.ts +138 -130
  39. package/test/diff.test.ts +2 -2
  40. package/test/effect.test.ts +36 -35
  41. package/test/list.test.ts +754 -0
  42. package/test/match.test.ts +25 -25
  43. package/test/ref.test.ts +227 -0
  44. package/test/resolve.test.ts +17 -19
  45. package/test/signal.test.ts +70 -119
  46. package/test/state.test.ts +44 -44
  47. package/test/store.test.ts +253 -929
  48. package/types/index.d.ts +12 -9
  49. package/types/src/classes/collection.d.ts +46 -0
  50. package/types/src/classes/composite.d.ts +15 -0
  51. package/types/src/classes/computed.d.ts +97 -0
  52. package/types/src/classes/list.d.ts +41 -0
  53. package/types/src/classes/ref.d.ts +39 -0
  54. package/types/src/classes/state.d.ts +52 -0
  55. package/types/src/classes/store.d.ts +51 -0
  56. package/types/src/diff.d.ts +8 -12
  57. package/types/src/errors.d.ts +17 -11
  58. package/types/src/signal.d.ts +27 -14
  59. package/types/src/system.d.ts +41 -20
  60. package/types/src/util.d.ts +6 -4
  61. package/src/store.ts +0 -474
  62. package/types/src/collection.d.ts +0 -26
  63. package/types/src/computed.d.ts +0 -33
  64. package/types/src/scheduler.d.ts +0 -55
  65. package/types/src/state.d.ts +0 -24
  66. package/types/src/store.d.ts +0 -65
@@ -1,51 +1,50 @@
1
1
  import { describe, expect, test } from 'bun:test'
2
2
  import {
3
3
  type Computed,
4
- createComputed,
5
- createState,
6
- createStore,
4
+ createSignal,
7
5
  isComputed,
6
+ isList,
8
7
  isState,
9
8
  isStore,
9
+ type List,
10
10
  type Signal,
11
- type State,
11
+ State,
12
12
  type Store,
13
- toSignal,
14
13
  type UnknownRecord,
15
- } from '..'
14
+ } from '../index.ts'
16
15
 
17
16
  /* === Tests === */
18
17
 
19
- describe('toSignal', () => {
18
+ describe('createSignal', () => {
20
19
  describe('type inference and runtime behavior', () => {
21
- test('converts array to Store<Record<string, T>>', () => {
22
- const result = toSignal([
20
+ test('converts array to List<T>', () => {
21
+ const result = createSignal([
23
22
  { id: 1, name: 'Alice' },
24
23
  { id: 2, name: 'Bob' },
25
24
  ])
26
25
 
27
26
  // Runtime behavior
28
- expect(isStore(result)).toBe(true)
29
- expect(result['0'].get()).toEqual({ id: 1, name: 'Alice' })
30
- expect(result['1'].get()).toEqual({ id: 2, name: 'Bob' })
27
+ expect(isList(result)).toBe(true)
28
+ expect(result.at(0)?.get()).toEqual({ id: 1, name: 'Alice' })
29
+ expect(result.at(1)?.get()).toEqual({ id: 2, name: 'Bob' })
31
30
 
32
- // Type inference test - now correctly returns Store<Record<number, {id: number, name: string}>>
33
- const typedResult: Store<{ id: number; name: string }[]> = result
31
+ // Type inference test - now correctly returns List<{ id: number; name: string }>
32
+ const typedResult: List<{ id: number; name: string }> = result
34
33
  expect(typedResult).toBeDefined()
35
34
  })
36
35
 
37
36
  test('converts empty array to ArrayStore<never[]>', () => {
38
- const result = toSignal([])
37
+ const result = createSignal([])
39
38
 
40
39
  // Runtime behavior
41
- expect(isStore(result)).toBe(true)
40
+ expect(isList(result)).toBe(true)
42
41
  expect(result.length).toBe(0)
43
42
  expect(Object.keys(result).length).toBe(0)
44
43
  })
45
44
 
46
45
  test('converts record to Store<T>', () => {
47
46
  const record = { name: 'Alice', age: 30 }
48
- const result = toSignal(record)
47
+ const result = createSignal(record)
49
48
 
50
49
  // Runtime behavior
51
50
  expect(isStore(result)).toBe(true)
@@ -57,51 +56,9 @@ describe('toSignal', () => {
57
56
  expect(typedResult).toBeDefined()
58
57
  })
59
58
 
60
- test('passes through existing Store unchanged', () => {
61
- const originalStore = createStore({ count: 5 })
62
- const result = toSignal(originalStore)
63
-
64
- // Runtime behavior
65
- expect(result).toBe(originalStore) // Should be the same instance
66
- expect(isStore(result)).toBe(true)
67
- expect(result.count.get()).toBe(5)
68
-
69
- // Type inference test - should remain Store<{count: number}>
70
- const typedResult: Store<{ count: number }> = result
71
- expect(typedResult).toBeDefined()
72
- })
73
-
74
- test('passes through existing State unchanged', () => {
75
- const originalState = createState(42)
76
- const result = toSignal(originalState)
77
-
78
- // Runtime behavior
79
- expect(result).toBe(originalState) // Should be the same instance
80
- expect(isState(result)).toBe(true)
81
- expect(result.get()).toBe(42)
82
-
83
- // Type inference test - should remain State<number>
84
- const typedResult: State<number> = result
85
- expect(typedResult).toBeDefined()
86
- })
87
-
88
- test('passes through existing Computed unchanged', () => {
89
- const originalComputed = createComputed(() => 'hello world')
90
- const result = toSignal(originalComputed)
91
-
92
- // Runtime behavior
93
- expect(result).toBe(originalComputed) // Should be the same instance
94
- expect(isComputed(result)).toBe(true)
95
- expect(result.get()).toBe('hello world')
96
-
97
- // Type inference test - should remain Computed<string>
98
- const typedResult: Computed<string> = result
99
- expect(typedResult).toBeDefined()
100
- })
101
-
102
59
  test('converts function to Computed<T>', () => {
103
60
  const fn = () => Math.random()
104
- const result = toSignal(fn)
61
+ const result = createSignal(fn)
105
62
 
106
63
  // Runtime behavior - functions are correctly converted to Computed
107
64
  expect(isComputed(result)).toBe(true)
@@ -114,7 +71,7 @@ describe('toSignal', () => {
114
71
 
115
72
  test('converts primitive to State<T>', () => {
116
73
  const num = 42
117
- const result = toSignal(num)
74
+ const result = createSignal(num)
118
75
 
119
76
  // Runtime behavior - primitives are correctly converted to State
120
77
  expect(isState(result)).toBe(true)
@@ -127,7 +84,7 @@ describe('toSignal', () => {
127
84
 
128
85
  test('converts object to State<T>', () => {
129
86
  const obj = new Date('2024-01-01')
130
- const result = toSignal(obj)
87
+ const result = createSignal(obj)
131
88
 
132
89
  // Runtime behavior - objects are correctly converted to State
133
90
  expect(isState(result)).toBe(true)
@@ -141,15 +98,15 @@ describe('toSignal', () => {
141
98
 
142
99
  describe('edge cases', () => {
143
100
  test('handles nested arrays', () => {
144
- const result = toSignal([
101
+ const result = createSignal([
145
102
  [1, 2],
146
103
  [3, 4],
147
104
  ])
148
105
 
149
- expect(isStore(result)).toBe(true)
106
+ expect(isList(result)).toBe(true)
150
107
  // With the fixed behavior, nested arrays should be recovered as arrays
151
- const firstElement = result[0].get()
152
- const secondElement = result[1].get()
108
+ const firstElement = result.at(0)?.get()
109
+ const secondElement = result.at(1)?.get()
153
110
 
154
111
  // The expected behavior - nested arrays are recovered as arrays
155
112
  expect(firstElement).toEqual([1, 2])
@@ -158,34 +115,23 @@ describe('toSignal', () => {
158
115
 
159
116
  test('handles arrays with mixed types', () => {
160
117
  const mixedArr = [1, 'hello', { key: 'value' }]
161
- const result = toSignal(mixedArr)
162
-
163
- expect(isStore(result)).toBe(true)
164
- expect(result['0'].get()).toBe(1)
165
- expect(result['1'].get()).toBe('hello')
166
- expect(result['2'].get()).toEqual({ key: 'value' })
167
- })
168
-
169
- test('handles sparse arrays', () => {
170
- const sparseArr = new Array(3)
171
- sparseArr[1] = 'middle'
172
- const result = toSignal(sparseArr)
118
+ const result = createSignal(mixedArr)
173
119
 
174
- expect(isStore(result)).toBe(true)
175
- expect('0' in result).toBe(false)
176
- expect(result['1'].get()).toBe('middle')
177
- expect('2' in result).toBe(false)
120
+ expect(isList(result)).toBe(true)
121
+ expect(result.at(0)?.get()).toBe(1)
122
+ expect(result.at(1)?.get()).toBe('hello')
123
+ expect(result.at(2)?.get()).toEqual({ key: 'value' })
178
124
  })
179
125
  })
180
126
  })
181
127
 
182
128
  describe('Signal compatibility', () => {
183
129
  test('all results implement Signal<T> interface', () => {
184
- const arraySignal = toSignal([1, 2, 3])
185
- const recordSignal = toSignal({ a: 1, b: 2 })
186
- const primitiveSignal = toSignal(42)
187
- const functionSignal = toSignal(() => 'hello')
188
- const stateSignal = toSignal(createState(true))
130
+ const arraySignal = createSignal([1, 2, 3])
131
+ const recordSignal = createSignal({ a: 1, b: 2 })
132
+ const primitiveSignal = createSignal(42)
133
+ const functionSignal = createSignal(() => 'hello')
134
+ const stateSignal = createSignal(new State(true))
189
135
 
190
136
  // All should have get() method
191
137
  expect(typeof arraySignal.get).toBe('function')
@@ -210,16 +156,16 @@ describe('Type precision tests', () => {
210
156
  test('array type should infer element type correctly', () => {
211
157
  // Test that arrays infer the correct element type
212
158
  const stringArray = ['a', 'b', 'c']
213
- const stringArraySignal = toSignal(stringArray)
159
+ const stringArraySignal = createSignal(stringArray)
214
160
 
215
- // Should be Store<Record<string, string>>
216
- expect(stringArraySignal['0'].get()).toBe('a')
161
+ // Should be List<string>
162
+ expect(stringArraySignal.at(0)?.get()).toBe('a')
217
163
 
218
164
  const numberArray = [1, 2, 3]
219
- const numberArraySignal = toSignal(numberArray)
165
+ const numberArraySignal = createSignal(numberArray)
220
166
 
221
- // Should be Store<Record<string, number>>
222
- expect(typeof numberArraySignal['0'].get()).toBe('number')
167
+ // Should be List<number>
168
+ expect(typeof numberArraySignal.at(0)?.get()).toBe('number')
223
169
  })
224
170
 
225
171
  test('complex object arrays maintain precise typing', () => {
@@ -234,31 +180,31 @@ describe('Type precision tests', () => {
234
180
  { id: 2, name: 'Bob', email: 'bob@example.com' },
235
181
  ]
236
182
 
237
- const usersSignal = toSignal(users)
183
+ const usersSignal = createSignal(users)
238
184
 
239
185
  // Should maintain User type for each element
240
- const firstUser = usersSignal['0'].get()
241
- expect(firstUser.id).toBe(1)
242
- expect(firstUser.name).toBe('Alice')
243
- expect(firstUser.email).toBe('alice@example.com')
186
+ const firstUser = usersSignal.at(0)?.get()
187
+ expect(firstUser?.id).toBe(1)
188
+ expect(firstUser?.name).toBe('Alice')
189
+ expect(firstUser?.email).toBe('alice@example.com')
244
190
  })
245
191
 
246
192
  describe('Type inference issues', () => {
247
193
  test('demonstrates current type inference problem', () => {
248
- const result = toSignal([{ id: 1 }, { id: 2 }])
194
+ const result = createSignal([{ id: 1 }, { id: 2 }])
249
195
 
250
196
  // Let's verify the actual behavior
251
- expect(isStore(result)).toBe(true)
252
- expect(result['0'].get()).toEqual({ id: 1 })
253
- expect(result['1'].get()).toEqual({ id: 2 })
197
+ expect(isList(result)).toBe(true)
198
+ expect(result.at(0)?.get()).toEqual({ id: 1 })
199
+ expect(result.at(1)?.get()).toEqual({ id: 2 })
254
200
 
255
201
  // Type assertion test - this should now work with correct typing
256
- const typedResult: Store<{ id: number }[]> = result
202
+ const typedResult: List<{ id: number }> = result
257
203
  expect(typedResult).toBeDefined()
258
204
 
259
205
  // Simulate external library usage where P[K] represents element type
260
206
  interface ExternalLibraryConstraint<P extends UnknownRecord> {
261
- process<K extends keyof P>(signal: Signal<P[K]>): void
207
+ process<K extends keyof P>(signal: Signal<P[K] & {}>): void
262
208
  }
263
209
 
264
210
  // This should work if types are correct
@@ -266,7 +212,7 @@ describe('Type precision tests', () => {
266
212
  Record<string, { id: number }>
267
213
  > = {
268
214
  process: <K extends keyof Record<string, { id: number }>>(
269
- signal: Signal<Record<string, { id: number }>[K]>,
215
+ signal: Signal<Record<string, { id: number }>[K] & {}>,
270
216
  ) => {
271
217
  // Process the signal
272
218
  const value = signal.get()
@@ -275,7 +221,8 @@ describe('Type precision tests', () => {
275
221
  }
276
222
 
277
223
  // This call should work without type errors
278
- processor.process(result['0'])
224
+ const item = result.at(0)
225
+ if (item) processor.process(item)
279
226
  })
280
227
 
281
228
  test('verifies fixed type inference for external library compatibility', () => {
@@ -283,23 +230,26 @@ describe('Type precision tests', () => {
283
230
  { id: 1, name: 'Alice' },
284
231
  { id: 2, name: 'Bob' },
285
232
  ]
286
- const signal = toSignal(items)
287
- const firstItemSignal = signal['0']
288
- const secondItemSignal = signal['1']
233
+ const signal = createSignal(items)
234
+ const firstItemSignal = signal.at(0)
235
+ const secondItemSignal = signal.at(1)
289
236
 
290
237
  // Runtime behavior works correctly
291
- expect(isStore(signal)).toBe(true)
292
- expect(firstItemSignal.get()).toEqual({ id: 1, name: 'Alice' })
293
- expect(secondItemSignal.get()).toEqual({ id: 2, name: 'Bob' })
238
+ expect(isList(signal)).toBe(true)
239
+ expect(firstItemSignal?.get()).toEqual({ id: 1, name: 'Alice' })
240
+ expect(secondItemSignal?.get()).toEqual({ id: 2, name: 'Bob' })
294
241
 
295
242
  // Type inference should now work correctly:
296
- const properlyTyped: Store<{ id: number; name: string }[]> = signal
243
+ const properlyTyped: List<{ id: number; name: string }> = signal
297
244
  expect(properlyTyped).toBeDefined()
298
245
 
299
246
  // These should work without type errors in external libraries
300
247
  // that expect Signal<P[K]> where P[K] is the individual element type
301
- interface ExternalAPI<P extends UnknownRecord> {
302
- process<K extends keyof P>(key: K, signal: Signal<P[K]>): P[K]
248
+ interface ExternalAPI<P extends Record<string, object>> {
249
+ process<K extends keyof P>(
250
+ key: K,
251
+ signal: Signal<P[K] & object>,
252
+ ): P[K]
303
253
  }
304
254
 
305
255
  const api: ExternalAPI<
@@ -309,15 +259,16 @@ describe('Type precision tests', () => {
309
259
  }
310
260
 
311
261
  // These calls should work with proper typing now
312
- const result1 = api.process('0', firstItemSignal)
313
- const result2 = api.process('1', secondItemSignal)
262
+ const result1 = firstItemSignal && api.process('0', firstItemSignal)
263
+ const result2 =
264
+ secondItemSignal && api.process('1', secondItemSignal)
314
265
 
315
266
  expect(result1).toEqual({ id: 1, name: 'Alice' })
316
267
  expect(result2).toEqual({ id: 2, name: 'Bob' })
317
268
 
318
269
  // Verify the types are precise
319
- expect(typeof result1.id).toBe('number')
320
- expect(typeof result1.name).toBe('string')
270
+ expect(typeof result1?.id).toBe('number')
271
+ expect(typeof result1?.name).toBe('string')
321
272
  })
322
273
  })
323
274
  })
@@ -1,12 +1,12 @@
1
1
  import { describe, expect, test } from 'bun:test'
2
- import { createState, isComputed, isState } from '..'
2
+ import { isComputed, isState, State } from '../index.ts'
3
3
 
4
4
  /* === Tests === */
5
5
 
6
6
  describe('State', () => {
7
7
  describe('State type guard', () => {
8
8
  test('isState identifies state signals', () => {
9
- const count = createState(42)
9
+ const count = new State(42)
10
10
  expect(isState(count)).toBe(true)
11
11
  expect(isComputed(count)).toBe(false)
12
12
  })
@@ -14,28 +14,28 @@ describe('State', () => {
14
14
 
15
15
  describe('Boolean cause', () => {
16
16
  test('should be boolean', () => {
17
- const cause = createState(false)
17
+ const cause = new State(false)
18
18
  expect(typeof cause.get()).toBe('boolean')
19
19
  })
20
20
 
21
21
  test('should set initial value to false', () => {
22
- const cause = createState(false)
22
+ const cause = new State(false)
23
23
  expect(cause.get()).toBe(false)
24
24
  })
25
25
 
26
26
  test('should set initial value to true', () => {
27
- const cause = createState(true)
27
+ const cause = new State(true)
28
28
  expect(cause.get()).toBe(true)
29
29
  })
30
30
 
31
31
  test('should set new value with .set(true)', () => {
32
- const cause = createState(false)
32
+ const cause = new State(false)
33
33
  cause.set(true)
34
34
  expect(cause.get()).toBe(true)
35
35
  })
36
36
 
37
37
  test('should toggle initial value with .set(v => !v)', () => {
38
- const cause = createState(false)
38
+ const cause = new State(false)
39
39
  cause.update(v => !v)
40
40
  expect(cause.get()).toBe(true)
41
41
  })
@@ -43,23 +43,23 @@ describe('State', () => {
43
43
 
44
44
  describe('Number cause', () => {
45
45
  test('should be number', () => {
46
- const cause = createState(0)
46
+ const cause = new State(0)
47
47
  expect(typeof cause.get()).toBe('number')
48
48
  })
49
49
 
50
50
  test('should set initial value to 0', () => {
51
- const cause = createState(0)
51
+ const cause = new State(0)
52
52
  expect(cause.get()).toBe(0)
53
53
  })
54
54
 
55
55
  test('should set new value with .set(42)', () => {
56
- const cause = createState(0)
56
+ const cause = new State(0)
57
57
  cause.set(42)
58
58
  expect(cause.get()).toBe(42)
59
59
  })
60
60
 
61
61
  test('should increment value with .set(v => ++v)', () => {
62
- const cause = createState(0)
62
+ const cause = new State(0)
63
63
  cause.update(v => ++v)
64
64
  expect(cause.get()).toBe(1)
65
65
  })
@@ -67,23 +67,23 @@ describe('State', () => {
67
67
 
68
68
  describe('String cause', () => {
69
69
  test('should be string', () => {
70
- const cause = createState('foo')
70
+ const cause = new State('foo')
71
71
  expect(typeof cause.get()).toBe('string')
72
72
  })
73
73
 
74
74
  test('should set initial value to "foo"', () => {
75
- const cause = createState('foo')
75
+ const cause = new State('foo')
76
76
  expect(cause.get()).toBe('foo')
77
77
  })
78
78
 
79
79
  test('should set new value with .set("bar")', () => {
80
- const cause = createState('foo')
80
+ const cause = new State('foo')
81
81
  cause.set('bar')
82
82
  expect(cause.get()).toBe('bar')
83
83
  })
84
84
 
85
85
  test('should upper case value with .set(v => v.toUpperCase())', () => {
86
- const cause = createState('foo')
86
+ const cause = new State('foo')
87
87
  cause.update(v => (v ? v.toUpperCase() : ''))
88
88
  expect(cause.get()).toBe('FOO')
89
89
  })
@@ -91,31 +91,31 @@ describe('State', () => {
91
91
 
92
92
  describe('Array cause', () => {
93
93
  test('should be array', () => {
94
- const cause = createState([1, 2, 3])
94
+ const cause = new State([1, 2, 3])
95
95
  expect(Array.isArray(cause.get())).toBe(true)
96
96
  })
97
97
 
98
98
  test('should set initial value to [1, 2, 3]', () => {
99
- const cause = createState([1, 2, 3])
99
+ const cause = new State([1, 2, 3])
100
100
  expect(cause.get()).toEqual([1, 2, 3])
101
101
  })
102
102
 
103
103
  test('should set new value with .set([4, 5, 6])', () => {
104
- const cause = createState([1, 2, 3])
104
+ const cause = new 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
109
  test('should reflect current value of array after modification', () => {
110
110
  const array = [1, 2, 3]
111
- const cause = createState(array)
111
+ const cause = new 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
116
  test('should set new value with .set([...array, 4])', () => {
117
117
  const array = [1, 2, 3]
118
- const cause = createState(array)
118
+ const cause = new State(array)
119
119
  cause.set([...array, 4]) // use destructuring instead!
120
120
  expect(cause.get()).toEqual([1, 2, 3, 4])
121
121
  })
@@ -124,17 +124,17 @@ describe('State', () => {
124
124
  test('should throw NullishSignalValueError when initialValue is nullish', () => {
125
125
  expect(() => {
126
126
  // @ts-expect-error - Testing invalid input
127
- createState(null)
127
+ new State(null)
128
128
  }).toThrow('Nullish signal values are not allowed in state')
129
129
 
130
130
  expect(() => {
131
131
  // @ts-expect-error - Testing invalid input
132
- createState(undefined)
132
+ new State(undefined)
133
133
  }).toThrow('Nullish signal values are not allowed in state')
134
134
  })
135
135
 
136
136
  test('should throw NullishSignalValueError when newValue is nullish in set()', () => {
137
- const state = createState(42)
137
+ const state = new State(42)
138
138
 
139
139
  expect(() => {
140
140
  // @ts-expect-error - Testing invalid input
@@ -150,7 +150,7 @@ describe('State', () => {
150
150
  test('should throw specific error types for nullish values', () => {
151
151
  try {
152
152
  // @ts-expect-error - Testing invalid input
153
- createState(null)
153
+ new State(null)
154
154
  expect(true).toBe(false) // Should not reach here
155
155
  } catch (error) {
156
156
  expect(error).toBeInstanceOf(TypeError)
@@ -160,7 +160,7 @@ describe('State', () => {
160
160
  )
161
161
  }
162
162
 
163
- const state = createState(42)
163
+ const state = new State(42)
164
164
  try {
165
165
  // @ts-expect-error - Testing invalid input
166
166
  state.set(null)
@@ -177,26 +177,26 @@ describe('State', () => {
177
177
  test('should allow valid non-nullish values', () => {
178
178
  // These should not throw
179
179
  expect(() => {
180
- createState(0)
180
+ new State(0)
181
181
  }).not.toThrow()
182
182
 
183
183
  expect(() => {
184
- createState('')
184
+ new State('')
185
185
  }).not.toThrow()
186
186
 
187
187
  expect(() => {
188
- createState(false)
188
+ new State(false)
189
189
  }).not.toThrow()
190
190
 
191
191
  expect(() => {
192
- createState({})
192
+ new State({})
193
193
  }).not.toThrow()
194
194
 
195
195
  expect(() => {
196
- createState([])
196
+ new State([])
197
197
  }).not.toThrow()
198
198
 
199
- const state = createState(42)
199
+ const state = new State(42)
200
200
  expect(() => {
201
201
  state.set(0)
202
202
  }).not.toThrow()
@@ -208,7 +208,7 @@ describe('State', () => {
208
208
  })
209
209
 
210
210
  test('should throw InvalidCallbackError for non-function updater in update()', () => {
211
- const state = createState(42)
211
+ const state = new State(42)
212
212
 
213
213
  expect(() => {
214
214
  // @ts-expect-error - Testing invalid input
@@ -232,7 +232,7 @@ describe('State', () => {
232
232
  })
233
233
 
234
234
  test('should throw specific error type for non-function updater', () => {
235
- const state = createState(42)
235
+ const state = new State(42)
236
236
 
237
237
  try {
238
238
  // @ts-expect-error - Testing invalid input
@@ -248,7 +248,7 @@ describe('State', () => {
248
248
  })
249
249
 
250
250
  test('should handle updater function that throws an error', () => {
251
- const state = createState(42)
251
+ const state = new State(42)
252
252
 
253
253
  expect(() => {
254
254
  state.update(() => {
@@ -261,7 +261,7 @@ describe('State', () => {
261
261
  })
262
262
 
263
263
  test('should handle updater function that returns nullish value', () => {
264
- const state = createState(42)
264
+ const state = new State(42)
265
265
 
266
266
  expect(() => {
267
267
  // @ts-expect-error - Testing invalid return value
@@ -278,25 +278,25 @@ describe('State', () => {
278
278
  })
279
279
 
280
280
  test('should handle valid updater functions', () => {
281
- const numberState = createState(10)
281
+ const numberState = new State(10)
282
282
  expect(() => {
283
283
  numberState.update(x => x + 5)
284
284
  }).not.toThrow()
285
285
  expect(numberState.get()).toBe(15)
286
286
 
287
- const stringState = createState('hello')
287
+ const stringState = new State('hello')
288
288
  expect(() => {
289
289
  stringState.update(x => x.toUpperCase())
290
290
  }).not.toThrow()
291
291
  expect(stringState.get()).toBe('HELLO')
292
292
 
293
- const arrayState = createState([1, 2, 3])
293
+ const arrayState = new State([1, 2, 3])
294
294
  expect(() => {
295
295
  arrayState.update(arr => [...arr, 4])
296
296
  }).not.toThrow()
297
297
  expect(arrayState.get()).toEqual([1, 2, 3, 4])
298
298
 
299
- const objectState = createState({ count: 0 })
299
+ const objectState = new State({ count: 0 })
300
300
  expect(() => {
301
301
  objectState.update(obj => ({
302
302
  ...obj,
@@ -310,24 +310,24 @@ describe('State', () => {
310
310
 
311
311
  describe('Object cause', () => {
312
312
  test('should be object', () => {
313
- const cause = createState({ a: 'a', b: 1 })
313
+ const cause = new State({ a: 'a', b: 1 })
314
314
  expect(typeof cause.get()).toBe('object')
315
315
  })
316
316
 
317
317
  test('should set initial value to { a: "a", b: 1 }', () => {
318
- const cause = createState({ a: 'a', b: 1 })
318
+ const cause = new State({ a: 'a', b: 1 })
319
319
  expect(cause.get()).toEqual({ a: 'a', b: 1 })
320
320
  })
321
321
 
322
322
  test('should set new value with .set({ c: true })', () => {
323
- const cause = createState<Record<string, unknown>>({ a: 'a', b: 1 })
323
+ const cause = new State<Record<string, unknown>>({ a: 'a', b: 1 })
324
324
  cause.set({ c: true })
325
325
  expect(cause.get()).toEqual({ c: true })
326
326
  })
327
327
 
328
328
  test('should reflect current value of object after modification', () => {
329
329
  const obj = { a: 'a', b: 1 }
330
- const cause = createState<Record<string, unknown>>(obj)
330
+ const cause = new State<Record<string, unknown>>(obj)
331
331
  // @ts-expect-error Property 'c' does not exist on type '{ a: string; b: number; }'. (ts 2339)
332
332
  obj.c = true // don't do this! the result will be correct, but we can't trigger effects
333
333
  expect(cause.get()).toEqual({ a: 'a', b: 1, c: true })
@@ -335,7 +335,7 @@ describe('State', () => {
335
335
 
336
336
  test('should set new value with .set({...obj, c: true})', () => {
337
337
  const obj = { a: 'a', b: 1 }
338
- const cause = createState<Record<string, unknown>>(obj)
338
+ const cause = new State<Record<string, unknown>>(obj)
339
339
  cause.set({ ...obj, c: true }) // use destructuring instead!
340
340
  expect(cause.get()).toEqual({ a: 'a', b: 1, c: true })
341
341
  })