@alwatr/signal 4.1.0 → 5.0.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/CHANGELOG.md +143 -0
- package/README.md +390 -41
- package/dist/computed-signal.d.ts +95 -0
- package/dist/computed-signal.d.ts.map +1 -0
- package/dist/effect-signal.d.ts +75 -0
- package/dist/effect-signal.d.ts.map +1 -0
- package/dist/event-signal.d.ts +46 -0
- package/dist/event-signal.d.ts.map +1 -0
- package/dist/main.cjs +439 -33
- package/dist/main.cjs.map +4 -4
- package/dist/main.d.ts +6 -2
- package/dist/main.d.ts.map +1 -1
- package/dist/main.mjs +434 -31
- package/dist/main.mjs.map +4 -4
- package/dist/signal-base.d.ts +90 -0
- package/dist/signal-base.d.ts.map +1 -0
- package/dist/state-signal.d.ts +83 -0
- package/dist/state-signal.d.ts.map +1 -0
- package/dist/type.d.ts +240 -0
- package/dist/type.d.ts.map +1 -0
- package/package.json +11 -9
- package/src/computed-signal.test.js +150 -0
- package/src/effect-signal.test.js +150 -0
- package/src/event-signal.test.js +194 -0
- package/src/state-signal.test.js +236 -0
- package/dist/logger.d.ts +0 -2
- package/dist/logger.d.ts.map +0 -1
- package/dist/signal.d.ts +0 -16
- package/dist/signal.d.ts.map +0 -1
- package/dist/trigger.d.ts +0 -16
- package/dist/trigger.d.ts.map +0 -1
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { StateSignal } from './state-signal.js';
|
|
2
|
+
import type { ComputedSignalConfig, IComputedSignal, SubscribeResult } from './type.js';
|
|
3
|
+
/**
|
|
4
|
+
* A read-only signal that derives its value from a set of dependency signals.
|
|
5
|
+
*
|
|
6
|
+
* `ComputedSignal` is a powerful tool for creating values that reactively update when their underlying
|
|
7
|
+
* data sources change. Its value is memoized, meaning the `get` function is only re-evaluated when
|
|
8
|
+
* one of its dependencies has actually changed.
|
|
9
|
+
*
|
|
10
|
+
* A key feature is its lifecycle management: a `ComputedSignal` **must** be destroyed when no longer
|
|
11
|
+
* needed to prevent memory leaks from its subscriptions to dependency signals.
|
|
12
|
+
*
|
|
13
|
+
* @template T The type of the computed value.
|
|
14
|
+
* @implements {IComputedSignal<T>}
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* // --- Create dependency signals ---
|
|
18
|
+
* const firstName = new StateSignal({ signalId: 'firstName', initialValue: 'John' });
|
|
19
|
+
* const lastName = new StateSignal({ signalId: 'lastName', initialValue: 'Doe' });
|
|
20
|
+
*
|
|
21
|
+
* // --- Create a computed signal ---
|
|
22
|
+
* const fullName = new ComputedSignal({
|
|
23
|
+
* signalId: 'fullName',
|
|
24
|
+
* deps: [firstName, lastName],
|
|
25
|
+
* get: () => `${firstName.value} ${lastName.value}`,
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* console.log(fullName.value); // Outputs: "John Doe"
|
|
29
|
+
*
|
|
30
|
+
* // --- Subscribe to the computed value ---
|
|
31
|
+
* fullName.subscribe(newFullName => {
|
|
32
|
+
* console.log(`Name changed to: ${newFullName}`);
|
|
33
|
+
* });
|
|
34
|
+
*
|
|
35
|
+
* // --- Update a dependency ---
|
|
36
|
+
* lastName.set('Smith'); // Recalculates and logs: "Name changed to: John Smith"
|
|
37
|
+
* console.log(fullName.value); // Outputs: "John Smith"
|
|
38
|
+
*
|
|
39
|
+
* // --- IMPORTANT: Clean up when done ---
|
|
40
|
+
* fullName.destroy();
|
|
41
|
+
*/
|
|
42
|
+
export declare class ComputedSignal<T> implements IComputedSignal<T> {
|
|
43
|
+
protected config_: ComputedSignalConfig<T>;
|
|
44
|
+
readonly signalId: string;
|
|
45
|
+
protected readonly logger_: import("@alwatr/logger").AlwatrLogger;
|
|
46
|
+
/**
|
|
47
|
+
* The internal `StateSignal` that holds the computed value.
|
|
48
|
+
* This is how the computed signal provides `.value` and `.subscribe()` methods.
|
|
49
|
+
* @protected
|
|
50
|
+
*/
|
|
51
|
+
protected readonly internalSignal_: StateSignal<T>;
|
|
52
|
+
private readonly subscriptionList__;
|
|
53
|
+
private isRecalculating__;
|
|
54
|
+
/**
|
|
55
|
+
* Initializes a new `ComputedSignal`.
|
|
56
|
+
* @param config The configuration, including dependencies (`deps`) and the getter function (`get`).
|
|
57
|
+
*/
|
|
58
|
+
constructor(config_: ComputedSignalConfig<T>);
|
|
59
|
+
/**
|
|
60
|
+
* The current value of the computed signal.
|
|
61
|
+
* Accessing this property returns the memoized value and does not trigger a recalculation.
|
|
62
|
+
*
|
|
63
|
+
* @returns The current computed value.
|
|
64
|
+
* @throws {Error} If accessed after the signal has been destroyed.
|
|
65
|
+
*/
|
|
66
|
+
get value(): T;
|
|
67
|
+
/**
|
|
68
|
+
* Indicates whether the computed signal has been destroyed.
|
|
69
|
+
* A destroyed signal cannot be used and will throw an error if interacted with.
|
|
70
|
+
* @returns `true` if the signal is destroyed, `false` otherwise.
|
|
71
|
+
*/
|
|
72
|
+
get isDestroyed(): boolean;
|
|
73
|
+
readonly subscribe: (callback: import("./type.js").ListenerCallback<T>, options?: import("./type.js").SubscribeOptions) => SubscribeResult;
|
|
74
|
+
readonly untilNext: () => Promise<T>;
|
|
75
|
+
/**
|
|
76
|
+
* Permanently disposes of the computed signal.
|
|
77
|
+
*
|
|
78
|
+
* This is a critical cleanup step. It unsubscribes from all dependency signals,
|
|
79
|
+
* stopping future recalculations and allowing the signal to be garbage collected.
|
|
80
|
+
* Failure to call `destroy()` will result in memory leaks.
|
|
81
|
+
*
|
|
82
|
+
* After `destroy()` is called, any attempt to access `.value` or `.subscribe()` will throw an error.
|
|
83
|
+
*/
|
|
84
|
+
destroy(): void;
|
|
85
|
+
/**
|
|
86
|
+
* Schedules a recalculation of the signal's value.
|
|
87
|
+
*
|
|
88
|
+
* This method batches updates using a macrotask (`delay.nextMacrotask`) to ensure the
|
|
89
|
+
* `get` function runs only once per event loop tick, even if multiple dependencies
|
|
90
|
+
* change in the same synchronous block of code.
|
|
91
|
+
* @protected
|
|
92
|
+
*/
|
|
93
|
+
protected recalculate_(): Promise<void>;
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=computed-signal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"computed-signal.d.ts","sourceRoot":"","sources":["../src/computed-signal.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,WAAW,EAAC,MAAM,mBAAmB,CAAC;AAE9C,OAAO,KAAK,EAAC,oBAAoB,EAAE,eAAe,EAAE,eAAe,EAAC,MAAM,WAAW,CAAC;AAEtF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,qBAAa,cAAc,CAAC,CAAC,CAAE,YAAW,eAAe,CAAC,CAAC,CAAC;IAsBvC,SAAS,CAAC,OAAO,EAAE,oBAAoB,CAAC,CAAC,CAAC;IArB7D,SAAgB,QAAQ,SAAyB;IAEjD,SAAS,CAAC,QAAQ,CAAC,OAAO,wCAAqD;IAE/E;;;;OAIG;IACH,SAAS,CAAC,QAAQ,CAAC,eAAe,iBAG/B;IAEH,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAyB;IAC5D,OAAO,CAAC,iBAAiB,CAAS;IAElC;;;OAGG;gBAC0B,OAAO,EAAE,oBAAoB,CAAC,CAAC,CAAC;IAU7D;;;;;;OAMG;IACH,IAAW,KAAK,IAAI,CAAC,CAEpB;IAED;;;;OAIG;IACH,IAAW,WAAW,IAAI,OAAO,CAEhC;IAED,SAAgB,SAAS,yHAA6D;IAEtF,SAAgB,SAAS,mBAA6D;IAEtF;;;;;;;;OAQG;IACI,OAAO,IAAI,IAAI;IAmBtB;;;;;;;OAOG;cACa,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;CAqC9C"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { EffectSignalConfig, IEffectSignal } from './type.js';
|
|
2
|
+
/**
|
|
3
|
+
* Manages a side-effect that runs in response to changes in dependency signals.
|
|
4
|
+
*
|
|
5
|
+
* `EffectSignal` is designed for running logic that interacts with the "outside world"—such as
|
|
6
|
+
* logging, network requests, or DOM manipulation—whenever its dependencies are updated.
|
|
7
|
+
* It encapsulates the subscription and cleanup logic, providing a robust and memory-safe
|
|
8
|
+
* way to handle reactive side-effects.
|
|
9
|
+
*
|
|
10
|
+
* A key feature is its lifecycle management: an `EffectSignal` **must** be destroyed when no longer
|
|
11
|
+
* needed to prevent memory leaks and stop the effect from running unnecessarily.
|
|
12
|
+
*
|
|
13
|
+
* @implements {IEffectSignal}
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* // --- Create dependency signals ---
|
|
17
|
+
* const counter = new StateSignal({ initialValue: 0, signalId: 'counter' });
|
|
18
|
+
* const user = new StateSignal({ initialValue: 'guest', signalId: 'user' });
|
|
19
|
+
*
|
|
20
|
+
* // --- Create an effect ---
|
|
21
|
+
* const analyticsEffect = new EffectSignal({
|
|
22
|
+
* deps: [counter, user],
|
|
23
|
+
* run: () => {
|
|
24
|
+
* console.log(`Analytics: User '${user.value}' clicked ${counter.value} times.`);
|
|
25
|
+
* },
|
|
26
|
+
* runImmediately: true, // Optional: run once on creation
|
|
27
|
+
* });
|
|
28
|
+
* // Immediately logs: "Analytics: User 'guest' clicked 0 times."
|
|
29
|
+
*
|
|
30
|
+
* // --- Trigger the effect by updating a dependency ---
|
|
31
|
+
* counter.set(1);
|
|
32
|
+
* // After a macrotask, logs: "Analytics: User 'guest' clicked 1 times."
|
|
33
|
+
*
|
|
34
|
+
* // --- IMPORTANT: Clean up when the effect is no longer needed ---
|
|
35
|
+
* analyticsEffect.destroy();
|
|
36
|
+
*
|
|
37
|
+
* // Further updates will not trigger the effect.
|
|
38
|
+
* counter.set(2); // Nothing is logged.
|
|
39
|
+
*/
|
|
40
|
+
export declare class EffectSignal implements IEffectSignal {
|
|
41
|
+
protected config_: EffectSignalConfig;
|
|
42
|
+
protected readonly logger_: import("@alwatr/logger").AlwatrLogger;
|
|
43
|
+
private readonly subscriptionList__;
|
|
44
|
+
private isRunning__;
|
|
45
|
+
private isDestroyed__;
|
|
46
|
+
/**
|
|
47
|
+
* Indicates whether the effect signal has been destroyed.
|
|
48
|
+
* A destroyed signal cannot be used and will throw an error if interacted with.
|
|
49
|
+
* @returns `true` if the signal is destroyed, `false` otherwise.
|
|
50
|
+
*/
|
|
51
|
+
get isDestroyed(): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Initializes a new `EffectSignal`.
|
|
54
|
+
* @param config The configuration, including dependencies (`deps`) and the `run` function.
|
|
55
|
+
*/
|
|
56
|
+
constructor(config_: EffectSignalConfig);
|
|
57
|
+
/**
|
|
58
|
+
* Schedules the execution of the effect's `run` function.
|
|
59
|
+
*
|
|
60
|
+
* This method batches updates using a macrotask (`delay.nextMacrotask`) to ensure the
|
|
61
|
+
* `run` function executes only once per event loop tick, even if multiple
|
|
62
|
+
* dependencies change simultaneously.
|
|
63
|
+
* @protected
|
|
64
|
+
*/
|
|
65
|
+
protected run_(): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Permanently disposes of the effect signal.
|
|
68
|
+
*
|
|
69
|
+
* This is a critical cleanup step. It unsubscribes from all dependency signals,
|
|
70
|
+
* stopping any future executions of the effect and allowing it to be garbage collected.
|
|
71
|
+
* Failure to call `destroy()` will result in memory leaks and potentially unwanted side effects.
|
|
72
|
+
*/
|
|
73
|
+
destroy(): void;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=effect-signal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effect-signal.d.ts","sourceRoot":"","sources":["../src/effect-signal.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,kBAAkB,EAAE,aAAa,EAAkB,MAAM,WAAW,CAAC;AAElF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,qBAAa,YAAa,YAAW,aAAa;IAoB7B,SAAS,CAAC,OAAO,EAAE,kBAAkB;IAnBxD,SAAS,CAAC,QAAQ,CAAC,OAAO,wCAAiC;IAE3D,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAyB;IAC5D,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,aAAa,CAAS;IAE9B;;;;OAIG;IACH,IAAW,WAAW,IAAI,OAAO,CAEhC;IAED;;;OAGG;gBAC0B,OAAO,EAAE,kBAAkB;IAiBxD;;;;;;;OAOG;cACa,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAmCrC;;;;;;OAMG;IACI,OAAO,IAAI,IAAI;CAgBvB"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { SignalBase } from './signal-base.js';
|
|
2
|
+
import type { SignalConfig } from './type.js';
|
|
3
|
+
/**
|
|
4
|
+
* A stateless signal for dispatching transient events.
|
|
5
|
+
*
|
|
6
|
+
* `EventSignal` is ideal for broadcasting events that do not have a persistent state.
|
|
7
|
+
* Unlike `StateSignal`, it does not hold a value. Listeners are only notified of new
|
|
8
|
+
* events as they are dispatched. This makes it suitable for modeling user interactions,
|
|
9
|
+
* system notifications, or any one-off message.
|
|
10
|
+
*
|
|
11
|
+
* @template T The type of the payload for the events. Defaults to `void` for events without a payload.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* // Create a signal for user click events.
|
|
15
|
+
* const onUserClick = new EventSignal<{ x: number, y: number }>({ signalId: 'on-user-click' });
|
|
16
|
+
*
|
|
17
|
+
* // Subscribe to the event.
|
|
18
|
+
* onUserClick.subscribe(clickPosition => {
|
|
19
|
+
* console.log(`User clicked at: ${clickPosition.x}, ${clickPosition.y}`);
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* // Dispatch an event.
|
|
23
|
+
* onUserClick.dispatch({ x: 100, y: 250 }); // Notifies the listener.
|
|
24
|
+
*
|
|
25
|
+
* // --- Example with no payload ---
|
|
26
|
+
* const onAppReady = new EventSignal({ signalId: 'on-app-ready' });
|
|
27
|
+
* onAppReady.subscribe(() => console.log('Application is ready!'));
|
|
28
|
+
* onAppReady.dispatch(); // Notifies the listener.
|
|
29
|
+
*/
|
|
30
|
+
export declare class EventSignal<T = void> extends SignalBase<T> {
|
|
31
|
+
protected logger_: import("@alwatr/logger").AlwatrLogger;
|
|
32
|
+
/**
|
|
33
|
+
* Initializes a new `EventSignal`.
|
|
34
|
+
* @param config The configuration for the signal, containing its `signalId`.
|
|
35
|
+
*/
|
|
36
|
+
constructor(config: SignalConfig);
|
|
37
|
+
/**
|
|
38
|
+
* Dispatches an event with an optional payload to all active listeners.
|
|
39
|
+
* The notification is scheduled as a microtask to prevent blocking and ensure
|
|
40
|
+
* a consistent, non-blocking flow.
|
|
41
|
+
*
|
|
42
|
+
* @param payload The data to send with the event.
|
|
43
|
+
*/
|
|
44
|
+
dispatch(payload: T): void;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=event-signal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-signal.d.ts","sourceRoot":"","sources":["../src/event-signal.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAE5C,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,WAAW,CAAC;AAE5C;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBAAa,WAAW,CAAC,CAAC,GAAG,IAAI,CAAE,SAAQ,UAAU,CAAC,CAAC,CAAC;IACtD,SAAS,CAAC,OAAO,wCAAkD;IAEnE;;;OAGG;gBACgB,MAAM,EAAE,YAAY;IAKvC;;;;;;OAMG;IACI,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI;CAQlC"}
|