@zeix/cause-effect 0.15.0 → 0.15.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/README.md +2 -10
- package/index.dev.js +50 -49
- package/index.js +1 -1
- package/index.ts +12 -2
- package/package.json +1 -1
- package/src/diff.ts +15 -3
- package/src/effect.ts +1 -2
- package/src/match.ts +13 -18
- package/src/resolve.ts +7 -17
- package/src/signal.ts +44 -30
- package/src/store.ts +62 -63
- package/src/util.ts +15 -17
- package/test/signal.test.ts +451 -0
- package/test/store.test.ts +27 -0
- package/types/index.d.ts +4 -4
- package/types/src/diff.d.ts +6 -3
- package/types/src/match.d.ts +3 -3
- package/types/src/resolve.d.ts +3 -3
- package/types/src/signal.d.ts +17 -13
- package/types/src/store.d.ts +20 -15
- package/types/src/util.d.ts +3 -4
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import {
|
|
3
|
+
type Computed,
|
|
4
|
+
computed,
|
|
5
|
+
isComputed,
|
|
6
|
+
isState,
|
|
7
|
+
isStore,
|
|
8
|
+
type Signal,
|
|
9
|
+
type State,
|
|
10
|
+
type Store,
|
|
11
|
+
state,
|
|
12
|
+
store,
|
|
13
|
+
toMutableSignal,
|
|
14
|
+
toSignal,
|
|
15
|
+
type UnknownRecord,
|
|
16
|
+
} from '..'
|
|
17
|
+
|
|
18
|
+
/* === Tests === */
|
|
19
|
+
|
|
20
|
+
describe('toSignal', () => {
|
|
21
|
+
describe('type inference and runtime behavior', () => {
|
|
22
|
+
test('converts array to Store<Record<string, T>>', () => {
|
|
23
|
+
const arr = [
|
|
24
|
+
{ id: 1, name: 'Alice' },
|
|
25
|
+
{ id: 2, name: 'Bob' },
|
|
26
|
+
]
|
|
27
|
+
const result = toSignal(arr)
|
|
28
|
+
|
|
29
|
+
// Runtime behavior
|
|
30
|
+
expect(isStore(result)).toBe(true)
|
|
31
|
+
expect(result['0'].get()).toEqual({ id: 1, name: 'Alice' })
|
|
32
|
+
expect(result['1'].get()).toEqual({ id: 2, name: 'Bob' })
|
|
33
|
+
|
|
34
|
+
// Type inference test - now correctly returns Store<Record<string, {id: number, name: string}>>
|
|
35
|
+
const typedResult: Store<
|
|
36
|
+
Record<string, { id: number; name: string }>
|
|
37
|
+
> = result
|
|
38
|
+
expect(typedResult).toBeDefined()
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test('converts empty array to Store<Record<string, never>>', () => {
|
|
42
|
+
const arr: never[] = []
|
|
43
|
+
const result = toSignal(arr)
|
|
44
|
+
|
|
45
|
+
// Runtime behavior
|
|
46
|
+
expect(isStore(result)).toBe(true)
|
|
47
|
+
expect(Object.keys(result).length).toBe(0)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('converts record to Store<T>', () => {
|
|
51
|
+
const record = { name: 'Alice', age: 30 }
|
|
52
|
+
const result = toSignal(record)
|
|
53
|
+
|
|
54
|
+
// Runtime behavior
|
|
55
|
+
expect(isStore(result)).toBe(true)
|
|
56
|
+
expect(result.name.get()).toBe('Alice')
|
|
57
|
+
expect(result.age.get()).toBe(30)
|
|
58
|
+
|
|
59
|
+
// Type inference test - should be Store<{name: string, age: number}>
|
|
60
|
+
const typedResult: Store<{ name: string; age: number }> = result
|
|
61
|
+
expect(typedResult).toBeDefined()
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test('passes through existing Store unchanged', () => {
|
|
65
|
+
const originalStore = store({ count: 5 })
|
|
66
|
+
const result = toSignal(originalStore)
|
|
67
|
+
|
|
68
|
+
// Runtime behavior
|
|
69
|
+
expect(result).toBe(originalStore) // Should be the same instance
|
|
70
|
+
expect(isStore(result)).toBe(true)
|
|
71
|
+
expect(result.count.get()).toBe(5)
|
|
72
|
+
|
|
73
|
+
// Type inference test - should remain Store<{count: number}>
|
|
74
|
+
const typedResult: Store<{ count: number }> = result
|
|
75
|
+
expect(typedResult).toBeDefined()
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
test('passes through existing State unchanged', () => {
|
|
79
|
+
const originalState = state(42)
|
|
80
|
+
const result = toSignal(originalState)
|
|
81
|
+
|
|
82
|
+
// Runtime behavior
|
|
83
|
+
expect(result).toBe(originalState) // Should be the same instance
|
|
84
|
+
expect(isState(result)).toBe(true)
|
|
85
|
+
expect(result.get()).toBe(42)
|
|
86
|
+
|
|
87
|
+
// Type inference test - should remain State<number>
|
|
88
|
+
const typedResult: State<number> = result
|
|
89
|
+
expect(typedResult).toBeDefined()
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('passes through existing Computed unchanged', () => {
|
|
93
|
+
const originalComputed = computed(() => 'hello world')
|
|
94
|
+
const result = toSignal(originalComputed)
|
|
95
|
+
|
|
96
|
+
// Runtime behavior
|
|
97
|
+
expect(result).toBe(originalComputed) // Should be the same instance
|
|
98
|
+
expect(isComputed(result)).toBe(true)
|
|
99
|
+
expect(result.get()).toBe('hello world')
|
|
100
|
+
|
|
101
|
+
// Type inference test - should remain Computed<string>
|
|
102
|
+
const typedResult: Computed<string> = result
|
|
103
|
+
expect(typedResult).toBeDefined()
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test('converts function to Computed<T>', () => {
|
|
107
|
+
const fn = () => Math.random()
|
|
108
|
+
const result = toSignal(fn)
|
|
109
|
+
|
|
110
|
+
// Runtime behavior - functions are correctly converted to Computed
|
|
111
|
+
expect(isComputed(result)).toBe(true)
|
|
112
|
+
expect(typeof result.get()).toBe('number')
|
|
113
|
+
|
|
114
|
+
// Type inference test - should be Computed<number>
|
|
115
|
+
const typedResult: Computed<number> = result
|
|
116
|
+
expect(typedResult).toBeDefined()
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
test('converts primitive to State<T>', () => {
|
|
120
|
+
const num = 42
|
|
121
|
+
const result = toSignal(num)
|
|
122
|
+
|
|
123
|
+
// Runtime behavior - primitives are correctly converted to State
|
|
124
|
+
expect(isState(result)).toBe(true)
|
|
125
|
+
expect(result.get()).toBe(42)
|
|
126
|
+
|
|
127
|
+
// Type inference test - should be State<number>
|
|
128
|
+
const typedResult: State<number> = result
|
|
129
|
+
expect(typedResult).toBeDefined()
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
test('converts object to State<T>', () => {
|
|
133
|
+
const obj = new Date('2024-01-01')
|
|
134
|
+
const result = toSignal(obj)
|
|
135
|
+
|
|
136
|
+
// Runtime behavior - objects are correctly converted to State
|
|
137
|
+
expect(isState(result)).toBe(true)
|
|
138
|
+
expect(result.get()).toBe(obj)
|
|
139
|
+
|
|
140
|
+
// Type inference test - should be State<Date>
|
|
141
|
+
const typedResult: State<Date> = result
|
|
142
|
+
expect(typedResult).toBeDefined()
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
describe('edge cases', () => {
|
|
147
|
+
test('handles nested arrays', () => {
|
|
148
|
+
const nestedArr = [
|
|
149
|
+
[1, 2],
|
|
150
|
+
[3, 4],
|
|
151
|
+
]
|
|
152
|
+
const result = toSignal(nestedArr)
|
|
153
|
+
|
|
154
|
+
expect(isStore(result)).toBe(true)
|
|
155
|
+
// With the fixed behavior, nested arrays should be recovered as arrays
|
|
156
|
+
const firstElement = result[0].get()
|
|
157
|
+
const secondElement = result[1].get()
|
|
158
|
+
|
|
159
|
+
// The expected behavior - nested arrays are recovered as arrays
|
|
160
|
+
expect(firstElement).toEqual([1, 2])
|
|
161
|
+
expect(secondElement).toEqual([3, 4])
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
test('handles arrays with mixed types', () => {
|
|
165
|
+
const mixedArr = [1, 'hello', { key: 'value' }]
|
|
166
|
+
const result = toSignal(mixedArr)
|
|
167
|
+
|
|
168
|
+
expect(isStore(result)).toBe(true)
|
|
169
|
+
expect(result['0'].get()).toBe(1)
|
|
170
|
+
expect(result['1'].get()).toBe('hello')
|
|
171
|
+
expect(result['2'].get()).toEqual({ key: 'value' })
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
test('handles sparse arrays', () => {
|
|
175
|
+
const sparseArr = new Array(3)
|
|
176
|
+
sparseArr[1] = 'middle'
|
|
177
|
+
const result = toSignal(sparseArr)
|
|
178
|
+
|
|
179
|
+
expect(isStore(result)).toBe(true)
|
|
180
|
+
expect('0' in result).toBe(false)
|
|
181
|
+
expect(result['1'].get()).toBe('middle')
|
|
182
|
+
expect('2' in result).toBe(false)
|
|
183
|
+
})
|
|
184
|
+
})
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
describe('toMutableSignal', () => {
|
|
188
|
+
describe('type inference and runtime behavior', () => {
|
|
189
|
+
test('converts array to Store<Record<string, T>>', () => {
|
|
190
|
+
const arr = [
|
|
191
|
+
{ id: 1, name: 'Alice' },
|
|
192
|
+
{ id: 2, name: 'Bob' },
|
|
193
|
+
]
|
|
194
|
+
const result = toMutableSignal(arr)
|
|
195
|
+
|
|
196
|
+
// Runtime behavior
|
|
197
|
+
expect(isStore(result)).toBe(true)
|
|
198
|
+
expect(result['0'].get()).toEqual({ id: 1, name: 'Alice' })
|
|
199
|
+
expect(result['1'].get()).toEqual({ id: 2, name: 'Bob' })
|
|
200
|
+
|
|
201
|
+
// Type inference test - now correctly returns Store<Record<string, {id: number, name: string}>>
|
|
202
|
+
const typedResult: Store<
|
|
203
|
+
Record<string, { id: number; name: string }>
|
|
204
|
+
> = result
|
|
205
|
+
expect(typedResult).toBeDefined()
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
test('converts record to Store<T>', () => {
|
|
209
|
+
const record = { name: 'Alice', age: 30 }
|
|
210
|
+
const result = toMutableSignal(record)
|
|
211
|
+
|
|
212
|
+
// Runtime behavior
|
|
213
|
+
expect(isStore(result)).toBe(true)
|
|
214
|
+
expect(result.name.get()).toBe('Alice')
|
|
215
|
+
expect(result.age.get()).toBe(30)
|
|
216
|
+
|
|
217
|
+
// Type inference test - should be Store<{name: string, age: number}>
|
|
218
|
+
const typedResult: Store<{ name: string; age: number }> = result
|
|
219
|
+
expect(typedResult).toBeDefined()
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
test('passes through existing Store unchanged', () => {
|
|
223
|
+
const originalStore = store({ count: 5 })
|
|
224
|
+
const result = toMutableSignal(originalStore)
|
|
225
|
+
|
|
226
|
+
// Runtime behavior
|
|
227
|
+
expect(result).toBe(originalStore) // Should be the same instance
|
|
228
|
+
expect(isStore(result)).toBe(true)
|
|
229
|
+
expect(result.count.get()).toBe(5)
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
test('passes through existing State unchanged', () => {
|
|
233
|
+
const originalState = state(42)
|
|
234
|
+
const result = toMutableSignal(originalState)
|
|
235
|
+
|
|
236
|
+
// Runtime behavior
|
|
237
|
+
expect(result).toBe(originalState) // Should be the same instance
|
|
238
|
+
expect(isState(result)).toBe(true)
|
|
239
|
+
expect(result.get()).toBe(42)
|
|
240
|
+
|
|
241
|
+
// Type inference test - should be State<number>
|
|
242
|
+
const typedResult: State<number> = result
|
|
243
|
+
expect(typedResult).toBeDefined()
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
test('converts primitive to State<T>', () => {
|
|
247
|
+
const num = 42
|
|
248
|
+
const result = toMutableSignal(num)
|
|
249
|
+
|
|
250
|
+
// Runtime behavior - primitives are correctly converted to State
|
|
251
|
+
expect(isState(result)).toBe(true)
|
|
252
|
+
expect(result.get()).toBe(42)
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
test('converts object to State<T> (not Store)', () => {
|
|
256
|
+
const obj = new Date('2024-01-01')
|
|
257
|
+
const result = toMutableSignal(obj)
|
|
258
|
+
|
|
259
|
+
// Runtime behavior - objects are correctly converted to State
|
|
260
|
+
expect(isState(result)).toBe(true)
|
|
261
|
+
expect(result.get()).toBe(obj)
|
|
262
|
+
|
|
263
|
+
// Type inference test - should be State<Date>
|
|
264
|
+
const typedResult: State<Date> = result
|
|
265
|
+
expect(typedResult).toBeDefined()
|
|
266
|
+
})
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
describe('differences from toSignal', () => {
|
|
270
|
+
test('does not accept functions (only mutable signals)', () => {
|
|
271
|
+
// toMutableSignal should not have a function overload
|
|
272
|
+
// This test documents the expected behavior difference
|
|
273
|
+
const fn = () => 'test'
|
|
274
|
+
const result = toMutableSignal(fn)
|
|
275
|
+
|
|
276
|
+
// Should treat function as a regular value and create State
|
|
277
|
+
expect(isState(result)).toBe(true)
|
|
278
|
+
expect(result.get()).toBe(fn)
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
test('does not accept Computed signals', () => {
|
|
282
|
+
// toMutableSignal should not accept Computed signals
|
|
283
|
+
const comp = computed(() => 'computed value')
|
|
284
|
+
const result = toMutableSignal(comp)
|
|
285
|
+
|
|
286
|
+
// Should treat Computed as a regular object and create State
|
|
287
|
+
expect(isState(result)).toBe(true)
|
|
288
|
+
expect(result.get()).toBe(comp)
|
|
289
|
+
})
|
|
290
|
+
})
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
describe('Signal compatibility', () => {
|
|
294
|
+
test('all results implement Signal<T> interface', () => {
|
|
295
|
+
const arraySignal = toSignal([1, 2, 3])
|
|
296
|
+
const recordSignal = toSignal({ a: 1, b: 2 })
|
|
297
|
+
const primitiveSignal = toSignal(42)
|
|
298
|
+
const functionSignal = toSignal(() => 'hello')
|
|
299
|
+
const stateSignal = toSignal(state(true))
|
|
300
|
+
|
|
301
|
+
// All should have get() method
|
|
302
|
+
expect(typeof arraySignal.get).toBe('function')
|
|
303
|
+
expect(typeof recordSignal.get).toBe('function')
|
|
304
|
+
expect(typeof primitiveSignal.get).toBe('function')
|
|
305
|
+
expect(typeof functionSignal.get).toBe('function')
|
|
306
|
+
expect(typeof stateSignal.get).toBe('function')
|
|
307
|
+
|
|
308
|
+
// All should be assignable to Signal<T>
|
|
309
|
+
const signals: Signal<unknown & {}>[] = [
|
|
310
|
+
arraySignal,
|
|
311
|
+
recordSignal,
|
|
312
|
+
primitiveSignal,
|
|
313
|
+
functionSignal,
|
|
314
|
+
stateSignal,
|
|
315
|
+
]
|
|
316
|
+
expect(signals.length).toBe(5)
|
|
317
|
+
})
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
describe('Type precision tests', () => {
|
|
321
|
+
test('array type should infer element type correctly', () => {
|
|
322
|
+
// Test that arrays infer the correct element type
|
|
323
|
+
const stringArray = ['a', 'b', 'c']
|
|
324
|
+
const stringArraySignal = toSignal(stringArray)
|
|
325
|
+
|
|
326
|
+
// Should be Store<Record<string, string>>
|
|
327
|
+
expect(stringArraySignal['0'].get()).toBe('a')
|
|
328
|
+
|
|
329
|
+
const numberArray = [1, 2, 3]
|
|
330
|
+
const numberArraySignal = toSignal(numberArray)
|
|
331
|
+
|
|
332
|
+
// Should be Store<Record<string, number>>
|
|
333
|
+
expect(typeof numberArraySignal['0'].get()).toBe('number')
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
test('complex object arrays maintain precise typing', () => {
|
|
337
|
+
interface User {
|
|
338
|
+
id: number
|
|
339
|
+
name: string
|
|
340
|
+
email: string
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const users: User[] = [
|
|
344
|
+
{ id: 1, name: 'Alice', email: 'alice@example.com' },
|
|
345
|
+
{ id: 2, name: 'Bob', email: 'bob@example.com' },
|
|
346
|
+
]
|
|
347
|
+
|
|
348
|
+
const usersSignal = toSignal(users)
|
|
349
|
+
|
|
350
|
+
// Should maintain User type for each element
|
|
351
|
+
const firstUser = usersSignal['0'].get()
|
|
352
|
+
expect(firstUser.id).toBe(1)
|
|
353
|
+
expect(firstUser.name).toBe('Alice')
|
|
354
|
+
expect(firstUser.email).toBe('alice@example.com')
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
describe('Type inference issues', () => {
|
|
358
|
+
test('demonstrates current type inference problem', () => {
|
|
359
|
+
// Current issue: when passing an array, T is inferred as the array type
|
|
360
|
+
// instead of the element type, causing type compatibility problems
|
|
361
|
+
const items = [{ id: 1 }, { id: 2 }]
|
|
362
|
+
const result = toSignal(items)
|
|
363
|
+
|
|
364
|
+
// This should work but may have type issues in external libraries
|
|
365
|
+
// The return type should be Store<Record<string, {id: number}>>
|
|
366
|
+
// But currently it might be inferred as Store<Record<string, {id: number}[]>>
|
|
367
|
+
|
|
368
|
+
// Let's verify the actual behavior
|
|
369
|
+
expect(isStore(result)).toBe(true)
|
|
370
|
+
expect(result['0'].get()).toEqual({ id: 1 })
|
|
371
|
+
expect(result['1'].get()).toEqual({ id: 2 })
|
|
372
|
+
|
|
373
|
+
// Type assertion test - this should now work with correct typing
|
|
374
|
+
const typedResult: Store<Record<string, { id: number }>> = result
|
|
375
|
+
expect(typedResult).toBeDefined()
|
|
376
|
+
|
|
377
|
+
// Simulate external library usage where P[K] represents element type
|
|
378
|
+
interface ExternalLibraryConstraint<P extends UnknownRecord> {
|
|
379
|
+
process<K extends keyof P>(signal: Signal<P[K]>): void
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// This should work if types are correct
|
|
383
|
+
const processor: ExternalLibraryConstraint<
|
|
384
|
+
Record<string, { id: number }>
|
|
385
|
+
> = {
|
|
386
|
+
process: <K extends keyof Record<string, { id: number }>>(
|
|
387
|
+
signal: Signal<Record<string, { id: number }>[K]>,
|
|
388
|
+
) => {
|
|
389
|
+
// Process the signal
|
|
390
|
+
const value = signal.get()
|
|
391
|
+
expect(value).toHaveProperty('id')
|
|
392
|
+
},
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// This call should work without type errors
|
|
396
|
+
processor.process(result['0'])
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
test('verifies fixed type inference for external library compatibility', () => {
|
|
400
|
+
// This test ensures the fix for the type inference issue works
|
|
401
|
+
// Fixed: toSignal<T extends unknown & {}>(value: T[]): Store<Record<string, T>>
|
|
402
|
+
// Now T = {id: number} (element type), T[] = {id: number}[] (array of elements)
|
|
403
|
+
// Return type: Store<Record<string, {id: number}>> (correct)
|
|
404
|
+
|
|
405
|
+
const items = [
|
|
406
|
+
{ id: 1, name: 'Alice' },
|
|
407
|
+
{ id: 2, name: 'Bob' },
|
|
408
|
+
]
|
|
409
|
+
const signal = toSignal(items)
|
|
410
|
+
|
|
411
|
+
// Type should be Store<Record<string, {id: number, name: string}>>
|
|
412
|
+
// Each property signal should be Signal<{id: number, name: string}>
|
|
413
|
+
const firstItemSignal = signal['0']
|
|
414
|
+
const secondItemSignal = signal['1']
|
|
415
|
+
|
|
416
|
+
// Runtime behavior works correctly
|
|
417
|
+
expect(isStore(signal)).toBe(true)
|
|
418
|
+
expect(firstItemSignal.get()).toEqual({ id: 1, name: 'Alice' })
|
|
419
|
+
expect(secondItemSignal.get()).toEqual({ id: 2, name: 'Bob' })
|
|
420
|
+
|
|
421
|
+
// Type inference should now work correctly:
|
|
422
|
+
const properlyTyped: Store<
|
|
423
|
+
Record<string, { id: number; name: string }>
|
|
424
|
+
> = signal
|
|
425
|
+
expect(properlyTyped).toBeDefined()
|
|
426
|
+
|
|
427
|
+
// These should work without type errors in external libraries
|
|
428
|
+
// that expect Signal<P[K]> where P[K] is the individual element type
|
|
429
|
+
interface ExternalAPI<P extends UnknownRecord> {
|
|
430
|
+
process<K extends keyof P>(key: K, signal: Signal<P[K]>): P[K]
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const api: ExternalAPI<
|
|
434
|
+
Record<string, { id: number; name: string }>
|
|
435
|
+
> = {
|
|
436
|
+
process: (_key, signal) => signal.get(),
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// These calls should work with proper typing now
|
|
440
|
+
const result1 = api.process('0', firstItemSignal)
|
|
441
|
+
const result2 = api.process('1', secondItemSignal)
|
|
442
|
+
|
|
443
|
+
expect(result1).toEqual({ id: 1, name: 'Alice' })
|
|
444
|
+
expect(result2).toEqual({ id: 2, name: 'Bob' })
|
|
445
|
+
|
|
446
|
+
// Verify the types are precise
|
|
447
|
+
expect(typeof result1.id).toBe('number')
|
|
448
|
+
expect(typeof result1.name).toBe('string')
|
|
449
|
+
})
|
|
450
|
+
})
|
|
451
|
+
})
|
package/test/store.test.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type StoreRemoveEvent,
|
|
9
9
|
state,
|
|
10
10
|
store,
|
|
11
|
+
toSignal,
|
|
11
12
|
UNSET,
|
|
12
13
|
} from '..'
|
|
13
14
|
|
|
@@ -44,6 +45,32 @@ describe('store', () => {
|
|
|
44
45
|
name: 'Hannah',
|
|
45
46
|
email: 'hannah@example.com',
|
|
46
47
|
})
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* store() only accepts object map types for arrays
|
|
51
|
+
*/
|
|
52
|
+
const participants = store<{
|
|
53
|
+
[x: number]: { name: string; tags: string[] }
|
|
54
|
+
}>([
|
|
55
|
+
{ name: 'Alice', tags: ['friends', 'mates'] },
|
|
56
|
+
{ name: 'Bob', tags: ['friends'] },
|
|
57
|
+
])
|
|
58
|
+
expect(participants.get()).toEqual([
|
|
59
|
+
{ name: 'Alice', tags: ['friends', 'mates'] },
|
|
60
|
+
{ name: 'Bob', tags: ['friends'] },
|
|
61
|
+
])
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* toSignal() converts arrays to object map types when creating stores
|
|
65
|
+
*/
|
|
66
|
+
const participants2 = toSignal<{ name: string; tags: string[] }[]>([
|
|
67
|
+
{ name: 'Alice', tags: ['friends', 'mates'] },
|
|
68
|
+
{ name: 'Bob', tags: ['friends'] },
|
|
69
|
+
])
|
|
70
|
+
expect(participants2.get()).toEqual([
|
|
71
|
+
{ name: 'Alice', tags: ['friends', 'mates'] },
|
|
72
|
+
{ name: 'Bob', tags: ['friends'] },
|
|
73
|
+
])
|
|
47
74
|
})
|
|
48
75
|
})
|
|
49
76
|
|
package/types/index.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @name Cause & Effect
|
|
3
|
-
* @version 0.15.
|
|
3
|
+
* @version 0.15.1
|
|
4
4
|
* @author Esther Brunner
|
|
5
5
|
*/
|
|
6
6
|
export { type Computed, type ComputedCallback, computed, isComputed, isComputedCallback, TYPE_COMPUTED, } from './src/computed';
|
|
7
|
-
export { type DiffResult, diff, isEqual, type UnknownRecord } from './src/diff';
|
|
7
|
+
export { type DiffResult, diff, isEqual, type UnknownRecord, type UnknownRecordOrArray, } from './src/diff';
|
|
8
8
|
export { type EffectCallback, effect, type MaybeCleanup } from './src/effect';
|
|
9
9
|
export { type MatchHandlers, match } from './src/match';
|
|
10
10
|
export { type ResolveResult, resolve } from './src/resolve';
|
|
11
11
|
export { batch, type Cleanup, enqueue, flush, notify, observe, subscribe, type Updater, type Watcher, watch, } from './src/scheduler';
|
|
12
|
-
export { isSignal, type MaybeSignal, type Signal, type SignalValues, toSignal, UNSET, } from './src/signal';
|
|
12
|
+
export { isSignal, type MaybeSignal, type Signal, type SignalValues, toMutableSignal, toSignal, UNSET, type UnknownSignalRecord, } from './src/signal';
|
|
13
13
|
export { isState, type State, state, TYPE_STATE } from './src/state';
|
|
14
14
|
export { isStore, type Store, type StoreAddEvent, type StoreChangeEvent, type StoreEventMap, type StoreRemoveEvent, store, TYPE_STORE, } from './src/store';
|
|
15
|
-
export { CircularDependencyError, isAbortError, isAsyncFunction, isFunction, toError, } from './src/util';
|
|
15
|
+
export { CircularDependencyError, isAbortError, isAsyncFunction, isFunction, isNumber, isString, toError, } from './src/util';
|
package/types/src/diff.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
type UnknownRecord = Record<string, unknown & {}>;
|
|
2
|
-
type
|
|
2
|
+
type UnknownRecordOrArray = {
|
|
3
|
+
[x: string | number]: unknown & {};
|
|
4
|
+
};
|
|
5
|
+
type DiffResult<T extends UnknownRecordOrArray = UnknownRecord> = {
|
|
3
6
|
changed: boolean;
|
|
4
7
|
add: Partial<T>;
|
|
5
8
|
change: Partial<T>;
|
|
@@ -23,5 +26,5 @@ declare const isEqual: <T>(a: T, b: T, visited?: WeakSet<object>) => boolean;
|
|
|
23
26
|
* @param {T} newObj - The new record to compare
|
|
24
27
|
* @returns {DiffResult<T>} The result of the comparison
|
|
25
28
|
*/
|
|
26
|
-
declare const diff: <T extends
|
|
27
|
-
export { type DiffResult, diff, isEqual, type UnknownRecord };
|
|
29
|
+
declare const diff: <T extends UnknownRecordOrArray>(oldObj: T, newObj: T) => DiffResult<T>;
|
|
30
|
+
export { type DiffResult, diff, isEqual, type UnknownRecord, type UnknownRecordOrArray, };
|
package/types/src/match.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ResolveResult } from './resolve';
|
|
2
|
-
import type {
|
|
3
|
-
type MatchHandlers<S extends
|
|
2
|
+
import type { SignalValues, UnknownSignalRecord } from './signal';
|
|
3
|
+
type MatchHandlers<S extends UnknownSignalRecord> = {
|
|
4
4
|
ok?: (values: SignalValues<S>) => void;
|
|
5
5
|
err?: (errors: readonly Error[]) => void;
|
|
6
6
|
nil?: () => void;
|
|
@@ -17,5 +17,5 @@ type MatchHandlers<S extends Record<string, Signal<unknown & {}>>> = {
|
|
|
17
17
|
* @param {MatchHandlers<S>} handlers - Handlers for different states (side effects only)
|
|
18
18
|
* @returns {void} - Always returns void
|
|
19
19
|
*/
|
|
20
|
-
declare function match<S extends
|
|
20
|
+
declare function match<S extends UnknownSignalRecord>(result: ResolveResult<S>, handlers: MatchHandlers<S>): void;
|
|
21
21
|
export { match, type MatchHandlers };
|
package/types/src/resolve.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
type ResolveResult<S extends
|
|
1
|
+
import { type SignalValues, type UnknownSignalRecord } from './signal';
|
|
2
|
+
type ResolveResult<S extends UnknownSignalRecord> = {
|
|
3
3
|
ok: true;
|
|
4
4
|
values: SignalValues<S>;
|
|
5
5
|
errors?: never;
|
|
@@ -25,5 +25,5 @@ type ResolveResult<S extends Record<string, Signal<unknown & {}>>> = {
|
|
|
25
25
|
* @param {S} signals - Signals to resolve
|
|
26
26
|
* @returns {ResolveResult<S>} - Discriminated union result
|
|
27
27
|
*/
|
|
28
|
-
declare function resolve<S extends
|
|
28
|
+
declare function resolve<S extends UnknownSignalRecord>(signals: S): ResolveResult<S>;
|
|
29
29
|
export { resolve, type ResolveResult };
|
package/types/src/signal.d.ts
CHANGED
|
@@ -5,7 +5,8 @@ type Signal<T extends {}> = {
|
|
|
5
5
|
get(): T;
|
|
6
6
|
};
|
|
7
7
|
type MaybeSignal<T extends {}> = T | Signal<T> | ComputedCallback<T>;
|
|
8
|
-
type
|
|
8
|
+
type UnknownSignalRecord = Record<string, Signal<unknown & {}>>;
|
|
9
|
+
type SignalValues<S extends UnknownSignalRecord> = {
|
|
9
10
|
[K in keyof S]: S[K] extends Signal<infer T> ? T : never;
|
|
10
11
|
};
|
|
11
12
|
declare const UNSET: any;
|
|
@@ -21,20 +22,23 @@ declare const isSignal: <T extends {}>(value: unknown) => value is Signal<T>;
|
|
|
21
22
|
* Convert a value to a Signal if it's not already a Signal
|
|
22
23
|
*
|
|
23
24
|
* @since 0.9.6
|
|
25
|
+
* @param {T} value - value to convert
|
|
26
|
+
* @returns {Signal<T>} - Signal instance
|
|
24
27
|
*/
|
|
25
|
-
declare function toSignal<T extends
|
|
26
|
-
declare function toSignal<T extends
|
|
27
|
-
declare function toSignal<T extends {}>(value:
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
declare function toSignal<T extends {}>(value: T[]): Store<Record<string, T>>;
|
|
29
|
+
declare function toSignal<T extends {}>(value: (() => T) | ((abort: AbortSignal) => Promise<T>)): Computed<T>;
|
|
30
|
+
declare function toSignal<T extends {}>(value: T): T extends Store<infer U> ? Store<U> : T extends State<infer U> ? State<U> : T extends Computed<infer U> ? Computed<U> : T extends Signal<infer U> ? Signal<U> : T extends Record<string, unknown & {}> ? Store<{
|
|
31
|
+
[K in keyof T]: T[K];
|
|
32
|
+
}> : State<T>;
|
|
30
33
|
/**
|
|
31
34
|
* Convert a value to a mutable Signal if it's not already a Signal
|
|
32
35
|
*
|
|
33
|
-
* @since 0.
|
|
36
|
+
* @since 0.15.0
|
|
37
|
+
* @param {T} value - value to convert
|
|
38
|
+
* @returns {State<T> | Store<T>} - Signal instance
|
|
34
39
|
*/
|
|
35
|
-
declare function toMutableSignal<T extends
|
|
36
|
-
declare function toMutableSignal<T extends
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
export { type Signal, type MaybeSignal, type SignalValues, UNSET, isSignal, toSignal, toMutableSignal, };
|
|
40
|
+
declare function toMutableSignal<T extends {}>(value: T[]): Store<Record<string, T>>;
|
|
41
|
+
declare function toMutableSignal<T extends {}>(value: T): T extends Store<infer U> ? Store<U> : T extends State<infer U> ? State<U> : T extends Record<string, unknown & {}> ? Store<{
|
|
42
|
+
[K in keyof T]: T[K];
|
|
43
|
+
}> : State<T>;
|
|
44
|
+
export { type Signal, type MaybeSignal, type UnknownSignalRecord, type SignalValues, UNSET, isSignal, toSignal, toMutableSignal, };
|