@zeix/cause-effect 0.16.1 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/.ai-context.md +71 -21
  2. package/.cursorrules +3 -2
  3. package/.github/copilot-instructions.md +59 -13
  4. package/CLAUDE.md +170 -24
  5. package/LICENSE +1 -1
  6. package/README.md +156 -52
  7. package/archive/benchmark.ts +688 -0
  8. package/archive/collection.ts +312 -0
  9. package/{src → archive}/computed.ts +19 -19
  10. package/archive/list.ts +551 -0
  11. package/archive/memo.ts +138 -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 +899 -503
  17. package/index.js +1 -1
  18. package/index.ts +41 -22
  19. package/package.json +1 -1
  20. package/src/classes/collection.ts +272 -0
  21. package/src/classes/composite.ts +176 -0
  22. package/src/classes/computed.ts +333 -0
  23. package/src/classes/list.ts +304 -0
  24. package/src/classes/state.ts +98 -0
  25. package/src/classes/store.ts +210 -0
  26. package/src/diff.ts +26 -53
  27. package/src/effect.ts +9 -9
  28. package/src/errors.ts +50 -25
  29. package/src/signal.ts +58 -41
  30. package/src/system.ts +79 -42
  31. package/src/util.ts +16 -30
  32. package/test/batch.test.ts +15 -17
  33. package/test/benchmark.test.ts +4 -4
  34. package/test/collection.test.ts +796 -0
  35. package/test/computed.test.ts +138 -130
  36. package/test/diff.test.ts +2 -2
  37. package/test/effect.test.ts +36 -35
  38. package/test/list.test.ts +754 -0
  39. package/test/match.test.ts +25 -25
  40. package/test/resolve.test.ts +17 -19
  41. package/test/signal.test.ts +70 -119
  42. package/test/state.test.ts +44 -44
  43. package/test/store.test.ts +253 -929
  44. package/types/index.d.ts +10 -8
  45. package/types/src/classes/collection.d.ts +32 -0
  46. package/types/src/classes/composite.d.ts +15 -0
  47. package/types/src/classes/computed.d.ts +97 -0
  48. package/types/src/classes/list.d.ts +41 -0
  49. package/types/src/classes/state.d.ts +52 -0
  50. package/types/src/classes/store.d.ts +51 -0
  51. package/types/src/diff.d.ts +8 -12
  52. package/types/src/errors.d.ts +12 -11
  53. package/types/src/signal.d.ts +27 -14
  54. package/types/src/system.d.ts +41 -20
  55. package/types/src/util.d.ts +6 -3
  56. package/src/store.ts +0 -474
  57. package/types/src/collection.d.ts +0 -26
  58. package/types/src/computed.d.ts +0 -33
  59. package/types/src/scheduler.d.ts +0 -55
  60. package/types/src/state.d.ts +0 -24
  61. package/types/src/store.d.ts +0 -65
@@ -1,12 +1,12 @@
1
1
  import { describe, expect, test } from 'bun:test'
2
- import { createComputed, createState, match, resolve, UNSET } from '..'
2
+ import { Memo, match, resolve, State, Task, UNSET } from '../index.ts'
3
3
 
4
4
  /* === Tests === */
5
5
 
6
6
  describe('Match Function', () => {
7
7
  test('should call ok handler for successful resolution', () => {
8
- const a = createState(10)
9
- const b = createState('hello')
8
+ const a = new State(10)
9
+ const b = new State('hello')
10
10
  let okCalled = false
11
11
  let okValues: { a: number; b: string } | null = null
12
12
 
@@ -32,8 +32,8 @@ describe('Match Function', () => {
32
32
  })
33
33
 
34
34
  test('should call nil handler for pending signals', () => {
35
- const a = createState(10)
36
- const b = createState(UNSET)
35
+ const a = new State(10)
36
+ const b = new State(UNSET)
37
37
  let nilCalled = false
38
38
 
39
39
  match(resolve({ a, b }), {
@@ -52,8 +52,8 @@ describe('Match Function', () => {
52
52
  })
53
53
 
54
54
  test('should call error handler for error signals', () => {
55
- const a = createState(10)
56
- const b = createComputed(() => {
55
+ const a = new State(10)
56
+ const b = new Memo(() => {
57
57
  throw new Error('Test error')
58
58
  })
59
59
  let errCalled = false
@@ -78,7 +78,7 @@ describe('Match Function', () => {
78
78
  })
79
79
 
80
80
  test('should handle missing optional handlers gracefully', () => {
81
- const a = createState(10)
81
+ const a = new State(10)
82
82
  const result = resolve({ a })
83
83
 
84
84
  // Should not throw even with only required ok handler (err and nil are optional)
@@ -92,7 +92,7 @@ describe('Match Function', () => {
92
92
  })
93
93
 
94
94
  test('should return void always', () => {
95
- const a = createState(42)
95
+ const a = new State(42)
96
96
 
97
97
  const returnValue = match(resolve({ a }), {
98
98
  ok: () => {
@@ -105,7 +105,7 @@ describe('Match Function', () => {
105
105
  })
106
106
 
107
107
  test('should handle handler errors by calling error handler', () => {
108
- const a = createState(10)
108
+ const a = new State(10)
109
109
  let handlerErrorCalled = false
110
110
  let handlerError: Error | null = null
111
111
 
@@ -125,7 +125,7 @@ describe('Match Function', () => {
125
125
  })
126
126
 
127
127
  test('should rethrow handler errors if no error handler available', () => {
128
- const a = createState(10)
128
+ const a = new State(10)
129
129
 
130
130
  expect(() => {
131
131
  match(resolve({ a }), {
@@ -137,7 +137,7 @@ describe('Match Function', () => {
137
137
  })
138
138
 
139
139
  test('should combine existing errors with handler errors', () => {
140
- const a = createComputed(() => {
140
+ const a = new Memo(() => {
141
141
  throw new Error('Signal error')
142
142
  })
143
143
  let allErrors: readonly Error[] | null = null
@@ -167,9 +167,9 @@ describe('Match Function', () => {
167
167
  })
168
168
 
169
169
  test('should work with complex type inference', () => {
170
- const user = createState({ id: 1, name: 'Alice' })
171
- const posts = createState([{ id: 1, title: 'Hello' }])
172
- const settings = createState({ theme: 'dark' })
170
+ const user = new State({ id: 1, name: 'Alice' })
171
+ const posts = new State([{ id: 1, title: 'Hello' }])
172
+ const settings = new State({ theme: 'dark' })
173
173
 
174
174
  let typeTestPassed = false
175
175
 
@@ -194,8 +194,8 @@ describe('Match Function', () => {
194
194
  })
195
195
 
196
196
  test('should handle side effects only pattern', () => {
197
- const count = createState(5)
198
- const name = createState('test')
197
+ const count = new State(5)
198
+ const name = new State('test')
199
199
  let sideEffectExecuted = false
200
200
  let capturedData = ''
201
201
 
@@ -214,10 +214,10 @@ describe('Match Function', () => {
214
214
  })
215
215
 
216
216
  test('should handle multiple error types correctly', () => {
217
- const error1 = createComputed(() => {
217
+ const error1 = new Memo(() => {
218
218
  throw new Error('First error')
219
219
  })
220
- const error2 = createComputed(() => {
220
+ const error2 = new Memo(() => {
221
221
  throw new Error('Second error')
222
222
  })
223
223
  let errorMessages: string[] = []
@@ -240,7 +240,7 @@ describe('Match Function', () => {
240
240
  const wait = (ms: number) =>
241
241
  new Promise(resolve => setTimeout(resolve, ms))
242
242
 
243
- const asyncSignal = createComputed(async () => {
243
+ const asyncSignal = new Task(async () => {
244
244
  await wait(10)
245
245
  return 'async result'
246
246
  })
@@ -280,7 +280,7 @@ describe('Match Function', () => {
280
280
  })
281
281
 
282
282
  test('should maintain referential transparency', () => {
283
- const a = createState(42)
283
+ const a = new State(42)
284
284
  const result = resolve({ a })
285
285
  let callCount = 0
286
286
 
@@ -303,7 +303,7 @@ describe('Match Function', () => {
303
303
 
304
304
  describe('Match Function Integration', () => {
305
305
  test('should work seamlessly with resolve', () => {
306
- const data = createState({ id: 1, value: 'test' })
306
+ const data = new State({ id: 1, value: 'test' })
307
307
  let processed = false
308
308
  let processedValue = ''
309
309
 
@@ -322,12 +322,12 @@ describe('Match Function Integration', () => {
322
322
  const wait = (ms: number) =>
323
323
  new Promise(resolve => setTimeout(resolve, ms))
324
324
 
325
- const syncData = createState('available')
326
- const asyncData = createComputed(async () => {
325
+ const syncData = new State('available')
326
+ const asyncData = new Task(async () => {
327
327
  await wait(10)
328
328
  return 'loaded'
329
329
  })
330
- const errorData = createComputed(() => {
330
+ const errorData = new Memo(() => {
331
331
  throw new Error('Failed to load')
332
332
  })
333
333
 
@@ -1,12 +1,12 @@
1
1
  import { describe, expect, test } from 'bun:test'
2
- import { createComputed, createState, resolve, UNSET } from '..'
2
+ import { Memo, resolve, State, Task, UNSET } from '../index.ts'
3
3
 
4
4
  /* === Tests === */
5
5
 
6
6
  describe('Resolve Function', () => {
7
7
  test('should return discriminated union for successful resolution', () => {
8
- const a = createState(10)
9
- const b = createState('hello')
8
+ const a = new State(10)
9
+ const b = new State('hello')
10
10
 
11
11
  const result = resolve({ a, b })
12
12
 
@@ -20,8 +20,8 @@ describe('Resolve Function', () => {
20
20
  })
21
21
 
22
22
  test('should return discriminated union for pending signals', () => {
23
- const a = createState(10)
24
- const b = createState(UNSET)
23
+ const a = new State(10)
24
+ const b = new State(UNSET)
25
25
 
26
26
  const result = resolve({ a, b })
27
27
 
@@ -32,8 +32,8 @@ describe('Resolve Function', () => {
32
32
  })
33
33
 
34
34
  test('should return discriminated union for error signals', () => {
35
- const a = createState(10)
36
- const b = createComputed(() => {
35
+ const a = new State(10)
36
+ const b = new Memo(() => {
37
37
  throw new Error('Test error')
38
38
  })
39
39
 
@@ -49,11 +49,11 @@ describe('Resolve Function', () => {
49
49
  })
50
50
 
51
51
  test('should handle mixed error and valid signals', () => {
52
- const valid = createState('valid')
53
- const error1 = createComputed(() => {
52
+ const valid = new State('valid')
53
+ const error1 = new Memo(() => {
54
54
  throw new Error('Error 1')
55
55
  })
56
- const error2 = createComputed(() => {
56
+ const error2 = new Memo(() => {
57
57
  throw new Error('Error 2')
58
58
  })
59
59
 
@@ -69,8 +69,8 @@ describe('Resolve Function', () => {
69
69
  })
70
70
 
71
71
  test('should prioritize pending over errors', () => {
72
- const pending = createState(UNSET)
73
- const error = createComputed(() => {
72
+ const pending = new State(UNSET)
73
+ const error = new Memo(() => {
74
74
  throw new Error('Test error')
75
75
  })
76
76
 
@@ -91,8 +91,8 @@ describe('Resolve Function', () => {
91
91
  })
92
92
 
93
93
  test('should handle complex nested object signals', () => {
94
- const user = createState({ name: 'Alice', age: 25 })
95
- const settings = createState({ theme: 'dark', lang: 'en' })
94
+ const user = new State({ name: 'Alice', age: 25 })
95
+ const settings = new State({ theme: 'dark', lang: 'en' })
96
96
 
97
97
  const result = resolve({ user, settings })
98
98
 
@@ -109,7 +109,7 @@ describe('Resolve Function', () => {
109
109
  const wait = (ms: number) =>
110
110
  new Promise(resolve => setTimeout(resolve, ms))
111
111
 
112
- const asyncSignal = createComputed(async () => {
112
+ const asyncSignal = new Task(async () => {
113
113
  await wait(10)
114
114
  return 'async result'
115
115
  })
@@ -124,16 +124,14 @@ describe('Resolve Function', () => {
124
124
 
125
125
  result = resolve({ asyncSignal })
126
126
  expect(result.ok).toBe(true)
127
- if (result.ok) {
128
- expect(result.values.asyncSignal).toBe('async result')
129
- }
127
+ if (result.ok) expect(result.values.asyncSignal).toBe('async result')
130
128
  })
131
129
 
132
130
  test('should handle async computed signals that error', async () => {
133
131
  const wait = (ms: number) =>
134
132
  new Promise(resolve => setTimeout(resolve, ms))
135
133
 
136
- const asyncError = createComputed(async () => {
134
+ const asyncError = new Task(async () => {
137
135
  await wait(10)
138
136
  throw new Error('Async error')
139
137
  })
@@ -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
  })