@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.
- package/.ai-context.md +71 -21
- package/.cursorrules +3 -2
- package/.github/copilot-instructions.md +59 -13
- package/CLAUDE.md +170 -24
- package/LICENSE +1 -1
- package/README.md +156 -52
- package/archive/benchmark.ts +688 -0
- package/archive/collection.ts +312 -0
- package/{src → archive}/computed.ts +33 -34
- package/archive/list.ts +551 -0
- package/archive/memo.ts +138 -0
- package/archive/state.ts +89 -0
- package/archive/store.ts +368 -0
- package/archive/task.ts +194 -0
- package/eslint.config.js +1 -0
- package/index.dev.js +902 -501
- package/index.js +1 -1
- package/index.ts +42 -22
- package/package.json +1 -1
- package/src/classes/collection.ts +272 -0
- package/src/classes/composite.ts +176 -0
- package/src/classes/computed.ts +333 -0
- package/src/classes/list.ts +304 -0
- package/src/classes/state.ts +98 -0
- package/src/classes/store.ts +210 -0
- package/src/diff.ts +28 -52
- package/src/effect.ts +9 -9
- package/src/errors.ts +50 -25
- package/src/signal.ts +58 -41
- package/src/system.ts +79 -42
- package/src/util.ts +16 -34
- package/test/batch.test.ts +15 -17
- package/test/benchmark.test.ts +4 -4
- package/test/collection.test.ts +796 -0
- package/test/computed.test.ts +138 -130
- package/test/diff.test.ts +2 -2
- package/test/effect.test.ts +36 -35
- package/test/list.test.ts +754 -0
- package/test/match.test.ts +25 -25
- package/test/resolve.test.ts +17 -19
- package/test/signal.test.ts +72 -121
- package/test/state.test.ts +44 -44
- package/test/store.test.ts +344 -1663
- package/types/index.d.ts +11 -9
- package/types/src/classes/collection.d.ts +32 -0
- package/types/src/classes/composite.d.ts +15 -0
- package/types/src/classes/computed.d.ts +97 -0
- package/types/src/classes/list.d.ts +41 -0
- package/types/src/classes/state.d.ts +52 -0
- package/types/src/classes/store.d.ts +51 -0
- package/types/src/diff.d.ts +8 -12
- package/types/src/errors.d.ts +12 -11
- package/types/src/signal.d.ts +27 -14
- package/types/src/system.d.ts +41 -20
- package/types/src/util.d.ts +6 -3
- package/src/state.ts +0 -98
- package/src/store.ts +0 -525
- package/types/src/collection.d.ts +0 -26
- package/types/src/computed.d.ts +0 -33
- package/types/src/scheduler.d.ts +0 -55
- package/types/src/state.d.ts +0 -24
- package/types/src/store.d.ts +0 -66
package/src/signal.ts
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type Computed,
|
|
3
|
-
type ComputedCallback,
|
|
4
|
-
createComputed,
|
|
5
3
|
isComputed,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
isMemoCallback,
|
|
5
|
+
isTaskCallback,
|
|
6
|
+
Memo,
|
|
7
|
+
Task,
|
|
8
|
+
} from './classes/computed'
|
|
9
|
+
import { isList, List } from './classes/list'
|
|
10
|
+
import { isState, State } from './classes/state'
|
|
11
|
+
import { createStore, isStore, type Store } from './classes/store'
|
|
12
|
+
import type { UnknownRecord } from './diff'
|
|
13
|
+
// import type { Collection } from './signals/collection'
|
|
14
|
+
import { isRecord, isUniformArray } from './util'
|
|
11
15
|
|
|
12
16
|
/* === Types === */
|
|
13
17
|
|
|
@@ -15,6 +19,13 @@ type Signal<T extends {}> = {
|
|
|
15
19
|
get(): T
|
|
16
20
|
}
|
|
17
21
|
|
|
22
|
+
type MutableSignal<T extends {}> = T extends readonly (infer U extends {})[]
|
|
23
|
+
? List<U>
|
|
24
|
+
: T extends UnknownRecord
|
|
25
|
+
? Store<T>
|
|
26
|
+
: State<T>
|
|
27
|
+
type ReadonlySignal<T extends {}> = Computed<T> // | Collection<T>
|
|
28
|
+
|
|
18
29
|
type UnknownSignalRecord = Record<string, Signal<unknown & {}>>
|
|
19
30
|
|
|
20
31
|
type SignalValues<S extends UnknownSignalRecord> = {
|
|
@@ -35,54 +46,60 @@ const isSignal = /*#__PURE__*/ <T extends {}>(
|
|
|
35
46
|
): value is Signal<T> => isState(value) || isComputed(value) || isStore(value)
|
|
36
47
|
|
|
37
48
|
/**
|
|
38
|
-
* Check whether a value is a State or
|
|
49
|
+
* Check whether a value is a State, Store, or List
|
|
39
50
|
*
|
|
40
51
|
* @since 0.15.2
|
|
41
|
-
* @param {unknown} value -
|
|
42
|
-
* @returns {boolean} -
|
|
52
|
+
* @param {unknown} value - Value to check
|
|
53
|
+
* @returns {boolean} - True if value is a State, Store, or List, false otherwise
|
|
43
54
|
*/
|
|
44
|
-
const isMutableSignal = /*#__PURE__*/
|
|
55
|
+
const isMutableSignal = /*#__PURE__*/ (
|
|
45
56
|
value: unknown,
|
|
46
|
-
): value is
|
|
57
|
+
): value is MutableSignal<unknown & {}> =>
|
|
58
|
+
isState(value) || isStore(value) || isList(value)
|
|
47
59
|
|
|
48
60
|
/**
|
|
49
|
-
* Convert a value to a Signal
|
|
61
|
+
* Convert a value to a Signal.
|
|
50
62
|
*
|
|
51
63
|
* @since 0.9.6
|
|
52
|
-
* @param {T} value - value to convert
|
|
53
|
-
* @returns {Signal<T>} - Signal instance
|
|
54
64
|
*/
|
|
55
|
-
function
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
65
|
+
function createSignal<T extends {}>(value: readonly T[]): List<T>
|
|
66
|
+
function createSignal<T extends {}>(value: T[]): List<T>
|
|
67
|
+
function createSignal<T extends UnknownRecord>(value: T): Store<T>
|
|
68
|
+
function createSignal<T extends {}>(value: () => T): Computed<T>
|
|
69
|
+
function createSignal<T extends {}>(value: T): State<T>
|
|
70
|
+
function createSignal(value: unknown): unknown {
|
|
71
|
+
if (isMemoCallback(value)) return new Memo(value)
|
|
72
|
+
if (isTaskCallback(value)) return new Task(value)
|
|
73
|
+
if (isUniformArray<unknown & {}>(value)) return new List(value)
|
|
74
|
+
if (isRecord(value)) return createStore(value as UnknownRecord)
|
|
75
|
+
return new State(value as unknown & {})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Convert a value to a MutableSignal.
|
|
80
|
+
*
|
|
81
|
+
* @since 0.17.0
|
|
82
|
+
*/
|
|
83
|
+
function createMutableSignal<T extends {}>(value: readonly T[]): List<T>
|
|
84
|
+
function createMutableSignal<T extends {}>(value: T[]): List<T>
|
|
85
|
+
function createMutableSignal<T extends UnknownRecord>(value: T): Store<T>
|
|
86
|
+
function createMutableSignal<T extends {}>(value: T): State<T>
|
|
87
|
+
function createMutableSignal(value: unknown): unknown {
|
|
88
|
+
if (isUniformArray<unknown & {}>(value)) return new List(value)
|
|
89
|
+
if (isRecord(value)) return createStore(value as UnknownRecord)
|
|
90
|
+
return new State(value as unknown & {})
|
|
77
91
|
}
|
|
78
92
|
|
|
79
93
|
/* === Exports === */
|
|
80
94
|
|
|
81
95
|
export {
|
|
96
|
+
createMutableSignal,
|
|
97
|
+
createSignal,
|
|
98
|
+
isMutableSignal,
|
|
99
|
+
isSignal,
|
|
100
|
+
type MutableSignal,
|
|
101
|
+
type ReadonlySignal,
|
|
82
102
|
type Signal,
|
|
83
|
-
type UnknownSignalRecord,
|
|
84
103
|
type SignalValues,
|
|
85
|
-
|
|
86
|
-
isMutableSignal,
|
|
87
|
-
toSignal,
|
|
104
|
+
type UnknownSignalRecord,
|
|
88
105
|
}
|
package/src/system.ts
CHANGED
|
@@ -4,8 +4,23 @@ type Cleanup = () => void
|
|
|
4
4
|
|
|
5
5
|
type Watcher = {
|
|
6
6
|
(): void
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
onCleanup(cleanup: Cleanup): void
|
|
8
|
+
stop(): void
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type Notifications = {
|
|
12
|
+
add: readonly string[]
|
|
13
|
+
change: readonly string[]
|
|
14
|
+
remove: readonly string[]
|
|
15
|
+
sort: readonly string[]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type Listener<K extends keyof Notifications> = (
|
|
19
|
+
payload: Notifications[K],
|
|
20
|
+
) => void
|
|
21
|
+
|
|
22
|
+
type Listeners = {
|
|
23
|
+
[K in keyof Notifications]: Set<Listener<K>>
|
|
9
24
|
}
|
|
10
25
|
|
|
11
26
|
/* === Internal === */
|
|
@@ -13,8 +28,8 @@ type Watcher = {
|
|
|
13
28
|
// Currently active watcher
|
|
14
29
|
let activeWatcher: Watcher | undefined
|
|
15
30
|
|
|
16
|
-
//
|
|
17
|
-
const
|
|
31
|
+
// Queue of pending watcher reactions for batched change notifications
|
|
32
|
+
const pendingReactions = new Set<() => void>()
|
|
18
33
|
let batchDepth = 0
|
|
19
34
|
|
|
20
35
|
/* === Functions === */
|
|
@@ -22,85 +37,87 @@ let batchDepth = 0
|
|
|
22
37
|
/**
|
|
23
38
|
* Create a watcher that can be used to observe changes to a signal
|
|
24
39
|
*
|
|
40
|
+
* A watcher is a reaction function with onCleanup and stop methods
|
|
41
|
+
*
|
|
25
42
|
* @since 0.14.1
|
|
26
|
-
* @param {() => void}
|
|
43
|
+
* @param {() => void} react - Function to be called when the state changes
|
|
27
44
|
* @returns {Watcher} - Watcher object with off and cleanup methods
|
|
28
45
|
*/
|
|
29
|
-
const createWatcher = (
|
|
46
|
+
const createWatcher = (react: () => void): Watcher => {
|
|
30
47
|
const cleanups = new Set<Cleanup>()
|
|
31
|
-
const
|
|
32
|
-
|
|
48
|
+
const watcher = react as Partial<Watcher>
|
|
49
|
+
watcher.onCleanup = (cleanup: Cleanup) => {
|
|
33
50
|
cleanups.add(cleanup)
|
|
34
51
|
}
|
|
35
|
-
|
|
52
|
+
watcher.stop = () => {
|
|
36
53
|
for (const cleanup of cleanups) cleanup()
|
|
37
54
|
cleanups.clear()
|
|
38
55
|
}
|
|
39
|
-
return
|
|
56
|
+
return watcher as Watcher
|
|
40
57
|
}
|
|
41
58
|
|
|
42
59
|
/**
|
|
43
|
-
*
|
|
60
|
+
* Subscribe by adding active watcher to the Set of watchers of a signal
|
|
44
61
|
*
|
|
45
|
-
* @param {Set<Watcher>} watchers -
|
|
62
|
+
* @param {Set<Watcher>} watchers - Watchers of the signal
|
|
46
63
|
*/
|
|
47
|
-
const
|
|
64
|
+
const subscribeActiveWatcher = (watchers: Set<Watcher>) => {
|
|
48
65
|
if (activeWatcher && !watchers.has(activeWatcher)) {
|
|
49
66
|
const watcher = activeWatcher
|
|
50
|
-
watcher.
|
|
51
|
-
watchers.delete(watcher)
|
|
52
|
-
})
|
|
67
|
+
watcher.onCleanup(() => watchers.delete(watcher))
|
|
53
68
|
watchers.add(watcher)
|
|
54
69
|
}
|
|
55
70
|
}
|
|
56
71
|
|
|
57
72
|
/**
|
|
58
|
-
*
|
|
73
|
+
* Notify watchers of a signal change
|
|
59
74
|
*
|
|
60
|
-
* @param {Set<Watcher>} watchers -
|
|
75
|
+
* @param {Set<Watcher>} watchers - Watchers of the signal
|
|
61
76
|
*/
|
|
62
|
-
const
|
|
63
|
-
for (const
|
|
64
|
-
if (batchDepth)
|
|
65
|
-
else
|
|
77
|
+
const notifyWatchers = (watchers: Set<Watcher>) => {
|
|
78
|
+
for (const react of watchers) {
|
|
79
|
+
if (batchDepth) pendingReactions.add(react)
|
|
80
|
+
else react()
|
|
66
81
|
}
|
|
67
82
|
}
|
|
68
83
|
|
|
69
84
|
/**
|
|
70
|
-
* Flush all pending
|
|
85
|
+
* Flush all pending reactions of enqueued watchers
|
|
71
86
|
*/
|
|
72
|
-
const
|
|
73
|
-
while (
|
|
74
|
-
const watchers = Array.from(
|
|
75
|
-
|
|
87
|
+
const flushPendingReactions = () => {
|
|
88
|
+
while (pendingReactions.size) {
|
|
89
|
+
const watchers = Array.from(pendingReactions)
|
|
90
|
+
pendingReactions.clear()
|
|
76
91
|
for (const watcher of watchers) watcher()
|
|
77
92
|
}
|
|
78
93
|
}
|
|
79
94
|
|
|
80
95
|
/**
|
|
81
|
-
* Batch multiple
|
|
96
|
+
* Batch multiple signal writes
|
|
82
97
|
*
|
|
83
|
-
* @param {() => void}
|
|
98
|
+
* @param {() => void} callback - Function with multiple signal writes to be batched
|
|
84
99
|
*/
|
|
85
|
-
const
|
|
100
|
+
const batchSignalWrites = (callback: () => void) => {
|
|
86
101
|
batchDepth++
|
|
87
102
|
try {
|
|
88
|
-
|
|
103
|
+
callback()
|
|
89
104
|
} finally {
|
|
90
|
-
|
|
105
|
+
flushPendingReactions()
|
|
91
106
|
batchDepth--
|
|
92
107
|
}
|
|
93
108
|
}
|
|
94
109
|
|
|
95
110
|
/**
|
|
96
|
-
* Run a function in a
|
|
111
|
+
* Run a function with signal reads in a tracking context (or temporarily untrack)
|
|
97
112
|
*
|
|
98
|
-
* @param {
|
|
99
|
-
*
|
|
113
|
+
* @param {Watcher | false} watcher - Watcher to be called when the signal changes
|
|
114
|
+
* or false for temporary untracking while inserting auto-hydrating DOM nodes
|
|
115
|
+
* that might read signals (e.g., Web Components)
|
|
116
|
+
* @param {() => void} run - Function to run the computation or effect
|
|
100
117
|
*/
|
|
101
|
-
const
|
|
118
|
+
const trackSignalReads = (watcher: Watcher | false, run: () => void): void => {
|
|
102
119
|
const prev = activeWatcher
|
|
103
|
-
activeWatcher = watcher
|
|
120
|
+
activeWatcher = watcher || undefined
|
|
104
121
|
try {
|
|
105
122
|
run()
|
|
106
123
|
} finally {
|
|
@@ -108,15 +125,35 @@ const observe = (run: () => void, watcher?: Watcher): void => {
|
|
|
108
125
|
}
|
|
109
126
|
}
|
|
110
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Emit a notification to listeners
|
|
130
|
+
*
|
|
131
|
+
* @param {Set<Listener>} listeners - Listeners to be notified
|
|
132
|
+
* @param {Notifications[K]} payload - Payload to be sent to listeners
|
|
133
|
+
*/
|
|
134
|
+
const emitNotification = <T extends keyof Notifications>(
|
|
135
|
+
listeners: Set<Listener<T>>,
|
|
136
|
+
payload: Notifications[T],
|
|
137
|
+
) => {
|
|
138
|
+
for (const listener of listeners) {
|
|
139
|
+
if (batchDepth) pendingReactions.add(() => listener(payload))
|
|
140
|
+
else listener(payload)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
111
144
|
/* === Exports === */
|
|
112
145
|
|
|
113
146
|
export {
|
|
114
147
|
type Cleanup,
|
|
115
148
|
type Watcher,
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
batch,
|
|
149
|
+
type Notifications,
|
|
150
|
+
type Listener,
|
|
151
|
+
type Listeners,
|
|
120
152
|
createWatcher,
|
|
121
|
-
|
|
153
|
+
subscribeActiveWatcher,
|
|
154
|
+
notifyWatchers,
|
|
155
|
+
flushPendingReactions,
|
|
156
|
+
batchSignalWrites,
|
|
157
|
+
trackSignalReads,
|
|
158
|
+
emitNotification,
|
|
122
159
|
}
|
package/src/util.ts
CHANGED
|
@@ -23,6 +23,15 @@ const isAsyncFunction = /*#__PURE__*/ <T>(
|
|
|
23
23
|
): fn is (...args: unknown[]) => Promise<T> =>
|
|
24
24
|
isFunction(fn) && fn.constructor.name === 'AsyncFunction'
|
|
25
25
|
|
|
26
|
+
const isSyncFunction = /*#__PURE__*/ <T extends unknown & { then?: undefined }>(
|
|
27
|
+
fn: unknown,
|
|
28
|
+
): fn is (...args: unknown[]) => T =>
|
|
29
|
+
isFunction(fn) && fn.constructor.name !== 'AsyncFunction'
|
|
30
|
+
|
|
31
|
+
const isNonNullObject = /*#__PURE__*/ (
|
|
32
|
+
value: unknown,
|
|
33
|
+
): value is NonNullable<object> => value != null && typeof value === 'object'
|
|
34
|
+
|
|
26
35
|
const isObjectOfType = /*#__PURE__*/ <T>(
|
|
27
36
|
value: unknown,
|
|
28
37
|
type: string,
|
|
@@ -38,17 +47,10 @@ const isRecordOrArray = /*#__PURE__*/ <
|
|
|
38
47
|
value: unknown,
|
|
39
48
|
): value is T => isRecord(value) || Array.isArray(value)
|
|
40
49
|
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
):
|
|
44
|
-
|
|
45
|
-
const indexes = keys.map(k =>
|
|
46
|
-
isString(k) ? parseInt(k, 10) : isNumber(k) ? k : NaN,
|
|
47
|
-
)
|
|
48
|
-
return indexes.every(index => Number.isFinite(index) && index >= 0)
|
|
49
|
-
? indexes.sort((a, b) => a - b)
|
|
50
|
-
: null
|
|
51
|
-
}
|
|
50
|
+
const isUniformArray = <T>(
|
|
51
|
+
value: unknown,
|
|
52
|
+
guard = (item: T): item is T & {} => item != null,
|
|
53
|
+
): value is T[] => Array.isArray(value) && value.every(guard)
|
|
52
54
|
|
|
53
55
|
const hasMethod = /*#__PURE__*/ <
|
|
54
56
|
T extends object & Record<string, (...args: unknown[]) => unknown>,
|
|
@@ -64,27 +66,6 @@ const isAbortError = /*#__PURE__*/ (error: unknown): boolean =>
|
|
|
64
66
|
const toError = /*#__PURE__*/ (reason: unknown): Error =>
|
|
65
67
|
reason instanceof Error ? reason : Error(String(reason))
|
|
66
68
|
|
|
67
|
-
const arrayToRecord = /*#__PURE__*/ <T>(array: T[]): Record<string, T> => {
|
|
68
|
-
const record: Record<string, T> = {}
|
|
69
|
-
for (let i = 0; i < array.length; i++) {
|
|
70
|
-
record[String(i)] = array[i]
|
|
71
|
-
}
|
|
72
|
-
return record
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const recordToArray = /*#__PURE__*/ <T>(
|
|
76
|
-
record: Record<string | number, T>,
|
|
77
|
-
): Record<string, T> | T[] => {
|
|
78
|
-
const indexes = validArrayIndexes(Object.keys(record))
|
|
79
|
-
if (indexes === null) return record
|
|
80
|
-
|
|
81
|
-
const array: T[] = []
|
|
82
|
-
for (const index of indexes) {
|
|
83
|
-
array.push(record[String(index)])
|
|
84
|
-
}
|
|
85
|
-
return array
|
|
86
|
-
}
|
|
87
|
-
|
|
88
69
|
const valueString = /*#__PURE__*/ (value: unknown): string =>
|
|
89
70
|
isString(value)
|
|
90
71
|
? `"${value}"`
|
|
@@ -101,13 +82,14 @@ export {
|
|
|
101
82
|
isSymbol,
|
|
102
83
|
isFunction,
|
|
103
84
|
isAsyncFunction,
|
|
85
|
+
isSyncFunction,
|
|
86
|
+
isNonNullObject,
|
|
104
87
|
isObjectOfType,
|
|
105
88
|
isRecord,
|
|
106
89
|
isRecordOrArray,
|
|
90
|
+
isUniformArray,
|
|
107
91
|
hasMethod,
|
|
108
92
|
isAbortError,
|
|
109
93
|
toError,
|
|
110
|
-
arrayToRecord,
|
|
111
|
-
recordToArray,
|
|
112
94
|
valueString,
|
|
113
95
|
}
|
package/test/batch.test.ts
CHANGED
|
@@ -1,38 +1,36 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test'
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
createComputed,
|
|
3
|
+
batchSignalWrites,
|
|
5
4
|
createEffect,
|
|
6
|
-
|
|
5
|
+
Memo,
|
|
7
6
|
match,
|
|
8
7
|
resolve,
|
|
9
|
-
|
|
8
|
+
State,
|
|
9
|
+
} from '../index.ts'
|
|
10
10
|
|
|
11
11
|
/* === Tests === */
|
|
12
12
|
|
|
13
13
|
describe('Batch', () => {
|
|
14
14
|
test('should be triggered only once after repeated state change', () => {
|
|
15
|
-
const cause =
|
|
15
|
+
const cause = new State(0)
|
|
16
16
|
let result = 0
|
|
17
17
|
let count = 0
|
|
18
18
|
createEffect((): undefined => {
|
|
19
19
|
result = cause.get()
|
|
20
20
|
count++
|
|
21
21
|
})
|
|
22
|
-
|
|
23
|
-
for (let i = 1; i <= 10; i++)
|
|
24
|
-
cause.set(i)
|
|
25
|
-
}
|
|
22
|
+
batchSignalWrites(() => {
|
|
23
|
+
for (let i = 1; i <= 10; i++) cause.set(i)
|
|
26
24
|
})
|
|
27
25
|
expect(result).toBe(10)
|
|
28
26
|
expect(count).toBe(2) // + 1 for effect initialization
|
|
29
27
|
})
|
|
30
28
|
|
|
31
29
|
test('should be triggered only once when multiple signals are set', () => {
|
|
32
|
-
const a =
|
|
33
|
-
const b =
|
|
34
|
-
const c =
|
|
35
|
-
const sum =
|
|
30
|
+
const a = new State(3)
|
|
31
|
+
const b = new State(4)
|
|
32
|
+
const c = new State(5)
|
|
33
|
+
const sum = new Memo(() => a.get() + b.get() + c.get())
|
|
36
34
|
let result = 0
|
|
37
35
|
let count = 0
|
|
38
36
|
createEffect(() => {
|
|
@@ -45,7 +43,7 @@ describe('Batch', () => {
|
|
|
45
43
|
err: () => {},
|
|
46
44
|
})
|
|
47
45
|
})
|
|
48
|
-
|
|
46
|
+
batchSignalWrites(() => {
|
|
49
47
|
a.set(6)
|
|
50
48
|
b.set(8)
|
|
51
49
|
c.set(10)
|
|
@@ -56,10 +54,10 @@ describe('Batch', () => {
|
|
|
56
54
|
|
|
57
55
|
test('should prove example from README works', () => {
|
|
58
56
|
// State: define an array of Signal<number>
|
|
59
|
-
const signals = [
|
|
57
|
+
const signals = [new State(2), new State(3), new State(5)]
|
|
60
58
|
|
|
61
59
|
// Computed: derive a calculation ...
|
|
62
|
-
const sum =
|
|
60
|
+
const sum = new Memo(() => {
|
|
63
61
|
const v = signals.reduce((total, v) => total + v.get(), 0)
|
|
64
62
|
if (!Number.isFinite(v)) throw new Error('Invalid value')
|
|
65
63
|
return v
|
|
@@ -89,7 +87,7 @@ describe('Batch', () => {
|
|
|
89
87
|
expect(result).toBe(10)
|
|
90
88
|
|
|
91
89
|
// Batch: apply changes to all signals in a single transaction
|
|
92
|
-
|
|
90
|
+
batchSignalWrites(() => {
|
|
93
91
|
signals.forEach(signal => signal.update(v => v * 2))
|
|
94
92
|
})
|
|
95
93
|
|
package/test/benchmark.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, mock, test } from 'bun:test'
|
|
2
|
-
import {
|
|
2
|
+
import { batchSignalWrites, createEffect, Memo, State } from '../index.ts'
|
|
3
3
|
import { Counter, makeGraph, runGraph } from './util/dependency-graph'
|
|
4
4
|
import type { Computed, ReactiveFramework } from './util/reactive-framework'
|
|
5
5
|
|
|
@@ -15,20 +15,20 @@ const busy = () => {
|
|
|
15
15
|
const framework = {
|
|
16
16
|
name: 'Cause & Effect',
|
|
17
17
|
signal: <T extends {}>(initialValue: T) => {
|
|
18
|
-
const s =
|
|
18
|
+
const s = new State<T>(initialValue)
|
|
19
19
|
return {
|
|
20
20
|
write: (v: T) => s.set(v),
|
|
21
21
|
read: () => s.get(),
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
24
|
computed: <T extends {}>(fn: () => T) => {
|
|
25
|
-
const c =
|
|
25
|
+
const c = new Memo(fn)
|
|
26
26
|
return {
|
|
27
27
|
read: () => c.get(),
|
|
28
28
|
}
|
|
29
29
|
},
|
|
30
30
|
effect: (fn: () => undefined) => createEffect(fn),
|
|
31
|
-
withBatch: (fn: () => undefined) =>
|
|
31
|
+
withBatch: (fn: () => undefined) => batchSignalWrites(fn),
|
|
32
32
|
withBuild: <T>(fn: () => T) => fn(),
|
|
33
33
|
}
|
|
34
34
|
const testPullCounts = true
|