@zeix/cause-effect 0.11.0 → 0.12.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 +72 -32
- package/index.d.ts +4 -3
- package/index.js +1 -1
- package/index.ts +9 -6
- package/lib/computed.d.ts +11 -8
- package/lib/computed.ts +101 -56
- package/lib/effect.d.ts +4 -10
- package/lib/effect.ts +12 -52
- package/lib/scheduler.d.ts +40 -0
- package/lib/scheduler.ts +127 -0
- package/lib/signal.d.ts +31 -27
- package/lib/signal.ts +83 -72
- package/lib/state.d.ts +11 -48
- package/lib/state.ts +88 -68
- package/lib/util.d.ts +2 -2
- package/lib/util.ts +4 -4
- package/package.json +3 -2
- package/test/batch.test.ts +99 -0
- package/test/benchmark.test.ts +124 -49
- package/test/computed.test.ts +329 -0
- package/test/effect.test.ts +157 -0
- package/test/state.test.ts +215 -0
- package/test/util/dependency-graph.ts +95 -37
- package/test/util/framework-types.ts +53 -0
- package/test/util/perf-tests.ts +42 -0
- package/test/util/reactive-framework.ts +22 -0
- package/test/cause-effect.test.ts +0 -597
- package/test/util/pseudo-random.ts +0 -45
package/lib/scheduler.ts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/* === Types === */
|
|
2
|
+
|
|
3
|
+
export type EnqueueDedupe = [Element, string]
|
|
4
|
+
|
|
5
|
+
export type Watcher = () => void
|
|
6
|
+
export type Updater = <T>() => T
|
|
7
|
+
|
|
8
|
+
/* === Internal === */
|
|
9
|
+
|
|
10
|
+
// Currently active watcher
|
|
11
|
+
let active: Watcher | undefined
|
|
12
|
+
|
|
13
|
+
// Pending queue for batched change notifications
|
|
14
|
+
const pending = new Set<Watcher>()
|
|
15
|
+
let batchDepth = 0
|
|
16
|
+
|
|
17
|
+
// Map of DOM elements to update functions
|
|
18
|
+
const updateMap = new Map<Element, Map<string, () => void>>()
|
|
19
|
+
let requestId: number | undefined
|
|
20
|
+
|
|
21
|
+
const updateDOM = () => {
|
|
22
|
+
requestId = undefined
|
|
23
|
+
for (const elementMap of updateMap.values()) {
|
|
24
|
+
for (const fn of elementMap.values()) {
|
|
25
|
+
fn()
|
|
26
|
+
}
|
|
27
|
+
elementMap.clear()
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const requestTick = () => {
|
|
32
|
+
if (requestId) cancelAnimationFrame(requestId)
|
|
33
|
+
requestId = requestAnimationFrame(updateDOM)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Initial render when the call stack is empty
|
|
37
|
+
queueMicrotask(updateDOM)
|
|
38
|
+
|
|
39
|
+
/* === Exported Functions === */
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Add active watcher to the array of watchers
|
|
43
|
+
*
|
|
44
|
+
* @param {Watcher[]} watchers - watchers of the signal
|
|
45
|
+
*/
|
|
46
|
+
export const subscribe = (watchers: Watcher[]) => {
|
|
47
|
+
// if (!active) console.warn('Calling .get() outside of a reactive context')
|
|
48
|
+
if (active && !watchers.includes(active)) {
|
|
49
|
+
watchers.push(active)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Add watchers to the pending set of change notifications
|
|
55
|
+
*
|
|
56
|
+
* @param {Watcher[]} watchers - watchers of the signal
|
|
57
|
+
*/
|
|
58
|
+
export const notify = (watchers: Watcher[]) => {
|
|
59
|
+
for (const mark of watchers) {
|
|
60
|
+
batchDepth ? pending.add(mark) : mark()
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Flush all pending changes to notify watchers
|
|
66
|
+
*/
|
|
67
|
+
export const flush = () => {
|
|
68
|
+
while (pending.size) {
|
|
69
|
+
const watchers = Array.from(pending)
|
|
70
|
+
pending.clear()
|
|
71
|
+
for (const mark of watchers) {
|
|
72
|
+
mark()
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Batch multiple changes in a single signal graph and DOM update cycle
|
|
79
|
+
*
|
|
80
|
+
* @param {() => void} fn - function with multiple signal writes to be batched
|
|
81
|
+
*/
|
|
82
|
+
export const batch = (fn: () => void) => {
|
|
83
|
+
batchDepth++
|
|
84
|
+
fn()
|
|
85
|
+
flush()
|
|
86
|
+
batchDepth--
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Run a function in a reactive context
|
|
91
|
+
*
|
|
92
|
+
* @param {() => void} run - function to run the computation or effect
|
|
93
|
+
* @param {Watcher} mark - function to be called when the state changes
|
|
94
|
+
*/
|
|
95
|
+
export const watch = (run: () => void, mark: Watcher): void => {
|
|
96
|
+
const prev = active
|
|
97
|
+
active = mark
|
|
98
|
+
run()
|
|
99
|
+
active = prev
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Enqueue a function to be executed on the next animation frame
|
|
104
|
+
*
|
|
105
|
+
* @param callback
|
|
106
|
+
* @param dedupe
|
|
107
|
+
* @returns
|
|
108
|
+
*/
|
|
109
|
+
export const enqueue = <T>(
|
|
110
|
+
update: Updater,
|
|
111
|
+
dedupe?: EnqueueDedupe
|
|
112
|
+
) => new Promise<T>((resolve, reject) => {
|
|
113
|
+
const wrappedCallback = () => {
|
|
114
|
+
try {
|
|
115
|
+
resolve(update())
|
|
116
|
+
} catch (error) {
|
|
117
|
+
reject(error)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (dedupe) {
|
|
121
|
+
const [el, op] = dedupe
|
|
122
|
+
if (!updateMap.has(el)) updateMap.set(el, new Map())
|
|
123
|
+
const elementMap = updateMap.get(el)!
|
|
124
|
+
elementMap.set(op, wrappedCallback)
|
|
125
|
+
}
|
|
126
|
+
requestTick()
|
|
127
|
+
})
|
package/lib/signal.d.ts
CHANGED
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
import { type State } from "./state";
|
|
2
2
|
import { type Computed } from "./computed";
|
|
3
3
|
type Signal<T extends {}> = State<T> | Computed<T>;
|
|
4
|
-
type MaybeSignal<T extends {}> =
|
|
5
|
-
type
|
|
6
|
-
|
|
4
|
+
type MaybeSignal<T extends {}> = Signal<T> | T | (() => T | Promise<T>);
|
|
5
|
+
type InferMaybeSignalType<T> = T extends Signal<infer U> ? U : T extends (() => infer U) ? U : T;
|
|
6
|
+
type OkCallback<T, U extends MaybeSignal<{}>[]> = (...values: {
|
|
7
|
+
[K in keyof U]: InferMaybeSignalType<U[K]>;
|
|
8
|
+
}) => T | Promise<T> | Error;
|
|
9
|
+
type NilCallback<T> = () => T | Promise<T> | Error;
|
|
10
|
+
type ErrCallback<T> = (...errors: Error[]) => T | Promise<T> | Error;
|
|
11
|
+
type ComputedCallbacks<T extends {}, U extends MaybeSignal<{}>[]> = OkCallback<T, U> | {
|
|
12
|
+
ok: OkCallback<T, U>;
|
|
13
|
+
nil?: NilCallback<T>;
|
|
14
|
+
err?: ErrCallback<T>;
|
|
15
|
+
};
|
|
16
|
+
type EffectCallbacks<U extends MaybeSignal<{}>[]> = OkCallback<void, U> | {
|
|
17
|
+
ok: OkCallback<void, U>;
|
|
18
|
+
nil?: NilCallback<void>;
|
|
19
|
+
err?: ErrCallback<void>;
|
|
20
|
+
};
|
|
21
|
+
type CallbackReturnType<T> = T | Promise<T> | Error | void;
|
|
22
|
+
declare const UNSET: any;
|
|
7
23
|
/**
|
|
8
24
|
* Check whether a value is a Signal or not
|
|
9
25
|
*
|
|
@@ -20,30 +36,18 @@ declare const isSignal: <T extends {}>(value: any) => value is Signal<T>;
|
|
|
20
36
|
* @param memo
|
|
21
37
|
* @returns {Signal<T>} - converted Signal
|
|
22
38
|
*/
|
|
23
|
-
declare const toSignal: <T extends {}>(value: MaybeSignal<T>) => Signal<T>;
|
|
39
|
+
declare const toSignal: <T extends {}>(value: MaybeSignal<T> | ComputedCallbacks<T, []>) => Signal<T>;
|
|
24
40
|
/**
|
|
25
|
-
*
|
|
41
|
+
* Resolve signals or functions using signals and apply callbacks based on the results
|
|
26
42
|
*
|
|
27
|
-
* @
|
|
43
|
+
* @since 0.12.0
|
|
44
|
+
* @param {U} maybeSignals - dependency signals (or functions using signals)
|
|
45
|
+
* @param {Record<string, (...args) => CallbackReturnType<T>} cb - object of ok, nil, err callbacks or just ok callback
|
|
46
|
+
* @returns {CallbackReturnType<T>} - result of chosen callback
|
|
28
47
|
*/
|
|
29
|
-
declare const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
declare const notify: (watchers: Watcher[]) => void;
|
|
36
|
-
/**
|
|
37
|
-
* Run a function in a reactive context
|
|
38
|
-
*
|
|
39
|
-
* @param {() => void} run - function to run the computation or effect
|
|
40
|
-
* @param {Watcher} mark - function to be called when the state changes
|
|
41
|
-
*/
|
|
42
|
-
declare const watch: (run: () => void, mark: Watcher) => void;
|
|
43
|
-
/**
|
|
44
|
-
* Batch multiple state changes into a single update
|
|
45
|
-
*
|
|
46
|
-
* @param {() => void} run - function to run the batch of state changes
|
|
47
|
-
*/
|
|
48
|
-
declare const batch: (run: () => void) => void;
|
|
49
|
-
export { type Signal, type MaybeSignal, type Watcher, isSignal, toSignal, subscribe, notify, watch, batch };
|
|
48
|
+
declare const resolve: <T, U extends MaybeSignal<{}>[]>(maybeSignals: U, cb: OkCallback<T | Promise<T>, U> | {
|
|
49
|
+
ok: OkCallback<T | Promise<T>, U>;
|
|
50
|
+
nil?: NilCallback<T>;
|
|
51
|
+
err?: ErrCallback<T>;
|
|
52
|
+
}) => CallbackReturnType<T>;
|
|
53
|
+
export { type Signal, type MaybeSignal, type InferMaybeSignalType, type EffectCallbacks, type ComputedCallbacks, type CallbackReturnType, UNSET, isSignal, toSignal, resolve, };
|
package/lib/signal.ts
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
1
|
import { type State, isState, state } from "./state"
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { type Computed, computed, isComputed } from "./computed"
|
|
3
|
+
import { isFunction, toError } from "./util"
|
|
4
4
|
|
|
5
5
|
/* === Types === */
|
|
6
6
|
|
|
7
7
|
type Signal<T extends {}> = State<T> | Computed<T>
|
|
8
|
+
type MaybeSignal<T extends {}> = Signal<T> | T | (() => T | Promise<T>)
|
|
9
|
+
type InferMaybeSignalType<T> = T extends Signal<infer U> ? U :
|
|
10
|
+
T extends (() => infer U) ? U :
|
|
11
|
+
T
|
|
12
|
+
|
|
13
|
+
type OkCallback<T, U extends MaybeSignal<{}>[]> = (...values: {
|
|
14
|
+
[K in keyof U]: InferMaybeSignalType<U[K]>
|
|
15
|
+
}) => T | Promise<T> | Error
|
|
16
|
+
type NilCallback<T> = () => T | Promise<T> | Error
|
|
17
|
+
type ErrCallback<T> = (...errors: Error[]) => T | Promise<T> | Error
|
|
18
|
+
|
|
19
|
+
type ComputedCallbacks<T extends {}, U extends MaybeSignal<{}>[]> = OkCallback<T, U> | {
|
|
20
|
+
ok: OkCallback<T, U>,
|
|
21
|
+
nil?: NilCallback<T>,
|
|
22
|
+
err?: ErrCallback<T>
|
|
23
|
+
}
|
|
8
24
|
|
|
9
|
-
type
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
// Currently active watcher
|
|
16
|
-
let active: () => void | undefined
|
|
17
|
-
|
|
18
|
-
// Batching state
|
|
19
|
-
let batchDepth = 0
|
|
25
|
+
type EffectCallbacks<U extends MaybeSignal<{}>[]> = OkCallback<void, U> | {
|
|
26
|
+
ok: OkCallback<void, U>,
|
|
27
|
+
nil?: NilCallback<void>,
|
|
28
|
+
err?: ErrCallback<void>
|
|
29
|
+
}
|
|
20
30
|
|
|
21
|
-
|
|
22
|
-
const markQueue: Set<Watcher> = new Set()
|
|
31
|
+
type CallbackReturnType<T> = T | Promise<T> | Error | void
|
|
23
32
|
|
|
24
|
-
|
|
25
|
-
const runQueue: Set<() => void> = new Set()
|
|
33
|
+
/* === Constants === */
|
|
26
34
|
|
|
27
|
-
|
|
28
|
-
* Flush pending notifications and runs
|
|
29
|
-
*/
|
|
30
|
-
const flush = () => {
|
|
31
|
-
while (markQueue.size || runQueue.size) {
|
|
32
|
-
markQueue.forEach(mark => mark())
|
|
33
|
-
markQueue.clear()
|
|
34
|
-
runQueue.forEach(run => run())
|
|
35
|
-
runQueue.clear()
|
|
36
|
-
}
|
|
37
|
-
}
|
|
35
|
+
const UNSET: any = Symbol()
|
|
38
36
|
|
|
39
|
-
/* ===
|
|
37
|
+
/* === Private Functions === */
|
|
40
38
|
|
|
41
|
-
|
|
39
|
+
const isComputedCallbacks = /*#__PURE__*/ <T extends {}>(value: unknown): value is ComputedCallbacks<T, []> =>
|
|
40
|
+
(isFunction(value) && !value.length)
|
|
41
|
+
|| (typeof value === 'object' && value !== null && 'ok' in value && isFunction(value.ok))
|
|
42
42
|
|
|
43
43
|
/* === Exported Functions === */
|
|
44
44
|
|
|
@@ -61,56 +61,67 @@ const isSignal = /*#__PURE__*/ <T extends {}>(value: any): value is Signal<T> =>
|
|
|
61
61
|
* @returns {Signal<T>} - converted Signal
|
|
62
62
|
*/
|
|
63
63
|
const toSignal = /*#__PURE__*/ <T extends {}>(
|
|
64
|
-
value: MaybeSignal<T>
|
|
64
|
+
value: MaybeSignal<T> | ComputedCallbacks<T, []>
|
|
65
65
|
): Signal<T> =>
|
|
66
66
|
isSignal<T>(value) ? value
|
|
67
|
-
:
|
|
68
|
-
: state(value)
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Add notify function of active watchers to the set of watchers
|
|
72
|
-
*
|
|
73
|
-
* @param {Watcher[]} watchers - set of current watchers
|
|
74
|
-
*/
|
|
75
|
-
const subscribe = (watchers: Watcher[]) => {
|
|
76
|
-
if (active && !watchers.includes(active)) watchers.push(active)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Notify all subscribers of the state change or add to the pending set if batching is enabled
|
|
81
|
-
*
|
|
82
|
-
* @param {Watcher[]} watchers
|
|
83
|
-
*/
|
|
84
|
-
const notify = (watchers: Watcher[]) => {
|
|
85
|
-
watchers.forEach(mark => batchDepth ? markQueue.add(mark) : mark())
|
|
86
|
-
}
|
|
67
|
+
: isComputedCallbacks<T>(value) ? computed(value)
|
|
68
|
+
: state(value as T)
|
|
87
69
|
|
|
88
|
-
/**
|
|
89
|
-
* Run a function in a reactive context
|
|
90
|
-
*
|
|
91
|
-
* @param {() => void} run - function to run the computation or effect
|
|
92
|
-
* @param {Watcher} mark - function to be called when the state changes
|
|
93
|
-
*/
|
|
94
|
-
const watch = (run: () => void, mark: Watcher): void => {
|
|
95
|
-
const prev = active
|
|
96
|
-
active = mark
|
|
97
|
-
run()
|
|
98
|
-
active = prev
|
|
99
|
-
}
|
|
100
70
|
|
|
101
71
|
/**
|
|
102
|
-
*
|
|
72
|
+
* Resolve signals or functions using signals and apply callbacks based on the results
|
|
103
73
|
*
|
|
104
|
-
* @
|
|
74
|
+
* @since 0.12.0
|
|
75
|
+
* @param {U} maybeSignals - dependency signals (or functions using signals)
|
|
76
|
+
* @param {Record<string, (...args) => CallbackReturnType<T>} cb - object of ok, nil, err callbacks or just ok callback
|
|
77
|
+
* @returns {CallbackReturnType<T>} - result of chosen callback
|
|
105
78
|
*/
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
79
|
+
const resolve = <T, U extends MaybeSignal<{}>[]>(
|
|
80
|
+
maybeSignals: U,
|
|
81
|
+
cb: OkCallback<T | Promise<T>, U> | {
|
|
82
|
+
ok: OkCallback<T | Promise<T>, U>
|
|
83
|
+
nil?: NilCallback<T>
|
|
84
|
+
err?: ErrCallback<T>
|
|
85
|
+
}
|
|
86
|
+
): CallbackReturnType<T> => {
|
|
87
|
+
const { ok, nil, err } = isFunction(cb)
|
|
88
|
+
? { ok: cb }
|
|
89
|
+
: cb as {
|
|
90
|
+
ok: OkCallback<T | Promise<T>, U>
|
|
91
|
+
nil?: NilCallback<T>
|
|
92
|
+
err?: ErrCallback<T>
|
|
93
|
+
}
|
|
94
|
+
const values = [] as {
|
|
95
|
+
[K in keyof U]: InferMaybeSignalType<U[K]>
|
|
96
|
+
}
|
|
97
|
+
const errors: Error[] = []
|
|
98
|
+
let hasUnset = false
|
|
99
|
+
|
|
100
|
+
for (let i = 0; i < maybeSignals.length; i++) {
|
|
101
|
+
const s = maybeSignals[i]
|
|
102
|
+
try {
|
|
103
|
+
const value = isSignal(s) ? s.get() : isFunction(s) ? s() : s
|
|
104
|
+
if (value === UNSET) hasUnset = true
|
|
105
|
+
values[i] = value as InferMaybeSignalType<typeof s>
|
|
106
|
+
} catch (e) {
|
|
107
|
+
errors.push(toError(e))
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let result: CallbackReturnType<T> = undefined
|
|
112
|
+
try {
|
|
113
|
+
if (hasUnset && nil) result = nil()
|
|
114
|
+
else if (errors.length) result = err ? err(...errors) : errors[0]
|
|
115
|
+
else if (!hasUnset) result = ok(...values) as CallbackReturnType<T>
|
|
116
|
+
} catch (e) {
|
|
117
|
+
result = toError(e)
|
|
118
|
+
if (err) result = err(result)
|
|
119
|
+
}
|
|
120
|
+
return result
|
|
111
121
|
}
|
|
112
122
|
|
|
113
123
|
export {
|
|
114
|
-
type Signal, type MaybeSignal, type
|
|
115
|
-
|
|
124
|
+
type Signal, type MaybeSignal, type InferMaybeSignalType,
|
|
125
|
+
type EffectCallbacks, type ComputedCallbacks, type CallbackReturnType,
|
|
126
|
+
UNSET, isSignal, toSignal, resolve,
|
|
116
127
|
}
|
package/lib/state.d.ts
CHANGED
|
@@ -1,58 +1,21 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* @since 0.9.0
|
|
6
|
-
* @class State
|
|
7
|
-
*/
|
|
8
|
-
export declare class State<T extends {}> {
|
|
9
|
-
private value;
|
|
10
|
-
private watchers;
|
|
11
|
-
constructor(value: T);
|
|
12
|
-
/**
|
|
13
|
-
* Get the current value of the state
|
|
14
|
-
*
|
|
15
|
-
* @since 0.9.0
|
|
16
|
-
* @method of State<T>
|
|
17
|
-
* @returns {T} - current value of the state
|
|
18
|
-
*/
|
|
1
|
+
import { type ComputedCallbacks, type EffectCallbacks } from './signal';
|
|
2
|
+
import { type Computed } from './computed';
|
|
3
|
+
export type State<T extends {}> = {
|
|
4
|
+
[Symbol.toStringTag]: 'State';
|
|
19
5
|
get(): T;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
* @param {T} value
|
|
26
|
-
* @returns {void}
|
|
27
|
-
*/
|
|
28
|
-
set(value: T): void;
|
|
29
|
-
/**
|
|
30
|
-
* Update the state with a new value using a function
|
|
31
|
-
*
|
|
32
|
-
* @since 0.10.0
|
|
33
|
-
* @method of State<T>
|
|
34
|
-
* @param {(value: T) => T} fn
|
|
35
|
-
* @returns {void} - updates the state with the result of the function
|
|
36
|
-
*/
|
|
37
|
-
update(fn: (value: T) => T): void;
|
|
38
|
-
/**
|
|
39
|
-
* Create a derived state from an existing state
|
|
40
|
-
*
|
|
41
|
-
* @since 0.9.0
|
|
42
|
-
* @method of State<T>
|
|
43
|
-
* @param {(value: T) => U} fn
|
|
44
|
-
* @returns {Computed<U>} - derived state
|
|
45
|
-
*/
|
|
46
|
-
map<U extends {}>(fn: (value: T) => U): Computed<U>;
|
|
47
|
-
}
|
|
6
|
+
set(v: T): void;
|
|
7
|
+
update(fn: (v: T) => T): void;
|
|
8
|
+
map<U extends {}>(cb: ComputedCallbacks<U, [State<T>]>): Computed<U>;
|
|
9
|
+
match: (cb: EffectCallbacks<[State<T>]>) => void;
|
|
10
|
+
};
|
|
48
11
|
/**
|
|
49
12
|
* Create a new state signal
|
|
50
13
|
*
|
|
51
14
|
* @since 0.9.0
|
|
52
|
-
* @param {T}
|
|
15
|
+
* @param {T} initialValue - initial value of the state
|
|
53
16
|
* @returns {State<T>} - new state signal
|
|
54
17
|
*/
|
|
55
|
-
export declare const state: <T extends {}>(
|
|
18
|
+
export declare const state: <T extends {}>(initialValue: T) => State<T>;
|
|
56
19
|
/**
|
|
57
20
|
* Check if the provided value is a State instance
|
|
58
21
|
*
|
package/lib/state.ts
CHANGED
|
@@ -1,84 +1,104 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import { type Computed, computed } from
|
|
1
|
+
import { UNSET, type ComputedCallbacks, type EffectCallbacks } from './signal'
|
|
2
|
+
import { type Computed, computed } from './computed'
|
|
3
|
+
import { isObjectOfType } from './util';
|
|
4
|
+
import { type Watcher, notify, subscribe } from './scheduler'
|
|
5
|
+
import { effect } from './effect';
|
|
3
6
|
|
|
4
|
-
/* ===
|
|
7
|
+
/* === Types === */
|
|
8
|
+
|
|
9
|
+
export type State<T extends {}> = {
|
|
10
|
+
[Symbol.toStringTag]: 'State';
|
|
11
|
+
get(): T;
|
|
12
|
+
set(v: T): void;
|
|
13
|
+
update(fn: (v: T) => T): void;
|
|
14
|
+
map<U extends {}>(cb: ComputedCallbacks<U, [State<T>]>): Computed<U>;
|
|
15
|
+
match: (cb: EffectCallbacks<[State<T>]>) => void
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/* === Constants === */
|
|
19
|
+
|
|
20
|
+
const TYPE_STATE = 'State'
|
|
21
|
+
|
|
22
|
+
/* === State Factory === */
|
|
5
23
|
|
|
6
24
|
/**
|
|
7
|
-
*
|
|
25
|
+
* Create a new state signal
|
|
8
26
|
*
|
|
9
27
|
* @since 0.9.0
|
|
10
|
-
* @
|
|
28
|
+
* @param {T} initialValue - initial value of the state
|
|
29
|
+
* @returns {State<T>} - new state signal
|
|
11
30
|
*/
|
|
12
|
-
export
|
|
13
|
-
|
|
31
|
+
export const state = /*#__PURE__*/ <T extends {}>(initialValue: T): State<T> => {
|
|
32
|
+
const watchers: Watcher[] = []
|
|
33
|
+
let value: T = initialValue
|
|
14
34
|
|
|
15
|
-
|
|
35
|
+
const s: State<T> = {
|
|
36
|
+
[Symbol.toStringTag]: TYPE_STATE,
|
|
16
37
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
38
|
+
/**
|
|
39
|
+
* Get the current value of the state
|
|
40
|
+
*
|
|
41
|
+
* @since 0.9.0
|
|
42
|
+
* @returns {T} - current value of the state
|
|
43
|
+
*/
|
|
44
|
+
get: (): T => {
|
|
45
|
+
subscribe(watchers)
|
|
46
|
+
return value
|
|
47
|
+
},
|
|
28
48
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
notify(this.watchers)
|
|
49
|
+
/**
|
|
50
|
+
* Set a new value of the state
|
|
51
|
+
*
|
|
52
|
+
* @since 0.9.0
|
|
53
|
+
* @param {T} v
|
|
54
|
+
* @returns {void}
|
|
55
|
+
*/
|
|
56
|
+
set: (v: T): void => {
|
|
57
|
+
if (Object.is(value, v)) return
|
|
58
|
+
value = v
|
|
59
|
+
notify(watchers)
|
|
41
60
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
61
|
+
// Setting to UNSET clears the watchers so the signal can be garbage collected
|
|
62
|
+
if (UNSET === value) watchers.length = 0 // head = tail = undefined
|
|
63
|
+
},
|
|
45
64
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
65
|
+
/**
|
|
66
|
+
* Update the state with a new value using a function
|
|
67
|
+
*
|
|
68
|
+
* @since 0.10.0
|
|
69
|
+
* @param {(v: T) => T} fn - function to update the state
|
|
70
|
+
* @returns {void} - updates the state with the result of the function
|
|
71
|
+
*/
|
|
72
|
+
update: (fn: (v: T) => T): void => {
|
|
73
|
+
s.set(fn(value))
|
|
74
|
+
},
|
|
57
75
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return computed<U>(() => fn(this.get()))
|
|
68
|
-
}
|
|
69
|
-
}
|
|
76
|
+
/**
|
|
77
|
+
* Create a computed signal from the current state signal
|
|
78
|
+
*
|
|
79
|
+
* @since 0.9.0
|
|
80
|
+
* @param {ComputedCallbacks<U, [State<T>]>} cb - compute callback or object of ok, nil, err callbacks to map this value to new computed
|
|
81
|
+
* @returns {Computed<U>} - computed signal
|
|
82
|
+
*/
|
|
83
|
+
map: <U extends {}>(cb: ComputedCallbacks<U, [State<T>]>): Computed<U> =>
|
|
84
|
+
computed(cb, s),
|
|
70
85
|
|
|
71
|
-
|
|
86
|
+
/**
|
|
87
|
+
* Case matching for the state signal with effect callbacks
|
|
88
|
+
*
|
|
89
|
+
* @since 0.12.0
|
|
90
|
+
* @method of State<T>
|
|
91
|
+
* @param {EffectCallbacks<[State<T>]>} cb - effect callback or object of ok, nil, err callbacks to be executed when the state changes
|
|
92
|
+
* @returns {State<T>} - self, for chaining effect callbacks
|
|
93
|
+
*/
|
|
94
|
+
match: (cb: EffectCallbacks<[State<T>]>): State<T> => {
|
|
95
|
+
effect(cb, s)
|
|
96
|
+
return s
|
|
97
|
+
}
|
|
98
|
+
}
|
|
72
99
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
*
|
|
76
|
-
* @since 0.9.0
|
|
77
|
-
* @param {T} value - initial value of the state
|
|
78
|
-
* @returns {State<T>} - new state signal
|
|
79
|
-
*/
|
|
80
|
-
export const state = /*#__PURE__*/ <T extends {}>(value: T): State<T> =>
|
|
81
|
-
new State(value)
|
|
100
|
+
return s
|
|
101
|
+
}
|
|
82
102
|
|
|
83
103
|
/**
|
|
84
104
|
* Check if the provided value is a State instance
|
|
@@ -88,4 +108,4 @@ export const state = /*#__PURE__*/ <T extends {}>(value: T): State<T> =>
|
|
|
88
108
|
* @returns {boolean} - true if the value is a State instance, false otherwise
|
|
89
109
|
*/
|
|
90
110
|
export const isState = /*#__PURE__*/ <T extends {}>(value: unknown): value is State<T> =>
|
|
91
|
-
value
|
|
111
|
+
isObjectOfType(value, TYPE_STATE)
|
package/lib/util.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
declare const isFunction: <T>(value: unknown) => value is (...args: unknown[]) => T;
|
|
2
2
|
declare const isAsyncFunction: <T>(value: unknown) => value is (...args: unknown[]) => Promise<T> | PromiseLike<T>;
|
|
3
|
-
declare const
|
|
3
|
+
declare const isObjectOfType: <T>(value: unknown, type: string) => value is T;
|
|
4
4
|
declare const isInstanceOf: <T>(type: new (...args: any[]) => T) => (value: unknown) => value is T;
|
|
5
5
|
declare const isError: (value: unknown) => value is Error;
|
|
6
6
|
declare const isPromise: (value: unknown) => value is Promise<unknown>;
|
|
7
7
|
declare const toError: (value: unknown) => Error;
|
|
8
|
-
export { isFunction, isAsyncFunction,
|
|
8
|
+
export { isFunction, isAsyncFunction, isObjectOfType, isInstanceOf, isError, isPromise, toError };
|
package/lib/util.ts
CHANGED
|
@@ -6,8 +6,8 @@ const isFunction = /*#__PURE__*/ <T>(value: unknown): value is (...args: unknown
|
|
|
6
6
|
const isAsyncFunction = /*#__PURE__*/ <T>(value: unknown): value is (...args: unknown[]) => Promise<T> | PromiseLike<T> =>
|
|
7
7
|
isFunction(value) && /^async\s+/.test(value.toString())
|
|
8
8
|
|
|
9
|
-
const
|
|
10
|
-
|
|
9
|
+
const isObjectOfType = /*#__PURE__*/ <T>(value: unknown, type: string): value is T =>
|
|
10
|
+
Object.prototype.toString.call(value) === `[object ${type}]`
|
|
11
11
|
|
|
12
12
|
const isInstanceOf = /*#__PURE__*/ <T>(type: new (...args: any[]) => T) =>
|
|
13
13
|
(value: unknown): value is T =>
|
|
@@ -20,6 +20,6 @@ const toError = (value: unknown): Error =>
|
|
|
20
20
|
isError(value) ? value : new Error(String(value))
|
|
21
21
|
|
|
22
22
|
export {
|
|
23
|
-
isFunction, isAsyncFunction,
|
|
24
|
-
isInstanceOf, isError, isPromise, toError
|
|
23
|
+
isFunction, isAsyncFunction,
|
|
24
|
+
isObjectOfType, isInstanceOf, isError, isPromise, toError
|
|
25
25
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeix/cause-effect",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"author": "Esther Brunner",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"module": "index.ts",
|
|
7
7
|
"devDependencies": {
|
|
8
|
-
"@types/bun": "latest"
|
|
8
|
+
"@types/bun": "latest",
|
|
9
|
+
"random": "^5.3.0"
|
|
9
10
|
},
|
|
10
11
|
"peerDependencies": {
|
|
11
12
|
"typescript": "^5.6.3"
|