@zeix/cause-effect 0.17.0 → 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.
- package/.ai-context.md +19 -5
- package/.cursorrules +8 -3
- package/.github/copilot-instructions.md +9 -4
- package/CLAUDE.md +96 -262
- package/README.md +232 -421
- package/archive/computed.ts +2 -2
- package/archive/memo.ts +3 -2
- package/archive/task.ts +2 -2
- package/index.dev.js +59 -26
- package/index.js +1 -1
- package/index.ts +11 -3
- package/package.json +1 -1
- package/src/classes/collection.ts +38 -28
- package/src/classes/computed.ts +3 -3
- package/src/classes/list.ts +8 -7
- package/src/classes/ref.ts +68 -0
- package/src/errors.ts +21 -0
- package/src/match.ts +5 -12
- package/src/resolve.ts +3 -2
- package/src/util.ts +0 -4
- package/test/collection.test.ts +104 -47
- package/test/ref.test.ts +227 -0
- package/types/index.d.ts +5 -4
- package/types/src/classes/collection.d.ts +21 -7
- package/types/src/classes/list.d.ts +4 -4
- package/types/src/classes/ref.d.ts +39 -0
- package/types/src/errors.d.ts +6 -1
- package/types/src/util.d.ts +1 -2
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { type Guard, validateSignalValue } from '../errors'
|
|
2
|
+
import { notifyWatchers, subscribeActiveWatcher, type Watcher } from '../system'
|
|
3
|
+
import { isObjectOfType } from '../util'
|
|
4
|
+
|
|
5
|
+
/* === Constants === */
|
|
6
|
+
|
|
7
|
+
const TYPE_REF = 'Ref'
|
|
8
|
+
|
|
9
|
+
/* === Class === */
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Create a new ref signal.
|
|
13
|
+
*
|
|
14
|
+
* @since 0.17.1
|
|
15
|
+
*/
|
|
16
|
+
class Ref<T extends {}> {
|
|
17
|
+
#watchers = new Set<Watcher>()
|
|
18
|
+
#value: T
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Create a new ref signal.
|
|
22
|
+
*
|
|
23
|
+
* @param {T} value - Reference to external object
|
|
24
|
+
* @param {Guard<T>} guard - Optional guard function to validate the value
|
|
25
|
+
* @throws {NullishSignalValueError} - If the value is null or undefined
|
|
26
|
+
* @throws {InvalidSignalValueError} - If the value is invalid
|
|
27
|
+
*/
|
|
28
|
+
constructor(value: T, guard?: Guard<T>) {
|
|
29
|
+
validateSignalValue('ref', value, guard)
|
|
30
|
+
|
|
31
|
+
this.#value = value
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get [Symbol.toStringTag](): string {
|
|
35
|
+
return TYPE_REF
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get the value of the ref signal.
|
|
40
|
+
*
|
|
41
|
+
* @returns {T} - Object reference
|
|
42
|
+
*/
|
|
43
|
+
get(): T {
|
|
44
|
+
subscribeActiveWatcher(this.#watchers)
|
|
45
|
+
return this.#value
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Notify watchers of relevant changes in the external reference
|
|
50
|
+
*/
|
|
51
|
+
notify(): void {
|
|
52
|
+
notifyWatchers(this.#watchers)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* === Functions === */
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Check if the provided value is a Ref instance
|
|
60
|
+
*
|
|
61
|
+
* @since 0.17.1
|
|
62
|
+
* @param {unknown} value - Value to check
|
|
63
|
+
* @returns {boolean} - True if the value is a Ref instance, false otherwise
|
|
64
|
+
*/
|
|
65
|
+
const isRef = /*#__PURE__*/ <T extends {}>(value: unknown): value is Ref<T> =>
|
|
66
|
+
isObjectOfType(value, TYPE_REF)
|
|
67
|
+
|
|
68
|
+
export { TYPE_REF, Ref, isRef }
|
package/src/errors.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { isMutableSignal, type MutableSignal } from './signal'
|
|
2
2
|
import { isFunction, isSymbol, UNSET, valueString } from './util'
|
|
3
3
|
|
|
4
|
+
/* === Types === */
|
|
5
|
+
|
|
6
|
+
type Guard<T> = (value: unknown) => value is T
|
|
7
|
+
|
|
8
|
+
/* === Classes === */
|
|
9
|
+
|
|
4
10
|
class CircularDependencyError extends Error {
|
|
5
11
|
constructor(where: string) {
|
|
6
12
|
super(`Circular dependency detected in ${where}`)
|
|
@@ -26,6 +32,13 @@ class InvalidCallbackError extends TypeError {
|
|
|
26
32
|
}
|
|
27
33
|
}
|
|
28
34
|
|
|
35
|
+
class InvalidCollectionSourceError extends TypeError {
|
|
36
|
+
constructor(where: string, value: unknown) {
|
|
37
|
+
super(`Invalid ${where} source ${valueString(value)}`)
|
|
38
|
+
this.name = 'InvalidCollectionSourceError'
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
29
42
|
class InvalidSignalValueError extends TypeError {
|
|
30
43
|
constructor(where: string, value: unknown) {
|
|
31
44
|
super(`Invalid signal value ${valueString(value)} in ${where}`)
|
|
@@ -49,6 +62,11 @@ class ReadonlySignalError extends Error {
|
|
|
49
62
|
}
|
|
50
63
|
}
|
|
51
64
|
|
|
65
|
+
/* === Functions === */
|
|
66
|
+
|
|
67
|
+
const createError = /*#__PURE__*/ (reason: unknown): Error =>
|
|
68
|
+
reason instanceof Error ? reason : Error(String(reason))
|
|
69
|
+
|
|
52
70
|
const validateCallback = (
|
|
53
71
|
where: string,
|
|
54
72
|
value: unknown,
|
|
@@ -77,12 +95,15 @@ const guardMutableSignal = <T extends {}>(
|
|
|
77
95
|
}
|
|
78
96
|
|
|
79
97
|
export {
|
|
98
|
+
type Guard,
|
|
80
99
|
CircularDependencyError,
|
|
81
100
|
DuplicateKeyError,
|
|
82
101
|
InvalidCallbackError,
|
|
102
|
+
InvalidCollectionSourceError,
|
|
83
103
|
InvalidSignalValueError,
|
|
84
104
|
NullishSignalValueError,
|
|
85
105
|
ReadonlySignalError,
|
|
106
|
+
createError,
|
|
86
107
|
validateCallback,
|
|
87
108
|
validateSignalValue,
|
|
88
109
|
guardMutableSignal,
|
package/src/match.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { createError } from './errors'
|
|
1
2
|
import type { ResolveResult } from './resolve'
|
|
2
3
|
import type { SignalValues, UnknownSignalRecord } from './signal'
|
|
3
|
-
import { toError } from './util'
|
|
4
4
|
|
|
5
5
|
/* === Types === */
|
|
6
6
|
|
|
@@ -32,17 +32,10 @@ function match<S extends UnknownSignalRecord>(
|
|
|
32
32
|
if (result.pending) handlers.nil?.()
|
|
33
33
|
else if (result.errors) handlers.err?.(result.errors)
|
|
34
34
|
else if (result.ok) handlers.ok(result.values)
|
|
35
|
-
} catch (
|
|
36
|
-
|
|
37
|
-
if (
|
|
38
|
-
handlers.err
|
|
39
|
-
(!result.errors || !result.errors.includes(toError(error)))
|
|
40
|
-
)
|
|
41
|
-
handlers.err(
|
|
42
|
-
result.errors
|
|
43
|
-
? [...result.errors, toError(error)]
|
|
44
|
-
: [toError(error)],
|
|
45
|
-
)
|
|
35
|
+
} catch (e) {
|
|
36
|
+
const error = createError(e)
|
|
37
|
+
if (handlers.err && (!result.errors || !result.errors.includes(error)))
|
|
38
|
+
handlers.err(result.errors ? [...result.errors, error] : [error])
|
|
46
39
|
else throw error
|
|
47
40
|
}
|
|
48
41
|
}
|
package/src/resolve.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { UnknownRecord } from './diff'
|
|
2
|
+
import { createError } from './errors'
|
|
2
3
|
import type { SignalValues, UnknownSignalRecord } from './signal'
|
|
3
|
-
import {
|
|
4
|
+
import { UNSET } from './util'
|
|
4
5
|
|
|
5
6
|
/* === Types === */
|
|
6
7
|
|
|
@@ -33,7 +34,7 @@ function resolve<S extends UnknownSignalRecord>(signals: S): ResolveResult<S> {
|
|
|
33
34
|
if (value === UNSET) pending = true
|
|
34
35
|
else values[key] = value
|
|
35
36
|
} catch (e) {
|
|
36
|
-
errors.push(
|
|
37
|
+
errors.push(createError(e))
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
|
package/src/util.ts
CHANGED
|
@@ -63,9 +63,6 @@ const hasMethod = /*#__PURE__*/ <
|
|
|
63
63
|
const isAbortError = /*#__PURE__*/ (error: unknown): boolean =>
|
|
64
64
|
error instanceof DOMException && error.name === 'AbortError'
|
|
65
65
|
|
|
66
|
-
const toError = /*#__PURE__*/ (reason: unknown): Error =>
|
|
67
|
-
reason instanceof Error ? reason : Error(String(reason))
|
|
68
|
-
|
|
69
66
|
const valueString = /*#__PURE__*/ (value: unknown): string =>
|
|
70
67
|
isString(value)
|
|
71
68
|
? `"${value}"`
|
|
@@ -90,6 +87,5 @@ export {
|
|
|
90
87
|
isUniformArray,
|
|
91
88
|
hasMethod,
|
|
92
89
|
isAbortError,
|
|
93
|
-
toError,
|
|
94
90
|
valueString,
|
|
95
91
|
}
|
package/test/collection.test.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test'
|
|
2
2
|
import {
|
|
3
|
-
Collection,
|
|
4
3
|
createEffect,
|
|
5
4
|
createStore,
|
|
5
|
+
DerivedCollection,
|
|
6
6
|
isCollection,
|
|
7
7
|
List,
|
|
8
8
|
UNSET,
|
|
@@ -12,7 +12,7 @@ describe('collection', () => {
|
|
|
12
12
|
describe('creation and basic operations', () => {
|
|
13
13
|
test('creates collection with initial values from list', () => {
|
|
14
14
|
const numbers = new List([1, 2, 3])
|
|
15
|
-
const doubled = new
|
|
15
|
+
const doubled = new DerivedCollection(
|
|
16
16
|
numbers,
|
|
17
17
|
(value: number) => value * 2,
|
|
18
18
|
)
|
|
@@ -24,7 +24,7 @@ describe('collection', () => {
|
|
|
24
24
|
})
|
|
25
25
|
|
|
26
26
|
test('creates collection from function source', () => {
|
|
27
|
-
const doubled = new
|
|
27
|
+
const doubled = new DerivedCollection(
|
|
28
28
|
() => new List([10, 20, 30]),
|
|
29
29
|
(value: number) => value * 2,
|
|
30
30
|
)
|
|
@@ -37,7 +37,7 @@ describe('collection', () => {
|
|
|
37
37
|
|
|
38
38
|
test('has Symbol.toStringTag of Collection', () => {
|
|
39
39
|
const list = new List([1, 2, 3])
|
|
40
|
-
const collection = new
|
|
40
|
+
const collection = new DerivedCollection(list, (x: number) => x)
|
|
41
41
|
expect(Object.prototype.toString.call(collection)).toBe(
|
|
42
42
|
'[object Collection]',
|
|
43
43
|
)
|
|
@@ -46,7 +46,7 @@ describe('collection', () => {
|
|
|
46
46
|
test('isCollection identifies collection instances correctly', () => {
|
|
47
47
|
const store = createStore({ a: 1 })
|
|
48
48
|
const list = new List([1, 2, 3])
|
|
49
|
-
const collection = new
|
|
49
|
+
const collection = new DerivedCollection(list, (x: number) => x)
|
|
50
50
|
|
|
51
51
|
expect(isCollection(collection)).toBe(true)
|
|
52
52
|
expect(isCollection(list)).toBe(false)
|
|
@@ -57,7 +57,7 @@ describe('collection', () => {
|
|
|
57
57
|
|
|
58
58
|
test('get() returns the complete collection value', () => {
|
|
59
59
|
const numbers = new List([1, 2, 3])
|
|
60
|
-
const doubled = new
|
|
60
|
+
const doubled = new DerivedCollection(
|
|
61
61
|
numbers,
|
|
62
62
|
(value: number) => value * 2,
|
|
63
63
|
)
|
|
@@ -71,13 +71,19 @@ describe('collection', () => {
|
|
|
71
71
|
describe('length property and sizing', () => {
|
|
72
72
|
test('length property works for collections', () => {
|
|
73
73
|
const numbers = new List([1, 2, 3, 4, 5])
|
|
74
|
-
const collection = new
|
|
74
|
+
const collection = new DerivedCollection(
|
|
75
|
+
numbers,
|
|
76
|
+
(x: number) => x * 2,
|
|
77
|
+
)
|
|
75
78
|
expect(collection.length).toBe(5)
|
|
76
79
|
})
|
|
77
80
|
|
|
78
81
|
test('length is reactive and updates with changes', () => {
|
|
79
82
|
const items = new List([1, 2])
|
|
80
|
-
const collection = new
|
|
83
|
+
const collection = new DerivedCollection(
|
|
84
|
+
items,
|
|
85
|
+
(x: number) => x * 2,
|
|
86
|
+
)
|
|
81
87
|
|
|
82
88
|
expect(collection.length).toBe(2)
|
|
83
89
|
items.add(3)
|
|
@@ -88,7 +94,7 @@ describe('collection', () => {
|
|
|
88
94
|
describe('index-based access', () => {
|
|
89
95
|
test('properties can be accessed via computed signals', () => {
|
|
90
96
|
const items = new List([10, 20, 30])
|
|
91
|
-
const doubled = new
|
|
97
|
+
const doubled = new DerivedCollection(items, (x: number) => x * 2)
|
|
92
98
|
|
|
93
99
|
expect(doubled.at(0)?.get()).toBe(20)
|
|
94
100
|
expect(doubled.at(1)?.get()).toBe(40)
|
|
@@ -97,13 +103,16 @@ describe('collection', () => {
|
|
|
97
103
|
|
|
98
104
|
test('returns undefined for non-existent properties', () => {
|
|
99
105
|
const items = new List([1, 2])
|
|
100
|
-
const collection = new
|
|
106
|
+
const collection = new DerivedCollection(items, (x: number) => x)
|
|
101
107
|
expect(collection[5]).toBeUndefined()
|
|
102
108
|
})
|
|
103
109
|
|
|
104
110
|
test('supports numeric key access', () => {
|
|
105
111
|
const numbers = new List([1, 2, 3])
|
|
106
|
-
const collection = new
|
|
112
|
+
const collection = new DerivedCollection(
|
|
113
|
+
numbers,
|
|
114
|
+
(x: number) => x * 2,
|
|
115
|
+
)
|
|
107
116
|
expect(collection.at(1)?.get()).toBe(4)
|
|
108
117
|
})
|
|
109
118
|
})
|
|
@@ -111,7 +120,7 @@ describe('collection', () => {
|
|
|
111
120
|
describe('key-based access methods', () => {
|
|
112
121
|
test('byKey() returns computed signal for existing keys', () => {
|
|
113
122
|
const numbers = new List([1, 2, 3])
|
|
114
|
-
const doubled = new
|
|
123
|
+
const doubled = new DerivedCollection(numbers, (x: number) => x * 2)
|
|
115
124
|
|
|
116
125
|
const key0 = numbers.keyAt(0)
|
|
117
126
|
const key1 = numbers.keyAt(1)
|
|
@@ -130,7 +139,7 @@ describe('collection', () => {
|
|
|
130
139
|
|
|
131
140
|
test('keyAt() and indexOfKey() work correctly', () => {
|
|
132
141
|
const numbers = new List([5, 10, 15])
|
|
133
|
-
const doubled = new
|
|
142
|
+
const doubled = new DerivedCollection(numbers, (x: number) => x * 2)
|
|
134
143
|
|
|
135
144
|
const key0 = doubled.keyAt(0)
|
|
136
145
|
const key1 = doubled.keyAt(1)
|
|
@@ -147,7 +156,7 @@ describe('collection', () => {
|
|
|
147
156
|
describe('reactivity', () => {
|
|
148
157
|
test('collection-level get() is reactive', () => {
|
|
149
158
|
const numbers = new List([1, 2, 3])
|
|
150
|
-
const doubled = new
|
|
159
|
+
const doubled = new DerivedCollection(numbers, (x: number) => x * 2)
|
|
151
160
|
|
|
152
161
|
let lastArray: number[] = []
|
|
153
162
|
createEffect(() => {
|
|
@@ -161,7 +170,7 @@ describe('collection', () => {
|
|
|
161
170
|
|
|
162
171
|
test('individual signal reactivity works', () => {
|
|
163
172
|
const items = new List([{ count: 1 }, { count: 2 }])
|
|
164
|
-
const doubled = new
|
|
173
|
+
const doubled = new DerivedCollection(
|
|
165
174
|
items,
|
|
166
175
|
(item: { count: number }) => ({ count: item.count * 2 }),
|
|
167
176
|
)
|
|
@@ -184,7 +193,7 @@ describe('collection', () => {
|
|
|
184
193
|
|
|
185
194
|
test('updates are reactive', () => {
|
|
186
195
|
const numbers = new List([1, 2, 3])
|
|
187
|
-
const doubled = new
|
|
196
|
+
const doubled = new DerivedCollection(numbers, (x: number) => x * 2)
|
|
188
197
|
|
|
189
198
|
let lastArray: number[] = []
|
|
190
199
|
let arrayEffectRuns = 0
|
|
@@ -205,7 +214,7 @@ describe('collection', () => {
|
|
|
205
214
|
describe('iteration and spreading', () => {
|
|
206
215
|
test('supports for...of iteration', () => {
|
|
207
216
|
const numbers = new List([1, 2, 3])
|
|
208
|
-
const doubled = new
|
|
217
|
+
const doubled = new DerivedCollection(numbers, (x: number) => x * 2)
|
|
209
218
|
const signals = [...doubled]
|
|
210
219
|
|
|
211
220
|
expect(signals).toHaveLength(3)
|
|
@@ -216,7 +225,7 @@ describe('collection', () => {
|
|
|
216
225
|
|
|
217
226
|
test('Symbol.isConcatSpreadable is true', () => {
|
|
218
227
|
const numbers = new List([1, 2, 3])
|
|
219
|
-
const collection = new
|
|
228
|
+
const collection = new DerivedCollection(numbers, (x: number) => x)
|
|
220
229
|
expect(collection[Symbol.isConcatSpreadable]).toBe(true)
|
|
221
230
|
})
|
|
222
231
|
})
|
|
@@ -224,7 +233,7 @@ describe('collection', () => {
|
|
|
224
233
|
describe('change notifications', () => {
|
|
225
234
|
test('emits add notifications', () => {
|
|
226
235
|
const numbers = new List([1, 2])
|
|
227
|
-
const doubled = new
|
|
236
|
+
const doubled = new DerivedCollection(numbers, (x: number) => x * 2)
|
|
228
237
|
|
|
229
238
|
let arrayAddNotification: readonly string[] = []
|
|
230
239
|
doubled.on('add', keys => {
|
|
@@ -239,7 +248,7 @@ describe('collection', () => {
|
|
|
239
248
|
|
|
240
249
|
test('emits remove notifications when items are removed', () => {
|
|
241
250
|
const items = new List([1, 2, 3])
|
|
242
|
-
const doubled = new
|
|
251
|
+
const doubled = new DerivedCollection(items, (x: number) => x * 2)
|
|
243
252
|
|
|
244
253
|
let arrayRemoveNotification: readonly string[] = []
|
|
245
254
|
doubled.on('remove', keys => {
|
|
@@ -252,7 +261,7 @@ describe('collection', () => {
|
|
|
252
261
|
|
|
253
262
|
test('emits sort notifications when source is sorted', () => {
|
|
254
263
|
const numbers = new List([3, 1, 2])
|
|
255
|
-
const doubled = new
|
|
264
|
+
const doubled = new DerivedCollection(numbers, (x: number) => x * 2)
|
|
256
265
|
|
|
257
266
|
let sortNotification: readonly string[] = []
|
|
258
267
|
doubled.on('sort', newOrder => {
|
|
@@ -268,14 +277,17 @@ describe('collection', () => {
|
|
|
268
277
|
describe('edge cases', () => {
|
|
269
278
|
test('handles empty collections correctly', () => {
|
|
270
279
|
const empty = new List<number>([])
|
|
271
|
-
const collection = new
|
|
280
|
+
const collection = new DerivedCollection(
|
|
281
|
+
empty,
|
|
282
|
+
(x: number) => x * 2,
|
|
283
|
+
)
|
|
272
284
|
expect(collection.length).toBe(0)
|
|
273
285
|
expect(collection.get()).toEqual([])
|
|
274
286
|
})
|
|
275
287
|
|
|
276
288
|
test('handles UNSET values', () => {
|
|
277
289
|
const list = new List([1, 2, 3])
|
|
278
|
-
const processed = new
|
|
290
|
+
const processed = new DerivedCollection(list, (x: number) =>
|
|
279
291
|
x > 2 ? x : UNSET,
|
|
280
292
|
)
|
|
281
293
|
|
|
@@ -285,7 +297,7 @@ describe('collection', () => {
|
|
|
285
297
|
|
|
286
298
|
test('handles primitive values', () => {
|
|
287
299
|
const list = new List(['hello', 'world'])
|
|
288
|
-
const lengths = new
|
|
300
|
+
const lengths = new DerivedCollection(list, (str: string) => ({
|
|
289
301
|
length: str.length,
|
|
290
302
|
}))
|
|
291
303
|
|
|
@@ -298,7 +310,10 @@ describe('collection', () => {
|
|
|
298
310
|
describe('synchronous transformations', () => {
|
|
299
311
|
test('transforms collection values with sync callback', () => {
|
|
300
312
|
const numbers = new List([1, 2, 3])
|
|
301
|
-
const doubled = new
|
|
313
|
+
const doubled = new DerivedCollection(
|
|
314
|
+
numbers,
|
|
315
|
+
(x: number) => x * 2,
|
|
316
|
+
)
|
|
302
317
|
const quadrupled = doubled.deriveCollection(
|
|
303
318
|
(x: number) => x * 2,
|
|
304
319
|
)
|
|
@@ -314,7 +329,7 @@ describe('collection', () => {
|
|
|
314
329
|
{ name: 'Alice', age: 25 },
|
|
315
330
|
{ name: 'Bob', age: 30 },
|
|
316
331
|
])
|
|
317
|
-
const basicInfo = new
|
|
332
|
+
const basicInfo = new DerivedCollection(
|
|
318
333
|
users,
|
|
319
334
|
(user: { name: string; age: number }) => ({
|
|
320
335
|
displayName: user.name.toUpperCase(),
|
|
@@ -342,10 +357,13 @@ describe('collection', () => {
|
|
|
342
357
|
|
|
343
358
|
test('transforms string values to different types', () => {
|
|
344
359
|
const words = new List(['hello', 'world', 'test'])
|
|
345
|
-
const wordInfo = new
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
360
|
+
const wordInfo = new DerivedCollection(
|
|
361
|
+
words,
|
|
362
|
+
(word: string) => ({
|
|
363
|
+
word,
|
|
364
|
+
length: word.length,
|
|
365
|
+
}),
|
|
366
|
+
)
|
|
349
367
|
const analysis = wordInfo.deriveCollection(
|
|
350
368
|
(info: { word: string; length: number }) => ({
|
|
351
369
|
...info,
|
|
@@ -366,7 +384,10 @@ describe('collection', () => {
|
|
|
366
384
|
|
|
367
385
|
test('derived collection reactivity with sync transformations', () => {
|
|
368
386
|
const numbers = new List([1, 2, 3])
|
|
369
|
-
const doubled = new
|
|
387
|
+
const doubled = new DerivedCollection(
|
|
388
|
+
numbers,
|
|
389
|
+
(x: number) => x * 2,
|
|
390
|
+
)
|
|
370
391
|
const quadrupled = doubled.deriveCollection(
|
|
371
392
|
(x: number) => x * 2,
|
|
372
393
|
)
|
|
@@ -392,7 +413,10 @@ describe('collection', () => {
|
|
|
392
413
|
|
|
393
414
|
test('derived collection responds to source removal', () => {
|
|
394
415
|
const numbers = new List([1, 2, 3, 4])
|
|
395
|
-
const doubled = new
|
|
416
|
+
const doubled = new DerivedCollection(
|
|
417
|
+
numbers,
|
|
418
|
+
(x: number) => x * 2,
|
|
419
|
+
)
|
|
396
420
|
const quadrupled = doubled.deriveCollection(
|
|
397
421
|
(x: number) => x * 2,
|
|
398
422
|
)
|
|
@@ -407,7 +431,10 @@ describe('collection', () => {
|
|
|
407
431
|
describe('asynchronous transformations', () => {
|
|
408
432
|
test('transforms values with async callback', async () => {
|
|
409
433
|
const numbers = new List([1, 2, 3])
|
|
410
|
-
const doubled = new
|
|
434
|
+
const doubled = new DerivedCollection(
|
|
435
|
+
numbers,
|
|
436
|
+
(x: number) => x * 2,
|
|
437
|
+
)
|
|
411
438
|
|
|
412
439
|
const asyncQuadrupled = doubled.deriveCollection(
|
|
413
440
|
async (x: number, abort: AbortSignal) => {
|
|
@@ -448,7 +475,7 @@ describe('collection', () => {
|
|
|
448
475
|
{ id: 1, name: 'Alice' },
|
|
449
476
|
{ id: 2, name: 'Bob' },
|
|
450
477
|
])
|
|
451
|
-
const basicInfo = new
|
|
478
|
+
const basicInfo = new DerivedCollection(
|
|
452
479
|
users,
|
|
453
480
|
(user: { id: number; name: string }) => ({
|
|
454
481
|
userId: user.id,
|
|
@@ -507,7 +534,10 @@ describe('collection', () => {
|
|
|
507
534
|
|
|
508
535
|
test('async derived collection reactivity', async () => {
|
|
509
536
|
const numbers = new List([1, 2, 3])
|
|
510
|
-
const doubled = new
|
|
537
|
+
const doubled = new DerivedCollection(
|
|
538
|
+
numbers,
|
|
539
|
+
(x: number) => x * 2,
|
|
540
|
+
)
|
|
511
541
|
const asyncQuadrupled = doubled.deriveCollection(
|
|
512
542
|
async (x: number, abort: AbortSignal) => {
|
|
513
543
|
await new Promise(resolve => setTimeout(resolve, 10))
|
|
@@ -544,7 +574,10 @@ describe('collection', () => {
|
|
|
544
574
|
|
|
545
575
|
test('handles AbortSignal cancellation', async () => {
|
|
546
576
|
const numbers = new List([1, 2, 3])
|
|
547
|
-
const doubled = new
|
|
577
|
+
const doubled = new DerivedCollection(
|
|
578
|
+
numbers,
|
|
579
|
+
(x: number) => x * 2,
|
|
580
|
+
)
|
|
548
581
|
let abortCalled = false
|
|
549
582
|
|
|
550
583
|
const slowCollection = doubled.deriveCollection(
|
|
@@ -580,7 +613,10 @@ describe('collection', () => {
|
|
|
580
613
|
describe('derived collection chaining', () => {
|
|
581
614
|
test('chains multiple sync derivations', () => {
|
|
582
615
|
const numbers = new List([1, 2, 3])
|
|
583
|
-
const doubled = new
|
|
616
|
+
const doubled = new DerivedCollection(
|
|
617
|
+
numbers,
|
|
618
|
+
(x: number) => x * 2,
|
|
619
|
+
)
|
|
584
620
|
const quadrupled = doubled.deriveCollection(
|
|
585
621
|
(x: number) => x * 2,
|
|
586
622
|
)
|
|
@@ -595,7 +631,10 @@ describe('collection', () => {
|
|
|
595
631
|
|
|
596
632
|
test('chains sync and async derivations', async () => {
|
|
597
633
|
const numbers = new List([1, 2, 3])
|
|
598
|
-
const doubled = new
|
|
634
|
+
const doubled = new DerivedCollection(
|
|
635
|
+
numbers,
|
|
636
|
+
(x: number) => x * 2,
|
|
637
|
+
)
|
|
599
638
|
const quadrupled = doubled.deriveCollection(
|
|
600
639
|
(x: number) => x * 2,
|
|
601
640
|
)
|
|
@@ -625,7 +664,10 @@ describe('collection', () => {
|
|
|
625
664
|
describe('derived collection access methods', () => {
|
|
626
665
|
test('provides index-based access to computed signals', () => {
|
|
627
666
|
const numbers = new List([1, 2, 3])
|
|
628
|
-
const doubled = new
|
|
667
|
+
const doubled = new DerivedCollection(
|
|
668
|
+
numbers,
|
|
669
|
+
(x: number) => x * 2,
|
|
670
|
+
)
|
|
629
671
|
const quadrupled = doubled.deriveCollection(
|
|
630
672
|
(x: number) => x * 2,
|
|
631
673
|
)
|
|
@@ -638,7 +680,10 @@ describe('collection', () => {
|
|
|
638
680
|
|
|
639
681
|
test('supports key-based access', () => {
|
|
640
682
|
const numbers = new List([1, 2, 3])
|
|
641
|
-
const doubled = new
|
|
683
|
+
const doubled = new DerivedCollection(
|
|
684
|
+
numbers,
|
|
685
|
+
(x: number) => x * 2,
|
|
686
|
+
)
|
|
642
687
|
const quadrupled = doubled.deriveCollection(
|
|
643
688
|
(x: number) => x * 2,
|
|
644
689
|
)
|
|
@@ -660,7 +705,10 @@ describe('collection', () => {
|
|
|
660
705
|
|
|
661
706
|
test('supports iteration', () => {
|
|
662
707
|
const numbers = new List([1, 2, 3])
|
|
663
|
-
const doubled = new
|
|
708
|
+
const doubled = new DerivedCollection(
|
|
709
|
+
numbers,
|
|
710
|
+
(x: number) => x * 2,
|
|
711
|
+
)
|
|
664
712
|
const quadrupled = doubled.deriveCollection(
|
|
665
713
|
(x: number) => x * 2,
|
|
666
714
|
)
|
|
@@ -676,7 +724,10 @@ describe('collection', () => {
|
|
|
676
724
|
describe('derived collection event handling', () => {
|
|
677
725
|
test('emits add events when source adds items', () => {
|
|
678
726
|
const numbers = new List([1, 2])
|
|
679
|
-
const doubled = new
|
|
727
|
+
const doubled = new DerivedCollection(
|
|
728
|
+
numbers,
|
|
729
|
+
(x: number) => x * 2,
|
|
730
|
+
)
|
|
680
731
|
const quadrupled = doubled.deriveCollection(
|
|
681
732
|
(x: number) => x * 2,
|
|
682
733
|
)
|
|
@@ -694,7 +745,10 @@ describe('collection', () => {
|
|
|
694
745
|
|
|
695
746
|
test('emits remove events when source removes items', () => {
|
|
696
747
|
const numbers = new List([1, 2, 3])
|
|
697
|
-
const doubled = new
|
|
748
|
+
const doubled = new DerivedCollection(
|
|
749
|
+
numbers,
|
|
750
|
+
(x: number) => x * 2,
|
|
751
|
+
)
|
|
698
752
|
const quadrupled = doubled.deriveCollection(
|
|
699
753
|
(x: number) => x * 2,
|
|
700
754
|
)
|
|
@@ -710,7 +764,10 @@ describe('collection', () => {
|
|
|
710
764
|
|
|
711
765
|
test('emits sort events when source is sorted', () => {
|
|
712
766
|
const numbers = new List([3, 1, 2])
|
|
713
|
-
const doubled = new
|
|
767
|
+
const doubled = new DerivedCollection(
|
|
768
|
+
numbers,
|
|
769
|
+
(x: number) => x * 2,
|
|
770
|
+
)
|
|
714
771
|
const quadrupled = doubled.deriveCollection(
|
|
715
772
|
(x: number) => x * 2,
|
|
716
773
|
)
|
|
@@ -729,7 +786,7 @@ describe('collection', () => {
|
|
|
729
786
|
describe('edge cases', () => {
|
|
730
787
|
test('handles empty collection derivation', () => {
|
|
731
788
|
const empty = new List<number>([])
|
|
732
|
-
const emptyCollection = new
|
|
789
|
+
const emptyCollection = new DerivedCollection(
|
|
733
790
|
empty,
|
|
734
791
|
(x: number) => x * 2,
|
|
735
792
|
)
|
|
@@ -743,7 +800,7 @@ describe('collection', () => {
|
|
|
743
800
|
|
|
744
801
|
test('handles UNSET values in transformation', () => {
|
|
745
802
|
const list = new List([1, 2, 3])
|
|
746
|
-
const filtered = new
|
|
803
|
+
const filtered = new DerivedCollection(list, (x: number) =>
|
|
747
804
|
x > 1 ? { value: x } : UNSET,
|
|
748
805
|
)
|
|
749
806
|
const doubled = filtered.deriveCollection(
|
|
@@ -759,7 +816,7 @@ describe('collection', () => {
|
|
|
759
816
|
{ id: 2, data: { value: 20, active: false } },
|
|
760
817
|
])
|
|
761
818
|
|
|
762
|
-
const processed = new
|
|
819
|
+
const processed = new DerivedCollection(
|
|
763
820
|
items,
|
|
764
821
|
(item: {
|
|
765
822
|
id: number
|