@zeix/cause-effect 0.16.1 → 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.
Files changed (66) hide show
  1. package/.ai-context.md +85 -21
  2. package/.cursorrules +11 -5
  3. package/.github/copilot-instructions.md +64 -13
  4. package/CLAUDE.md +143 -163
  5. package/LICENSE +1 -1
  6. package/README.md +248 -333
  7. package/archive/benchmark.ts +688 -0
  8. package/archive/collection.ts +312 -0
  9. package/{src → archive}/computed.ts +21 -21
  10. package/archive/list.ts +551 -0
  11. package/archive/memo.ts +139 -0
  12. package/{src → archive}/state.ts +13 -11
  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 +938 -509
  17. package/index.js +1 -1
  18. package/index.ts +50 -23
  19. package/package.json +1 -1
  20. package/src/classes/collection.ts +282 -0
  21. package/src/classes/composite.ts +176 -0
  22. package/src/classes/computed.ts +333 -0
  23. package/src/classes/list.ts +305 -0
  24. package/src/classes/ref.ts +68 -0
  25. package/src/classes/state.ts +98 -0
  26. package/src/classes/store.ts +210 -0
  27. package/src/diff.ts +26 -53
  28. package/src/effect.ts +9 -9
  29. package/src/errors.ts +71 -25
  30. package/src/match.ts +5 -12
  31. package/src/resolve.ts +3 -2
  32. package/src/signal.ts +58 -41
  33. package/src/system.ts +79 -42
  34. package/src/util.ts +16 -34
  35. package/test/batch.test.ts +15 -17
  36. package/test/benchmark.test.ts +4 -4
  37. package/test/collection.test.ts +853 -0
  38. package/test/computed.test.ts +138 -130
  39. package/test/diff.test.ts +2 -2
  40. package/test/effect.test.ts +36 -35
  41. package/test/list.test.ts +754 -0
  42. package/test/match.test.ts +25 -25
  43. package/test/ref.test.ts +227 -0
  44. package/test/resolve.test.ts +17 -19
  45. package/test/signal.test.ts +70 -119
  46. package/test/state.test.ts +44 -44
  47. package/test/store.test.ts +253 -929
  48. package/types/index.d.ts +12 -9
  49. package/types/src/classes/collection.d.ts +46 -0
  50. package/types/src/classes/composite.d.ts +15 -0
  51. package/types/src/classes/computed.d.ts +97 -0
  52. package/types/src/classes/list.d.ts +41 -0
  53. package/types/src/classes/ref.d.ts +39 -0
  54. package/types/src/classes/state.d.ts +52 -0
  55. package/types/src/classes/store.d.ts +51 -0
  56. package/types/src/diff.d.ts +8 -12
  57. package/types/src/errors.d.ts +17 -11
  58. package/types/src/signal.d.ts +27 -14
  59. package/types/src/system.d.ts +41 -20
  60. package/types/src/util.d.ts +6 -4
  61. package/src/store.ts +0 -474
  62. package/types/src/collection.d.ts +0 -26
  63. package/types/src/computed.d.ts +0 -33
  64. package/types/src/scheduler.d.ts +0 -55
  65. package/types/src/state.d.ts +0 -24
  66. package/types/src/store.d.ts +0 -65
@@ -0,0 +1,551 @@
1
+ import { type DiffResult, diff, isEqual, type UnknownArray } from '../src/diff'
2
+ import {
3
+ DuplicateKeyError,
4
+ InvalidSignalValueError,
5
+ NullishSignalValueError,
6
+ ReadonlySignalError,
7
+ } from '../src/errors'
8
+ import { isMutableSignal, type MutableSignal } from '../src/signal'
9
+ import {
10
+ batchSignalWrites,
11
+ type Cleanup,
12
+ createWatcher,
13
+ emitNotification,
14
+ type Listener,
15
+ type Listeners,
16
+ type Notifications,
17
+ notifyWatchers,
18
+ subscribeActiveWatcher,
19
+ trackSignalReads,
20
+ type Watcher,
21
+ } from '../src/system'
22
+ import {
23
+ isFunction,
24
+ isNumber,
25
+ isObjectOfType,
26
+ isRecord,
27
+ isString,
28
+ isSymbol,
29
+ UNSET,
30
+ } from '../src/util'
31
+ import {
32
+ type Collection,
33
+ type CollectionCallback,
34
+ createCollection,
35
+ } from './collection'
36
+ import { isComputed } from './computed'
37
+ import { createState, isState } from './state'
38
+ import { createStore, isStore } from './store'
39
+
40
+ /* === Types === */
41
+
42
+ type ArrayToRecord<T extends UnknownArray> = {
43
+ [key: string]: T extends Array<infer U extends {}> ? U : never
44
+ }
45
+
46
+ type KeyConfig<T> = string | ((item: T) => string)
47
+
48
+ type List<T extends {}> = {
49
+ readonly [Symbol.toStringTag]: 'List'
50
+ [Symbol.iterator](): IterableIterator<MutableSignal<T>>
51
+ readonly [Symbol.isConcatSpreadable]: boolean
52
+ [n: number]: MutableSignal<T>
53
+ readonly length: number
54
+ add(value: T): string
55
+ byKey(key: string): MutableSignal<T> | undefined
56
+ deriveCollection<U extends {}>(
57
+ callback: CollectionCallback<U, T extends UnknownArray ? T : never>,
58
+ ): Collection<U>
59
+ get(): T
60
+ keyAt(index: number): string | undefined
61
+ indexOfKey(key: string): number
62
+ set(value: T): void
63
+ update(fn: (value: T) => T): void
64
+ sort<U = T>(compareFn?: (a: U, b: U) => number): void
65
+ splice(start: number, deleteCount?: number, ...items: T[]): T[]
66
+ on<K extends keyof Notifications>(type: K, listener: Listener<K>): Cleanup
67
+ remove(index: number): void
68
+ }
69
+
70
+ /* === Constants === */
71
+
72
+ const TYPE_LIST = 'List' as const
73
+
74
+ /* === Functions === */
75
+
76
+ /**
77
+ * Create a new list with deeply nested reactive list items
78
+ *
79
+ * @since 0.16.2
80
+ * @param {T} initialValue - Initial array of the list
81
+ * @param {KeyConfig<T>} keyConfig - Optional key configuration:
82
+ * - string: used as prefix for auto-incrementing IDs (e.g., "item" → "item0", "item1")
83
+ * - function: computes key from array item at creation time
84
+ * @returns {List<T>} - New list with reactive items of type T
85
+ */
86
+ const createList = <T extends {}>(
87
+ initialValue: T[],
88
+ keyConfig?: KeyConfig<T>,
89
+ ): List<T> => {
90
+ if (initialValue == null) throw new NullishSignalValueError('store')
91
+
92
+ const watchers = new Set<Watcher>()
93
+ const listeners: Listeners = {
94
+ add: new Set<Listener<'add'>>(),
95
+ change: new Set<Listener<'change'>>(),
96
+ remove: new Set<Listener<'remove'>>(),
97
+ sort: new Set<Listener<'sort'>>(),
98
+ }
99
+ const signals = new Map<string, MutableSignal<T>>()
100
+ const ownWatchers = new Map<string, Watcher>()
101
+
102
+ // Stable key support for lists
103
+ let keyCounter = 0
104
+ let order: string[] = []
105
+
106
+ // Get signal by key or index
107
+ const getSignal = (prop: string): MutableSignal<T> | undefined => {
108
+ let key = prop
109
+ const index = Number(prop)
110
+ if (Number.isInteger(index) && index >= 0) key = order[index] ?? prop
111
+ return signals.get(key)
112
+ }
113
+
114
+ // Generate stable key for array items
115
+ const generateKey = (item: T): string => {
116
+ const id = keyCounter++
117
+ return isString(keyConfig)
118
+ ? `${keyConfig}${id}`
119
+ : isFunction<string>(keyConfig)
120
+ ? keyConfig(item)
121
+ : String(id)
122
+ }
123
+
124
+ // Convert array to record with stable keys
125
+ const arrayToRecord = (array: T[]): ArrayToRecord<T[]> => {
126
+ const record = {} as Record<string, T>
127
+
128
+ for (let i = 0; i < array.length; i++) {
129
+ const value = array[i]
130
+ if (value === undefined) continue // Skip sparse array positions
131
+
132
+ let key = order[i]
133
+ if (!key) {
134
+ key = generateKey(value)
135
+ order[i] = key
136
+ }
137
+ record[key] = value
138
+ }
139
+ return record
140
+ }
141
+
142
+ // Get current record
143
+ const current = (): T[] =>
144
+ order
145
+ .map(key => signals.get(key)?.get())
146
+ .filter(v => v !== undefined) as T[]
147
+
148
+ // Validate input
149
+ const isValidValue = <T>(
150
+ key: string,
151
+ value: T,
152
+ ): value is NonNullable<T> => {
153
+ if (value == null)
154
+ throw new NullishSignalValueError(`store for key "${key}"`)
155
+ if (value === UNSET) return true
156
+ if (isSymbol(value) || isFunction(value) || isComputed(value))
157
+ throw new InvalidSignalValueError(`store for key "${key}"`, value)
158
+ return true
159
+ }
160
+
161
+ // Add own watcher for nested signal
162
+ const addOwnWatcher = (key: string, signal: MutableSignal<T>) => {
163
+ const watcher = createWatcher(() => {
164
+ trackSignalReads(watcher, () => {
165
+ signal.get() // Subscribe to the signal
166
+ emitNotification(listeners.change, [key])
167
+ })
168
+ })
169
+ ownWatchers.set(key, watcher)
170
+ }
171
+
172
+ // Add nested signal and own watcher
173
+ const addProperty = <K extends keyof T & string>(
174
+ key: K,
175
+ value: T[K],
176
+ single = false,
177
+ ): boolean => {
178
+ if (!isValidValue(key, value)) return false
179
+
180
+ // Create signal for key
181
+ // @ts-expect-error ignore
182
+ const signal: MutableSignal<T[K] & {}> =
183
+ isState(value) || isStore(value)
184
+ ? (value as unknown as MutableSignal<T[K] & {}>)
185
+ : isRecord(value) || Array.isArray(value)
186
+ ? createStore(value)
187
+ : createState(value)
188
+
189
+ // Set internal states
190
+ // @ts-expect-error ignore
191
+ signals.set(key, signal)
192
+ if (!order.includes(key)) order.push(key)
193
+ // @ts-expect-error ignore
194
+ if (listeners.change.size) addOwnWatcher(key, signal)
195
+
196
+ if (single) {
197
+ notifyWatchers(watchers)
198
+ emitNotification(listeners.add, [key])
199
+ }
200
+ return true
201
+ }
202
+
203
+ // Remove nested signal and effect
204
+ const removeProperty = (key: string, single = false) => {
205
+ // Remove signal for key
206
+ const ok = signals.delete(key)
207
+ if (!ok) return
208
+
209
+ // Clean up internal states
210
+ const index = order.indexOf(key)
211
+ if (index >= 0) order.splice(index, 1)
212
+ const watcher = ownWatchers.get(key)
213
+ if (watcher) {
214
+ watcher.stop()
215
+ ownWatchers.delete(key)
216
+ }
217
+
218
+ if (single) {
219
+ order = order.filter(() => true) // Compact array
220
+ notifyWatchers(watchers)
221
+ emitNotification(listeners.remove, [key])
222
+ }
223
+ }
224
+
225
+ // Commit batched changes and emit notifications
226
+ const batchChanges = (changes: DiffResult, initialRun?: boolean) => {
227
+ // Additions
228
+ if (Object.keys(changes.add).length) {
229
+ for (const key in changes.add)
230
+ // @ts-expect-error ignore
231
+ addProperty(key, changes.add[key] as T, false)
232
+
233
+ // Queue initial additions event to allow listeners to be added first
234
+ if (initialRun)
235
+ setTimeout(() => {
236
+ emitNotification(listeners.add, Object.keys(changes.add))
237
+ }, 0)
238
+ else emitNotification(listeners.add, Object.keys(changes.add))
239
+ }
240
+
241
+ // Changes
242
+ if (Object.keys(changes.change).length) {
243
+ batchSignalWrites(() => {
244
+ for (const key in changes.change) {
245
+ const value = changes.change[key] as T
246
+ if (!isValidValue(key, value)) continue
247
+
248
+ const signal = signals.get(key)
249
+ if (isMutableSignal(signal)) signal.set(value)
250
+ else throw new ReadonlySignalError(key, value)
251
+ }
252
+ emitNotification(listeners.change, Object.keys(changes.change))
253
+ })
254
+ }
255
+
256
+ // Removals
257
+ if (Object.keys(changes.remove).length) {
258
+ for (const key in changes.remove) removeProperty(key)
259
+ order = order.filter(() => true)
260
+ emitNotification(listeners.remove, Object.keys(changes.remove))
261
+ }
262
+
263
+ return changes.changed
264
+ }
265
+
266
+ // Reconcile data and dispatch events
267
+ const reconcile = (
268
+ oldValue: T[],
269
+ newValue: T[],
270
+ initialRun?: boolean,
271
+ ): boolean =>
272
+ batchChanges(
273
+ diff(arrayToRecord(oldValue), arrayToRecord(newValue)),
274
+ initialRun,
275
+ )
276
+
277
+ // Initialize data
278
+ reconcile([] as T[], initialValue, true)
279
+
280
+ // Methods and Properties
281
+ const prototype: Record<PropertyKey, unknown> = {}
282
+ Object.defineProperties(prototype, {
283
+ [Symbol.toStringTag]: {
284
+ value: TYPE_LIST,
285
+ },
286
+ [Symbol.isConcatSpreadable]: {
287
+ value: true,
288
+ },
289
+ [Symbol.iterator]: {
290
+ value: function* () {
291
+ for (const key of order) {
292
+ const signal = signals.get(key)
293
+ if (signal) yield signal
294
+ }
295
+ },
296
+ },
297
+ length: {
298
+ get(): number {
299
+ subscribeActiveWatcher(watchers)
300
+ return signals.size
301
+ },
302
+ },
303
+ order: {
304
+ get(): string[] {
305
+ subscribeActiveWatcher(watchers)
306
+ return order
307
+ },
308
+ },
309
+ at: {
310
+ value(index: number): MutableSignal<T> | undefined {
311
+ return signals.get(order[index])
312
+ },
313
+ },
314
+ byKey: {
315
+ value: (key: string): MutableSignal<T> | undefined => {
316
+ return getSignal(key)
317
+ },
318
+ },
319
+ deriveCollection: {
320
+ value: <U extends {}>(
321
+ callback: CollectionCallback<U, T>,
322
+ ): Collection<U> => {
323
+ const collection = createCollection(list, callback)
324
+ return collection
325
+ },
326
+ },
327
+ keyAt: {
328
+ value(index: number): string | undefined {
329
+ return order[index]
330
+ },
331
+ },
332
+ indexOfKey: {
333
+ value(key: string): number {
334
+ return order.indexOf(key)
335
+ },
336
+ },
337
+ get: {
338
+ value: (): T[] => {
339
+ subscribeActiveWatcher(watchers)
340
+ return current()
341
+ },
342
+ },
343
+ set: {
344
+ value: (newValue: T[]): void => {
345
+ if (reconcile(current(), newValue)) {
346
+ notifyWatchers(watchers)
347
+ if (UNSET === newValue) watchers.clear()
348
+ }
349
+ },
350
+ },
351
+ update: {
352
+ value: (fn: (oldValue: T[]) => T[]): void => {
353
+ const oldValue = current()
354
+ const newValue = fn(oldValue)
355
+ if (reconcile(oldValue, newValue)) {
356
+ notifyWatchers(watchers)
357
+ if (UNSET === newValue) watchers.clear()
358
+ }
359
+ },
360
+ },
361
+ add: {
362
+ value: (value: T): string => {
363
+ const key = generateKey(value)
364
+ if (!signals.has(key)) {
365
+ // @ts-expect-error ignore
366
+ addProperty(key, value, true)
367
+ return key
368
+ } else throw new DuplicateKeyError('store', key, value)
369
+ },
370
+ },
371
+ remove: {
372
+ value: (keyOrIndex: string | number): void => {
373
+ const key = isNumber(keyOrIndex)
374
+ ? order[keyOrIndex]
375
+ : keyOrIndex
376
+ if (key && signals.has(key)) removeProperty(key, true)
377
+ },
378
+ },
379
+ sort: {
380
+ value: (
381
+ compareFn?: <
382
+ U = T extends UnknownArray
383
+ ? T
384
+ : T[Extract<keyof T, string>],
385
+ >(
386
+ a: U,
387
+ b: U,
388
+ ) => number,
389
+ ): void => {
390
+ const entries = order
391
+ .map(key => [key, signals.get(key)?.get()] as [string, T])
392
+ .sort(
393
+ compareFn
394
+ ? (a, b) => compareFn(a[1], b[1])
395
+ : (a, b) =>
396
+ String(a[1]).localeCompare(String(b[1])),
397
+ )
398
+
399
+ // Set new order
400
+ const newOrder = entries.map(([key]) => key)
401
+ if (!isEqual(newOrder, order)) {
402
+ order = newOrder
403
+ notifyWatchers(watchers)
404
+ emitNotification(listeners.sort, order)
405
+ }
406
+ },
407
+ },
408
+ splice: {
409
+ value: (
410
+ start: number,
411
+ deleteCount?: number,
412
+ ...items: T[]
413
+ ): T[] => {
414
+ // Normalize start and deleteCount
415
+ const length = signals.size
416
+ const actualStart =
417
+ start < 0
418
+ ? Math.max(0, length + start)
419
+ : Math.min(start, length)
420
+ const actualDeleteCount = Math.max(
421
+ 0,
422
+ Math.min(
423
+ deleteCount ??
424
+ Math.max(0, length - Math.max(0, actualStart)),
425
+ length - actualStart,
426
+ ),
427
+ )
428
+
429
+ const add = {} as Record<string, T>
430
+ const remove = {} as Record<string, T>
431
+
432
+ // Collect items to delete and their keys
433
+ for (let i = 0; i < actualDeleteCount; i++) {
434
+ const index = actualStart + i
435
+ const key = order[index]
436
+ if (key) {
437
+ const signal = signals.get(key)
438
+ if (signal) remove[key] = signal.get() as T
439
+ }
440
+ }
441
+
442
+ // Build new order: items before splice point
443
+ const newOrder = order.slice(0, actualStart)
444
+
445
+ // Add new items
446
+ for (const item of items) {
447
+ const key = generateKey(item)
448
+ newOrder.push(key)
449
+ add[key] = item as T
450
+ }
451
+
452
+ // Add items after splice point
453
+ newOrder.push(...order.slice(actualStart + actualDeleteCount))
454
+
455
+ // Update the order array
456
+ order = newOrder.filter(() => true) // Compact array
457
+
458
+ const changed = !!(
459
+ Object.keys(add).length || Object.keys(remove).length
460
+ )
461
+
462
+ if (changed)
463
+ batchChanges({
464
+ add,
465
+ change: {} as Record<string, T>,
466
+ remove,
467
+ changed,
468
+ })
469
+
470
+ notifyWatchers(watchers)
471
+
472
+ return Object.values(remove) as T[]
473
+ },
474
+ },
475
+ on: {
476
+ value: <K extends keyof Notifications>(
477
+ type: K,
478
+ listener: Listener<K>,
479
+ ): Cleanup => {
480
+ listeners[type].add(listener)
481
+ if (type === 'change' && !ownWatchers.size) {
482
+ for (const [key, signal] of signals)
483
+ addOwnWatcher(key, signal)
484
+ }
485
+ return () => {
486
+ listeners[type].delete(listener)
487
+ if (type === 'change' && !listeners.change.size) {
488
+ if (ownWatchers.size) {
489
+ for (const watcher of ownWatchers.values())
490
+ watcher.stop()
491
+ ownWatchers.clear()
492
+ }
493
+ }
494
+ }
495
+ },
496
+ },
497
+ })
498
+
499
+ // Return proxy directly with integrated signal methods
500
+ const list = new Proxy(prototype as List<T>, {
501
+ get(target, prop) {
502
+ if (prop in target) return Reflect.get(target, prop)
503
+ if (!isSymbol(prop)) return getSignal(prop)
504
+ },
505
+ has(target, prop) {
506
+ if (prop in target) return true
507
+ return signals.has(String(prop))
508
+ },
509
+ ownKeys(target) {
510
+ const staticKeys = Reflect.ownKeys(target)
511
+ return [...new Set([...order, ...staticKeys])]
512
+ },
513
+ getOwnPropertyDescriptor(target, prop) {
514
+ if (prop in target)
515
+ return Reflect.getOwnPropertyDescriptor(target, prop)
516
+ if (isSymbol(prop)) return undefined
517
+
518
+ const signal = getSignal(prop)
519
+ return signal
520
+ ? {
521
+ enumerable: true,
522
+ configurable: true,
523
+ writable: true,
524
+ value: signal,
525
+ }
526
+ : undefined
527
+ },
528
+ })
529
+ return list
530
+ }
531
+
532
+ /**
533
+ * Check if the provided value is a List instance
534
+ *
535
+ * @since 0.15.0
536
+ * @param {unknown} value - value to check
537
+ * @returns {boolean} - true if the value is a List instance, false otherwise
538
+ */
539
+ const isList = <T extends {}>(value: unknown): value is List<T> =>
540
+ isObjectOfType(value, TYPE_LIST)
541
+
542
+ /* === Exports === */
543
+
544
+ export {
545
+ TYPE_LIST,
546
+ isList,
547
+ createList,
548
+ type ArrayToRecord,
549
+ type List,
550
+ type KeyConfig,
551
+ }
@@ -0,0 +1,139 @@
1
+ import {
2
+ CircularDependencyError,
3
+ createError,
4
+ InvalidCallbackError,
5
+ NullishSignalValueError,
6
+ } from '../src/errors'
7
+ import {
8
+ createWatcher,
9
+ flushPendingReactions,
10
+ notifyWatchers,
11
+ subscribeActiveWatcher,
12
+ trackSignalReads,
13
+ type Watcher,
14
+ } from '../src/system'
15
+ import { isObjectOfType, isSyncFunction, UNSET } from '../src/util'
16
+
17
+ /* === Types === */
18
+
19
+ type Memo<T extends {}> = {
20
+ readonly [Symbol.toStringTag]: 'Memo'
21
+ get(): T
22
+ }
23
+
24
+ type MemoCallback<T extends {} & { then?: undefined }> = (oldValue: T) => T
25
+
26
+ /* === Constants === */
27
+
28
+ const TYPE_MEMO = 'Memo' as const
29
+
30
+ /* === Functions === */
31
+
32
+ /**
33
+ * Create a derived signal from existing signals
34
+ *
35
+ * @since 0.9.0
36
+ * @param {MemoCallback<T>} callback - Computation callback function
37
+ * @returns {Memo<T>} - Computed signal
38
+ */
39
+ const createMemo = <T extends {}>(
40
+ callback: MemoCallback<T>,
41
+ initialValue: T = UNSET,
42
+ ): Memo<T> => {
43
+ if (!isMemoCallback(callback))
44
+ throw new InvalidCallbackError('memo', callback)
45
+ if (initialValue == null) throw new NullishSignalValueError('memo')
46
+
47
+ const watchers: Set<Watcher> = new Set()
48
+
49
+ // Internal state
50
+ let value: T = initialValue
51
+ let error: Error | undefined
52
+ let dirty = true
53
+ let computing = false
54
+
55
+ // Own watcher: called when notified from sources (push)
56
+ const watcher = createWatcher(() => {
57
+ dirty = true
58
+ if (watchers.size) notifyWatchers(watchers)
59
+ else watcher.stop()
60
+ })
61
+
62
+ // Called when requested by dependencies (pull)
63
+ const compute = () =>
64
+ trackSignalReads(watcher, () => {
65
+ if (computing) throw new CircularDependencyError('memo')
66
+ let result: T
67
+ computing = true
68
+ try {
69
+ result = callback(value)
70
+ } catch (e) {
71
+ // Err track
72
+ value = UNSET
73
+ error = createError(e)
74
+ computing = false
75
+ return
76
+ }
77
+
78
+ if (null == result || UNSET === result) {
79
+ // Nil track
80
+ value = UNSET
81
+ error = undefined
82
+ } else {
83
+ // Ok track
84
+ value = result
85
+ error = undefined
86
+ dirty = false
87
+ }
88
+ computing = false
89
+ })
90
+
91
+ const memo: Record<PropertyKey, unknown> = {}
92
+ Object.defineProperties(memo, {
93
+ [Symbol.toStringTag]: {
94
+ value: TYPE_MEMO,
95
+ },
96
+ get: {
97
+ value: (): T => {
98
+ subscribeActiveWatcher(watchers)
99
+ flushPendingReactions()
100
+ if (dirty) compute()
101
+ if (error) throw error
102
+ return value
103
+ },
104
+ },
105
+ })
106
+ return memo as Memo<T>
107
+ }
108
+
109
+ /**
110
+ * Check if a value is a memoized signal
111
+ *
112
+ * @since 0.9.0
113
+ * @param {unknown} value - Value to check
114
+ * @returns {boolean} - True if value is a memo signal, false otherwise
115
+ */
116
+ const isMemo = /*#__PURE__*/ <T extends {}>(value: unknown): value is Memo<T> =>
117
+ isObjectOfType(value, TYPE_MEMO)
118
+
119
+ /**
120
+ * Check if the provided value is a callback that may be used as input for toSignal() to derive a computed state
121
+ *
122
+ * @since 0.12.0
123
+ * @param {unknown} value - Value to check
124
+ * @returns {boolean} - True if value is a sync callback, false otherwise
125
+ */
126
+ const isMemoCallback = /*#__PURE__*/ <T extends {} & { then?: undefined }>(
127
+ value: unknown,
128
+ ): value is MemoCallback<T> => isSyncFunction(value) && value.length < 2
129
+
130
+ /* === Exports === */
131
+
132
+ export {
133
+ TYPE_MEMO,
134
+ createMemo,
135
+ isMemo,
136
+ isMemoCallback,
137
+ type Memo,
138
+ type MemoCallback,
139
+ }