@zeix/cause-effect 0.17.3 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/.ai-context.md +169 -227
  2. package/.cursorrules +41 -35
  3. package/.github/copilot-instructions.md +176 -116
  4. package/ARCHITECTURE.md +276 -0
  5. package/CHANGELOG.md +29 -0
  6. package/CLAUDE.md +201 -143
  7. package/GUIDE.md +298 -0
  8. package/README.md +246 -193
  9. package/REQUIREMENTS.md +100 -0
  10. package/bench/reactivity.bench.ts +577 -0
  11. package/context7.json +4 -0
  12. package/examples/events-sensor.ts +187 -0
  13. package/examples/selector-sensor.ts +173 -0
  14. package/index.dev.js +1390 -1008
  15. package/index.js +1 -1
  16. package/index.ts +60 -74
  17. package/package.json +5 -2
  18. package/skills/changelog-keeper/SKILL.md +59 -0
  19. package/skills/changelog-keeper/agents/openai.yaml +4 -0
  20. package/src/errors.ts +118 -74
  21. package/src/graph.ts +612 -0
  22. package/src/nodes/collection.ts +512 -0
  23. package/src/nodes/effect.ts +149 -0
  24. package/src/nodes/list.ts +589 -0
  25. package/src/nodes/memo.ts +148 -0
  26. package/src/nodes/sensor.ts +149 -0
  27. package/src/nodes/state.ts +135 -0
  28. package/src/nodes/store.ts +378 -0
  29. package/src/nodes/task.ts +174 -0
  30. package/src/signal.ts +112 -66
  31. package/src/util.ts +26 -57
  32. package/test/batch.test.ts +96 -62
  33. package/test/benchmark.test.ts +473 -487
  34. package/test/collection.test.ts +456 -707
  35. package/test/effect.test.ts +293 -696
  36. package/test/list.test.ts +335 -592
  37. package/test/memo.test.ts +574 -0
  38. package/test/regression.test.ts +156 -0
  39. package/test/scope.test.ts +191 -0
  40. package/test/sensor.test.ts +454 -0
  41. package/test/signal.test.ts +220 -213
  42. package/test/state.test.ts +217 -265
  43. package/test/store.test.ts +346 -446
  44. package/test/task.test.ts +529 -0
  45. package/test/untrack.test.ts +167 -0
  46. package/types/index.d.ts +13 -15
  47. package/types/src/errors.d.ts +73 -17
  48. package/types/src/graph.d.ts +218 -0
  49. package/types/src/nodes/collection.d.ts +69 -0
  50. package/types/src/nodes/effect.d.ts +48 -0
  51. package/types/src/nodes/list.d.ts +66 -0
  52. package/types/src/nodes/memo.d.ts +63 -0
  53. package/types/src/nodes/sensor.d.ts +81 -0
  54. package/types/src/nodes/state.d.ts +78 -0
  55. package/types/src/nodes/store.d.ts +51 -0
  56. package/types/src/nodes/task.d.ts +79 -0
  57. package/types/src/signal.d.ts +43 -29
  58. package/types/src/util.d.ts +9 -16
  59. package/archive/benchmark.ts +0 -683
  60. package/archive/collection.ts +0 -253
  61. package/archive/composite.ts +0 -85
  62. package/archive/computed.ts +0 -195
  63. package/archive/list.ts +0 -483
  64. package/archive/memo.ts +0 -139
  65. package/archive/state.ts +0 -90
  66. package/archive/store.ts +0 -298
  67. package/archive/task.ts +0 -189
  68. package/src/classes/collection.ts +0 -245
  69. package/src/classes/computed.ts +0 -349
  70. package/src/classes/list.ts +0 -343
  71. package/src/classes/ref.ts +0 -70
  72. package/src/classes/state.ts +0 -102
  73. package/src/classes/store.ts +0 -262
  74. package/src/diff.ts +0 -138
  75. package/src/effect.ts +0 -93
  76. package/src/match.ts +0 -45
  77. package/src/resolve.ts +0 -49
  78. package/src/system.ts +0 -257
  79. package/test/computed.test.ts +0 -1108
  80. package/test/diff.test.ts +0 -955
  81. package/test/match.test.ts +0 -388
  82. package/test/ref.test.ts +0 -353
  83. package/test/resolve.test.ts +0 -154
  84. package/types/src/classes/collection.d.ts +0 -45
  85. package/types/src/classes/computed.d.ts +0 -94
  86. package/types/src/classes/list.d.ts +0 -43
  87. package/types/src/classes/ref.d.ts +0 -35
  88. package/types/src/classes/state.d.ts +0 -49
  89. package/types/src/classes/store.d.ts +0 -52
  90. package/types/src/diff.d.ts +0 -28
  91. package/types/src/effect.d.ts +0 -15
  92. package/types/src/match.d.ts +0 -21
  93. package/types/src/resolve.d.ts +0 -29
  94. package/types/src/system.d.ts +0 -78
@@ -0,0 +1,589 @@
1
+ import {
2
+ CircularDependencyError,
3
+ DuplicateKeyError,
4
+ validateSignalValue,
5
+ } from '../errors'
6
+ import {
7
+ activeSink,
8
+ batch,
9
+ batchDepth,
10
+ type Cleanup,
11
+ FLAG_CLEAN,
12
+ FLAG_DIRTY,
13
+ flush,
14
+ link,
15
+ type MemoNode,
16
+ propagate,
17
+ refresh,
18
+ type SinkNode,
19
+ TYPE_LIST,
20
+ untrack,
21
+ } from '../graph'
22
+ import { isFunction, isObjectOfType, isRecord } from '../util'
23
+ import {
24
+ type Collection,
25
+ type CollectionSource,
26
+ type DeriveCollectionCallback,
27
+ deriveCollection,
28
+ } from './collection'
29
+ import { createState, type State } from './state'
30
+
31
+ /* === Types === */
32
+
33
+ type UnknownRecord = Record<string, unknown>
34
+
35
+ type DiffResult = {
36
+ changed: boolean
37
+ add: UnknownRecord
38
+ change: UnknownRecord
39
+ remove: UnknownRecord
40
+ }
41
+
42
+ type KeyConfig<T> = string | ((item: T) => string | undefined)
43
+
44
+ type ListOptions<T extends {}> = {
45
+ keyConfig?: KeyConfig<T>
46
+ watched?: () => Cleanup
47
+ }
48
+
49
+ type List<T extends {}> = {
50
+ readonly [Symbol.toStringTag]: 'List'
51
+ readonly [Symbol.isConcatSpreadable]: true
52
+ [Symbol.iterator](): IterableIterator<State<T>>
53
+ readonly length: number
54
+ get(): T[]
55
+ set(next: T[]): void
56
+ update(fn: (prev: T[]) => T[]): void
57
+ at(index: number): State<T> | undefined
58
+ keys(): IterableIterator<string>
59
+ byKey(key: string): State<T> | undefined
60
+ keyAt(index: number): string | undefined
61
+ indexOfKey(key: string): number
62
+ add(value: T): string
63
+ remove(keyOrIndex: string | number): void
64
+ sort(compareFn?: (a: T, b: T) => number): void
65
+ splice(start: number, deleteCount?: number, ...items: T[]): T[]
66
+ deriveCollection<R extends {}>(
67
+ callback: (sourceValue: T) => R,
68
+ ): Collection<R>
69
+ deriveCollection<R extends {}>(
70
+ callback: (sourceValue: T, abort: AbortSignal) => Promise<R>,
71
+ ): Collection<R>
72
+ }
73
+
74
+ /* === Functions === */
75
+
76
+ /**
77
+ * Checks if two values are equal with cycle detection
78
+ *
79
+ * @since 0.15.0
80
+ * @param a - First value to compare
81
+ * @param b - Second value to compare
82
+ * @param visited - Set to track visited objects for cycle detection
83
+ * @returns Whether the two values are equal
84
+ */
85
+ function isEqual<T>(a: T, b: T, visited?: WeakSet<object>): boolean {
86
+ // Fast paths
87
+ if (Object.is(a, b)) return true
88
+ if (typeof a !== typeof b) return false
89
+ if (
90
+ a == null ||
91
+ typeof a !== 'object' ||
92
+ b == null ||
93
+ typeof b !== 'object'
94
+ )
95
+ return false
96
+
97
+ // Cycle detection (only allocate WeakSet when both values are objects)
98
+ if (!visited) visited = new WeakSet()
99
+ if (visited.has(a as object) || visited.has(b as object))
100
+ throw new CircularDependencyError('isEqual')
101
+ visited.add(a)
102
+ visited.add(b)
103
+
104
+ try {
105
+ const aIsArray = Array.isArray(a)
106
+ if (aIsArray !== Array.isArray(b)) return false
107
+
108
+ if (aIsArray) {
109
+ const aa = a as unknown[]
110
+ const ba = b as unknown[]
111
+ if (aa.length !== ba.length) return false
112
+ for (let i = 0; i < aa.length; i++)
113
+ if (!isEqual(aa[i], ba[i], visited)) return false
114
+ return true
115
+ }
116
+
117
+ if (isRecord(a) && isRecord(b)) {
118
+ const aKeys = Object.keys(a)
119
+ const bKeys = Object.keys(b)
120
+
121
+ if (aKeys.length !== bKeys.length) return false
122
+ for (const key of aKeys) {
123
+ if (!(key in b)) return false
124
+ if (!isEqual(a[key], b[key], visited)) return false
125
+ }
126
+ return true
127
+ }
128
+
129
+ // For non-records/non-arrays, they are only equal if they are the same reference
130
+ // (which would have been caught by Object.is at the beginning)
131
+ return false
132
+ } finally {
133
+ visited.delete(a)
134
+ visited.delete(b)
135
+ }
136
+ }
137
+
138
+ /** Shallow equality check for string arrays */
139
+ function keysEqual(a: string[], b: string[]): boolean {
140
+ if (a.length !== b.length) return false
141
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false
142
+ return true
143
+ }
144
+
145
+ function getKeyGenerator<T extends {}>(
146
+ keyConfig?: KeyConfig<T>,
147
+ ): [(item: T) => string, boolean] {
148
+ let keyCounter = 0
149
+ const contentBased = typeof keyConfig === 'function'
150
+ return [
151
+ typeof keyConfig === 'string'
152
+ ? () => `${keyConfig}${keyCounter++}`
153
+ : contentBased
154
+ ? (item: T) => keyConfig(item) || String(keyCounter++)
155
+ : () => String(keyCounter++),
156
+ contentBased,
157
+ ]
158
+ }
159
+
160
+ /**
161
+ * Compares two arrays using existing keys and returns differences as a DiffResult.
162
+ * Avoids object conversion by working directly with arrays and keys.
163
+ *
164
+ * @since 0.18.0
165
+ * @param prev - The old array
166
+ * @param next - The new array
167
+ * @param prevKeys - Current keys array (may be sparse or shorter than oldArray)
168
+ * @param generateKey - Function to generate keys for new items
169
+ * @param contentBased - When true, always use generateKey (content-based keys);
170
+ * when false, reuse positional keys from currentKeys (synthetic keys)
171
+ * @returns The differences in DiffResult format plus updated keys array
172
+ */
173
+ function diffArrays<T>(
174
+ prev: T[],
175
+ next: T[],
176
+ prevKeys: string[],
177
+ generateKey: (item: T) => string,
178
+ contentBased: boolean,
179
+ ): DiffResult & { newKeys: string[] } {
180
+ const visited = new WeakSet()
181
+ const add = {} as UnknownRecord
182
+ const change = {} as UnknownRecord
183
+ const remove = {} as UnknownRecord
184
+ const nextKeys: string[] = []
185
+ let changed = false
186
+
187
+ // Build a map of old values by key for quick lookup
188
+ const prevByKey = new Map<string, T>()
189
+ for (let i = 0; i < prev.length; i++) {
190
+ const key = prevKeys[i]
191
+ if (key && prev[i]) prevByKey.set(key, prev[i])
192
+ }
193
+
194
+ // Track which old keys we've seen
195
+ const seenKeys = new Set<string>()
196
+
197
+ // Process new array and build new keys array
198
+ for (let i = 0; i < next.length; i++) {
199
+ const val = next[i]
200
+ if (val === undefined) continue
201
+
202
+ // Content-based keys: always derive from item; synthetic keys: reuse by position
203
+ const key = contentBased
204
+ ? generateKey(val)
205
+ : (prevKeys[i] ?? generateKey(val))
206
+
207
+ if (seenKeys.has(key)) throw new DuplicateKeyError(TYPE_LIST, key, val)
208
+
209
+ nextKeys.push(key)
210
+ seenKeys.add(key)
211
+
212
+ // Check if this key existed before
213
+ if (!prevByKey.has(key)) {
214
+ add[key] = val
215
+ changed = true
216
+ } else if (!isEqual(prevByKey.get(key), val, visited)) {
217
+ change[key] = val
218
+ changed = true
219
+ }
220
+ }
221
+
222
+ // Find removed keys (existed in old but not in new)
223
+ for (const [key] of prevByKey) {
224
+ if (!seenKeys.has(key)) {
225
+ remove[key] = null
226
+ changed = true
227
+ }
228
+ }
229
+
230
+ // Detect reorder even when no values changed
231
+ if (!changed && !keysEqual(prevKeys, nextKeys)) changed = true
232
+
233
+ return { add, change, remove, newKeys: nextKeys, changed }
234
+ }
235
+
236
+ /**
237
+ * Creates a reactive list with stable keys and per-item reactivity.
238
+ *
239
+ * @since 0.18.0
240
+ * @param value - Initial array of items
241
+ * @param options - Optional configuration for key generation and watch lifecycle
242
+ * @returns A List signal
243
+ */
244
+ function createList<T extends {}>(
245
+ value: T[],
246
+ options?: ListOptions<T>,
247
+ ): List<T> {
248
+ validateSignalValue(TYPE_LIST, value, Array.isArray)
249
+
250
+ const signals = new Map<string, State<T>>()
251
+ let keys: string[] = []
252
+
253
+ const [generateKey, contentBased] = getKeyGenerator(options?.keyConfig)
254
+
255
+ // --- Internal helpers ---
256
+
257
+ // Build current value from child signals
258
+ const buildValue = (): T[] =>
259
+ keys
260
+ .map(key => signals.get(key)?.get())
261
+ .filter(v => v !== undefined) as T[]
262
+
263
+ // Structural tracking node — not a general-purpose Memo.
264
+ // On first get(): refresh() establishes edges from child signals.
265
+ // On subsequent get(): untrack(buildValue) rebuilds without re-linking.
266
+ // Mutation methods (add/remove/set/splice) null out sources to force re-establishment.
267
+ const node: MemoNode<T[]> = {
268
+ fn: buildValue,
269
+ value,
270
+ flags: FLAG_DIRTY,
271
+ sources: null,
272
+ sourcesTail: null,
273
+ sinks: null,
274
+ sinksTail: null,
275
+ equals: isEqual,
276
+ error: undefined,
277
+ }
278
+
279
+ const toRecord = (array: T[]): Record<string, T> => {
280
+ const record = {} as Record<string, T>
281
+ for (let i = 0; i < array.length; i++) {
282
+ const val = array[i]
283
+ if (val === undefined) continue
284
+ let key = keys[i]
285
+ if (!key) {
286
+ key = generateKey(val)
287
+ keys[i] = key
288
+ }
289
+ record[key] = val
290
+ }
291
+ return record
292
+ }
293
+
294
+ const applyChanges = (changes: DiffResult): boolean => {
295
+ let structural = false
296
+
297
+ // Additions
298
+ for (const key in changes.add) {
299
+ const val = changes.add[key] as T
300
+ validateSignalValue(`${TYPE_LIST} item for key "${key}"`, val)
301
+ signals.set(key, createState(val))
302
+ structural = true
303
+ }
304
+
305
+ // Changes
306
+ if (Object.keys(changes.change).length) {
307
+ batch(() => {
308
+ for (const key in changes.change) {
309
+ const val = changes.change[key]
310
+ validateSignalValue(
311
+ `${TYPE_LIST} item for key "${key}"`,
312
+ val,
313
+ )
314
+ const signal = signals.get(key)
315
+ if (signal) signal.set(val as T)
316
+ }
317
+ })
318
+ }
319
+
320
+ // Removals
321
+ for (const key in changes.remove) {
322
+ signals.delete(key)
323
+ const index = keys.indexOf(key)
324
+ if (index !== -1) keys.splice(index, 1)
325
+ structural = true
326
+ }
327
+
328
+ if (structural) {
329
+ node.sources = null
330
+ node.sourcesTail = null
331
+ }
332
+
333
+ return changes.changed
334
+ }
335
+
336
+ const watched = options?.watched
337
+ const subscribe = watched
338
+ ? () => {
339
+ if (activeSink) {
340
+ if (!node.sinks) node.stop = watched()
341
+ link(node, activeSink)
342
+ }
343
+ }
344
+ : () => {
345
+ if (activeSink) link(node, activeSink)
346
+ }
347
+
348
+ // --- Initialize ---
349
+ const initRecord = toRecord(value)
350
+ for (const key in initRecord) {
351
+ const val = initRecord[key]
352
+ validateSignalValue(`${TYPE_LIST} item for key "${key}"`, val)
353
+ signals.set(key, createState(val))
354
+ }
355
+
356
+ // Starts clean: mutation methods (add/remove/set/splice) explicitly call
357
+ // propagate() + invalidate edges, so refresh() on first get() is not needed.
358
+ node.value = value
359
+ node.flags = 0
360
+
361
+ // --- List object ---
362
+ const list: List<T> = {
363
+ [Symbol.toStringTag]: TYPE_LIST,
364
+ [Symbol.isConcatSpreadable]: true as const,
365
+
366
+ *[Symbol.iterator]() {
367
+ for (const key of keys) {
368
+ const signal = signals.get(key)
369
+ if (signal) yield signal
370
+ }
371
+ },
372
+
373
+ get length() {
374
+ subscribe()
375
+ return keys.length
376
+ },
377
+
378
+ get() {
379
+ subscribe()
380
+ if (node.sources) {
381
+ // Fast path: edges already established, rebuild value directly
382
+ if (node.flags) {
383
+ node.value = untrack(buildValue)
384
+ node.flags = FLAG_CLEAN
385
+ }
386
+ } else {
387
+ // First access: use refresh() to establish child → list edges
388
+ refresh(node as unknown as SinkNode)
389
+ if (node.error) throw node.error
390
+ }
391
+ return node.value
392
+ },
393
+
394
+ set(next: T[]) {
395
+ const prev = node.flags & FLAG_DIRTY ? buildValue() : node.value
396
+ const changes = diffArrays(
397
+ prev,
398
+ next,
399
+ keys,
400
+ generateKey,
401
+ contentBased,
402
+ )
403
+ if (changes.changed) {
404
+ keys = changes.newKeys
405
+ applyChanges(changes)
406
+ node.flags |= FLAG_DIRTY
407
+ for (let e = node.sinks; e; e = e.nextSink) propagate(e.sink)
408
+ if (batchDepth === 0) flush()
409
+ }
410
+ },
411
+
412
+ update(fn: (prev: T[]) => T[]) {
413
+ list.set(fn(list.get()))
414
+ },
415
+
416
+ at(index: number) {
417
+ return signals.get(keys[index])
418
+ },
419
+
420
+ keys() {
421
+ subscribe()
422
+ return keys.values()
423
+ },
424
+
425
+ byKey(key: string) {
426
+ return signals.get(key)
427
+ },
428
+
429
+ keyAt(index: number) {
430
+ return keys[index]
431
+ },
432
+
433
+ indexOfKey(key: string) {
434
+ return keys.indexOf(key)
435
+ },
436
+
437
+ add(value: T) {
438
+ const key = generateKey(value)
439
+ if (signals.has(key))
440
+ throw new DuplicateKeyError(TYPE_LIST, key, value)
441
+ if (!keys.includes(key)) keys.push(key)
442
+ validateSignalValue(`${TYPE_LIST} item for key "${key}"`, value)
443
+ signals.set(key, createState(value))
444
+ node.sources = null
445
+ node.sourcesTail = null
446
+ node.flags |= FLAG_DIRTY
447
+ for (let e = node.sinks; e; e = e.nextSink) propagate(e.sink)
448
+ if (batchDepth === 0) flush()
449
+ return key
450
+ },
451
+
452
+ remove(keyOrIndex: string | number) {
453
+ const key =
454
+ typeof keyOrIndex === 'number' ? keys[keyOrIndex] : keyOrIndex
455
+ const ok = signals.delete(key)
456
+ if (ok) {
457
+ const index =
458
+ typeof keyOrIndex === 'number'
459
+ ? keyOrIndex
460
+ : keys.indexOf(key)
461
+ if (index >= 0) keys.splice(index, 1)
462
+ node.sources = null
463
+ node.sourcesTail = null
464
+ node.flags |= FLAG_DIRTY
465
+ for (let e = node.sinks; e; e = e.nextSink) propagate(e.sink)
466
+ if (batchDepth === 0) flush()
467
+ }
468
+ },
469
+
470
+ sort(compareFn?: (a: T, b: T) => number) {
471
+ const entries = keys
472
+ .map(key => [key, signals.get(key)?.get()] as [string, T])
473
+ .sort(
474
+ isFunction(compareFn)
475
+ ? (a, b) => compareFn(a[1], b[1])
476
+ : (a, b) => String(a[1]).localeCompare(String(b[1])),
477
+ )
478
+ const newOrder = entries.map(([key]) => key)
479
+
480
+ if (!keysEqual(keys, newOrder)) {
481
+ keys = newOrder
482
+ node.flags |= FLAG_DIRTY
483
+ for (let e = node.sinks; e; e = e.nextSink) propagate(e.sink)
484
+ if (batchDepth === 0) flush()
485
+ }
486
+ },
487
+
488
+ splice(start: number, deleteCount?: number, ...items: T[]) {
489
+ const length = keys.length
490
+ const actualStart =
491
+ start < 0
492
+ ? Math.max(0, length + start)
493
+ : Math.min(start, length)
494
+ const actualDeleteCount = Math.max(
495
+ 0,
496
+ Math.min(
497
+ deleteCount ??
498
+ Math.max(0, length - Math.max(0, actualStart)),
499
+ length - actualStart,
500
+ ),
501
+ )
502
+
503
+ const add = {} as Record<string, T>
504
+ const remove = {} as Record<string, T>
505
+
506
+ // Collect items to delete
507
+ for (let i = 0; i < actualDeleteCount; i++) {
508
+ const index = actualStart + i
509
+ const key = keys[index]
510
+ if (key) {
511
+ const signal = signals.get(key)
512
+ if (signal) remove[key] = signal.get() as T
513
+ }
514
+ }
515
+
516
+ // Build new key order
517
+ const newOrder = keys.slice(0, actualStart)
518
+
519
+ for (const item of items) {
520
+ const key = generateKey(item)
521
+ if (signals.has(key) && !(key in remove))
522
+ throw new DuplicateKeyError(TYPE_LIST, key, item)
523
+ newOrder.push(key)
524
+ add[key] = item
525
+ }
526
+
527
+ newOrder.push(...keys.slice(actualStart + actualDeleteCount))
528
+
529
+ const changed = !!(
530
+ Object.keys(add).length || Object.keys(remove).length
531
+ )
532
+
533
+ if (changed) {
534
+ applyChanges({
535
+ add,
536
+ change: {},
537
+ remove,
538
+ changed,
539
+ })
540
+ keys = newOrder
541
+ node.flags |= FLAG_DIRTY
542
+ for (let e = node.sinks; e; e = e.nextSink) propagate(e.sink)
543
+ if (batchDepth === 0) flush()
544
+ }
545
+
546
+ return Object.values(remove)
547
+ },
548
+
549
+ deriveCollection<R extends {}>(
550
+ cb: DeriveCollectionCallback<R, T>,
551
+ ): Collection<R> {
552
+ return (
553
+ deriveCollection as <T2 extends {}, U2 extends {}>(
554
+ source: CollectionSource<U2>,
555
+ callback: DeriveCollectionCallback<T2, U2>,
556
+ ) => Collection<T2>
557
+ )(list, cb)
558
+ },
559
+ }
560
+
561
+ return list
562
+ }
563
+
564
+ /**
565
+ * Checks if a value is a List signal.
566
+ *
567
+ * @since 0.15.0
568
+ * @param value - The value to check
569
+ * @returns True if the value is a List
570
+ */
571
+ function isList<T extends {}>(value: unknown): value is List<T> {
572
+ return isObjectOfType(value, TYPE_LIST)
573
+ }
574
+
575
+ /* === Exports === */
576
+
577
+ export {
578
+ type DiffResult,
579
+ type KeyConfig,
580
+ type List,
581
+ type ListOptions,
582
+ type UnknownRecord,
583
+ createList,
584
+ isEqual,
585
+ isList,
586
+ getKeyGenerator,
587
+ keysEqual,
588
+ TYPE_LIST,
589
+ }