@zeix/cause-effect 0.14.2 → 0.15.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.
- package/README.md +256 -35
- package/index.d.ts +31 -6
- package/index.dev.js +383 -47
- package/index.js +1 -1
- package/index.ts +33 -4
- package/package.json +2 -2
- package/src/computed.ts +15 -6
- package/src/diff.ts +148 -0
- package/src/effect.ts +57 -50
- package/src/match.ts +52 -0
- package/src/resolve.ts +48 -0
- package/src/signal.ts +60 -14
- package/src/state.ts +4 -3
- package/src/store.ts +324 -0
- package/src/util.ts +54 -4
- package/test/batch.test.ts +23 -19
- package/test/benchmark.test.ts +8 -8
- package/test/computed.test.ts +15 -11
- package/test/diff.test.ts +638 -0
- package/test/effect.test.ts +656 -48
- package/test/match.test.ts +378 -0
- package/test/resolve.test.ts +156 -0
- package/test/signal.test.ts +451 -0
- package/test/store.test.ts +746 -0
- package/tsconfig.json +9 -10
- package/types/index.d.ts +15 -0
- package/types/src/diff.d.ts +30 -0
- package/types/src/effect.d.ts +16 -0
- package/types/src/match.d.ts +21 -0
- package/types/src/resolve.d.ts +29 -0
- package/types/src/signal.d.ts +44 -0
- package/{src → types/src}/state.d.ts +1 -1
- package/types/src/store.d.ts +62 -0
- package/types/src/util.d.ts +14 -0
- package/types/test-new-effect.d.ts +1 -0
- package/src/effect.d.ts +0 -17
- package/src/signal.d.ts +0 -26
- package/src/util.d.ts +0 -7
- /package/{src → types/src}/computed.d.ts +0 -0
- /package/{src → types/src}/scheduler.d.ts +0 -0
package/src/store.ts
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { diff, type UnknownRecord, type UnknownRecordOrArray } from './diff'
|
|
2
|
+
import { effect } from './effect'
|
|
3
|
+
import {
|
|
4
|
+
batch,
|
|
5
|
+
type Cleanup,
|
|
6
|
+
notify,
|
|
7
|
+
subscribe,
|
|
8
|
+
type Watcher,
|
|
9
|
+
} from './scheduler'
|
|
10
|
+
import { type Signal, toMutableSignal, UNSET } from './signal'
|
|
11
|
+
import { type State, state } from './state'
|
|
12
|
+
import { hasMethod, isObjectOfType, validArrayIndexes } from './util'
|
|
13
|
+
|
|
14
|
+
/* === Constants === */
|
|
15
|
+
|
|
16
|
+
const TYPE_STORE = 'Store'
|
|
17
|
+
|
|
18
|
+
/* === Types === */
|
|
19
|
+
|
|
20
|
+
interface StoreAddEvent<T extends UnknownRecordOrArray> extends CustomEvent {
|
|
21
|
+
type: 'store-add'
|
|
22
|
+
detail: Partial<T>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface StoreChangeEvent<T extends UnknownRecordOrArray> extends CustomEvent {
|
|
26
|
+
type: 'store-change'
|
|
27
|
+
detail: Partial<T>
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface StoreRemoveEvent<T extends UnknownRecordOrArray> extends CustomEvent {
|
|
31
|
+
type: 'store-remove'
|
|
32
|
+
detail: Partial<T>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type StoreEventMap<T extends UnknownRecordOrArray> = {
|
|
36
|
+
'store-add': StoreAddEvent<T>
|
|
37
|
+
'store-change': StoreChangeEvent<T>
|
|
38
|
+
'store-remove': StoreRemoveEvent<T>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface StoreEventTarget<T extends UnknownRecordOrArray> extends EventTarget {
|
|
42
|
+
addEventListener<K extends keyof StoreEventMap<T>>(
|
|
43
|
+
type: K,
|
|
44
|
+
listener: (event: StoreEventMap<T>[K]) => void,
|
|
45
|
+
options?: boolean | AddEventListenerOptions,
|
|
46
|
+
): void
|
|
47
|
+
addEventListener(
|
|
48
|
+
type: string,
|
|
49
|
+
listener: EventListenerOrEventListenerObject,
|
|
50
|
+
options?: boolean | AddEventListenerOptions,
|
|
51
|
+
): void
|
|
52
|
+
|
|
53
|
+
removeEventListener<K extends keyof StoreEventMap<T>>(
|
|
54
|
+
type: K,
|
|
55
|
+
listener: (event: StoreEventMap<T>[K]) => void,
|
|
56
|
+
options?: boolean | EventListenerOptions,
|
|
57
|
+
): void
|
|
58
|
+
removeEventListener(
|
|
59
|
+
type: string,
|
|
60
|
+
listener: EventListenerOrEventListenerObject,
|
|
61
|
+
options?: boolean | EventListenerOptions,
|
|
62
|
+
): void
|
|
63
|
+
|
|
64
|
+
dispatchEvent(event: Event): boolean
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
type Store<T extends UnknownRecordOrArray = UnknownRecord> = {
|
|
68
|
+
[K in keyof T]: T[K] extends UnknownRecord ? Store<T[K]> : State<T[K]>
|
|
69
|
+
} & StoreEventTarget<T> & {
|
|
70
|
+
[Symbol.toStringTag]: 'Store'
|
|
71
|
+
[Symbol.iterator](): IterableIterator<[keyof T, Signal<T[keyof T]>]>
|
|
72
|
+
|
|
73
|
+
add<K extends keyof T>(key: K, value: T[K]): void
|
|
74
|
+
get(): T
|
|
75
|
+
remove<K extends keyof T>(key: K): void
|
|
76
|
+
set(value: T): void
|
|
77
|
+
update(updater: (value: T) => T): void
|
|
78
|
+
size: State<number>
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* === Functions === */
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Create a new store with deeply nested reactive properties
|
|
85
|
+
*
|
|
86
|
+
* Supports both objects and arrays as initial values. Arrays are converted
|
|
87
|
+
* to records internally for storage but maintain their array type through
|
|
88
|
+
* the .get() method, which automatically converts objects with consecutive
|
|
89
|
+
* numeric keys back to arrays.
|
|
90
|
+
*
|
|
91
|
+
* @since 0.15.0
|
|
92
|
+
* @param {T} initialValue - initial object or array value of the store
|
|
93
|
+
* @returns {Store<T>} - new store with reactive properties that preserves the original type T
|
|
94
|
+
*/
|
|
95
|
+
const store = <T extends UnknownRecordOrArray>(initialValue: T): Store<T> => {
|
|
96
|
+
const watchers: Set<Watcher> = new Set()
|
|
97
|
+
const eventTarget = new EventTarget()
|
|
98
|
+
const signals: Map<keyof T, Store<T[keyof T]> | State<T[keyof T]>> =
|
|
99
|
+
new Map()
|
|
100
|
+
const cleanups = new Map<keyof T, Cleanup>()
|
|
101
|
+
|
|
102
|
+
// Internal state
|
|
103
|
+
const size = state(0)
|
|
104
|
+
|
|
105
|
+
// Get current record or array
|
|
106
|
+
const current = (): T => {
|
|
107
|
+
const keys = Array.from(signals.keys())
|
|
108
|
+
const arrayIndexes = validArrayIndexes(keys)
|
|
109
|
+
if (arrayIndexes)
|
|
110
|
+
return arrayIndexes.map(index =>
|
|
111
|
+
signals.get(String(index))?.get(),
|
|
112
|
+
) as unknown as T
|
|
113
|
+
const record: Partial<T> = {}
|
|
114
|
+
for (const [key, signal] of signals) {
|
|
115
|
+
record[key] = signal.get()
|
|
116
|
+
}
|
|
117
|
+
return record as T
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Emit event
|
|
121
|
+
const emit = (type: keyof StoreEventMap<T>, detail: Partial<T>) =>
|
|
122
|
+
eventTarget.dispatchEvent(new CustomEvent(type, { detail }))
|
|
123
|
+
|
|
124
|
+
// Add nested signal and effect
|
|
125
|
+
const addProperty = <K extends keyof T>(key: K, value: T[K]) => {
|
|
126
|
+
const stringKey = String(key)
|
|
127
|
+
const signal = toMutableSignal(value)
|
|
128
|
+
signals.set(stringKey, signal as Store<T[keyof T]> | State<T[keyof T]>)
|
|
129
|
+
const cleanup = effect(() => {
|
|
130
|
+
const currentValue = signal.get()
|
|
131
|
+
if (currentValue != null)
|
|
132
|
+
emit('store-change', { [key]: currentValue } as Partial<T>)
|
|
133
|
+
})
|
|
134
|
+
cleanups.set(stringKey, cleanup)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Remove nested signal and effect
|
|
138
|
+
const removeProperty = <K extends keyof T>(key: K) => {
|
|
139
|
+
const stringKey = String(key)
|
|
140
|
+
signals.delete(stringKey)
|
|
141
|
+
const cleanup = cleanups.get(stringKey)
|
|
142
|
+
if (cleanup) cleanup()
|
|
143
|
+
cleanups.delete(stringKey)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Reconcile data and dispatch events
|
|
147
|
+
const reconcile = (oldValue: T, newValue: T): boolean => {
|
|
148
|
+
const changes = diff(oldValue, newValue)
|
|
149
|
+
|
|
150
|
+
batch(() => {
|
|
151
|
+
if (Object.keys(changes.add).length) {
|
|
152
|
+
for (const key in changes.add) {
|
|
153
|
+
const value = changes.add[key]
|
|
154
|
+
if (value != null) addProperty(key, value as T[keyof T])
|
|
155
|
+
}
|
|
156
|
+
emit('store-add', changes.add)
|
|
157
|
+
}
|
|
158
|
+
if (Object.keys(changes.change).length) {
|
|
159
|
+
for (const key in changes.change) {
|
|
160
|
+
const signal = signals.get(key)
|
|
161
|
+
const value = changes.change[key]
|
|
162
|
+
if (
|
|
163
|
+
signal &&
|
|
164
|
+
value != null &&
|
|
165
|
+
hasMethod<Signal<T[keyof T]>>(signal, 'set')
|
|
166
|
+
)
|
|
167
|
+
signal.set(value)
|
|
168
|
+
}
|
|
169
|
+
emit('store-change', changes.change)
|
|
170
|
+
}
|
|
171
|
+
if (Object.keys(changes.remove).length) {
|
|
172
|
+
for (const key in changes.remove) {
|
|
173
|
+
removeProperty(key)
|
|
174
|
+
}
|
|
175
|
+
emit('store-remove', changes.remove)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
size.set(signals.size)
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
return changes.changed
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Initialize data
|
|
185
|
+
reconcile({} as T, initialValue)
|
|
186
|
+
|
|
187
|
+
// Queue initial additions event to allow listeners to be added first
|
|
188
|
+
setTimeout(() => {
|
|
189
|
+
const initialAdditionsEvent = new CustomEvent('store-add', {
|
|
190
|
+
detail: initialValue,
|
|
191
|
+
})
|
|
192
|
+
eventTarget.dispatchEvent(initialAdditionsEvent)
|
|
193
|
+
}, 0)
|
|
194
|
+
|
|
195
|
+
const storeProps = [
|
|
196
|
+
'add',
|
|
197
|
+
'get',
|
|
198
|
+
'remove',
|
|
199
|
+
'set',
|
|
200
|
+
'update',
|
|
201
|
+
'addEventListener',
|
|
202
|
+
'removeEventListener',
|
|
203
|
+
'dispatchEvent',
|
|
204
|
+
'size',
|
|
205
|
+
]
|
|
206
|
+
|
|
207
|
+
// Return proxy directly with integrated signal methods
|
|
208
|
+
return new Proxy({} as Store<T>, {
|
|
209
|
+
get(_target, prop) {
|
|
210
|
+
// Handle signal methods and size property
|
|
211
|
+
switch (prop) {
|
|
212
|
+
case 'add':
|
|
213
|
+
return <K extends keyof T>(k: K, v: T[K]): void => {
|
|
214
|
+
if (!signals.has(k)) {
|
|
215
|
+
addProperty(k, v)
|
|
216
|
+
notify(watchers)
|
|
217
|
+
emit('store-add', {
|
|
218
|
+
[k]: v,
|
|
219
|
+
} as unknown as Partial<T>)
|
|
220
|
+
size.set(signals.size)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
case 'get':
|
|
224
|
+
return (): T => {
|
|
225
|
+
subscribe(watchers)
|
|
226
|
+
return current()
|
|
227
|
+
}
|
|
228
|
+
case 'remove':
|
|
229
|
+
return <K extends keyof T>(k: K): void => {
|
|
230
|
+
if (signals.has(k)) {
|
|
231
|
+
removeProperty(k)
|
|
232
|
+
notify(watchers)
|
|
233
|
+
emit('store-remove', { [k]: UNSET } as Partial<T>)
|
|
234
|
+
size.set(signals.size)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
case 'set':
|
|
238
|
+
return (v: T): void => {
|
|
239
|
+
if (reconcile(current(), v)) {
|
|
240
|
+
notify(watchers)
|
|
241
|
+
if (UNSET === v) watchers.clear()
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
case 'update':
|
|
245
|
+
return (fn: (v: T) => T): void => {
|
|
246
|
+
const oldValue = current()
|
|
247
|
+
const newValue = fn(oldValue)
|
|
248
|
+
if (reconcile(oldValue, newValue)) {
|
|
249
|
+
notify(watchers)
|
|
250
|
+
if (UNSET === newValue) watchers.clear()
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
case 'addEventListener':
|
|
254
|
+
return eventTarget.addEventListener.bind(eventTarget)
|
|
255
|
+
case 'removeEventListener':
|
|
256
|
+
return eventTarget.removeEventListener.bind(eventTarget)
|
|
257
|
+
case 'dispatchEvent':
|
|
258
|
+
return eventTarget.dispatchEvent.bind(eventTarget)
|
|
259
|
+
case 'size':
|
|
260
|
+
return size
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Handle symbol properties
|
|
264
|
+
if (prop === Symbol.toStringTag) return TYPE_STORE
|
|
265
|
+
if (prop === Symbol.iterator) {
|
|
266
|
+
return function* () {
|
|
267
|
+
for (const [key, signal] of signals) {
|
|
268
|
+
yield [key, signal]
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Handle data properties - return signals
|
|
274
|
+
return signals.get(String(prop))
|
|
275
|
+
},
|
|
276
|
+
has(_target, prop) {
|
|
277
|
+
const key = String(prop)
|
|
278
|
+
return (
|
|
279
|
+
signals.has(key) ||
|
|
280
|
+
storeProps.includes(key) ||
|
|
281
|
+
prop === Symbol.toStringTag ||
|
|
282
|
+
prop === Symbol.iterator
|
|
283
|
+
)
|
|
284
|
+
},
|
|
285
|
+
ownKeys() {
|
|
286
|
+
return Array.from(signals.keys()).map(key => String(key))
|
|
287
|
+
},
|
|
288
|
+
getOwnPropertyDescriptor(_target, prop) {
|
|
289
|
+
const signal = signals.get(String(prop))
|
|
290
|
+
return signal
|
|
291
|
+
? {
|
|
292
|
+
enumerable: true,
|
|
293
|
+
configurable: true,
|
|
294
|
+
writable: true,
|
|
295
|
+
value: signal,
|
|
296
|
+
}
|
|
297
|
+
: undefined
|
|
298
|
+
},
|
|
299
|
+
})
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Check if the provided value is a Store instance
|
|
304
|
+
*
|
|
305
|
+
* @since 0.15.0
|
|
306
|
+
* @param {unknown} value - value to check
|
|
307
|
+
* @returns {boolean} - true if the value is a Store instance, false otherwise
|
|
308
|
+
*/
|
|
309
|
+
const isStore = <T extends UnknownRecordOrArray>(
|
|
310
|
+
value: unknown,
|
|
311
|
+
): value is Store<T> => isObjectOfType(value, TYPE_STORE)
|
|
312
|
+
|
|
313
|
+
/* === Exports === */
|
|
314
|
+
|
|
315
|
+
export {
|
|
316
|
+
TYPE_STORE,
|
|
317
|
+
isStore,
|
|
318
|
+
store,
|
|
319
|
+
type Store,
|
|
320
|
+
type StoreAddEvent,
|
|
321
|
+
type StoreChangeEvent,
|
|
322
|
+
type StoreRemoveEvent,
|
|
323
|
+
type StoreEventMap,
|
|
324
|
+
}
|
package/src/util.ts
CHANGED
|
@@ -1,15 +1,53 @@
|
|
|
1
1
|
/* === Utility Functions === */
|
|
2
2
|
|
|
3
|
+
const isString = /*#__PURE__*/ (value: unknown): value is string =>
|
|
4
|
+
typeof value === 'string'
|
|
5
|
+
|
|
6
|
+
const isNumber = /*#__PURE__*/ (value: unknown): value is number =>
|
|
7
|
+
typeof value === 'number'
|
|
8
|
+
|
|
3
9
|
const isFunction = /*#__PURE__*/ <T>(
|
|
4
|
-
|
|
5
|
-
):
|
|
10
|
+
fn: unknown,
|
|
11
|
+
): fn is (...args: unknown[]) => T => typeof fn === 'function'
|
|
12
|
+
|
|
13
|
+
const isAsyncFunction = /*#__PURE__*/ <T>(
|
|
14
|
+
fn: unknown,
|
|
15
|
+
): fn is (...args: unknown[]) => Promise<T> =>
|
|
16
|
+
isFunction(fn) && fn.constructor.name === 'AsyncFunction'
|
|
6
17
|
|
|
7
18
|
const isObjectOfType = /*#__PURE__*/ <T>(
|
|
8
19
|
value: unknown,
|
|
9
20
|
type: string,
|
|
10
21
|
): value is T => Object.prototype.toString.call(value) === `[object ${type}]`
|
|
11
22
|
|
|
12
|
-
const
|
|
23
|
+
const isRecord = /*#__PURE__*/ <T extends Record<string, unknown>>(
|
|
24
|
+
value: unknown,
|
|
25
|
+
): value is T => isObjectOfType(value, 'Object')
|
|
26
|
+
|
|
27
|
+
const validArrayIndexes = /*#__PURE__*/ (
|
|
28
|
+
keys: Array<PropertyKey>,
|
|
29
|
+
): number[] | null => {
|
|
30
|
+
if (!keys.length) return null
|
|
31
|
+
const indexes = keys.map(k =>
|
|
32
|
+
isString(k) ? parseInt(k, 10) : isNumber(k) ? k : NaN,
|
|
33
|
+
)
|
|
34
|
+
return indexes.every(index => Number.isFinite(index) && index >= 0)
|
|
35
|
+
? indexes.sort((a, b) => a - b)
|
|
36
|
+
: null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const hasMethod = /*#__PURE__*/ <
|
|
40
|
+
T extends object & Record<string, (...args: unknown[]) => unknown>,
|
|
41
|
+
>(
|
|
42
|
+
obj: T,
|
|
43
|
+
methodName: string,
|
|
44
|
+
): obj is T & Record<string, (...args: unknown[]) => unknown> =>
|
|
45
|
+
methodName in obj && isFunction(obj[methodName])
|
|
46
|
+
|
|
47
|
+
const isAbortError = /*#__PURE__*/ (error: unknown): boolean =>
|
|
48
|
+
error instanceof DOMException && error.name === 'AbortError'
|
|
49
|
+
|
|
50
|
+
const toError = /*#__PURE__*/ (reason: unknown): Error =>
|
|
13
51
|
reason instanceof Error ? reason : Error(String(reason))
|
|
14
52
|
|
|
15
53
|
class CircularDependencyError extends Error {
|
|
@@ -21,4 +59,16 @@ class CircularDependencyError extends Error {
|
|
|
21
59
|
|
|
22
60
|
/* === Exports === */
|
|
23
61
|
|
|
24
|
-
export {
|
|
62
|
+
export {
|
|
63
|
+
isString,
|
|
64
|
+
isNumber,
|
|
65
|
+
isFunction,
|
|
66
|
+
isAsyncFunction,
|
|
67
|
+
isObjectOfType,
|
|
68
|
+
isRecord,
|
|
69
|
+
validArrayIndexes,
|
|
70
|
+
hasMethod,
|
|
71
|
+
isAbortError,
|
|
72
|
+
toError,
|
|
73
|
+
CircularDependencyError,
|
|
74
|
+
}
|
package/test/batch.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test'
|
|
2
|
-
import { batch, computed, effect, state } from '../'
|
|
2
|
+
import { batch, computed, effect, match, resolve, state } from '../'
|
|
3
3
|
|
|
4
4
|
/* === Tests === */
|
|
5
5
|
|
|
@@ -28,13 +28,15 @@ describe('Batch', () => {
|
|
|
28
28
|
const sum = computed(() => a.get() + b.get() + c.get())
|
|
29
29
|
let result = 0
|
|
30
30
|
let count = 0
|
|
31
|
-
effect({
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
effect(() => {
|
|
32
|
+
const resolved = resolve({ sum })
|
|
33
|
+
match(resolved, {
|
|
34
|
+
ok: ({ sum: res }) => {
|
|
35
|
+
result = res
|
|
36
|
+
count++
|
|
37
|
+
},
|
|
38
|
+
err: () => {},
|
|
39
|
+
})
|
|
38
40
|
})
|
|
39
41
|
batch(() => {
|
|
40
42
|
a.set(6)
|
|
@@ -61,17 +63,19 @@ describe('Batch', () => {
|
|
|
61
63
|
let errCount = 0
|
|
62
64
|
|
|
63
65
|
// Effect: switch cases for the result
|
|
64
|
-
effect({
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
66
|
+
effect(() => {
|
|
67
|
+
const resolved = resolve({ sum })
|
|
68
|
+
match(resolved, {
|
|
69
|
+
ok: ({ sum: v }) => {
|
|
70
|
+
result = v
|
|
71
|
+
okCount++
|
|
72
|
+
// console.log('Sum:', v)
|
|
73
|
+
},
|
|
74
|
+
err: () => {
|
|
75
|
+
errCount++
|
|
76
|
+
// console.error('Error:', error)
|
|
77
|
+
},
|
|
78
|
+
})
|
|
75
79
|
})
|
|
76
80
|
|
|
77
81
|
expect(okCount).toBe(1)
|
package/test/benchmark.test.ts
CHANGED
|
@@ -169,7 +169,7 @@ describe('Basic test', () => {
|
|
|
169
169
|
describe('Kairo tests', () => {
|
|
170
170
|
const name = framework.name
|
|
171
171
|
|
|
172
|
-
test(`${name} | avoidable propagation`, () => {
|
|
172
|
+
test(`${name} | avoidable propagation`, async () => {
|
|
173
173
|
const head = framework.signal(0)
|
|
174
174
|
const computed1 = framework.computed(() => head.read())
|
|
175
175
|
const computed2 = framework.computed(() => {
|
|
@@ -201,7 +201,7 @@ describe('Kairo tests', () => {
|
|
|
201
201
|
}
|
|
202
202
|
})
|
|
203
203
|
|
|
204
|
-
test(`${name} | broad propagation`, () => {
|
|
204
|
+
test(`${name} | broad propagation`, async () => {
|
|
205
205
|
const head = framework.signal(0)
|
|
206
206
|
let last = head as { read: () => number }
|
|
207
207
|
const callCounter = new Counter()
|
|
@@ -235,7 +235,7 @@ describe('Kairo tests', () => {
|
|
|
235
235
|
}
|
|
236
236
|
})
|
|
237
237
|
|
|
238
|
-
test(`${name} | deep propagation`, () => {
|
|
238
|
+
test(`${name} | deep propagation`, async () => {
|
|
239
239
|
const len = 50
|
|
240
240
|
const head = framework.signal(0)
|
|
241
241
|
let current = head as { read: () => number }
|
|
@@ -268,7 +268,7 @@ describe('Kairo tests', () => {
|
|
|
268
268
|
}
|
|
269
269
|
})
|
|
270
270
|
|
|
271
|
-
test(`${name} | diamond`, () => {
|
|
271
|
+
test(`${name} | diamond`, async () => {
|
|
272
272
|
const width = 5
|
|
273
273
|
const head = framework.signal(0)
|
|
274
274
|
const current: { read(): number }[] = []
|
|
@@ -301,7 +301,7 @@ describe('Kairo tests', () => {
|
|
|
301
301
|
}
|
|
302
302
|
})
|
|
303
303
|
|
|
304
|
-
test(`${name} | mux`, () => {
|
|
304
|
+
test(`${name} | mux`, async () => {
|
|
305
305
|
const heads = new Array(100).fill(null).map(_ => framework.signal(0))
|
|
306
306
|
const mux = framework.computed(() => {
|
|
307
307
|
return Object.fromEntries(heads.map(h => h.read()).entries())
|
|
@@ -332,7 +332,7 @@ describe('Kairo tests', () => {
|
|
|
332
332
|
}
|
|
333
333
|
})
|
|
334
334
|
|
|
335
|
-
test(`${name} | repeated observers`, () => {
|
|
335
|
+
test(`${name} | repeated observers`, async () => {
|
|
336
336
|
const size = 30
|
|
337
337
|
const head = framework.signal(0)
|
|
338
338
|
const current = framework.computed(() => {
|
|
@@ -365,7 +365,7 @@ describe('Kairo tests', () => {
|
|
|
365
365
|
}
|
|
366
366
|
})
|
|
367
367
|
|
|
368
|
-
test(`${name} | triangle`, () => {
|
|
368
|
+
test(`${name} | triangle`, async () => {
|
|
369
369
|
const width = 10
|
|
370
370
|
const head = framework.signal(0)
|
|
371
371
|
let current = head as { read: () => number }
|
|
@@ -410,7 +410,7 @@ describe('Kairo tests', () => {
|
|
|
410
410
|
}
|
|
411
411
|
})
|
|
412
412
|
|
|
413
|
-
test(`${name} | unstable`, () => {
|
|
413
|
+
test(`${name} | unstable`, async () => {
|
|
414
414
|
const head = framework.signal(0)
|
|
415
415
|
const double = framework.computed(() => head.read() * 2)
|
|
416
416
|
const inverse = framework.computed(() => -head.read())
|
package/test/computed.test.ts
CHANGED
|
@@ -4,9 +4,11 @@ import {
|
|
|
4
4
|
effect,
|
|
5
5
|
isComputed,
|
|
6
6
|
isState,
|
|
7
|
+
match,
|
|
8
|
+
resolve,
|
|
7
9
|
state,
|
|
8
10
|
UNSET,
|
|
9
|
-
} from '../
|
|
11
|
+
} from '../'
|
|
10
12
|
|
|
11
13
|
/* === Utility Functions === */
|
|
12
14
|
|
|
@@ -134,7 +136,7 @@ describe('Computed', () => {
|
|
|
134
136
|
const b = computed(() => x.get())
|
|
135
137
|
const c = computed(() => {
|
|
136
138
|
count++
|
|
137
|
-
return a.get()
|
|
139
|
+
return `${a.get()} ${b.get()}`
|
|
138
140
|
})
|
|
139
141
|
expect(c.get()).toBe('a a')
|
|
140
142
|
expect(count).toBe(1)
|
|
@@ -282,15 +284,17 @@ describe('Computed', () => {
|
|
|
282
284
|
let okCount = 0
|
|
283
285
|
let nilCount = 0
|
|
284
286
|
let result: number = 0
|
|
285
|
-
effect({
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
287
|
+
effect(() => {
|
|
288
|
+
const resolved = resolve({ derived })
|
|
289
|
+
match(resolved, {
|
|
290
|
+
ok: ({ derived: v }) => {
|
|
291
|
+
result = v
|
|
292
|
+
okCount++
|
|
293
|
+
},
|
|
294
|
+
nil: () => {
|
|
295
|
+
nilCount++
|
|
296
|
+
},
|
|
297
|
+
})
|
|
294
298
|
})
|
|
295
299
|
cause.set(43)
|
|
296
300
|
expect(okCount).toBe(0)
|