@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.
@@ -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 (error) {
36
- // If handler throws, try error handler, otherwise rethrow
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 { toError, UNSET } from './util'
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(toError(e))
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
  }
@@ -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 Collection(
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 Collection(
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 Collection(list, (x: number) => x)
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 Collection(list, (x: number) => x)
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 Collection(
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 Collection(numbers, (x: number) => x * 2)
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 Collection(items, (x: number) => x * 2)
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 Collection(items, (x: number) => x * 2)
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 Collection(items, (x: number) => x)
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 Collection(numbers, (x: number) => x * 2)
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 Collection(numbers, (x: number) => x * 2)
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 Collection(numbers, (x: number) => x * 2)
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 Collection(numbers, (x: number) => x * 2)
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 Collection(
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 Collection(numbers, (x: number) => x * 2)
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 Collection(numbers, (x: number) => x * 2)
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 Collection(numbers, (x: number) => x)
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 Collection(numbers, (x: number) => x * 2)
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 Collection(items, (x: number) => x * 2)
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 Collection(numbers, (x: number) => x * 2)
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 Collection(empty, (x: number) => x * 2)
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 Collection(list, (x: number) =>
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 Collection(list, (str: string) => ({
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 Collection(numbers, (x: number) => x * 2)
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 Collection(
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 Collection(words, (word: string) => ({
346
- word,
347
- length: word.length,
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 Collection(numbers, (x: number) => x * 2)
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 Collection(numbers, (x: number) => x * 2)
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 Collection(numbers, (x: number) => x * 2)
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 Collection(
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 Collection(numbers, (x: number) => x * 2)
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 Collection(numbers, (x: number) => x * 2)
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 Collection(numbers, (x: number) => x * 2)
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 Collection(numbers, (x: number) => x * 2)
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 Collection(numbers, (x: number) => x * 2)
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 Collection(numbers, (x: number) => x * 2)
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 Collection(numbers, (x: number) => x * 2)
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 Collection(numbers, (x: number) => x * 2)
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 Collection(numbers, (x: number) => x * 2)
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 Collection(numbers, (x: number) => x * 2)
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 Collection(
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 Collection(list, (x: number) =>
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 Collection(
819
+ const processed = new DerivedCollection(
763
820
  items,
764
821
  (item: {
765
822
  id: number