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