@zeix/cause-effect 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/.ai-context.md +71 -21
  2. package/.cursorrules +3 -2
  3. package/.github/copilot-instructions.md +59 -13
  4. package/CLAUDE.md +170 -24
  5. package/LICENSE +1 -1
  6. package/README.md +156 -52
  7. package/archive/benchmark.ts +688 -0
  8. package/archive/collection.ts +312 -0
  9. package/{src → archive}/computed.ts +33 -34
  10. package/archive/list.ts +551 -0
  11. package/archive/memo.ts +138 -0
  12. package/archive/state.ts +89 -0
  13. package/archive/store.ts +368 -0
  14. package/archive/task.ts +194 -0
  15. package/eslint.config.js +1 -0
  16. package/index.dev.js +902 -501
  17. package/index.js +1 -1
  18. package/index.ts +42 -22
  19. package/package.json +1 -1
  20. package/src/classes/collection.ts +272 -0
  21. package/src/classes/composite.ts +176 -0
  22. package/src/classes/computed.ts +333 -0
  23. package/src/classes/list.ts +304 -0
  24. package/src/classes/state.ts +98 -0
  25. package/src/classes/store.ts +210 -0
  26. package/src/diff.ts +28 -52
  27. package/src/effect.ts +9 -9
  28. package/src/errors.ts +50 -25
  29. package/src/signal.ts +58 -41
  30. package/src/system.ts +79 -42
  31. package/src/util.ts +16 -34
  32. package/test/batch.test.ts +15 -17
  33. package/test/benchmark.test.ts +4 -4
  34. package/test/collection.test.ts +796 -0
  35. package/test/computed.test.ts +138 -130
  36. package/test/diff.test.ts +2 -2
  37. package/test/effect.test.ts +36 -35
  38. package/test/list.test.ts +754 -0
  39. package/test/match.test.ts +25 -25
  40. package/test/resolve.test.ts +17 -19
  41. package/test/signal.test.ts +72 -121
  42. package/test/state.test.ts +44 -44
  43. package/test/store.test.ts +344 -1663
  44. package/types/index.d.ts +11 -9
  45. package/types/src/classes/collection.d.ts +32 -0
  46. package/types/src/classes/composite.d.ts +15 -0
  47. package/types/src/classes/computed.d.ts +97 -0
  48. package/types/src/classes/list.d.ts +41 -0
  49. package/types/src/classes/state.d.ts +52 -0
  50. package/types/src/classes/store.d.ts +51 -0
  51. package/types/src/diff.d.ts +8 -12
  52. package/types/src/errors.d.ts +12 -11
  53. package/types/src/signal.d.ts +27 -14
  54. package/types/src/system.d.ts +41 -20
  55. package/types/src/util.d.ts +6 -3
  56. package/src/state.ts +0 -98
  57. package/src/store.ts +0 -525
  58. package/types/src/collection.d.ts +0 -26
  59. package/types/src/computed.d.ts +0 -33
  60. package/types/src/scheduler.d.ts +0 -55
  61. package/types/src/state.d.ts +0 -24
  62. package/types/src/store.d.ts +0 -66
@@ -0,0 +1,333 @@
1
+ import { isEqual } from '../diff'
2
+ import {
3
+ CircularDependencyError,
4
+ validateCallback,
5
+ validateSignalValue,
6
+ } from '../errors'
7
+ import {
8
+ createWatcher,
9
+ flushPendingReactions,
10
+ notifyWatchers,
11
+ subscribeActiveWatcher,
12
+ trackSignalReads,
13
+ type Watcher,
14
+ } from '../system'
15
+ import {
16
+ isAbortError,
17
+ isAsyncFunction,
18
+ isObjectOfType,
19
+ isSyncFunction,
20
+ toError,
21
+ UNSET,
22
+ } from '../util'
23
+
24
+ /* === Types === */
25
+
26
+ type Computed<T extends {}> = {
27
+ readonly [Symbol.toStringTag]: 'Computed'
28
+ get(): T
29
+ }
30
+
31
+ type MemoCallback<T extends {} & { then?: undefined }> = (oldValue: T) => T
32
+
33
+ type TaskCallback<T extends {} & { then?: undefined }> = (
34
+ oldValue: T,
35
+ abort: AbortSignal,
36
+ ) => Promise<T>
37
+
38
+ /* === Constants === */
39
+
40
+ const TYPE_COMPUTED = 'Computed' as const
41
+
42
+ /* === Classes === */
43
+
44
+ /**
45
+ * Create a new memoized signal for a synchronous function.
46
+ *
47
+ * @since 0.17.0
48
+ */
49
+ class Memo<T extends {}> {
50
+ #watchers: Set<Watcher> = new Set()
51
+ #callback: MemoCallback<T>
52
+ #value: T
53
+ #error: Error | undefined
54
+ #dirty = true
55
+ #computing = false
56
+ #watcher: Watcher
57
+
58
+ /**
59
+ * Create a new memoized signal.
60
+ *
61
+ * @param {MemoCallback<T>} callback - Callback function to compute the memoized value
62
+ * @param {T} [initialValue = UNSET] - Initial value of the signal
63
+ * @throws {InvalidCallbackError} If the callback is not an sync function
64
+ * @throws {InvalidSignalValueError} If the initial value is not valid
65
+ */
66
+ constructor(callback: MemoCallback<T>, initialValue: T = UNSET) {
67
+ validateCallback('memo', callback, isMemoCallback)
68
+ validateSignalValue('memo', initialValue)
69
+
70
+ this.#callback = callback
71
+ this.#value = initialValue
72
+
73
+ // Own watcher: called by notifyWatchers() in upstream signals (push)
74
+ this.#watcher = createWatcher(() => {
75
+ this.#dirty = true
76
+ if (this.#watchers.size) notifyWatchers(this.#watchers)
77
+ else this.#watcher.stop()
78
+ })
79
+ }
80
+
81
+ get [Symbol.toStringTag](): 'Computed' {
82
+ return TYPE_COMPUTED
83
+ }
84
+
85
+ /**
86
+ * Return the memoized value after computing it if necessary.
87
+ *
88
+ * @returns {T}
89
+ * @throws {CircularDependencyError} If a circular dependency is detected
90
+ * @throws {Error} If an error occurs during computation
91
+ */
92
+ get(): T {
93
+ subscribeActiveWatcher(this.#watchers)
94
+ flushPendingReactions()
95
+
96
+ if (this.#dirty) {
97
+ trackSignalReads(this.#watcher, () => {
98
+ if (this.#computing) throw new CircularDependencyError('memo')
99
+
100
+ let result: T
101
+ this.#computing = true
102
+ try {
103
+ result = this.#callback(this.#value)
104
+ } catch (e) {
105
+ // Err track
106
+ this.#value = UNSET
107
+ this.#error = toError(e)
108
+ this.#computing = false
109
+ return
110
+ }
111
+
112
+ if (null == result || UNSET === result) {
113
+ // Nil track
114
+ this.#value = UNSET
115
+ this.#error = undefined
116
+ } else {
117
+ // Ok track
118
+ this.#value = result
119
+ this.#error = undefined
120
+ this.#dirty = false
121
+ }
122
+ this.#computing = false
123
+ })
124
+ }
125
+
126
+ if (this.#error) throw this.#error
127
+ return this.#value
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Create a new task signals that memoizes the result of an asynchronous function.
133
+ *
134
+ * @since 0.17.0
135
+ */
136
+ class Task<T extends {}> {
137
+ #watchers: Set<Watcher> = new Set()
138
+ #callback: TaskCallback<T>
139
+ #value: T
140
+ #error: Error | undefined
141
+ #dirty = true
142
+ #computing = false
143
+ #changed = false
144
+ #watcher: Watcher
145
+ #controller: AbortController | undefined
146
+
147
+ /**
148
+ * Create a new task signal for an asynchronous function.
149
+ *
150
+ * @param {TaskCallback<T>} callback - The asynchronous function to compute the memoized value
151
+ * @param {T} [initialValue = UNSET] - Initial value of the signal
152
+ * @throws {InvalidCallbackError} If the callback is not an async function
153
+ * @throws {InvalidSignalValueError} If the initial value is not valid
154
+ */
155
+ constructor(callback: TaskCallback<T>, initialValue: T = UNSET) {
156
+ validateCallback('task', callback, isTaskCallback)
157
+ validateSignalValue('task', initialValue)
158
+
159
+ this.#callback = callback
160
+ this.#value = initialValue
161
+
162
+ // Own watcher: called by notifyWatchers() in upstream signals (push)
163
+ this.#watcher = createWatcher(() => {
164
+ this.#dirty = true
165
+ this.#controller?.abort()
166
+ if (this.#watchers.size) notifyWatchers(this.#watchers)
167
+ else this.#watcher.stop()
168
+ })
169
+ this.#watcher.onCleanup(() => {
170
+ this.#controller?.abort()
171
+ })
172
+ }
173
+
174
+ get [Symbol.toStringTag](): 'Computed' {
175
+ return TYPE_COMPUTED
176
+ }
177
+
178
+ /**
179
+ * Return the memoized value after executing the async function if necessary.
180
+ *
181
+ * @returns {T}
182
+ * @throws {CircularDependencyError} If a circular dependency is detected
183
+ * @throws {Error} If an error occurs during computation
184
+ */
185
+ get(): T {
186
+ subscribeActiveWatcher(this.#watchers)
187
+ flushPendingReactions()
188
+
189
+ // Functions to update internal state
190
+ const ok = (v: T): undefined => {
191
+ if (!isEqual(v, this.#value)) {
192
+ this.#value = v
193
+ this.#changed = true
194
+ }
195
+ this.#error = undefined
196
+ this.#dirty = false
197
+ }
198
+ const nil = (): undefined => {
199
+ this.#changed = UNSET !== this.#value
200
+ this.#value = UNSET
201
+ this.#error = undefined
202
+ }
203
+ const err = (e: unknown): undefined => {
204
+ const newError = toError(e)
205
+ this.#changed =
206
+ !this.#error ||
207
+ newError.name !== this.#error.name ||
208
+ newError.message !== this.#error.message
209
+ this.#value = UNSET
210
+ this.#error = newError
211
+ }
212
+ const settle =
213
+ <T>(fn: (arg: T) => void) =>
214
+ (arg: T) => {
215
+ this.#computing = false
216
+ this.#controller = undefined
217
+ fn(arg)
218
+ if (this.#changed) notifyWatchers(this.#watchers)
219
+ }
220
+
221
+ const compute = () =>
222
+ trackSignalReads(this.#watcher, () => {
223
+ if (this.#computing) throw new CircularDependencyError('task')
224
+ this.#changed = false
225
+
226
+ // Return current value until promise resolves
227
+ if (this.#controller) return this.#value
228
+
229
+ this.#controller = new AbortController()
230
+ this.#controller.signal.addEventListener(
231
+ 'abort',
232
+ () => {
233
+ this.#computing = false
234
+ this.#controller = undefined
235
+
236
+ // Retry computation with updated state
237
+ compute()
238
+ },
239
+ {
240
+ once: true,
241
+ },
242
+ )
243
+ let result: Promise<T>
244
+ this.#computing = true
245
+ try {
246
+ result = this.#callback(
247
+ this.#value,
248
+ this.#controller.signal,
249
+ )
250
+ } catch (e) {
251
+ if (isAbortError(e)) nil()
252
+ else err(e)
253
+ this.#computing = false
254
+ return
255
+ }
256
+
257
+ if (result instanceof Promise)
258
+ result.then(settle(ok), settle(err))
259
+ else if (null == result || UNSET === result) nil()
260
+ else ok(result)
261
+ this.#computing = false
262
+ })
263
+
264
+ if (this.#dirty) compute()
265
+
266
+ if (this.#error) throw this.#error
267
+ return this.#value
268
+ }
269
+ }
270
+
271
+ /* === Functions === */
272
+
273
+ /**
274
+ * Create a derived signal from existing signals
275
+ *
276
+ * @since 0.9.0
277
+ * @param {MemoCallback<T> | TaskCallback<T>} callback - Computation callback function
278
+ */
279
+ const createComputed = <T extends {}>(
280
+ callback: TaskCallback<T> | MemoCallback<T>,
281
+ initialValue: T = UNSET,
282
+ ) =>
283
+ isAsyncFunction(callback)
284
+ ? new Task(callback as TaskCallback<T>, initialValue)
285
+ : new Memo(callback as MemoCallback<T>, initialValue)
286
+
287
+ /**
288
+ * Check if a value is a computed signal
289
+ *
290
+ * @since 0.9.0
291
+ * @param {unknown} value - Value to check
292
+ * @returns {boolean} - True if value is a computed signal, false otherwise
293
+ */
294
+ const isComputed = /*#__PURE__*/ <T extends {}>(
295
+ value: unknown,
296
+ ): value is Memo<T> => isObjectOfType(value, TYPE_COMPUTED)
297
+
298
+ /**
299
+ * Check if the provided value is a callback that may be used as input for createSignal() to derive a computed state
300
+ *
301
+ * @since 0.12.0
302
+ * @param {unknown} value - Value to check
303
+ * @returns {boolean} - True if value is a sync callback, false otherwise
304
+ */
305
+ const isMemoCallback = /*#__PURE__*/ <T extends {} & { then?: undefined }>(
306
+ value: unknown,
307
+ ): value is MemoCallback<T> => isSyncFunction(value) && value.length < 2
308
+
309
+ /**
310
+ * Check if the provided value is a callback that may be used as input for createSignal() to derive a computed state
311
+ *
312
+ * @since 0.17.0
313
+ * @param {unknown} value - Value to check
314
+ * @returns {boolean} - True if value is an async callback, false otherwise
315
+ */
316
+ const isTaskCallback = /*#__PURE__*/ <T extends {}>(
317
+ value: unknown,
318
+ ): value is TaskCallback<T> => isAsyncFunction(value) && value.length < 3
319
+
320
+ /* === Exports === */
321
+
322
+ export {
323
+ TYPE_COMPUTED,
324
+ createComputed,
325
+ isComputed,
326
+ isMemoCallback,
327
+ isTaskCallback,
328
+ Memo,
329
+ Task,
330
+ type Computed,
331
+ type MemoCallback,
332
+ type TaskCallback,
333
+ }
@@ -0,0 +1,304 @@
1
+ import { diff, isEqual, type UnknownArray } from '../diff'
2
+ import { DuplicateKeyError, validateSignalValue } from '../errors'
3
+ import {
4
+ type Cleanup,
5
+ emitNotification,
6
+ type Listener,
7
+ type Listeners,
8
+ type Notifications,
9
+ notifyWatchers,
10
+ subscribeActiveWatcher,
11
+ type Watcher,
12
+ } from '../system'
13
+ import { isFunction, isNumber, isObjectOfType, isString, UNSET } from '../util'
14
+ import { Collection, type CollectionCallback } from './collection'
15
+ import { Composite } from './composite'
16
+ import { State } from './state'
17
+
18
+ /* === Types === */
19
+
20
+ type ArrayToRecord<T extends UnknownArray> = {
21
+ [key: string]: T extends Array<infer U extends {}> ? U : never
22
+ }
23
+
24
+ type KeyConfig<T> = string | ((item: T) => string)
25
+
26
+ /* === Constants === */
27
+
28
+ const TYPE_LIST = 'List' as const
29
+
30
+ /* === Class === */
31
+
32
+ class List<T extends {}> {
33
+ #composite: Composite<Record<string, T>, State<T>>
34
+ #watchers = new Set<Watcher>()
35
+ #listeners: Pick<Listeners, 'sort'> = {
36
+ sort: new Set<Listener<'sort'>>(),
37
+ }
38
+ #order: string[] = []
39
+ #generateKey: (item: T) => string
40
+
41
+ constructor(initialValue: T[], keyConfig?: KeyConfig<T>) {
42
+ validateSignalValue('list', initialValue, Array.isArray)
43
+
44
+ let keyCounter = 0
45
+ this.#generateKey = isString(keyConfig)
46
+ ? () => `${keyConfig}${keyCounter++}`
47
+ : isFunction<string>(keyConfig)
48
+ ? (item: T) => keyConfig(item)
49
+ : () => String(keyCounter++)
50
+
51
+ this.#composite = new Composite<ArrayToRecord<T[]>, State<T>>(
52
+ this.#toRecord(initialValue),
53
+ (key: string, value: unknown): value is T => {
54
+ validateSignalValue(`list for key "${key}"`, value)
55
+ return true
56
+ },
57
+ value => new State(value),
58
+ )
59
+ }
60
+
61
+ // Convert array to record with stable keys
62
+ #toRecord(array: T[]): ArrayToRecord<T[]> {
63
+ const record = {} as Record<string, T>
64
+
65
+ for (let i = 0; i < array.length; i++) {
66
+ const value = array[i]
67
+ if (value === undefined) continue // Skip sparse array positions
68
+
69
+ let key = this.#order[i]
70
+ if (!key) {
71
+ key = this.#generateKey(value)
72
+ this.#order[i] = key
73
+ }
74
+ record[key] = value
75
+ }
76
+ return record
77
+ }
78
+
79
+ get #value(): T[] {
80
+ return this.#order
81
+ .map(key => this.#composite.signals.get(key)?.get())
82
+ .filter(v => v !== undefined) as T[]
83
+ }
84
+
85
+ // Public methods
86
+ get [Symbol.toStringTag](): 'List' {
87
+ return TYPE_LIST
88
+ }
89
+
90
+ get [Symbol.isConcatSpreadable](): boolean {
91
+ return true
92
+ }
93
+
94
+ *[Symbol.iterator](): IterableIterator<State<T>> {
95
+ for (const key of this.#order) {
96
+ const signal = this.#composite.signals.get(key)
97
+ if (signal) yield signal as State<T>
98
+ }
99
+ }
100
+
101
+ get length(): number {
102
+ subscribeActiveWatcher(this.#watchers)
103
+ return this.#order.length
104
+ }
105
+
106
+ get(): T[] {
107
+ subscribeActiveWatcher(this.#watchers)
108
+ return this.#value
109
+ }
110
+
111
+ set(newValue: T[]): void {
112
+ if (UNSET === newValue) {
113
+ this.#composite.clear()
114
+ notifyWatchers(this.#watchers)
115
+ this.#watchers.clear()
116
+ return
117
+ }
118
+
119
+ const oldValue = this.#value
120
+ const changes = diff(this.#toRecord(oldValue), this.#toRecord(newValue))
121
+ const removedKeys = Object.keys(changes.remove)
122
+
123
+ const changed = this.#composite.change(changes)
124
+ if (changed) {
125
+ for (const key of removedKeys) {
126
+ const index = this.#order.indexOf(key)
127
+ if (index !== -1) this.#order.splice(index, 1)
128
+ }
129
+ this.#order = this.#order.filter(() => true)
130
+ notifyWatchers(this.#watchers)
131
+ }
132
+ }
133
+
134
+ update(fn: (oldValue: T[]) => T[]): void {
135
+ this.set(fn(this.get()))
136
+ }
137
+
138
+ at(index: number): State<T> | undefined {
139
+ return this.#composite.signals.get(this.#order[index])
140
+ }
141
+
142
+ keys(): IterableIterator<string> {
143
+ return this.#order.values()
144
+ }
145
+
146
+ byKey(key: string): State<T> | undefined {
147
+ return this.#composite.signals.get(key)
148
+ }
149
+
150
+ keyAt(index: number): string | undefined {
151
+ return this.#order[index]
152
+ }
153
+
154
+ indexOfKey(key: string): number {
155
+ return this.#order.indexOf(key)
156
+ }
157
+
158
+ add(value: T): string {
159
+ const key = this.#generateKey(value)
160
+ if (this.#composite.signals.has(key))
161
+ throw new DuplicateKeyError('store', key, value)
162
+
163
+ if (!this.#order.includes(key)) this.#order.push(key)
164
+ const ok = this.#composite.add(key, value)
165
+ if (ok) notifyWatchers(this.#watchers)
166
+ return key
167
+ }
168
+
169
+ remove(keyOrIndex: string | number): void {
170
+ const key = isNumber(keyOrIndex) ? this.#order[keyOrIndex] : keyOrIndex
171
+ const ok = this.#composite.remove(key)
172
+ if (ok) {
173
+ const index = isNumber(keyOrIndex)
174
+ ? keyOrIndex
175
+ : this.#order.indexOf(key)
176
+ if (index >= 0) this.#order.splice(index, 1)
177
+ this.#order = this.#order.filter(() => true)
178
+ notifyWatchers(this.#watchers)
179
+ }
180
+ }
181
+
182
+ sort(compareFn?: (a: T, b: T) => number): void {
183
+ const entries = this.#order
184
+ .map(
185
+ key =>
186
+ [key, this.#composite.signals.get(key)?.get()] as [
187
+ string,
188
+ T,
189
+ ],
190
+ )
191
+ .sort(
192
+ isFunction(compareFn)
193
+ ? (a, b) => compareFn(a[1], b[1])
194
+ : (a, b) => String(a[1]).localeCompare(String(b[1])),
195
+ )
196
+ const newOrder = entries.map(([key]) => key)
197
+
198
+ if (!isEqual(this.#order, newOrder)) {
199
+ this.#order = newOrder
200
+ notifyWatchers(this.#watchers)
201
+ emitNotification(this.#listeners.sort, this.#order)
202
+ }
203
+ }
204
+
205
+ splice(start: number, deleteCount?: number, ...items: T[]): T[] {
206
+ const length = this.#order.length
207
+ const actualStart =
208
+ start < 0 ? Math.max(0, length + start) : Math.min(start, length)
209
+ const actualDeleteCount = Math.max(
210
+ 0,
211
+ Math.min(
212
+ deleteCount ?? Math.max(0, length - Math.max(0, actualStart)),
213
+ length - actualStart,
214
+ ),
215
+ )
216
+
217
+ const add = {} as Record<string, T>
218
+ const remove = {} as Record<string, T>
219
+
220
+ // Collect items to delete and their keys
221
+ for (let i = 0; i < actualDeleteCount; i++) {
222
+ const index = actualStart + i
223
+ const key = this.#order[index]
224
+ if (key) {
225
+ const signal = this.#composite.signals.get(key)
226
+ if (signal) remove[key] = signal.get() as T
227
+ }
228
+ }
229
+
230
+ // Build new order: items before splice point
231
+ const newOrder = this.#order.slice(0, actualStart)
232
+
233
+ // Add new items
234
+ for (const item of items) {
235
+ const key = this.#generateKey(item)
236
+ newOrder.push(key)
237
+ add[key] = item as T
238
+ }
239
+
240
+ // Add items after splice point
241
+ newOrder.push(...this.#order.slice(actualStart + actualDeleteCount))
242
+
243
+ const changed = !!(
244
+ Object.keys(add).length || Object.keys(remove).length
245
+ )
246
+
247
+ if (changed) {
248
+ this.#composite.change({
249
+ add,
250
+ change: {} as Record<string, T>,
251
+ remove,
252
+ changed,
253
+ })
254
+ this.#order = newOrder.filter(() => true) // Update order array
255
+ notifyWatchers(this.#watchers)
256
+ }
257
+
258
+ return Object.values(remove)
259
+ }
260
+
261
+ on<K extends keyof Notifications>(type: K, listener: Listener<K>): Cleanup {
262
+ if (type === 'sort') {
263
+ this.#listeners.sort.add(listener as Listener<'sort'>)
264
+ return () =>
265
+ this.#listeners.sort.delete(listener as Listener<'sort'>)
266
+ }
267
+
268
+ // For other types, delegate to the composite
269
+ return this.#composite.on(
270
+ type,
271
+ listener as Listener<
272
+ keyof Pick<Notifications, 'add' | 'remove' | 'change'>
273
+ >,
274
+ )
275
+ }
276
+
277
+ deriveCollection<R extends {}>(
278
+ callback: (sourceValue: T) => R,
279
+ ): Collection<R, T>
280
+ deriveCollection<R extends {}>(
281
+ callback: (sourceValue: T, abort: AbortSignal) => Promise<R>,
282
+ ): Collection<R, T>
283
+ deriveCollection<R extends {}>(
284
+ callback: CollectionCallback<R, T>,
285
+ ): Collection<R, T> {
286
+ return new Collection(this, callback)
287
+ }
288
+ }
289
+
290
+ /* === Functions === */
291
+
292
+ /**
293
+ * Check if the provided value is a List instance
294
+ *
295
+ * @since 0.15.0
296
+ * @param {unknown} value - Value to check
297
+ * @returns {boolean} - True if the value is a List instance, false otherwise
298
+ */
299
+ const isList = <T extends {}>(value: unknown): value is List<T> =>
300
+ isObjectOfType(value, TYPE_LIST)
301
+
302
+ /* === Exports === */
303
+
304
+ export { isList, List, TYPE_LIST, type ArrayToRecord, type KeyConfig }