@alwatr/signal 6.2.0 → 9.1.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/dist/core/computed-signal.d.ts +2 -1
- package/dist/core/computed-signal.d.ts.map +1 -1
- package/dist/core/effect-signal.d.ts +2 -1
- package/dist/core/effect-signal.d.ts.map +1 -1
- package/dist/core/event-signal.d.ts +2 -1
- package/dist/core/event-signal.d.ts.map +1 -1
- package/dist/core/signal-base.d.ts.map +1 -1
- package/dist/core/state-signal.d.ts +2 -1
- package/dist/core/state-signal.d.ts.map +1 -1
- package/dist/main.js +5 -0
- package/dist/main.js.map +25 -0
- package/dist/operators/debounce.d.ts.map +1 -1
- package/dist/type.d.ts.map +1 -1
- package/package.json +48 -53
- package/src/core/computed-signal.ts +216 -0
- package/src/core/effect-signal.ts +176 -0
- package/src/core/event-signal.ts +61 -0
- package/src/core/persistent-state-signal.ts +98 -0
- package/src/core/session-state-signal.ts +145 -0
- package/src/core/signal-base.ts +191 -0
- package/src/core/state-signal.ts +178 -0
- package/src/creators/computed.ts +37 -0
- package/src/creators/effect.ts +46 -0
- package/src/creators/event.ts +31 -0
- package/src/creators/persistent-state.ts +33 -0
- package/src/creators/session-state.ts +42 -0
- package/src/creators/state.ts +28 -0
- package/src/main.ts +20 -0
- package/src/operators/debounce.ts +91 -0
- package/src/operators/filter.ts +78 -0
- package/src/operators/map.ts +48 -0
- package/src/type.ts +357 -0
- package/CHANGELOG.md +0 -908
- package/dist/main.cjs +0 -4
- package/dist/main.cjs.map +0 -7
- package/dist/main.mjs +0 -4
- package/dist/main.mjs.map +0 -7
- package/src/core/computed-signal.test.js +0 -166
- package/src/core/effect-signal.test.js +0 -150
- package/src/core/event-signal.test.js +0 -210
- package/src/core/state-signal.test.js +0 -251
- package/src/operators/debounce.test.js +0 -206
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import {createDebouncer} from '@alwatr/debounce';
|
|
2
|
+
|
|
3
|
+
import {StateSignal} from '../core/state-signal.js';
|
|
4
|
+
import {createComputedSignal} from '../creators/computed.js';
|
|
5
|
+
|
|
6
|
+
import type {ComputedSignal} from '../core/computed-signal.js';
|
|
7
|
+
import type {IReadonlySignal, DebounceSignalConfig} from '../type.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Creates a new computed signal that debounces updates from a source signal.
|
|
11
|
+
*
|
|
12
|
+
* The returned signal is a `ComputedSignal`, meaning it is read-only and its value is
|
|
13
|
+
* derived from the source. It only updates its value after a specified period of
|
|
14
|
+
* inactivity from the source signal.
|
|
15
|
+
*
|
|
16
|
+
* This operator is essential for handling high-frequency events, such as user input
|
|
17
|
+
* in a search box, resizing a window, or any other event that fires rapidly.
|
|
18
|
+
* By debouncing, you can ensure that expensive operations (like API calls or heavy
|
|
19
|
+
* computations) are only executed once the events have settled.
|
|
20
|
+
*
|
|
21
|
+
* @template T The type of the signal's value.
|
|
22
|
+
*
|
|
23
|
+
* @param {IReadonlySignal<T>} sourceSignal The original signal to debounce.
|
|
24
|
+
* It can be a `StateSignal`, `ComputedSignal`, or any signal implementing `IReadonlySignal`.
|
|
25
|
+
* @param {DebounceSignalConfig} config Configuration object for the debouncer,
|
|
26
|
+
* including `delay`, `leading`, and `trailing` options from `@alwatr/debounce`.
|
|
27
|
+
*
|
|
28
|
+
* @returns {IComputedSignal<T>} A new, read-only computed signal that emits debounced values.
|
|
29
|
+
* Crucially, you **must** call `.destroy()` on this signal when it's no longer
|
|
30
|
+
* needed to prevent memory leaks by cleaning up internal subscriptions and timers.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* // Create a source signal for user input.
|
|
35
|
+
* const searchInput = createStateSignal({
|
|
36
|
+
* name: 'search-input',
|
|
37
|
+
* initialValue: '',
|
|
38
|
+
* });
|
|
39
|
+
*
|
|
40
|
+
* // Create a debounced signal that waits 300ms after the user stops typing.
|
|
41
|
+
* const debouncedSearch = createDebouncedSignal(searchInput, { delay: 300 });
|
|
42
|
+
*
|
|
43
|
+
* // Use an effect to react to the debounced value.
|
|
44
|
+
* createEffect({
|
|
45
|
+
* deps: [debouncedSearch],
|
|
46
|
+
* run: () => {
|
|
47
|
+
* if (debouncedSearch.get()) {
|
|
48
|
+
* console.log(`🚀 Sending API request for: "${debouncedSearch.get()}"`);
|
|
49
|
+
* }
|
|
50
|
+
* },
|
|
51
|
+
* });
|
|
52
|
+
*
|
|
53
|
+
* searchInput.set('Alwatr');
|
|
54
|
+
* searchInput.set('Alwatr Signal');
|
|
55
|
+
* // (after 300ms of inactivity)
|
|
56
|
+
* // Logs: "🚀 Sending API request for: "Alwatr Signal""
|
|
57
|
+
*
|
|
58
|
+
* // IMPORTANT: Clean up when the component unmounts.
|
|
59
|
+
* // debouncedSearch.destroy();
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export function createDebouncedSignal<T>(sourceSignal: IReadonlySignal<T>, config: DebounceSignalConfig): ComputedSignal<T> {
|
|
63
|
+
const name = config.name ?? `${sourceSignal.name}-debounced`;
|
|
64
|
+
|
|
65
|
+
const internalSignal = new StateSignal<T>({
|
|
66
|
+
name: `${name}-internal`,
|
|
67
|
+
initialValue: sourceSignal.get(),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const debouncer = createDebouncer({
|
|
71
|
+
...config,
|
|
72
|
+
thisContext: internalSignal,
|
|
73
|
+
func: internalSignal.set,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const subscription = sourceSignal.subscribe(debouncer.trigger, {receivePrevious: false});
|
|
77
|
+
|
|
78
|
+
return createComputedSignal({
|
|
79
|
+
name: name,
|
|
80
|
+
deps: [internalSignal],
|
|
81
|
+
get: () => internalSignal.get(),
|
|
82
|
+
onDestroy: () => {
|
|
83
|
+
if (internalSignal.isDestroyed) return;
|
|
84
|
+
subscription.unsubscribe();
|
|
85
|
+
debouncer.cancel();
|
|
86
|
+
internalSignal.destroy();
|
|
87
|
+
config.onDestroy?.();
|
|
88
|
+
config = null as unknown as DebounceSignalConfig;
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {createComputedSignal} from '../creators/computed.js';
|
|
2
|
+
import {createStateSignal} from '../creators/state.js';
|
|
3
|
+
|
|
4
|
+
import type {ComputedSignal} from '../core/computed-signal.js';
|
|
5
|
+
import type {IReadonlySignal} from '../type.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Creates a new computed signal that only emits values from a source signal
|
|
9
|
+
* that satisfy a predicate function.
|
|
10
|
+
*
|
|
11
|
+
* This operator is analogous to `Array.prototype.filter`. It is particularly
|
|
12
|
+
* useful for creating effects or other computed signals that should only react
|
|
13
|
+
* to a specific subset of state changes.
|
|
14
|
+
*
|
|
15
|
+
* Note: The resulting signal's value will be `undefined` until the source
|
|
16
|
+
* emits a value that passes the filter.
|
|
17
|
+
*
|
|
18
|
+
* @template T The type of the signal's value.
|
|
19
|
+
*
|
|
20
|
+
* @param sourceSignal The original signal to filter.
|
|
21
|
+
* @param predicate A function that returns `true` if the value should be passed.
|
|
22
|
+
* @param name An optional, unique identifier for the new signal for debugging. default: `${sourceSignal.name}-filtered`
|
|
23
|
+
*
|
|
24
|
+
* @returns A new computed signal that emits filtered values.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* const numberSignal = createStateSignal({ name: 'number', initialValue: 0 });
|
|
28
|
+
*
|
|
29
|
+
* const evenNumberSignal = createFilteredSignal(
|
|
30
|
+
* numberSignal,
|
|
31
|
+
* (num) => num % 2 === 0,
|
|
32
|
+
* );
|
|
33
|
+
*
|
|
34
|
+
* createEffect({
|
|
35
|
+
* deps: [evenNumberSignal],
|
|
36
|
+
* run: () => {
|
|
37
|
+
* // This effect only runs for even numbers.
|
|
38
|
+
* // The value can be `undefined` on the first run if initialValue is not even.
|
|
39
|
+
* if (evenNumberSignal.get() !== undefined) {
|
|
40
|
+
* console.log(`Even number detected: ${evenNumberSignal.get()}`);
|
|
41
|
+
* }
|
|
42
|
+
* },
|
|
43
|
+
* runImmediately: true,
|
|
44
|
+
* });
|
|
45
|
+
* // Logs: "Even number detected: 0"
|
|
46
|
+
*
|
|
47
|
+
* numberSignal.set(1); // Effect does not run
|
|
48
|
+
* numberSignal.set(2); // Logs: "Even number detected: 2"
|
|
49
|
+
*/
|
|
50
|
+
export function createFilteredSignal<T>(
|
|
51
|
+
sourceSignal: IReadonlySignal<T>,
|
|
52
|
+
predicate: (value: T) => boolean,
|
|
53
|
+
name = `${sourceSignal.name}-filtered`,
|
|
54
|
+
): ComputedSignal<T | undefined> {
|
|
55
|
+
const sourceValue = sourceSignal.get();
|
|
56
|
+
const initialValue = predicate(sourceValue) ? sourceValue : undefined;
|
|
57
|
+
|
|
58
|
+
const internalSignal = createStateSignal({
|
|
59
|
+
name: `${name}-internal`,
|
|
60
|
+
initialValue,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const subscription = sourceSignal.subscribe((newValue) => {
|
|
64
|
+
if (predicate(newValue)) {
|
|
65
|
+
internalSignal.set(newValue);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return createComputedSignal({
|
|
70
|
+
name: name,
|
|
71
|
+
deps: [internalSignal],
|
|
72
|
+
get: () => internalSignal.get(),
|
|
73
|
+
onDestroy: () => {
|
|
74
|
+
subscription.unsubscribe();
|
|
75
|
+
internalSignal.destroy();
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {createComputedSignal} from '../creators/computed.js';
|
|
2
|
+
|
|
3
|
+
import type {ComputedSignal} from '../core/computed-signal.js';
|
|
4
|
+
import type {IReadonlySignal} from '../type.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Creates a new read-only computed signal that transforms the value of a source
|
|
8
|
+
* signal using a projection function.
|
|
9
|
+
*
|
|
10
|
+
* This operator is analogous to `Array.prototype.map`. It applies a function to
|
|
11
|
+
* each value emitted by the source signal and emits the result.
|
|
12
|
+
*
|
|
13
|
+
* @template T The type of the source signal's value.
|
|
14
|
+
* @template R The type of the projected value.
|
|
15
|
+
*
|
|
16
|
+
* @param sourceSignal The original signal to transform.
|
|
17
|
+
* @param projectFunction A function to apply to each value from the source signal.
|
|
18
|
+
* @param [name] An optional, unique identifier for the new signal for debugging. default: `${sourceSignal.name}-mapped`
|
|
19
|
+
*
|
|
20
|
+
* @returns A new, read-only computed signal with the transformed values.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* const userSignal = createStateSignal({
|
|
24
|
+
* name: 'user',
|
|
25
|
+
* initialValue: { name: 'John', age: 30 },
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* const userNameSignal = createMappedSignal(
|
|
29
|
+
* userSignal,
|
|
30
|
+
* (user) => user.name,
|
|
31
|
+
* );
|
|
32
|
+
*
|
|
33
|
+
* console.log(userNameSignal.get()); // Outputs: "John"
|
|
34
|
+
* // in next macro-task ...
|
|
35
|
+
* userSignal.set({ name: 'Jane', age: 32 });
|
|
36
|
+
* console.log(userNameSignal.get()); // Outputs: "Jane"
|
|
37
|
+
*/
|
|
38
|
+
export function createMappedSignal<T, R>(
|
|
39
|
+
sourceSignal: IReadonlySignal<T>,
|
|
40
|
+
projectFunction: (value: T) => R,
|
|
41
|
+
name = `${sourceSignal.name}-mapped`,
|
|
42
|
+
): ComputedSignal<R> {
|
|
43
|
+
return createComputedSignal({
|
|
44
|
+
name: name,
|
|
45
|
+
deps: [sourceSignal],
|
|
46
|
+
get: () => projectFunction(sourceSignal.get()),
|
|
47
|
+
});
|
|
48
|
+
}
|
package/src/type.ts
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import type {DebouncerConfig} from '@alwatr/debounce';
|
|
2
|
+
import type {LocalStorageProviderConfig} from '@alwatr/local-storage';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @package @alwatr/signal
|
|
6
|
+
*
|
|
7
|
+
* The callback function signature for a signal listener. It's a function that receives a value of type `T`
|
|
8
|
+
* and returns `void` or `Promise<void>`.
|
|
9
|
+
*
|
|
10
|
+
* @template T The type of the value that the signal holds or dispatches.
|
|
11
|
+
*/
|
|
12
|
+
export type ListenerCallback<T> = (value: T) => Awaitable<void>;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Options for fine-tuning the behavior of a subscription to a signal.
|
|
16
|
+
*/
|
|
17
|
+
export interface SubscribeOptions {
|
|
18
|
+
/**
|
|
19
|
+
* If `true`, the listener will be called only once and then automatically unsubscribed.
|
|
20
|
+
* This is useful for scenarios where you only need to react to the next change.
|
|
21
|
+
*
|
|
22
|
+
* @default false
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* // The listener will be removed after the first click.
|
|
26
|
+
* onUserClick.subscribe(() => console.log('User clicked!'), { once: true });
|
|
27
|
+
*/
|
|
28
|
+
once?: boolean;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* If `true`, the listener will be placed at the beginning of the notification queue and will be called before
|
|
32
|
+
* other, non-priority listeners.
|
|
33
|
+
*
|
|
34
|
+
* @default false
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* // This listener will run before the others.
|
|
38
|
+
* mySignal.subscribe(() => console.log('High-priority action'), { priority: true });
|
|
39
|
+
*/
|
|
40
|
+
priority?: boolean;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* **For `StateSignal` only.** If `true` (the default), the listener will be called immediately with the
|
|
44
|
+
* signal's current value upon subscription. Set to `false` if you only want to be notified of *future* changes.
|
|
45
|
+
*
|
|
46
|
+
* @default true
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* const counter = new StateSignal({initialValue: 10});
|
|
50
|
+
*
|
|
51
|
+
* // This will log "Current value: 10" immediately.
|
|
52
|
+
* counter.subscribe(value => console.log(`Current value: ${value}`));
|
|
53
|
+
*
|
|
54
|
+
* // This will *not* log immediately, only when the counter is next updated.
|
|
55
|
+
* counter.subscribe(value => console.log(`New value: ${value}`), { receivePrevious: false });
|
|
56
|
+
*/
|
|
57
|
+
receivePrevious?: boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The object returned from a `subscribe` call, which contains the `unsubscribe` method.
|
|
62
|
+
* This allows for easy removal of the subscription when it's no longer needed.
|
|
63
|
+
*/
|
|
64
|
+
export interface SubscribeResult {
|
|
65
|
+
/**
|
|
66
|
+
* A function that, when called, removes the listener from the signal, preventing future notifications.
|
|
67
|
+
* It's crucial to call this to avoid memory leaks when a component or listener is destroyed.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* const subscription = mySignal.subscribe(value => console.log(value));
|
|
71
|
+
* // ... later ...
|
|
72
|
+
* subscription.unsubscribe(); // The listener is now removed.
|
|
73
|
+
*/
|
|
74
|
+
unsubscribe: () => void;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Internal representation of an observer, containing the listener's callback and its subscription options.
|
|
79
|
+
* @internal
|
|
80
|
+
*/
|
|
81
|
+
export interface Observer_<T> {
|
|
82
|
+
/**
|
|
83
|
+
* The listener's callback function.
|
|
84
|
+
*/
|
|
85
|
+
callback: ListenerCallback<T>;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Subscription options for the observer.
|
|
89
|
+
*/
|
|
90
|
+
options?: SubscribeOptions;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Basic configuration for creating any signal.
|
|
95
|
+
*/
|
|
96
|
+
export interface SignalConfig {
|
|
97
|
+
/**
|
|
98
|
+
* A unique identifier for the signal. This is crucial for debugging, logging, and differentiating signals,
|
|
99
|
+
* especially in large applications.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* 'user-profile-signal'
|
|
103
|
+
* 'app-theme-signal'
|
|
104
|
+
*/
|
|
105
|
+
name: string;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* An optional callback function that will be executed when the signal's `destroy` method is called.
|
|
109
|
+
* This is useful for cleaning up additional resources used by the signal,
|
|
110
|
+
* such as subscriptions or timers created in operators.
|
|
111
|
+
*/
|
|
112
|
+
onDestroy?: () => void;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Configuration specifically for creating a `StateSignal`.
|
|
117
|
+
* @template T The type of the state held by the signal.
|
|
118
|
+
*/
|
|
119
|
+
export interface StateSignalConfig<T> extends SignalConfig {
|
|
120
|
+
/**
|
|
121
|
+
* The initial value of the `StateSignal`. A `StateSignal` must always have a value.
|
|
122
|
+
*/
|
|
123
|
+
readonly initialValue: T;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Represents a signal that can be read from but not written to.
|
|
128
|
+
* Both `StateSignal` and `ComputedSignal` implement this interface, allowing them to be used
|
|
129
|
+
* as dependencies in other signals without exposing their `set` or `dispatch` methods.
|
|
130
|
+
*
|
|
131
|
+
* @template T The type of the signal's value.
|
|
132
|
+
*/
|
|
133
|
+
export interface IReadonlySignal<T> {
|
|
134
|
+
/**
|
|
135
|
+
* The unique identifier for this signal instance. Useful for debugging.
|
|
136
|
+
*/
|
|
137
|
+
readonly name: string;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The current value of the signal.
|
|
141
|
+
*/
|
|
142
|
+
get: () => T;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Indicates whether the signal has been destroyed.
|
|
146
|
+
* A destroyed signal cannot be used and will throw an error if interacted with.
|
|
147
|
+
* @returns `true` if the signal is destroyed, `false` otherwise.
|
|
148
|
+
*/
|
|
149
|
+
readonly isDestroyed: boolean;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Subscribes a listener to this signal.
|
|
153
|
+
*
|
|
154
|
+
* @param callback The function to be called when the signal's value changes.
|
|
155
|
+
* @param options Optional settings for the subscription.
|
|
156
|
+
* @returns An object with an `unsubscribe` method for cleanup.
|
|
157
|
+
*/
|
|
158
|
+
subscribe(callback: ListenerCallback<T>, options?: SubscribeOptions): SubscribeResult;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Returns a Promise that resolves with the next value dispatched by the signal.
|
|
162
|
+
* This provides an elegant way to wait for a single, future event using `async/await`.
|
|
163
|
+
*
|
|
164
|
+
* @returns A Promise that resolves with the next dispatched value.
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* async function onButtonClick() {
|
|
168
|
+
* console.log('Waiting for the next signal...');
|
|
169
|
+
* const nextValue = await mySignal.untilNext();
|
|
170
|
+
* console.log('Signal received:', nextValue);
|
|
171
|
+
* }
|
|
172
|
+
*/
|
|
173
|
+
untilNext(): Promise<T>;
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Destroys the signal, clearing all its listeners and making it inactive.
|
|
177
|
+
*
|
|
178
|
+
* After destruction, any interaction with the signal (like `subscribe` or `untilNext`)
|
|
179
|
+
* will throw an error. This is crucial for preventing memory leaks by allowing
|
|
180
|
+
* garbage collection of the signal and its observers.
|
|
181
|
+
*/
|
|
182
|
+
destroy(): void;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* A list of `IReadonlySignal` instances that a computed or effect signal depends on.
|
|
187
|
+
* This ensures that dependencies can be read from but not modified by the dependent signal.
|
|
188
|
+
*/
|
|
189
|
+
export type DependencyList = readonly IReadonlySignal<unknown>[];
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Configuration for creating a `ComputedSignal`.
|
|
193
|
+
* @template T The type of the value computed by the signal.
|
|
194
|
+
*/
|
|
195
|
+
export interface ComputedSignalConfig<T> extends SignalConfig {
|
|
196
|
+
/**
|
|
197
|
+
* An array of dependency signals (`StateSignal` or other `ComputedSignal` instances).
|
|
198
|
+
* The `ComputedSignal` will automatically re-evaluate its value whenever any of these dependencies change.
|
|
199
|
+
*/
|
|
200
|
+
deps: DependencyList;
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* The function that computes the signal's value.
|
|
204
|
+
* It is executed once initially and then again whenever a dependency changes.
|
|
205
|
+
* This function should be pure and not have side effects.
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* // A computed signal that derives a boolean from a number.
|
|
209
|
+
* const counter = new StateSignal({initialValue: 0});
|
|
210
|
+
* const isEven = new ComputedSignal({
|
|
211
|
+
* deps: [counter],
|
|
212
|
+
* get: () => counter.get() % 2 === 0,
|
|
213
|
+
* });
|
|
214
|
+
*/
|
|
215
|
+
get: () => T;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Configuration for creating an `EffectSignal`.
|
|
220
|
+
*/
|
|
221
|
+
export interface EffectSignalConfig {
|
|
222
|
+
/**
|
|
223
|
+
* A unique identifier for the signal. This is crucial for debugging, logging, and differentiating signals,
|
|
224
|
+
* especially in large applications.
|
|
225
|
+
* @default auto-generated based on dependencies
|
|
226
|
+
*
|
|
227
|
+
* @example
|
|
228
|
+
* 'user-profile-signal'
|
|
229
|
+
* 'app-theme-signal'
|
|
230
|
+
*/
|
|
231
|
+
name?: string;
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* An array of dependency signals (`StateSignal` or `ComputedSignal` instances).
|
|
235
|
+
* The effect's `run` function will be executed whenever any of these signals change.
|
|
236
|
+
*/
|
|
237
|
+
readonly deps: DependencyList;
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* The function to execute as the side-effect (e.g., logging, DOM updates, network requests).
|
|
241
|
+
* It can be synchronous or asynchronous.
|
|
242
|
+
*
|
|
243
|
+
* @example
|
|
244
|
+
* // An effect that logs the counter's value to the console.
|
|
245
|
+
* const counter = new StateSignal({initialValue: 0});
|
|
246
|
+
* new EffectSignal({
|
|
247
|
+
* deps: [counter],
|
|
248
|
+
* run: () => console.log(`The counter is now: ${counter.get()}`),
|
|
249
|
+
* });
|
|
250
|
+
*/
|
|
251
|
+
run: () => Awaitable<void>;
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* If `true`, the effect's `run` function will be executed once immediately upon initialization.
|
|
255
|
+
* @default false
|
|
256
|
+
*/
|
|
257
|
+
runImmediately?: boolean;
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* An optional callback function that will be executed when the signal's `destroy` method is called.
|
|
261
|
+
* This is useful for cleaning up additional resources used by the signal,
|
|
262
|
+
* such as subscriptions or timers created in operators.
|
|
263
|
+
*/
|
|
264
|
+
onDestroy?: () => void;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* The public interface for an `EffectSignal`, which provides a `destroy` method for cleanup.
|
|
269
|
+
*/
|
|
270
|
+
export interface IEffectSignal {
|
|
271
|
+
/**
|
|
272
|
+
* The unique identifier for this signal instance.
|
|
273
|
+
*/
|
|
274
|
+
name: string;
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Permanently disposes of the effect, unsubscribing from all dependencies
|
|
278
|
+
* and stopping any future executions. This is crucial for preventing memory leaks
|
|
279
|
+
* and unwanted side effects from running.
|
|
280
|
+
*/
|
|
281
|
+
destroy: () => void;
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Indicates whether the signal has been destroyed.
|
|
285
|
+
* A destroyed signal cannot be used and will throw an error if interacted with.
|
|
286
|
+
* @returns `true` if the signal is destroyed, `false` otherwise.
|
|
287
|
+
*/
|
|
288
|
+
readonly isDestroyed: boolean;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Configuration for creating a debounced signal using `createDebouncedSignal`.
|
|
293
|
+
*
|
|
294
|
+
* @see {@link createDebouncedSignal}
|
|
295
|
+
* @see {@link DebouncerConfig}
|
|
296
|
+
*/
|
|
297
|
+
export interface DebounceSignalConfig extends Omit<DebouncerConfig<never>, 'func' | 'thisContext'> {
|
|
298
|
+
/**
|
|
299
|
+
* A unique identifier for the signal. This is crucial for debugging and differentiating signals.
|
|
300
|
+
* @default `${sourceSignal.name}-debounced`
|
|
301
|
+
*/
|
|
302
|
+
name?: string;
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* An optional callback executed when the signal's `destroy` method is called.
|
|
306
|
+
* Useful for cleaning up resources tied to the debounced signal.
|
|
307
|
+
*/
|
|
308
|
+
onDestroy?: () => void;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Configuration for a persistent state signal.
|
|
313
|
+
* It combines the core signal configuration with the necessary options for local storage persistence.
|
|
314
|
+
*
|
|
315
|
+
* @template T The type of the state it holds.
|
|
316
|
+
*/
|
|
317
|
+
export interface PersistentStateSignalConfig<T extends JsonValue> extends StateSignalConfig<T>, LocalStorageProviderConfig {
|
|
318
|
+
/**
|
|
319
|
+
* The key under which to store the signal's state in localStorage.
|
|
320
|
+
* @default `signal-name`
|
|
321
|
+
*/
|
|
322
|
+
storageKey?: string;
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* The debounce delay (in milliseconds) for saving changes to localStorage.
|
|
326
|
+
* This helps to reduce the frequency of write operations, which can be costly in terms of performance.
|
|
327
|
+
* @default 500
|
|
328
|
+
*/
|
|
329
|
+
saveDebounceDelay?: number;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Configuration for a session state signal.
|
|
334
|
+
* The state is persisted in `sessionStorage` and cleared when the tab closes.
|
|
335
|
+
*
|
|
336
|
+
* @template T The type of the state it holds.
|
|
337
|
+
*/
|
|
338
|
+
export interface SessionStateSignalConfig<T extends JsonValue> extends StateSignalConfig<T> {
|
|
339
|
+
/**
|
|
340
|
+
* The key used to store data in `sessionStorage`.
|
|
341
|
+
* Defaults to the signal's `name` if not provided.
|
|
342
|
+
*
|
|
343
|
+
* @default `name`
|
|
344
|
+
*
|
|
345
|
+
* @example
|
|
346
|
+
* 'checkout-wizard-state'
|
|
347
|
+
*/
|
|
348
|
+
storageKey?: string;
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* The debounce delay in milliseconds for writing changes to `sessionStorage`.
|
|
352
|
+
* A lower value than `PersistentStateSignal` is used because session writes are less costly.
|
|
353
|
+
*
|
|
354
|
+
* @default 500
|
|
355
|
+
*/
|
|
356
|
+
saveDebounceDelay?: number;
|
|
357
|
+
}
|