@alwatr/signal 5.2.0 → 5.2.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/CHANGELOG.md +11 -0
- package/README.md +12 -12
- package/dist/core/computed-signal.d.ts +6 -6
- package/dist/core/computed-signal.d.ts.map +1 -1
- package/dist/core/effect-signal.d.ts +2 -2
- package/dist/core/effect-signal.d.ts.map +1 -1
- package/dist/core/state-signal.d.ts +4 -4
- package/dist/core/state-signal.d.ts.map +1 -1
- package/dist/creators/computed.d.ts +2 -2
- package/dist/creators/effect.d.ts +1 -1
- package/dist/creators/state.d.ts +2 -2
- package/dist/main.cjs +20 -20
- package/dist/main.cjs.map +2 -2
- package/dist/main.mjs +20 -20
- package/dist/main.mjs.map +2 -2
- package/dist/operators/debounce.d.ts +2 -2
- package/dist/operators/filter.d.ts +2 -2
- package/dist/operators/filter.d.ts.map +1 -1
- package/dist/operators/map.d.ts +2 -2
- package/dist/type.d.ts +3 -3
- package/dist/type.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/core/computed-signal.test.js +6 -6
- package/src/core/state-signal.test.js +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [5.2.1](https://github.com/Alwatr/flux/compare/v5.2.0...v5.2.1) (2025-09-15)
|
|
7
|
+
|
|
8
|
+
### ⚡ Performance Improvements
|
|
9
|
+
|
|
10
|
+
* To improve readability and avoid calling sourceSignal.get() twice, used temporary variable before using it. ([2aaa3bd](https://github.com/Alwatr/flux/commit/2aaa3bdd06745885495ca64a71c4040b9ec57cea))
|
|
11
|
+
|
|
12
|
+
### 🔨 Code Refactoring
|
|
13
|
+
|
|
14
|
+
* change the `signal.value` to `signal.get()` ([fcdcb6c](https://github.com/Alwatr/flux/commit/fcdcb6caf82747b8e6d7ad846d6babead385c603))
|
|
15
|
+
* rename run_ method to scheduleExecution_ for clarity in EffectSignal ([402af2f](https://github.com/Alwatr/flux/commit/402af2f7b84357ade4f79b33611d6968ec6b8efd))
|
|
16
|
+
|
|
6
17
|
## [5.2.0](https://github.com/Alwatr/flux/compare/v5.1.0...v5.2.0) (2025-09-15)
|
|
7
18
|
|
|
8
19
|
### ✨ Features
|
package/README.md
CHANGED
|
@@ -69,10 +69,10 @@ import {ComputedSignal} from '@alwatr/signal';
|
|
|
69
69
|
const fullName = new ComputedSignal<string>({
|
|
70
70
|
signalId: 'user-fullName',
|
|
71
71
|
deps: [firstName], // This computed signal depends on firstName.
|
|
72
|
-
get: () => `User: ${firstName.
|
|
72
|
+
get: () => `User: ${firstName.get()}`,
|
|
73
73
|
});
|
|
74
74
|
|
|
75
|
-
console.log(fullName.
|
|
75
|
+
console.log(fullName.get()); // Outputs: "User: John"
|
|
76
76
|
```
|
|
77
77
|
|
|
78
78
|
### 4. Create an Effect Signal
|
|
@@ -85,7 +85,7 @@ import {EffectSignal} from '@alwatr/signal';
|
|
|
85
85
|
const loggerEffect = new EffectSignal({
|
|
86
86
|
deps: [fullName, counter], // This effect depends on fullName and counter.
|
|
87
87
|
run: () => {
|
|
88
|
-
console.log(`${fullName.
|
|
88
|
+
console.log(`${fullName.get()} has clicked ${counter.get()} times.`);
|
|
89
89
|
},
|
|
90
90
|
});
|
|
91
91
|
```
|
|
@@ -134,7 +134,7 @@ Signals that depend on other signals (like `ComputedSignal` and `EffectSignal`)
|
|
|
134
134
|
// Create a computed signal
|
|
135
135
|
const isEven = new ComputedSignal({
|
|
136
136
|
deps: [counter],
|
|
137
|
-
get: () => counter.
|
|
137
|
+
get: () => counter.get() % 2 === 0,
|
|
138
138
|
});
|
|
139
139
|
|
|
140
140
|
// ... use it for a while ...
|
|
@@ -167,7 +167,7 @@ The `subscribe` method accepts an optional second argument to customize its beha
|
|
|
167
167
|
- **`constructor(config)`**: Creates a new state signal.
|
|
168
168
|
- `config.signalId`: `string`
|
|
169
169
|
- `config.initialValue`: `T`
|
|
170
|
-
- **`.
|
|
170
|
+
- **`.get()`**: `T` - Gets the current value.
|
|
171
171
|
- **`.set(newValue: T)`**: Sets a new value and notifies listeners.
|
|
172
172
|
|
|
173
173
|
### `ComputedSignal<T>`
|
|
@@ -176,7 +176,7 @@ The `subscribe` method accepts an optional second argument to customize its beha
|
|
|
176
176
|
- `config.signalId`: `string`
|
|
177
177
|
- `config.deps`: `IReadonlySignal<unknown>[]` - Array of dependency signals.
|
|
178
178
|
- `config.get`: `() => T` - The function to compute the value.
|
|
179
|
-
- **`.
|
|
179
|
+
- **`.get()`**: `T` - Gets the current (memoized) value.
|
|
180
180
|
- **`.destroy()`**: Cleans up the signal's subscriptions. **(Important!)**
|
|
181
181
|
|
|
182
182
|
### `EffectSignal`
|
|
@@ -284,10 +284,10 @@ import {ComputedSignal} from '@alwatr/signal';
|
|
|
284
284
|
const fullName = new ComputedSignal<string>({
|
|
285
285
|
signalId: 'user-fullName',
|
|
286
286
|
deps: [firstName], // این سیگنال محاسباتی به firstName وابسته است
|
|
287
|
-
get: () => `User: ${firstName.
|
|
287
|
+
get: () => `User: ${firstName.get()}`,
|
|
288
288
|
});
|
|
289
289
|
|
|
290
|
-
console.log(fullName.
|
|
290
|
+
console.log(fullName.get()); // خروجی: "User: John"
|
|
291
291
|
```
|
|
292
292
|
|
|
293
293
|
### ۴. ایجاد `EffectSignal`
|
|
@@ -300,7 +300,7 @@ import {EffectSignal} from '@alwatr/signal';
|
|
|
300
300
|
const loggerEffect = new EffectSignal({
|
|
301
301
|
deps: [fullName, counter], // این افکت به fullName و counter وابسته است
|
|
302
302
|
run: () => {
|
|
303
|
-
console.log(`${fullName.
|
|
303
|
+
console.log(`${fullName.get()} has clicked ${counter.get()} times.`);
|
|
304
304
|
},
|
|
305
305
|
});
|
|
306
306
|
```
|
|
@@ -349,7 +349,7 @@ User: Jane has clicked 1 times.
|
|
|
349
349
|
// یک سیگنال محاسباتی ایجاد کنید
|
|
350
350
|
const isEven = new ComputedSignal({
|
|
351
351
|
deps: [counter],
|
|
352
|
-
get: () => counter.
|
|
352
|
+
get: () => counter.get() % 2 === 0,
|
|
353
353
|
});
|
|
354
354
|
|
|
355
355
|
// ... مدتی از آن استفاده کنید ...
|
|
@@ -382,7 +382,7 @@ Alwatr Signal از یک مدل ناهمزمان قابل پیشبینی بر
|
|
|
382
382
|
- **`constructor(config)`**: یک سیگنال وضعیت جدید ایجاد میکند.
|
|
383
383
|
- `config.signalId`: `string`
|
|
384
384
|
- `config.initialValue`: `T`
|
|
385
|
-
- **`.
|
|
385
|
+
- **`.get()`**: `T` - مقدار فعلی را دریافت میکند.
|
|
386
386
|
- **`.set(newValue: T)`**: مقدار جدیدی را تنظیم کرده و شنوندگان را مطلع میکند.
|
|
387
387
|
|
|
388
388
|
### `ComputedSignal<T>`
|
|
@@ -391,7 +391,7 @@ Alwatr Signal از یک مدل ناهمزمان قابل پیشبینی بر
|
|
|
391
391
|
- `config.signalId`: `string`
|
|
392
392
|
- `config.deps`: `IReadonlySignal<unknown>[]` - آرایهای از سیگنالهای وابسته.
|
|
393
393
|
- `config.get`: `() => T` - تابعی برای محاسبه مقدار.
|
|
394
|
-
- **`.
|
|
394
|
+
- **`.get()`**: `T` - مقدار فعلی (کش شده) را دریافت میکند.
|
|
395
395
|
- **`.destroy()`**: اشتراکهای سیگنال را پاکسازی میکند. **(مهم!)**
|
|
396
396
|
|
|
397
397
|
### `EffectSignal`
|
|
@@ -21,10 +21,10 @@ import type { ComputedSignalConfig, IReadonlySignal, SubscribeResult, SubscribeO
|
|
|
21
21
|
* const fullName = new ComputedSignal({
|
|
22
22
|
* signalId: 'fullName',
|
|
23
23
|
* deps: [firstName, lastName],
|
|
24
|
-
* get: () => `${firstName.
|
|
24
|
+
* get: () => `${firstName.get()} ${lastName.get()}`,
|
|
25
25
|
* });
|
|
26
26
|
*
|
|
27
|
-
* console.log(fullName.
|
|
27
|
+
* console.log(fullName.get()); // Outputs: "John Doe"
|
|
28
28
|
*
|
|
29
29
|
* // --- Subscribe to the computed value ---
|
|
30
30
|
* fullName.subscribe(newFullName => {
|
|
@@ -33,7 +33,7 @@ import type { ComputedSignalConfig, IReadonlySignal, SubscribeResult, SubscribeO
|
|
|
33
33
|
*
|
|
34
34
|
* // --- Update a dependency ---
|
|
35
35
|
* lastName.set('Smith'); // Recalculates and logs: "Name changed to: John Smith"
|
|
36
|
-
* console.log(fullName.
|
|
36
|
+
* console.log(fullName.get()); // Outputs: "John Smith"
|
|
37
37
|
*
|
|
38
38
|
* // --- IMPORTANT: Clean up when done ---
|
|
39
39
|
* fullName.destroy();
|
|
@@ -51,7 +51,7 @@ export declare class ComputedSignal<T> implements IReadonlySignal<T> {
|
|
|
51
51
|
protected readonly logger_: import("@alwatr/logger").AlwatrLogger;
|
|
52
52
|
/**
|
|
53
53
|
* The internal `StateSignal` that holds the computed value.
|
|
54
|
-
* This is how the computed signal provides `.
|
|
54
|
+
* This is how the computed signal provides `.get()` and `.subscribe()` methods.
|
|
55
55
|
* @protected
|
|
56
56
|
*/
|
|
57
57
|
protected readonly internalSignal_: StateSignal<T>;
|
|
@@ -73,7 +73,7 @@ export declare class ComputedSignal<T> implements IReadonlySignal<T> {
|
|
|
73
73
|
* @returns The current computed value.
|
|
74
74
|
* @throws {Error} If accessed after the signal has been destroyed.
|
|
75
75
|
*/
|
|
76
|
-
get
|
|
76
|
+
get(): T;
|
|
77
77
|
/**
|
|
78
78
|
* Indicates whether the computed signal has been destroyed.
|
|
79
79
|
* A destroyed signal cannot be used and will throw an error if interacted with.
|
|
@@ -102,7 +102,7 @@ export declare class ComputedSignal<T> implements IReadonlySignal<T> {
|
|
|
102
102
|
* stopping future recalculations and allowing the signal to be garbage collected.
|
|
103
103
|
* Failure to call `destroy()` will result in memory leaks.
|
|
104
104
|
*
|
|
105
|
-
* After `destroy()` is called, any attempt to access `.
|
|
105
|
+
* After `destroy()` is called, any attempt to access `.get()` or `.subscribe()` will throw an error.
|
|
106
106
|
*/
|
|
107
107
|
destroy(): void;
|
|
108
108
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"computed-signal.d.ts","sourceRoot":"","sources":["../../src/core/computed-signal.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,WAAW,EAAC,MAAM,mBAAmB,CAAC;AAE9C,OAAO,KAAK,EAAC,oBAAoB,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAC,MAAM,YAAY,CAAC;AAEzG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,qBAAa,cAAc,CAAC,CAAC,CAAE,YAAW,eAAe,CAAC,CAAC,CAAC;IAmCvC,SAAS,CAAC,OAAO,EAAE,oBAAoB,CAAC,CAAC,CAAC;IAlC7D;;OAEG;IACH,SAAgB,QAAQ,SAAyB;IAEjD;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,OAAO,wCAAqD;IAE/E;;;;OAIG;IACH,SAAS,CAAC,QAAQ,CAAC,eAAe,iBAG/B;IAEH;;;OAGG;IAEH,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAyB;IAEnE;;;OAGG;IACH,OAAO,CAAC,iBAAiB,CAAS;gBAEL,OAAO,EAAE,oBAAoB,CAAC,CAAC,CAAC;IAU7D;;;;;;OAMG;
|
|
1
|
+
{"version":3,"file":"computed-signal.d.ts","sourceRoot":"","sources":["../../src/core/computed-signal.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,WAAW,EAAC,MAAM,mBAAmB,CAAC;AAE9C,OAAO,KAAK,EAAC,oBAAoB,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAC,MAAM,YAAY,CAAC;AAEzG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,qBAAa,cAAc,CAAC,CAAC,CAAE,YAAW,eAAe,CAAC,CAAC,CAAC;IAmCvC,SAAS,CAAC,OAAO,EAAE,oBAAoB,CAAC,CAAC,CAAC;IAlC7D;;OAEG;IACH,SAAgB,QAAQ,SAAyB;IAEjD;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,OAAO,wCAAqD;IAE/E;;;;OAIG;IACH,SAAS,CAAC,QAAQ,CAAC,eAAe,iBAG/B;IAEH;;;OAGG;IAEH,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAyB;IAEnE;;;OAGG;IACH,OAAO,CAAC,iBAAiB,CAAS;gBAEL,OAAO,EAAE,oBAAoB,CAAC,CAAC,CAAC;IAU7D;;;;;;OAMG;IACI,GAAG,IAAI,CAAC;IAIf;;;;OAIG;IACH,IAAW,WAAW,IAAI,OAAO,CAEhC;IAED;;;;;;;OAOG;IACI,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,eAAe;IAI3F;;;;OAIG;IACI,SAAS,IAAI,OAAO,CAAC,CAAC,CAAC;IAI9B;;;;;;;;OAQG;IACI,OAAO,IAAI,IAAI;IAqBtB;;;;;;;OAOG;cACa,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;CAwC9C"}
|
|
@@ -22,7 +22,7 @@ import type { EffectSignalConfig, IEffectSignal } from '../type.js';
|
|
|
22
22
|
* signalId: 'analytics-effect',
|
|
23
23
|
* deps: [counter, user],
|
|
24
24
|
* run: () => {
|
|
25
|
-
* console.log(`Analytics: User '${user.
|
|
25
|
+
* console.log(`Analytics: User '${user.get()}' clicked ${counter.get()} times.`);
|
|
26
26
|
* },
|
|
27
27
|
* runImmediately: true, // Optional: run once on creation
|
|
28
28
|
* });
|
|
@@ -80,7 +80,7 @@ export declare class EffectSignal implements IEffectSignal {
|
|
|
80
80
|
* dependencies change simultaneously.
|
|
81
81
|
* @protected
|
|
82
82
|
*/
|
|
83
|
-
protected
|
|
83
|
+
protected scheduleExecution_(): Promise<void>;
|
|
84
84
|
/**
|
|
85
85
|
* Permanently disposes of the effect signal.
|
|
86
86
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"effect-signal.d.ts","sourceRoot":"","sources":["../../src/core/effect-signal.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,kBAAkB,EAAE,aAAa,EAAkB,MAAM,YAAY,CAAC;AAEnF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,qBAAa,YAAa,YAAW,aAAa;IAwC7B,SAAS,CAAC,OAAO,EAAE,kBAAkB;IAvCxD;;OAEG;IACH,SAAgB,QAAQ,SAAkH;IAE1I;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,OAAO,wCAAmD;IAE7E;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAyB;IAEnE;;;OAGG;IACH,OAAO,CAAC,WAAW,CAAS;IAE5B;;;OAGG;IACH,OAAO,CAAC,aAAa,CAAS;IAE9B;;;;;OAKG;IACH,IAAW,WAAW,IAAI,OAAO,CAEhC;gBAE4B,OAAO,EAAE,kBAAkB;IAiBxD;;;;;;;OAOG;cACa,
|
|
1
|
+
{"version":3,"file":"effect-signal.d.ts","sourceRoot":"","sources":["../../src/core/effect-signal.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,kBAAkB,EAAE,aAAa,EAAkB,MAAM,YAAY,CAAC;AAEnF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,qBAAa,YAAa,YAAW,aAAa;IAwC7B,SAAS,CAAC,OAAO,EAAE,kBAAkB;IAvCxD;;OAEG;IACH,SAAgB,QAAQ,SAAkH;IAE1I;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,OAAO,wCAAmD;IAE7E;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAyB;IAEnE;;;OAGG;IACH,OAAO,CAAC,WAAW,CAAS;IAE5B;;;OAGG;IACH,OAAO,CAAC,aAAa,CAAS;IAE9B;;;;;OAKG;IACH,IAAW,WAAW,IAAI,OAAO,CAEhC;gBAE4B,OAAO,EAAE,kBAAkB;IAiBxD;;;;;;;OAOG;cACa,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;IAmCnD;;;;;;OAMG;IACI,OAAO,IAAI,IAAI;CAmBvB"}
|
|
@@ -17,7 +17,7 @@ import type { StateSignalConfig, ListenerCallback, SubscribeOptions, SubscribeRe
|
|
|
17
17
|
* });
|
|
18
18
|
*
|
|
19
19
|
* // Get the current value.
|
|
20
|
-
* console.log(counter.
|
|
20
|
+
* console.log(counter.get()); // Outputs: 0
|
|
21
21
|
*
|
|
22
22
|
* // Subscribe to changes.
|
|
23
23
|
* const subscription = counter.subscribe(newValue => {
|
|
@@ -51,9 +51,9 @@ export declare class StateSignal<T> extends SignalBase<T> implements IReadonlySi
|
|
|
51
51
|
* @returns The current value.
|
|
52
52
|
*
|
|
53
53
|
* @example
|
|
54
|
-
* console.log(mySignal.
|
|
54
|
+
* console.log(mySignal.get());
|
|
55
55
|
*/
|
|
56
|
-
get
|
|
56
|
+
get(): T;
|
|
57
57
|
/**
|
|
58
58
|
* Updates the signal's value and notifies all active listeners.
|
|
59
59
|
*
|
|
@@ -67,7 +67,7 @@ export declare class StateSignal<T> extends SignalBase<T> implements IReadonlySi
|
|
|
67
67
|
* mySignal.set(42);
|
|
68
68
|
*
|
|
69
69
|
* // For object types, it's best practice to set an immutable new object.
|
|
70
|
-
* mySignal.set({ ...mySignal.
|
|
70
|
+
* mySignal.set({ ...mySignal.get(), property: 'new-value' });
|
|
71
71
|
*/
|
|
72
72
|
set(newValue: T): void;
|
|
73
73
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state-signal.d.ts","sourceRoot":"","sources":["../../src/core/state-signal.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAE5C,OAAO,KAAK,EAAC,iBAAiB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,eAAe,EAAE,eAAe,EAAC,MAAM,YAAY,CAAC;AAExH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,qBAAa,WAAW,CAAC,CAAC,CAAE,SAAQ,UAAU,CAAC,CAAC,CAAE,YAAW,eAAe,CAAC,CAAC,CAAC;IAC7E;;;OAGG;IACH,OAAO,CAAC,OAAO,CAAI;IAEnB;;;OAGG;IACH,SAAS,CAAC,OAAO,wCAAkD;gBAEhD,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAM/C;;;;;;;OAOG;
|
|
1
|
+
{"version":3,"file":"state-signal.d.ts","sourceRoot":"","sources":["../../src/core/state-signal.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAE5C,OAAO,KAAK,EAAC,iBAAiB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,eAAe,EAAE,eAAe,EAAC,MAAM,YAAY,CAAC;AAExH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,qBAAa,WAAW,CAAC,CAAC,CAAE,SAAQ,UAAU,CAAC,CAAC,CAAE,YAAW,eAAe,CAAC,CAAC,CAAC;IAC7E;;;OAGG;IACH,OAAO,CAAC,OAAO,CAAI;IAEnB;;;OAGG;IACH,SAAS,CAAC,OAAO,wCAAkD;gBAEhD,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAM/C;;;;;;;OAOG;IACI,GAAG,IAAI,CAAC;IAKf;;;;;;;;;;;;;;OAcG;IACI,GAAG,CAAC,QAAQ,EAAE,CAAC,GAAG,IAAI;IAe7B;;;;;;;;;;;;;;OAcG;IACI,MAAM,CAAC,OAAO,EAAE,CAAC,aAAa,EAAE,CAAC,KAAK,CAAC,GAAG,IAAI;IAQrD;;;;;;;;;OASG;IACa,SAAS,CAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAAE,OAAO,GAAE,gBAAqB,GAAG,eAAe;IAwBzG;;;OAGG;IACa,OAAO,IAAI,IAAI;CAIhC"}
|
|
@@ -22,10 +22,10 @@ import type { ComputedSignalConfig } from '../type.js';
|
|
|
22
22
|
* const fullName = createComputedSignal({
|
|
23
23
|
* signalId: 'fullName',
|
|
24
24
|
* deps: [firstName, lastName],
|
|
25
|
-
* get: () => `${firstName.
|
|
25
|
+
* get: () => `${firstName.get()} ${lastName.get()}`,
|
|
26
26
|
* });
|
|
27
27
|
*
|
|
28
|
-
* console.log(fullName.
|
|
28
|
+
* console.log(fullName.get()); // "John Doe"
|
|
29
29
|
*
|
|
30
30
|
* // IMPORTANT: Always destroy a computed signal when no longer needed.
|
|
31
31
|
* // fullName.destroy();
|
|
@@ -23,7 +23,7 @@ import type { EffectSignalConfig } from '../type.js';
|
|
|
23
23
|
* const analyticsEffect = createEffect({
|
|
24
24
|
* deps: [counter, user],
|
|
25
25
|
* run: () => {
|
|
26
|
-
* console.log(`Analytics: User '${user.
|
|
26
|
+
* console.log(`Analytics: User '${user.get()}' clicked ${counter.get()} times.`);
|
|
27
27
|
* },
|
|
28
28
|
* runImmediately: true, // Optional: run once on creation
|
|
29
29
|
* });
|
package/dist/creators/state.d.ts
CHANGED
|
@@ -17,9 +17,9 @@ import type { StateSignalConfig } from '../type.js';
|
|
|
17
17
|
* initialValue: 0,
|
|
18
18
|
* });
|
|
19
19
|
*
|
|
20
|
-
* console.log(counter.
|
|
20
|
+
* console.log(counter.get()); // Outputs: 0
|
|
21
21
|
* counter.set(1);
|
|
22
|
-
* console.log(counter.
|
|
22
|
+
* console.log(counter.get()); // Outputs: 1
|
|
23
23
|
*/
|
|
24
24
|
export declare function createStateSignal<T>(config: StateSignalConfig<T>): StateSignal<T>;
|
|
25
25
|
//# sourceMappingURL=state.d.ts.map
|
package/dist/main.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* @alwatr/signal v5.2.
|
|
1
|
+
/* @alwatr/signal v5.2.1 */
|
|
2
2
|
"use strict";
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -236,9 +236,9 @@ var StateSignal = class extends SignalBase {
|
|
|
236
236
|
* @returns The current value.
|
|
237
237
|
*
|
|
238
238
|
* @example
|
|
239
|
-
* console.log(mySignal.
|
|
239
|
+
* console.log(mySignal.get());
|
|
240
240
|
*/
|
|
241
|
-
get
|
|
241
|
+
get() {
|
|
242
242
|
this.checkDestroyed_();
|
|
243
243
|
return this.value__;
|
|
244
244
|
}
|
|
@@ -255,7 +255,7 @@ var StateSignal = class extends SignalBase {
|
|
|
255
255
|
* mySignal.set(42);
|
|
256
256
|
*
|
|
257
257
|
* // For object types, it's best practice to set an immutable new object.
|
|
258
|
-
* mySignal.set({ ...mySignal.
|
|
258
|
+
* mySignal.set({ ...mySignal.get(), property: 'new-value' });
|
|
259
259
|
*/
|
|
260
260
|
set(newValue) {
|
|
261
261
|
this.logger_.logMethodArgs?.("set", { newValue });
|
|
@@ -335,7 +335,7 @@ var ComputedSignal = class {
|
|
|
335
335
|
this.logger_ = (0, import_logger3.createLogger)(`computed-signal: ${this.signalId}`);
|
|
336
336
|
/**
|
|
337
337
|
* The internal `StateSignal` that holds the computed value.
|
|
338
|
-
* This is how the computed signal provides `.
|
|
338
|
+
* This is how the computed signal provides `.get()` and `.subscribe()` methods.
|
|
339
339
|
* @protected
|
|
340
340
|
*/
|
|
341
341
|
this.internalSignal_ = new StateSignal({
|
|
@@ -365,8 +365,8 @@ var ComputedSignal = class {
|
|
|
365
365
|
* @returns The current computed value.
|
|
366
366
|
* @throws {Error} If accessed after the signal has been destroyed.
|
|
367
367
|
*/
|
|
368
|
-
get
|
|
369
|
-
return this.internalSignal_.
|
|
368
|
+
get() {
|
|
369
|
+
return this.internalSignal_.get();
|
|
370
370
|
}
|
|
371
371
|
/**
|
|
372
372
|
* Indicates whether the computed signal has been destroyed.
|
|
@@ -402,7 +402,7 @@ var ComputedSignal = class {
|
|
|
402
402
|
* stopping future recalculations and allowing the signal to be garbage collected.
|
|
403
403
|
* Failure to call `destroy()` will result in memory leaks.
|
|
404
404
|
*
|
|
405
|
-
* After `destroy()` is called, any attempt to access `.
|
|
405
|
+
* After `destroy()` is called, any attempt to access `.get()` or `.subscribe()` will throw an error.
|
|
406
406
|
*/
|
|
407
407
|
destroy() {
|
|
408
408
|
this.logger_.logMethod?.("destroy");
|
|
@@ -484,12 +484,12 @@ var EffectSignal = class {
|
|
|
484
484
|
*/
|
|
485
485
|
this.isDestroyed__ = false;
|
|
486
486
|
this.logger_.logMethod?.("constructor");
|
|
487
|
-
this.
|
|
487
|
+
this.scheduleExecution_ = this.scheduleExecution_.bind(this);
|
|
488
488
|
for (const signal of config_.deps) {
|
|
489
|
-
this.dependencySubscriptions__.push(signal.subscribe(this.
|
|
489
|
+
this.dependencySubscriptions__.push(signal.subscribe(this.scheduleExecution_, { receivePrevious: false }));
|
|
490
490
|
}
|
|
491
491
|
if (config_.runImmediately === true) {
|
|
492
|
-
void this.
|
|
492
|
+
void this.scheduleExecution_();
|
|
493
493
|
}
|
|
494
494
|
}
|
|
495
495
|
/**
|
|
@@ -509,28 +509,28 @@ var EffectSignal = class {
|
|
|
509
509
|
* dependencies change simultaneously.
|
|
510
510
|
* @protected
|
|
511
511
|
*/
|
|
512
|
-
async
|
|
513
|
-
this.logger_.logMethod?.("
|
|
512
|
+
async scheduleExecution_() {
|
|
513
|
+
this.logger_.logMethod?.("scheduleExecution_");
|
|
514
514
|
if (this.isDestroyed__) {
|
|
515
|
-
this.logger_.incident?.("
|
|
515
|
+
this.logger_.incident?.("scheduleExecution_", "schedule_execution_on_destroyed_signal");
|
|
516
516
|
return;
|
|
517
517
|
}
|
|
518
518
|
if (this.isRunning__) {
|
|
519
|
-
this.logger_.logStep?.("
|
|
519
|
+
this.logger_.logStep?.("scheduleExecution_", "skipped_because_already_running");
|
|
520
520
|
return;
|
|
521
521
|
}
|
|
522
522
|
this.isRunning__ = true;
|
|
523
523
|
try {
|
|
524
524
|
await import_delay4.delay.nextMacrotask();
|
|
525
525
|
if (this.isDestroyed__) {
|
|
526
|
-
this.logger_.incident?.("
|
|
526
|
+
this.logger_.incident?.("scheduleExecution_", "destroyed_during_delay");
|
|
527
527
|
this.isRunning__ = false;
|
|
528
528
|
return;
|
|
529
529
|
}
|
|
530
|
-
this.logger_.logStep?.("
|
|
530
|
+
this.logger_.logStep?.("scheduleExecution_", "executing_effect");
|
|
531
531
|
await this.config_.run();
|
|
532
532
|
} catch (err) {
|
|
533
|
-
this.logger_.error("
|
|
533
|
+
this.logger_.error("scheduleExecution_", "effect_failed", err);
|
|
534
534
|
}
|
|
535
535
|
this.isRunning__ = false;
|
|
536
536
|
}
|
|
@@ -583,7 +583,7 @@ function createDebouncedSignal(sourceSignal, config) {
|
|
|
583
583
|
const signalId = config.signalId ?? `${sourceSignal.signalId}-debounced`;
|
|
584
584
|
const internalSignal = new StateSignal({
|
|
585
585
|
signalId: `${signalId}-internal`,
|
|
586
|
-
initialValue: sourceSignal.
|
|
586
|
+
initialValue: sourceSignal.get()
|
|
587
587
|
});
|
|
588
588
|
const debouncer = (0, import_debounce.createDebouncer)({
|
|
589
589
|
...config,
|
|
@@ -595,7 +595,7 @@ function createDebouncedSignal(sourceSignal, config) {
|
|
|
595
595
|
return createComputedSignal({
|
|
596
596
|
signalId,
|
|
597
597
|
deps: [internalSignal],
|
|
598
|
-
get: () => internalSignal.
|
|
598
|
+
get: () => internalSignal.get(),
|
|
599
599
|
onDestroy: () => {
|
|
600
600
|
if (internalSignal.isDestroyed) return;
|
|
601
601
|
subscription.unsubscribe();
|
package/dist/main.cjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/main.ts", "../src/core/signal-base.ts", "../src/core/event-signal.ts", "../src/core/state-signal.ts", "../src/core/computed-signal.ts", "../src/core/effect-signal.ts", "../src/creators/event.ts", "../src/creators/state.ts", "../src/creators/computed.ts", "../src/creators/effect.ts", "../src/operators/debounce.ts"],
|
|
4
|
-
"sourcesContent": ["export * from './core/signal-base.js';\nexport * from './core/event-signal.js';\nexport * from './core/state-signal.js';\nexport * from './core/computed-signal.js';\nexport * from './core/effect-signal.js';\n\nexport * from './creators/event.js';\nexport * from './creators/state.js';\nexport * from './creators/computed.js';\nexport * from './creators/effect.js';\n\nexport * from './operators/debounce.js';\n\nexport type * from './type.js';\n", "import type {Observer_, SubscribeOptions, SubscribeResult, ListenerCallback, SignalConfig} from '../type.js';\nimport type {AlwatrLogger} from '@alwatr/logger';\nimport type {} from '@alwatr/nano-build';\n\n/**\n * An abstract base class for signal implementations.\n * It provides the core functionality for managing subscriptions (observers).\n *\n * @template T The type of data that the signal holds or dispatches.\n */\nexport abstract class SignalBase<T> {\n /**\n * The unique identifier for this signal instance.\n * Useful for debugging and logging.\n */\n public readonly signalId = this.config_.signalId;\n\n /**\n * The logger instance for this signal.\n * @protected\n */\n protected abstract logger_: AlwatrLogger;\n\n /**\n * The list of observers (listeners) subscribed to this signal.\n * @protected\n */\n protected readonly observers_: Observer_<T>[] = [];\n\n /**\n * A flag indicating whether the signal has been destroyed.\n * @private\n */\n private isDestroyed__ = false;\n\n /**\n * Indicates whether the signal has been destroyed.\n * A destroyed signal cannot be used and will throw an error if interacted with.\n *\n * @returns `true` if the signal is destroyed, `false` otherwise.\n */\n public get isDestroyed(): boolean {\n return this.isDestroyed__;\n }\n\n public constructor(protected config_: SignalConfig) {}\n\n /**\n * Removes a specific observer from the observers list.\n *\n * @param observer The observer instance to remove.\n * @protected\n */\n protected removeObserver_(observer: Observer_<T>): void {\n this.logger_.logMethod?.('removeObserver_');\n\n if (this.isDestroyed__) {\n this.logger_.incident?.('removeObserver_', 'remove_observer_on_destroyed_signal');\n return;\n }\n\n const index = this.observers_.indexOf(observer);\n if (index !== -1) {\n this.observers_.splice(index, 1);\n }\n }\n\n /**\n * Subscribes a listener function to this signal.\n *\n * The listener will be called whenever the signal is notified (e.g., when `dispatch` or `set` is called).\n *\n * @param callback The function to be called when the signal is dispatched.\n * @param options Subscription options to customize the behavior (e.g., `once`, `priority`).\n * @returns A `SubscribeResult` object with an `unsubscribe` method to remove the listener.\n */\n public subscribe(callback: ListenerCallback<T>, options?: SubscribeOptions): SubscribeResult {\n this.logger_.logMethodArgs?.('subscribe.base', {options});\n this.checkDestroyed_();\n\n const observer: Observer_<T> = {callback, options};\n\n if (options?.priority) {\n // High-priority observers are added to the front of the queue.\n this.observers_.unshift(observer);\n }\n else {\n this.observers_.push(observer);\n }\n\n // The returned unsubscribe function is a closure that calls the internal removal method.\n return {\n unsubscribe: (): void => this.removeObserver_(observer),\n };\n }\n\n /**\n * Notifies all registered observers about a new value.\n *\n * This method iterates through a snapshot of the current observers to prevent issues\n * with subscriptions changing during notification (e.g., an observer unsubscribing itself).\n *\n * @param value The new value to notify observers about.\n * @protected\n */\n protected notify_(value: T): void {\n this.logger_.logMethodArgs?.('notify_', value);\n\n if (this.isDestroyed__) {\n this.logger_.incident?.('notify_', 'notify_on_destroyed_signal');\n return;\n }\n\n // Create a snapshot of the observers array to iterate over.\n // This prevents issues if the observers_ array is modified during the loop.\n const currentObservers = [...this.observers_];\n\n for (const observer of currentObservers) {\n if (observer.options?.once) {\n this.removeObserver_(observer);\n }\n\n try {\n const result = observer.callback(value);\n if (result instanceof Promise) {\n result.catch((err) => this.logger_.error('notify_', 'async_callback_failed', err, {observer}));\n }\n }\n catch (err) {\n this.logger_.error('notify_', 'sync_callback_failed', err);\n }\n }\n }\n\n /**\n * Returns a Promise that resolves with the next value dispatched by the signal.\n * This provides an elegant way to wait for a single, future event using `async/await`.\n *\n * @returns A Promise that resolves with the next dispatched value.\n *\n * @example\n * async function onButtonClick() {\n * console.log('Waiting for the next signal...');\n * const nextValue = await mySignal.untilNext();\n * console.log('Signal received:', nextValue);\n * }\n */\n public untilNext(): Promise<T> {\n this.logger_.logMethod?.('untilNext');\n this.checkDestroyed_();\n return new Promise((resolve) => {\n this.subscribe(resolve, {\n once: true,\n priority: true, // Resolve the promise before other listeners are called.\n receivePrevious: false, // We only want the *next* value, not the current one.\n });\n });\n }\n\n /**\n * Destroys the signal, clearing all its listeners and making it inactive.\n *\n * After destruction, any interaction with the signal (like `subscribe` or `untilNext`)\n * will throw an error. This is crucial for preventing memory leaks by allowing\n * garbage collection of the signal and its observers.\n */\n public destroy(): void {\n this.logger_.logMethod?.('destroy');\n if (this.isDestroyed__) {\n this.logger_.incident?.('destroy_', 'double_destroy_attempt');\n return;\n }\n this.isDestroyed__ = true;\n this.observers_.length = 0; // Clear all observers.\n this.config_.onDestroy?.(); // Call the optional onDestroy callback.\n this.config_ = null as unknown as SignalConfig; // Help GC by breaking references.\n }\n\n /**\n * Throws an error if the signal has been destroyed.\n * This is a safeguard to prevent interaction with a defunct signal.\n * @protected\n */\n protected checkDestroyed_(): void {\n if (this.isDestroyed__) {\n this.logger_.accident('checkDestroyed_', 'attempt_to_use_destroyed_signal');\n throw new Error(`Cannot interact with a destroyed signal (id: ${this.signalId})`);\n }\n }\n}\n", "import {delay} from '@alwatr/delay';\nimport {createLogger} from '@alwatr/logger';\n\nimport {SignalBase} from './signal-base.js';\n\nimport type {SignalConfig} from '../type.js';\n\n/**\n * A stateless signal for dispatching transient events.\n *\n * `EventSignal` is ideal for broadcasting events that do not have a persistent state.\n * Unlike `StateSignal`, it does not hold a value. Listeners are only notified of new\n * events as they are dispatched. This makes it suitable for modeling user interactions,\n * system notifications, or any one-off message.\n *\n * @template T The type of the payload for the events. Defaults to `void` for events without a payload.\n *\n * @example\n * // Create a signal for user click events.\n * const onUserClick = new EventSignal<{ x: number, y: number }>({ signalId: 'on-user-click' });\n *\n * // Subscribe to the event.\n * onUserClick.subscribe(clickPosition => {\n * console.log(`User clicked at: ${clickPosition.x}, ${clickPosition.y}`);\n * });\n *\n * // Dispatch an event.\n * onUserClick.dispatch({ x: 100, y: 250 }); // Notifies the listener.\n *\n * // --- Example with no payload ---\n * const onAppReady = new EventSignal({ signalId: 'on-app-ready' });\n * onAppReady.subscribe(() => console.log('Application is ready!'));\n * onAppReady.dispatch(); // Notifies the listener.\n */\nexport class EventSignal<T = void> extends SignalBase<T> {\n /**\n * The logger instance for this signal.\n * @protected\n */\n protected logger_ = createLogger(`event-signal: ${this.signalId}`);\n\n public constructor(config: SignalConfig) {\n super(config);\n this.logger_.logMethod?.('constructor');\n }\n\n /**\n * Dispatches an event with an optional payload to all active listeners.\n * The notification is scheduled as a microtask to prevent blocking and ensure\n * a consistent, non-blocking flow.\n *\n * @param payload The data to send with the event.\n */\n public dispatch(payload: T): void {\n this.logger_.logMethodArgs?.('dispatch', {payload});\n this.checkDestroyed_();\n // Dispatch as a microtask to ensure consistent, non-blocking behavior.\n delay.nextMicrotask().then(() => this.notify_(payload));\n }\n}\n", "import {delay} from '@alwatr/delay';\nimport {createLogger} from '@alwatr/logger';\n\nimport {SignalBase} from './signal-base.js';\n\nimport type {StateSignalConfig, ListenerCallback, SubscribeOptions, SubscribeResult, IReadonlySignal} from '../type.js';\n\n/**\n * A stateful signal that holds a value and notifies listeners when the value changes.\n *\n * `StateSignal` is the core of the signal library, representing a piece of mutable state.\n * It always has a value, and new subscribers immediately receive the current value by default.\n *\n * @template T The type of the state it holds.\n * @implements {IReadonlySignal<T>}\n *\n * @example\n * // Create a new state signal with an initial value.\n * const counter = new StateSignal<number>({\n * signalId: 'counter-signal',\n * initialValue: 0,\n * });\n *\n * // Get the current value.\n * console.log(counter.value); // Outputs: 0\n *\n * // Subscribe to changes.\n * const subscription = counter.subscribe(newValue => {\n * console.log(`Counter changed to: ${newValue}`);\n * });\n *\n * // Set a new value, which triggers the notification.\n * counter.set(1); // Outputs: \"Counter changed to: 1\"\n *\n * // Update value based on the previous value.\n * counter.update(current => current + 1); // Outputs: \"Counter changed to: 2\"\n *\n * // Unsubscribe when no longer needed.\n * subscription.unsubscribe();\n */\nexport class StateSignal<T> extends SignalBase<T> implements IReadonlySignal<T> {\n /**\n * The current value of the signal.\n * @private\n */\n private value__: T;\n\n /**\n * The logger instance for this signal.\n * @protected\n */\n protected logger_ = createLogger(`state-signal: ${this.signalId}`);\n\n public constructor(config: StateSignalConfig<T>) {\n super(config);\n this.value__ = config.initialValue;\n this.logger_.logMethodArgs?.('constructor', {initialValue: this.value__});\n }\n\n /**\n * Retrieves the current value of the signal.\n *\n * @returns The current value.\n *\n * @example\n * console.log(mySignal.value);\n */\n public get value(): T {\n this.checkDestroyed_();\n return this.value__;\n }\n\n /**\n * Updates the signal's value and notifies all active listeners.\n *\n * The notification is scheduled as a microtask, which means the update is deferred\n * slightly to batch multiple synchronous changes.\n *\n * @param newValue The new value to set.\n *\n * @example\n * // For primitive types\n * mySignal.set(42);\n *\n * // For object types, it's best practice to set an immutable new object.\n * mySignal.set({ ...mySignal.value, property: 'new-value' });\n */\n public set(newValue: T): void {\n this.logger_.logMethodArgs?.('set', {newValue});\n this.checkDestroyed_();\n\n // For primitives (including null), do not notify if the value is the same.\n if (Object.is(this.value__, newValue) && (typeof newValue !== 'object' || newValue === null)) {\n return;\n }\n\n this.value__ = newValue;\n\n // Dispatch as a microtask to ensure consistent, non-blocking behavior.\n delay.nextMicrotask().then(() => this.notify_(newValue));\n }\n\n /**\n * Updates the signal's value based on its previous value.\n *\n * This method is particularly useful for state transitions that depend on the current value,\n * especially for objects or arrays, as it promotes an immutable update pattern.\n *\n * @param updater A function that receives the current value and returns the new value.\n *\n * @example\n * // For a counter\n * counterSignal.update(current => current + 1);\n *\n * // For an object state\n * userSignal.update(currentUser => ({ ...currentUser, loggedIn: true }));\n */\n public update(updater: (previousValue: T) => T): void {\n this.logger_.logMethod?.('update');\n this.checkDestroyed_();\n // The updater function is called with the current value to compute the new value,\n // which is then passed to the `set` method.\n this.set(updater(this.value__));\n }\n\n /**\n * Subscribes a listener to this signal.\n *\n * By default, the listener is immediately called with the signal's current value (`receivePrevious: true`).\n * This behavior can be customized via the `options` parameter.\n *\n * @param callback The function to be called when the signal's value changes.\n * @param options Subscription options, including `receivePrevious` and `once`.\n * @returns An object with an `unsubscribe` method to remove the listener.\n */\n public override subscribe(callback: ListenerCallback<T>, options: SubscribeOptions = {}): SubscribeResult {\n this.logger_.logMethodArgs?.('subscribe', {options});\n this.checkDestroyed_();\n\n // By default, new subscribers to a StateSignal should receive the current value.\n if (options.receivePrevious !== false) {\n // Immediately (but asynchronously) call the listener with the current value.\n // This is done in a microtask to ensure it happens after the subscription is fully registered.\n delay\n .nextMicrotask()\n .then(() => callback(this.value__))\n .catch((err) => this.logger_.error('subscribe', 'immediate_callback_failed', err));\n\n // If it's a 'once' subscription that receives the previous value, it's now fulfilled.\n // We don't need to add it to the observers list for future updates.\n if (options.once) {\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n return {unsubscribe: () => {}};\n }\n }\n\n return super.subscribe(callback, options);\n }\n\n /**\n * Destroys the signal, clearing its value and all listeners.\n * This is crucial for memory management to prevent leaks.\n */\n public override destroy(): void {\n this.value__ = null as T; // Clear the value to allow for garbage collection.\n super.destroy();\n }\n}\n", "import {delay} from '@alwatr/delay';\nimport {createLogger} from '@alwatr/logger';\n\nimport {StateSignal} from './state-signal.js';\n\nimport type {ComputedSignalConfig, IReadonlySignal, SubscribeResult, SubscribeOptions} from '../type.js';\n\n/**\n * A read-only signal that derives its value from a set of dependency signals.\n *\n * `ComputedSignal` is a powerful tool for creating values that reactively update when their underlying\n * data sources change. Its value is memoized, meaning the `get` function is only re-evaluated when\n * one of its dependencies has actually changed.\n *\n * A key feature is its lifecycle management: a `ComputedSignal` **must** be destroyed when no longer\n * needed to prevent memory leaks from its subscriptions to dependency signals.\n *\n * @template T The type of the computed value.\n *\n * @example\n * // --- Create dependency signals ---\n * const firstName = new StateSignal({ signalId: 'firstName', initialValue: 'John' });\n * const lastName = new StateSignal({ signalId: 'lastName', initialValue: 'Doe' });\n *\n * // --- Create a computed signal ---\n * const fullName = new ComputedSignal({\n * signalId: 'fullName',\n * deps: [firstName, lastName],\n * get: () => `${firstName.value} ${lastName.value}`,\n * });\n *\n * console.log(fullName.value); // Outputs: \"John Doe\"\n *\n * // --- Subscribe to the computed value ---\n * fullName.subscribe(newFullName => {\n * console.log(`Name changed to: ${newFullName}`);\n * });\n *\n * // --- Update a dependency ---\n * lastName.set('Smith'); // Recalculates and logs: \"Name changed to: John Smith\"\n * console.log(fullName.value); // Outputs: \"John Smith\"\n *\n * // --- IMPORTANT: Clean up when done ---\n * fullName.destroy();\n */\nexport class ComputedSignal<T> implements IReadonlySignal<T> {\n /**\n * The unique identifier for this signal instance.\n */\n public readonly signalId = this.config_.signalId;\n\n /**\n * The logger instance for this signal.\n * @protected\n */\n protected readonly logger_ = createLogger(`computed-signal: ${this.signalId}`);\n\n /**\n * The internal `StateSignal` that holds the computed value.\n * This is how the computed signal provides `.value` and `.subscribe()` methods.\n * @protected\n */\n protected readonly internalSignal_ = new StateSignal<T>({\n signalId: `${this.signalId}-internal`,\n initialValue: this.config_.get(),\n });\n\n /**\n * A list of subscriptions to dependency signals.\n * @private\n */\n\n private readonly dependencySubscriptions__: SubscribeResult[] = [];\n\n /**\n * A flag to prevent concurrent recalculations.\n * @private\n */\n private isRecalculating__ = false;\n\n public constructor(protected config_: ComputedSignalConfig<T>) {\n this.logger_.logMethod?.('constructor');\n this.recalculate_ = this.recalculate_.bind(this);\n\n // Subscribe to all dependencies to trigger recalculation on change.\n for (const signal of config_.deps) {\n this.dependencySubscriptions__.push(signal.subscribe(this.recalculate_, {receivePrevious: false}));\n }\n }\n\n /**\n * The current value of the computed signal.\n * Accessing this property returns the memoized value and does not trigger a recalculation.\n *\n * @returns The current computed value.\n * @throws {Error} If accessed after the signal has been destroyed.\n */\n public get value(): T {\n return this.internalSignal_.value;\n }\n\n /**\n * Indicates whether the computed signal has been destroyed.\n * A destroyed signal cannot be used and will throw an error if interacted with.\n * @returns `true` if the signal is destroyed, `false` otherwise.\n */\n public get isDestroyed(): boolean {\n return this.internalSignal_.isDestroyed;\n }\n\n /**\n * Subscribes a listener to this signal.\n * The listener will be called whenever the computed value changes.\n *\n * @param callback The function to be called with the new value.\n * @param options Subscription options.\n * @returns A `SubscribeResult` object with an `unsubscribe` method.\n */\n public subscribe(callback: (value: T) => void, options?: SubscribeOptions): SubscribeResult {\n return this.internalSignal_.subscribe(callback, options);\n }\n\n /**\n * Returns a Promise that resolves with the next computed value.\n *\n * @returns A Promise that resolves with the next value.\n */\n public untilNext(): Promise<T> {\n return this.internalSignal_.untilNext();\n }\n\n /**\n * Permanently disposes of the computed signal.\n *\n * This is a critical cleanup step. It unsubscribes from all dependency signals,\n * stopping future recalculations and allowing the signal to be garbage collected.\n * Failure to call `destroy()` will result in memory leaks.\n *\n * After `destroy()` is called, any attempt to access `.value` or `.subscribe()` will throw an error.\n */\n public destroy(): void {\n this.logger_.logMethod?.('destroy');\n /**\n * If already destroyed, log an incident and return early.\n */\n if (this.isDestroyed) {\n this.logger_.incident?.('destroy', 'already_destroyed');\n return;\n }\n\n // Unsubscribe from all upstream dependencies.\n for (const subscription of this.dependencySubscriptions__) {\n subscription.unsubscribe();\n }\n this.dependencySubscriptions__.length = 0; // Clear the array of subscriptions.\n\n this.internalSignal_.destroy(); // Destroy the internal signal.\n this.config_.onDestroy?.(); // Call the optional onDestroy callback.\n this.config_ = null as unknown as ComputedSignalConfig<T>; // Release config closure.\n }\n\n /**\n * Schedules a recalculation of the signal's value.\n *\n * This method batches updates using a macrotask (`delay.nextMacrotask`) to ensure the\n * `get` function runs only once per event loop tick, even if multiple dependencies\n * change in the same synchronous block of code.\n * @protected\n */\n protected async recalculate_(): Promise<void> {\n this.logger_.logMethod?.('recalculate_');\n\n if (this.internalSignal_.isDestroyed) {\n // This check is important in case a dependency fires after this signal is destroyed.\n this.logger_.incident?.('recalculate_', 'recalculate_on_destroyed_signal');\n return;\n }\n\n if (this.isRecalculating__) {\n // If a recalculation is already scheduled, do nothing.\n this.logger_.logStep?.('recalculate_', 'skipping_recalculation_already_scheduled');\n return;\n }\n\n this.isRecalculating__ = true;\n\n try {\n // Wait for the next macrotask to start the recalculation.\n // This batches all synchronous dependency updates in the current event loop.\n await delay.nextMacrotask();\n \n if (this.isDestroyed) {\n this.logger_.incident?.('recalculate_', 'destroyed_during_delay');\n this.isRecalculating__ = false;\n return;\n }\n\n this.logger_.logStep?.('recalculate_', 'recalculating_value');\n\n // Set the new value on the internal signal, which will notify our subscribers.\n this.internalSignal_.set(this.config_.get());\n }\n catch (err) {\n this.logger_.error('recalculate_', 'recalculation_failed', err);\n }\n\n // Allow the next recalculation to be scheduled.\n this.isRecalculating__ = false;\n }\n}\n", "import {delay} from '@alwatr/delay';\nimport {createLogger} from '@alwatr/logger';\n\nimport type {EffectSignalConfig, IEffectSignal, SubscribeResult} from '../type.js';\n\n/**\n * Manages a side-effect that runs in response to changes in dependency signals.\n *\n * `EffectSignal` is designed for running logic that interacts with the \"outside world\"—such as\n * logging, network requests, or DOM manipulation—whenever its dependencies are updated.\n * It encapsulates the subscription and cleanup logic, providing a robust and memory-safe\n * way to handle reactive side-effects.\n *\n * A key feature is its lifecycle management: an `EffectSignal` **must** be destroyed when no longer\n * needed to prevent memory leaks and stop the effect from running unnecessarily.\n *\n * @implements {IEffectSignal}\n *\n * @example\n * // --- Create dependency signals ---\n * const counter = new StateSignal({ initialValue: 0, signalId: 'counter' });\n * const user = new StateSignal({ initialValue: 'guest', signalId: 'user' });\n *\n * // --- Create an effect ---\n * const analyticsEffect = new EffectSignal({\n * signalId: 'analytics-effect',\n * deps: [counter, user],\n * run: () => {\n * console.log(`Analytics: User '${user.value}' clicked ${counter.value} times.`);\n * },\n * runImmediately: true, // Optional: run once on creation\n * });\n * // Immediately logs: \"Analytics: User 'guest' clicked 0 times.\"\n *\n * // --- Trigger the effect by updating a dependency ---\n * counter.set(1);\n * // After a macrotask, logs: \"Analytics: User 'guest' clicked 1 times.\"\n *\n * // --- IMPORTANT: Clean up when the effect is no longer needed ---\n * analyticsEffect.destroy();\n *\n * // Further updates will not trigger the effect.\n * counter.set(2); // Nothing is logged.\n */\nexport class EffectSignal implements IEffectSignal {\n /**\n * The unique identifier for this signal instance.\n */\n public readonly signalId = this.config_.signalId ? this.config_.signalId : `[${this.config_.deps.map((dep) => dep.signalId).join(', ')}]`;\n\n /**\n * The logger instance for this signal.\n * @protected\n */\n protected readonly logger_ = createLogger(`effect-signal: ${this.signalId}`);\n\n /**\n * A list of subscriptions to dependency signals.\n * @private\n */\n private readonly dependencySubscriptions__: SubscribeResult[] = [];\n\n /**\n * A flag to prevent concurrent executions of the effect.\n * @private\n */\n private isRunning__ = false;\n\n /**\n * A flag indicating whether the effect has been destroyed.\n * @private\n */\n private isDestroyed__ = false;\n\n /**\n * Indicates whether the effect signal has been destroyed.\n * A destroyed signal will no longer execute its effect and cannot be reused.\n *\n * @returns `true` if the signal is destroyed, `false` otherwise.\n */\n public get isDestroyed(): boolean {\n return this.isDestroyed__;\n }\n\n public constructor(protected config_: EffectSignalConfig) {\n this.logger_.logMethod?.('constructor');\n this.run_ = this.run_.bind(this);\n\n // Subscribe to all dependencies. We don't need the previous value,\n // as the `runImmediately` option controls the initial execution.\n for (const signal of config_.deps) {\n this.dependencySubscriptions__.push(signal.subscribe(this.run_, {receivePrevious: false}));\n }\n\n // Run the effect immediately if requested.\n if (config_.runImmediately === true) {\n // We don't need to await this, let it run in the background.\n void this.run_();\n }\n }\n\n /**\n * Schedules the execution of the effect's `run` function.\n *\n * This method batches updates using a macrotask (`delay.nextMacrotask`) to ensure the\n * `run` function executes only once per event loop tick, even if multiple\n * dependencies change simultaneously.\n * @protected\n */\n protected async run_(): Promise<void> {\n this.logger_.logMethod?.('run_');\n\n if (this.isDestroyed__) {\n this.logger_.incident?.('run_', 'run_on_destroyed_signal');\n return;\n }\n if (this.isRunning__) {\n // If an execution is already scheduled, do nothing.\n this.logger_.logStep?.('run_', 'skipped_because_already_running');\n return;\n }\n\n this.isRunning__ = true;\n\n try {\n // Wait for the next macrotask to batch simultaneous updates.\n await delay.nextMacrotask();\n if (this.isDestroyed__) {\n this.logger_.incident?.('run_', 'destroyed_during_delay');\n this.isRunning__ = false;\n return;\n }\n\n this.logger_.logStep?.('run_', 'executing_effect');\n await this.config_.run();\n }\n catch (err) {\n this.logger_.error('run_', 'effect_failed', err);\n }\n\n // Reset the flag after the current execution is complete.\n this.isRunning__ = false;\n }\n\n /**\n * Permanently disposes of the effect signal.\n *\n * This is a critical cleanup step. It unsubscribes from all dependency signals,\n * stopping any future executions of the effect and allowing it to be garbage collected.\n * Failure to call `destroy()` will result in memory leaks and potentially unwanted side effects.\n */\n public destroy(): void {\n this.logger_.logMethod?.('destroy');\n\n if (this.isDestroyed__) {\n this.logger_.incident?.('destroy', 'already_destroyed');\n return;\n }\n\n this.isDestroyed__ = true;\n\n // Unsubscribe from all upstream dependencies.\n for (const subscription of this.dependencySubscriptions__) {\n subscription.unsubscribe();\n }\n this.dependencySubscriptions__.length = 0; // Clear the array of subscriptions.\n\n this.config_.onDestroy?.(); // Call the optional onDestroy callback.\n this.config_ = null as unknown as EffectSignalConfig; // Release config closure.\n }\n}\n", "import {EventSignal} from '../core/event-signal.js';\n\nimport type {SignalConfig} from '../type.js';\n\n/**\n * Creates a stateless signal for dispatching transient events.\n *\n * `EventSignal` is ideal for broadcasting events that do not have a persistent state.\n * Unlike `StateSignal`, it does not hold a value. Listeners are only notified of new\n * events as they are dispatched. This makes it suitable for modeling user interactions,\n * system notifications, or any one-off message.\n *\n * @template T The type of the payload for the events.\n *\n * @param config The configuration for the event signal.\n * @returns A new instance of EventSignal.\n *\n * @example\n * const onUserClick = createEventSignal<{ x: number, y: number }>({\n * signalId: 'on-user-click'\n * });\n *\n * onUserClick.subscribe(pos => {\n * console.log(`User clicked at: ${pos.x}, ${pos.y}`);\n * });\n *\n * onUserClick.dispatch({ x: 100, y: 250 });\n */\nexport function createEventSignal<T = void>(config: SignalConfig): EventSignal<T> {\n return new EventSignal<T>(config);\n}\n", "import {StateSignal} from '../core/state-signal.js';\n\nimport type {StateSignalConfig} from '../type.js';\n\n/**\n * Creates a stateful signal that holds a value and notifies listeners when the value changes.\n *\n * `StateSignal` is the core of the signal library, representing a piece of mutable state.\n * It always has a value, and new subscribers immediately receive the current value by default.\n *\n * @template T The type of the state it holds.\n *\n * @param config The configuration for the state signal.\n * @returns A new instance of StateSignal.\n *\n * @example\n * const counter = createStateSignal({\n * signalId: 'counter-signal',\n * initialValue: 0,\n * });\n *\n * console.log(counter.value); // Outputs: 0\n * counter.set(1);\n * console.log(counter.value); // Outputs: 1\n */\nexport function createStateSignal<T>(config: StateSignalConfig<T>): StateSignal<T> {\n return new StateSignal(config);\n}\n", "import {ComputedSignal} from '../core/computed-signal.js';\n\nimport type {ComputedSignalConfig} from '../type.js';\n\n/**\n * Creates a read-only signal that derives its value from a set of dependency signals.\n *\n * `ComputedSignal` is a powerful tool for creating values that reactively update when their underlying\n * data sources change. Its value is memoized, meaning the `get` function is only re-evaluated when\n * one of its dependencies has actually changed.\n *\n * A key feature is its lifecycle management: a `ComputedSignal` **must** be destroyed when no longer\n * needed to prevent memory leaks from its subscriptions to dependency signals.\n *\n * @template T The type of the computed value.\n *\n * @param config The configuration for the computed signal.\n * @returns A new, read-only computed signal.\n *\n * @example\n * const firstName = createStateSignal({ signalId: 'firstName', initialValue: 'John' });\n * const lastName = createStateSignal({ signalId: 'lastName', initialValue: 'Doe' });\n *\n * const fullName = createComputedSignal({\n * signalId: 'fullName',\n * deps: [firstName, lastName],\n * get: () => `${firstName.value} ${lastName.value}`,\n * });\n *\n * console.log(fullName.value); // \"John Doe\"\n *\n * // IMPORTANT: Always destroy a computed signal when no longer needed.\n * // fullName.destroy();\n */\nexport function createComputedSignal<T>(config: ComputedSignalConfig<T>): ComputedSignal<T> {\n return new ComputedSignal(config);\n}\n", "import {EffectSignal} from '../core/effect-signal.js';\n\nimport type {EffectSignalConfig} from '../type.js';\n\n/**\n * Creates a side-effect that runs in response to changes in dependency signals.\n *\n * `EffectSignal` is designed for running logic that interacts with the \"outside world\"—such as\n * logging, network requests, or DOM manipulation—whenever its dependencies are updated.\n * It encapsulates the subscription and cleanup logic, providing a robust and memory-safe\n * way to handle reactive side-effects.\n *\n * A key feature is its lifecycle management: an `EffectSignal` **must** be destroyed when no longer\n * needed to prevent memory leaks and stop the effect from running unnecessarily.\n *\n * @param config The configuration for the effect.\n * @returns An object with a `destroy` method to stop the effect.\n *\n * @example\n * // --- Create dependency signals ---\n * const counter = createStateSignal({ initialValue: 0, signalId: 'counter' });\n * const user = createStateSignal({ initialValue: 'guest', signalId: 'user' });\n *\n * // --- Create an effect ---\n * const analyticsEffect = createEffect({\n * deps: [counter, user],\n * run: () => {\n * console.log(`Analytics: User '${user.value}' clicked ${counter.value} times.`);\n * },\n * runImmediately: true, // Optional: run once on creation\n * });\n * // Immediately logs: \"Analytics: User 'guest' clicked 0 times.\"\n *\n * // --- Trigger the effect by updating a dependency ---\n * counter.set(1);\n * // After a macrotask, logs: \"Analytics: User 'guest' clicked 1 times.\"\n *\n * // --- IMPORTANT: Clean up when the effect is no longer needed ---\n * analyticsEffect.destroy();\n *\n * // Further updates will not trigger the effect.\n * counter.set(2); // Nothing is logged.\n */\nexport function createEffect(config: EffectSignalConfig): EffectSignal {\n return new EffectSignal(config);\n}\n", "import {createDebouncer} from '@alwatr/debounce';\n\nimport {StateSignal} from '../core/state-signal.js';\nimport {createComputedSignal} from '../creators/computed.js';\n\nimport type {ComputedSignal} from '../core/computed-signal.js';\nimport type {IReadonlySignal, DebounceSignalConfig} from '../type.js';\n\n/**\n * Creates a new computed signal that debounces updates from a source signal.\n *\n * The returned signal is a `ComputedSignal`, meaning it is read-only and its value is\n * derived from the source. It only updates its value after a specified period of\n * inactivity from the source signal.\n *\n * This operator is essential for handling high-frequency events, such as user input\n * in a search box, resizing a window, or any other event that fires rapidly.\n * By debouncing, you can ensure that expensive operations (like API calls or heavy\n * computations) are only executed once the events have settled.\n *\n * @template T The type of the signal's value.\n *\n * @param {IReadonlySignal<T>} sourceSignal The original signal to debounce.\n * It can be a `StateSignal`, `ComputedSignal`, or any signal implementing `IReadonlySignal`.\n * @param {DebounceSignalConfig} config Configuration object for the debouncer,\n * including `delay`, `leading`, and `trailing` options from `@alwatr/debounce`.\n *\n * @returns {IComputedSignal<T>} A new, read-only computed signal that emits debounced values.\n * Crucially, you **must** call `.destroy()` on this signal when it's no longer\n * needed to prevent memory leaks by cleaning up internal subscriptions and timers.\n *\n * @example\n * ```typescript\n * // Create a source signal for user input.\n * const searchInput = createStateSignal({\n * signalId: 'search-input',\n * initialValue: '',\n * });\n *\n * // Create a debounced signal that waits 300ms after the user stops typing.\n * const debouncedSearch = createDebouncedSignal(searchInput, { delay: 300 });\n *\n * // Use an effect to react to the debounced value.\n * createEffect({\n * deps: [debouncedSearch],\n * run: () => {\n * if (debouncedSearch.value) {\n * console.log(`🚀 Sending API request for: \"${debouncedSearch.value}\"`);\n * }\n * },\n * });\n *\n * searchInput.set('Alwatr');\n * searchInput.set('Alwatr Signal');\n * // (after 300ms of inactivity)\n * // Logs: \"🚀 Sending API request for: \"Alwatr Signal\"\"\n *\n * // IMPORTANT: Clean up when the component unmounts.\n * // debouncedSearch.destroy();\n * ```\n */\nexport function createDebouncedSignal<T>(sourceSignal: IReadonlySignal<T>, config: DebounceSignalConfig): ComputedSignal<T> {\n const signalId = config.signalId ?? `${sourceSignal.signalId}-debounced`;\n\n const internalSignal = new StateSignal<T>({\n signalId: `${signalId}-internal`,\n initialValue: sourceSignal.value,\n });\n\n const debouncer = createDebouncer({\n ...config,\n func: (value: T): void => {\n internalSignal.set(value);\n },\n });\n\n const subscription = sourceSignal.subscribe(debouncer.trigger);\n\n return createComputedSignal({\n signalId,\n deps: [internalSignal],\n get: () => internalSignal.value,\n onDestroy: () => {\n if (internalSignal.isDestroyed) return;\n subscription.unsubscribe();\n debouncer.cancel();\n internalSignal.destroy();\n config.onDestroy?.();\n config = null as unknown as DebounceSignalConfig;\n },\n });\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["export * from './core/signal-base.js';\nexport * from './core/event-signal.js';\nexport * from './core/state-signal.js';\nexport * from './core/computed-signal.js';\nexport * from './core/effect-signal.js';\n\nexport * from './creators/event.js';\nexport * from './creators/state.js';\nexport * from './creators/computed.js';\nexport * from './creators/effect.js';\n\nexport * from './operators/debounce.js';\n\nexport type * from './type.js';\n", "import type {Observer_, SubscribeOptions, SubscribeResult, ListenerCallback, SignalConfig} from '../type.js';\nimport type {AlwatrLogger} from '@alwatr/logger';\nimport type {} from '@alwatr/nano-build';\n\n/**\n * An abstract base class for signal implementations.\n * It provides the core functionality for managing subscriptions (observers).\n *\n * @template T The type of data that the signal holds or dispatches.\n */\nexport abstract class SignalBase<T> {\n /**\n * The unique identifier for this signal instance.\n * Useful for debugging and logging.\n */\n public readonly signalId = this.config_.signalId;\n\n /**\n * The logger instance for this signal.\n * @protected\n */\n protected abstract logger_: AlwatrLogger;\n\n /**\n * The list of observers (listeners) subscribed to this signal.\n * @protected\n */\n protected readonly observers_: Observer_<T>[] = [];\n\n /**\n * A flag indicating whether the signal has been destroyed.\n * @private\n */\n private isDestroyed__ = false;\n\n /**\n * Indicates whether the signal has been destroyed.\n * A destroyed signal cannot be used and will throw an error if interacted with.\n *\n * @returns `true` if the signal is destroyed, `false` otherwise.\n */\n public get isDestroyed(): boolean {\n return this.isDestroyed__;\n }\n\n public constructor(protected config_: SignalConfig) {}\n\n /**\n * Removes a specific observer from the observers list.\n *\n * @param observer The observer instance to remove.\n * @protected\n */\n protected removeObserver_(observer: Observer_<T>): void {\n this.logger_.logMethod?.('removeObserver_');\n\n if (this.isDestroyed__) {\n this.logger_.incident?.('removeObserver_', 'remove_observer_on_destroyed_signal');\n return;\n }\n\n const index = this.observers_.indexOf(observer);\n if (index !== -1) {\n this.observers_.splice(index, 1);\n }\n }\n\n /**\n * Subscribes a listener function to this signal.\n *\n * The listener will be called whenever the signal is notified (e.g., when `dispatch` or `set` is called).\n *\n * @param callback The function to be called when the signal is dispatched.\n * @param options Subscription options to customize the behavior (e.g., `once`, `priority`).\n * @returns A `SubscribeResult` object with an `unsubscribe` method to remove the listener.\n */\n public subscribe(callback: ListenerCallback<T>, options?: SubscribeOptions): SubscribeResult {\n this.logger_.logMethodArgs?.('subscribe.base', {options});\n this.checkDestroyed_();\n\n const observer: Observer_<T> = {callback, options};\n\n if (options?.priority) {\n // High-priority observers are added to the front of the queue.\n this.observers_.unshift(observer);\n }\n else {\n this.observers_.push(observer);\n }\n\n // The returned unsubscribe function is a closure that calls the internal removal method.\n return {\n unsubscribe: (): void => this.removeObserver_(observer),\n };\n }\n\n /**\n * Notifies all registered observers about a new value.\n *\n * This method iterates through a snapshot of the current observers to prevent issues\n * with subscriptions changing during notification (e.g., an observer unsubscribing itself).\n *\n * @param value The new value to notify observers about.\n * @protected\n */\n protected notify_(value: T): void {\n this.logger_.logMethodArgs?.('notify_', value);\n\n if (this.isDestroyed__) {\n this.logger_.incident?.('notify_', 'notify_on_destroyed_signal');\n return;\n }\n\n // Create a snapshot of the observers array to iterate over.\n // This prevents issues if the observers_ array is modified during the loop.\n const currentObservers = [...this.observers_];\n\n for (const observer of currentObservers) {\n if (observer.options?.once) {\n this.removeObserver_(observer);\n }\n\n try {\n const result = observer.callback(value);\n if (result instanceof Promise) {\n result.catch((err) => this.logger_.error('notify_', 'async_callback_failed', err, {observer}));\n }\n }\n catch (err) {\n this.logger_.error('notify_', 'sync_callback_failed', err);\n }\n }\n }\n\n /**\n * Returns a Promise that resolves with the next value dispatched by the signal.\n * This provides an elegant way to wait for a single, future event using `async/await`.\n *\n * @returns A Promise that resolves with the next dispatched value.\n *\n * @example\n * async function onButtonClick() {\n * console.log('Waiting for the next signal...');\n * const nextValue = await mySignal.untilNext();\n * console.log('Signal received:', nextValue);\n * }\n */\n public untilNext(): Promise<T> {\n this.logger_.logMethod?.('untilNext');\n this.checkDestroyed_();\n return new Promise((resolve) => {\n this.subscribe(resolve, {\n once: true,\n priority: true, // Resolve the promise before other listeners are called.\n receivePrevious: false, // We only want the *next* value, not the current one.\n });\n });\n }\n\n /**\n * Destroys the signal, clearing all its listeners and making it inactive.\n *\n * After destruction, any interaction with the signal (like `subscribe` or `untilNext`)\n * will throw an error. This is crucial for preventing memory leaks by allowing\n * garbage collection of the signal and its observers.\n */\n public destroy(): void {\n this.logger_.logMethod?.('destroy');\n if (this.isDestroyed__) {\n this.logger_.incident?.('destroy_', 'double_destroy_attempt');\n return;\n }\n this.isDestroyed__ = true;\n this.observers_.length = 0; // Clear all observers.\n this.config_.onDestroy?.(); // Call the optional onDestroy callback.\n this.config_ = null as unknown as SignalConfig; // Help GC by breaking references.\n }\n\n /**\n * Throws an error if the signal has been destroyed.\n * This is a safeguard to prevent interaction with a defunct signal.\n * @protected\n */\n protected checkDestroyed_(): void {\n if (this.isDestroyed__) {\n this.logger_.accident('checkDestroyed_', 'attempt_to_use_destroyed_signal');\n throw new Error(`Cannot interact with a destroyed signal (id: ${this.signalId})`);\n }\n }\n}\n", "import {delay} from '@alwatr/delay';\nimport {createLogger} from '@alwatr/logger';\n\nimport {SignalBase} from './signal-base.js';\n\nimport type {SignalConfig} from '../type.js';\n\n/**\n * A stateless signal for dispatching transient events.\n *\n * `EventSignal` is ideal for broadcasting events that do not have a persistent state.\n * Unlike `StateSignal`, it does not hold a value. Listeners are only notified of new\n * events as they are dispatched. This makes it suitable for modeling user interactions,\n * system notifications, or any one-off message.\n *\n * @template T The type of the payload for the events. Defaults to `void` for events without a payload.\n *\n * @example\n * // Create a signal for user click events.\n * const onUserClick = new EventSignal<{ x: number, y: number }>({ signalId: 'on-user-click' });\n *\n * // Subscribe to the event.\n * onUserClick.subscribe(clickPosition => {\n * console.log(`User clicked at: ${clickPosition.x}, ${clickPosition.y}`);\n * });\n *\n * // Dispatch an event.\n * onUserClick.dispatch({ x: 100, y: 250 }); // Notifies the listener.\n *\n * // --- Example with no payload ---\n * const onAppReady = new EventSignal({ signalId: 'on-app-ready' });\n * onAppReady.subscribe(() => console.log('Application is ready!'));\n * onAppReady.dispatch(); // Notifies the listener.\n */\nexport class EventSignal<T = void> extends SignalBase<T> {\n /**\n * The logger instance for this signal.\n * @protected\n */\n protected logger_ = createLogger(`event-signal: ${this.signalId}`);\n\n public constructor(config: SignalConfig) {\n super(config);\n this.logger_.logMethod?.('constructor');\n }\n\n /**\n * Dispatches an event with an optional payload to all active listeners.\n * The notification is scheduled as a microtask to prevent blocking and ensure\n * a consistent, non-blocking flow.\n *\n * @param payload The data to send with the event.\n */\n public dispatch(payload: T): void {\n this.logger_.logMethodArgs?.('dispatch', {payload});\n this.checkDestroyed_();\n // Dispatch as a microtask to ensure consistent, non-blocking behavior.\n delay.nextMicrotask().then(() => this.notify_(payload));\n }\n}\n", "import {delay} from '@alwatr/delay';\nimport {createLogger} from '@alwatr/logger';\n\nimport {SignalBase} from './signal-base.js';\n\nimport type {StateSignalConfig, ListenerCallback, SubscribeOptions, SubscribeResult, IReadonlySignal} from '../type.js';\n\n/**\n * A stateful signal that holds a value and notifies listeners when the value changes.\n *\n * `StateSignal` is the core of the signal library, representing a piece of mutable state.\n * It always has a value, and new subscribers immediately receive the current value by default.\n *\n * @template T The type of the state it holds.\n * @implements {IReadonlySignal<T>}\n *\n * @example\n * // Create a new state signal with an initial value.\n * const counter = new StateSignal<number>({\n * signalId: 'counter-signal',\n * initialValue: 0,\n * });\n *\n * // Get the current value.\n * console.log(counter.get()); // Outputs: 0\n *\n * // Subscribe to changes.\n * const subscription = counter.subscribe(newValue => {\n * console.log(`Counter changed to: ${newValue}`);\n * });\n *\n * // Set a new value, which triggers the notification.\n * counter.set(1); // Outputs: \"Counter changed to: 1\"\n *\n * // Update value based on the previous value.\n * counter.update(current => current + 1); // Outputs: \"Counter changed to: 2\"\n *\n * // Unsubscribe when no longer needed.\n * subscription.unsubscribe();\n */\nexport class StateSignal<T> extends SignalBase<T> implements IReadonlySignal<T> {\n /**\n * The current value of the signal.\n * @private\n */\n private value__: T;\n\n /**\n * The logger instance for this signal.\n * @protected\n */\n protected logger_ = createLogger(`state-signal: ${this.signalId}`);\n\n public constructor(config: StateSignalConfig<T>) {\n super(config);\n this.value__ = config.initialValue;\n this.logger_.logMethodArgs?.('constructor', {initialValue: this.value__});\n }\n\n /**\n * Retrieves the current value of the signal.\n *\n * @returns The current value.\n *\n * @example\n * console.log(mySignal.get());\n */\n public get(): T {\n this.checkDestroyed_();\n return this.value__;\n }\n\n /**\n * Updates the signal's value and notifies all active listeners.\n *\n * The notification is scheduled as a microtask, which means the update is deferred\n * slightly to batch multiple synchronous changes.\n *\n * @param newValue The new value to set.\n *\n * @example\n * // For primitive types\n * mySignal.set(42);\n *\n * // For object types, it's best practice to set an immutable new object.\n * mySignal.set({ ...mySignal.get(), property: 'new-value' });\n */\n public set(newValue: T): void {\n this.logger_.logMethodArgs?.('set', {newValue});\n this.checkDestroyed_();\n\n // For primitives (including null), do not notify if the value is the same.\n if (Object.is(this.value__, newValue) && (typeof newValue !== 'object' || newValue === null)) {\n return;\n }\n\n this.value__ = newValue;\n\n // Dispatch as a microtask to ensure consistent, non-blocking behavior.\n delay.nextMicrotask().then(() => this.notify_(newValue));\n }\n\n /**\n * Updates the signal's value based on its previous value.\n *\n * This method is particularly useful for state transitions that depend on the current value,\n * especially for objects or arrays, as it promotes an immutable update pattern.\n *\n * @param updater A function that receives the current value and returns the new value.\n *\n * @example\n * // For a counter\n * counterSignal.update(current => current + 1);\n *\n * // For an object state\n * userSignal.update(currentUser => ({ ...currentUser, loggedIn: true }));\n */\n public update(updater: (previousValue: T) => T): void {\n this.logger_.logMethod?.('update');\n this.checkDestroyed_();\n // The updater function is called with the current value to compute the new value,\n // which is then passed to the `set` method.\n this.set(updater(this.value__));\n }\n\n /**\n * Subscribes a listener to this signal.\n *\n * By default, the listener is immediately called with the signal's current value (`receivePrevious: true`).\n * This behavior can be customized via the `options` parameter.\n *\n * @param callback The function to be called when the signal's value changes.\n * @param options Subscription options, including `receivePrevious` and `once`.\n * @returns An object with an `unsubscribe` method to remove the listener.\n */\n public override subscribe(callback: ListenerCallback<T>, options: SubscribeOptions = {}): SubscribeResult {\n this.logger_.logMethodArgs?.('subscribe', {options});\n this.checkDestroyed_();\n\n // By default, new subscribers to a StateSignal should receive the current value.\n if (options.receivePrevious !== false) {\n // Immediately (but asynchronously) call the listener with the current value.\n // This is done in a microtask to ensure it happens after the subscription is fully registered.\n delay\n .nextMicrotask()\n .then(() => callback(this.value__))\n .catch((err) => this.logger_.error('subscribe', 'immediate_callback_failed', err));\n\n // If it's a 'once' subscription that receives the previous value, it's now fulfilled.\n // We don't need to add it to the observers list for future updates.\n if (options.once) {\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n return {unsubscribe: () => {}};\n }\n }\n\n return super.subscribe(callback, options);\n }\n\n /**\n * Destroys the signal, clearing its value and all listeners.\n * This is crucial for memory management to prevent leaks.\n */\n public override destroy(): void {\n this.value__ = null as T; // Clear the value to allow for garbage collection.\n super.destroy();\n }\n}\n", "import {delay} from '@alwatr/delay';\nimport {createLogger} from '@alwatr/logger';\n\nimport {StateSignal} from './state-signal.js';\n\nimport type {ComputedSignalConfig, IReadonlySignal, SubscribeResult, SubscribeOptions} from '../type.js';\n\n/**\n * A read-only signal that derives its value from a set of dependency signals.\n *\n * `ComputedSignal` is a powerful tool for creating values that reactively update when their underlying\n * data sources change. Its value is memoized, meaning the `get` function is only re-evaluated when\n * one of its dependencies has actually changed.\n *\n * A key feature is its lifecycle management: a `ComputedSignal` **must** be destroyed when no longer\n * needed to prevent memory leaks from its subscriptions to dependency signals.\n *\n * @template T The type of the computed value.\n *\n * @example\n * // --- Create dependency signals ---\n * const firstName = new StateSignal({ signalId: 'firstName', initialValue: 'John' });\n * const lastName = new StateSignal({ signalId: 'lastName', initialValue: 'Doe' });\n *\n * // --- Create a computed signal ---\n * const fullName = new ComputedSignal({\n * signalId: 'fullName',\n * deps: [firstName, lastName],\n * get: () => `${firstName.get()} ${lastName.get()}`,\n * });\n *\n * console.log(fullName.get()); // Outputs: \"John Doe\"\n *\n * // --- Subscribe to the computed value ---\n * fullName.subscribe(newFullName => {\n * console.log(`Name changed to: ${newFullName}`);\n * });\n *\n * // --- Update a dependency ---\n * lastName.set('Smith'); // Recalculates and logs: \"Name changed to: John Smith\"\n * console.log(fullName.get()); // Outputs: \"John Smith\"\n *\n * // --- IMPORTANT: Clean up when done ---\n * fullName.destroy();\n */\nexport class ComputedSignal<T> implements IReadonlySignal<T> {\n /**\n * The unique identifier for this signal instance.\n */\n public readonly signalId = this.config_.signalId;\n\n /**\n * The logger instance for this signal.\n * @protected\n */\n protected readonly logger_ = createLogger(`computed-signal: ${this.signalId}`);\n\n /**\n * The internal `StateSignal` that holds the computed value.\n * This is how the computed signal provides `.get()` and `.subscribe()` methods.\n * @protected\n */\n protected readonly internalSignal_ = new StateSignal<T>({\n signalId: `${this.signalId}-internal`,\n initialValue: this.config_.get(),\n });\n\n /**\n * A list of subscriptions to dependency signals.\n * @private\n */\n\n private readonly dependencySubscriptions__: SubscribeResult[] = [];\n\n /**\n * A flag to prevent concurrent recalculations.\n * @private\n */\n private isRecalculating__ = false;\n\n public constructor(protected config_: ComputedSignalConfig<T>) {\n this.logger_.logMethod?.('constructor');\n this.recalculate_ = this.recalculate_.bind(this);\n\n // Subscribe to all dependencies to trigger recalculation on change.\n for (const signal of config_.deps) {\n this.dependencySubscriptions__.push(signal.subscribe(this.recalculate_, {receivePrevious: false}));\n }\n }\n\n /**\n * The current value of the computed signal.\n * Accessing this property returns the memoized value and does not trigger a recalculation.\n *\n * @returns The current computed value.\n * @throws {Error} If accessed after the signal has been destroyed.\n */\n public get(): T {\n return this.internalSignal_.get();\n }\n\n /**\n * Indicates whether the computed signal has been destroyed.\n * A destroyed signal cannot be used and will throw an error if interacted with.\n * @returns `true` if the signal is destroyed, `false` otherwise.\n */\n public get isDestroyed(): boolean {\n return this.internalSignal_.isDestroyed;\n }\n\n /**\n * Subscribes a listener to this signal.\n * The listener will be called whenever the computed value changes.\n *\n * @param callback The function to be called with the new value.\n * @param options Subscription options.\n * @returns A `SubscribeResult` object with an `unsubscribe` method.\n */\n public subscribe(callback: (value: T) => void, options?: SubscribeOptions): SubscribeResult {\n return this.internalSignal_.subscribe(callback, options);\n }\n\n /**\n * Returns a Promise that resolves with the next computed value.\n *\n * @returns A Promise that resolves with the next value.\n */\n public untilNext(): Promise<T> {\n return this.internalSignal_.untilNext();\n }\n\n /**\n * Permanently disposes of the computed signal.\n *\n * This is a critical cleanup step. It unsubscribes from all dependency signals,\n * stopping future recalculations and allowing the signal to be garbage collected.\n * Failure to call `destroy()` will result in memory leaks.\n *\n * After `destroy()` is called, any attempt to access `.get()` or `.subscribe()` will throw an error.\n */\n public destroy(): void {\n this.logger_.logMethod?.('destroy');\n /**\n * If already destroyed, log an incident and return early.\n */\n if (this.isDestroyed) {\n this.logger_.incident?.('destroy', 'already_destroyed');\n return;\n }\n\n // Unsubscribe from all upstream dependencies.\n for (const subscription of this.dependencySubscriptions__) {\n subscription.unsubscribe();\n }\n this.dependencySubscriptions__.length = 0; // Clear the array of subscriptions.\n\n this.internalSignal_.destroy(); // Destroy the internal signal.\n this.config_.onDestroy?.(); // Call the optional onDestroy callback.\n this.config_ = null as unknown as ComputedSignalConfig<T>; // Release config closure.\n }\n\n /**\n * Schedules a recalculation of the signal's value.\n *\n * This method batches updates using a macrotask (`delay.nextMacrotask`) to ensure the\n * `get` function runs only once per event loop tick, even if multiple dependencies\n * change in the same synchronous block of code.\n * @protected\n */\n protected async recalculate_(): Promise<void> {\n this.logger_.logMethod?.('recalculate_');\n\n if (this.internalSignal_.isDestroyed) {\n // This check is important in case a dependency fires after this signal is destroyed.\n this.logger_.incident?.('recalculate_', 'recalculate_on_destroyed_signal');\n return;\n }\n\n if (this.isRecalculating__) {\n // If a recalculation is already scheduled, do nothing.\n this.logger_.logStep?.('recalculate_', 'skipping_recalculation_already_scheduled');\n return;\n }\n\n this.isRecalculating__ = true;\n\n try {\n // Wait for the next macrotask to start the recalculation.\n // This batches all synchronous dependency updates in the current event loop.\n await delay.nextMacrotask();\n \n if (this.isDestroyed) {\n this.logger_.incident?.('recalculate_', 'destroyed_during_delay');\n this.isRecalculating__ = false;\n return;\n }\n\n this.logger_.logStep?.('recalculate_', 'recalculating_value');\n\n // Set the new value on the internal signal, which will notify our subscribers.\n this.internalSignal_.set(this.config_.get());\n }\n catch (err) {\n this.logger_.error('recalculate_', 'recalculation_failed', err);\n }\n\n // Allow the next recalculation to be scheduled.\n this.isRecalculating__ = false;\n }\n}\n", "import {delay} from '@alwatr/delay';\nimport {createLogger} from '@alwatr/logger';\n\nimport type {EffectSignalConfig, IEffectSignal, SubscribeResult} from '../type.js';\n\n/**\n * Manages a side-effect that runs in response to changes in dependency signals.\n *\n * `EffectSignal` is designed for running logic that interacts with the \"outside world\"—such as\n * logging, network requests, or DOM manipulation—whenever its dependencies are updated.\n * It encapsulates the subscription and cleanup logic, providing a robust and memory-safe\n * way to handle reactive side-effects.\n *\n * A key feature is its lifecycle management: an `EffectSignal` **must** be destroyed when no longer\n * needed to prevent memory leaks and stop the effect from running unnecessarily.\n *\n * @implements {IEffectSignal}\n *\n * @example\n * // --- Create dependency signals ---\n * const counter = new StateSignal({ initialValue: 0, signalId: 'counter' });\n * const user = new StateSignal({ initialValue: 'guest', signalId: 'user' });\n *\n * // --- Create an effect ---\n * const analyticsEffect = new EffectSignal({\n * signalId: 'analytics-effect',\n * deps: [counter, user],\n * run: () => {\n * console.log(`Analytics: User '${user.get()}' clicked ${counter.get()} times.`);\n * },\n * runImmediately: true, // Optional: run once on creation\n * });\n * // Immediately logs: \"Analytics: User 'guest' clicked 0 times.\"\n *\n * // --- Trigger the effect by updating a dependency ---\n * counter.set(1);\n * // After a macrotask, logs: \"Analytics: User 'guest' clicked 1 times.\"\n *\n * // --- IMPORTANT: Clean up when the effect is no longer needed ---\n * analyticsEffect.destroy();\n *\n * // Further updates will not trigger the effect.\n * counter.set(2); // Nothing is logged.\n */\nexport class EffectSignal implements IEffectSignal {\n /**\n * The unique identifier for this signal instance.\n */\n public readonly signalId = this.config_.signalId ? this.config_.signalId : `[${this.config_.deps.map((dep) => dep.signalId).join(', ')}]`;\n\n /**\n * The logger instance for this signal.\n * @protected\n */\n protected readonly logger_ = createLogger(`effect-signal: ${this.signalId}`);\n\n /**\n * A list of subscriptions to dependency signals.\n * @private\n */\n private readonly dependencySubscriptions__: SubscribeResult[] = [];\n\n /**\n * A flag to prevent concurrent executions of the effect.\n * @private\n */\n private isRunning__ = false;\n\n /**\n * A flag indicating whether the effect has been destroyed.\n * @private\n */\n private isDestroyed__ = false;\n\n /**\n * Indicates whether the effect signal has been destroyed.\n * A destroyed signal will no longer execute its effect and cannot be reused.\n *\n * @returns `true` if the signal is destroyed, `false` otherwise.\n */\n public get isDestroyed(): boolean {\n return this.isDestroyed__;\n }\n\n public constructor(protected config_: EffectSignalConfig) {\n this.logger_.logMethod?.('constructor');\n this.scheduleExecution_ = this.scheduleExecution_.bind(this);\n\n // Subscribe to all dependencies. We don't need the previous value,\n // as the `runImmediately` option controls the initial execution.\n for (const signal of config_.deps) {\n this.dependencySubscriptions__.push(signal.subscribe(this.scheduleExecution_, {receivePrevious: false}));\n }\n\n // Run the effect immediately if requested.\n if (config_.runImmediately === true) {\n // We don't need to await this, let it run in the background.\n void this.scheduleExecution_();\n }\n }\n\n /**\n * Schedules the execution of the effect's `run` function.\n *\n * This method batches updates using a macrotask (`delay.nextMacrotask`) to ensure the\n * `run` function executes only once per event loop tick, even if multiple\n * dependencies change simultaneously.\n * @protected\n */\n protected async scheduleExecution_(): Promise<void> {\n this.logger_.logMethod?.('scheduleExecution_');\n\n if (this.isDestroyed__) {\n this.logger_.incident?.('scheduleExecution_', 'schedule_execution_on_destroyed_signal');\n return;\n }\n if (this.isRunning__) {\n // If an execution is already scheduled, do nothing.\n this.logger_.logStep?.('scheduleExecution_', 'skipped_because_already_running');\n return;\n }\n\n this.isRunning__ = true;\n\n try {\n // Wait for the next macrotask to batch simultaneous updates.\n await delay.nextMacrotask();\n if (this.isDestroyed__) {\n this.logger_.incident?.('scheduleExecution_', 'destroyed_during_delay');\n this.isRunning__ = false;\n return;\n }\n\n this.logger_.logStep?.('scheduleExecution_', 'executing_effect');\n await this.config_.run();\n }\n catch (err) {\n this.logger_.error('scheduleExecution_', 'effect_failed', err);\n }\n\n // Reset the flag after the current execution is complete.\n this.isRunning__ = false;\n }\n\n /**\n * Permanently disposes of the effect signal.\n *\n * This is a critical cleanup step. It unsubscribes from all dependency signals,\n * stopping any future executions of the effect and allowing it to be garbage collected.\n * Failure to call `destroy()` will result in memory leaks and potentially unwanted side effects.\n */\n public destroy(): void {\n this.logger_.logMethod?.('destroy');\n\n if (this.isDestroyed__) {\n this.logger_.incident?.('destroy', 'already_destroyed');\n return;\n }\n\n this.isDestroyed__ = true;\n\n // Unsubscribe from all upstream dependencies.\n for (const subscription of this.dependencySubscriptions__) {\n subscription.unsubscribe();\n }\n this.dependencySubscriptions__.length = 0; // Clear the array of subscriptions.\n\n this.config_.onDestroy?.(); // Call the optional onDestroy callback.\n this.config_ = null as unknown as EffectSignalConfig; // Release config closure.\n }\n}\n", "import {EventSignal} from '../core/event-signal.js';\n\nimport type {SignalConfig} from '../type.js';\n\n/**\n * Creates a stateless signal for dispatching transient events.\n *\n * `EventSignal` is ideal for broadcasting events that do not have a persistent state.\n * Unlike `StateSignal`, it does not hold a value. Listeners are only notified of new\n * events as they are dispatched. This makes it suitable for modeling user interactions,\n * system notifications, or any one-off message.\n *\n * @template T The type of the payload for the events.\n *\n * @param config The configuration for the event signal.\n * @returns A new instance of EventSignal.\n *\n * @example\n * const onUserClick = createEventSignal<{ x: number, y: number }>({\n * signalId: 'on-user-click'\n * });\n *\n * onUserClick.subscribe(pos => {\n * console.log(`User clicked at: ${pos.x}, ${pos.y}`);\n * });\n *\n * onUserClick.dispatch({ x: 100, y: 250 });\n */\nexport function createEventSignal<T = void>(config: SignalConfig): EventSignal<T> {\n return new EventSignal<T>(config);\n}\n", "import {StateSignal} from '../core/state-signal.js';\n\nimport type {StateSignalConfig} from '../type.js';\n\n/**\n * Creates a stateful signal that holds a value and notifies listeners when the value changes.\n *\n * `StateSignal` is the core of the signal library, representing a piece of mutable state.\n * It always has a value, and new subscribers immediately receive the current value by default.\n *\n * @template T The type of the state it holds.\n *\n * @param config The configuration for the state signal.\n * @returns A new instance of StateSignal.\n *\n * @example\n * const counter = createStateSignal({\n * signalId: 'counter-signal',\n * initialValue: 0,\n * });\n *\n * console.log(counter.get()); // Outputs: 0\n * counter.set(1);\n * console.log(counter.get()); // Outputs: 1\n */\nexport function createStateSignal<T>(config: StateSignalConfig<T>): StateSignal<T> {\n return new StateSignal(config);\n}\n", "import {ComputedSignal} from '../core/computed-signal.js';\n\nimport type {ComputedSignalConfig} from '../type.js';\n\n/**\n * Creates a read-only signal that derives its value from a set of dependency signals.\n *\n * `ComputedSignal` is a powerful tool for creating values that reactively update when their underlying\n * data sources change. Its value is memoized, meaning the `get` function is only re-evaluated when\n * one of its dependencies has actually changed.\n *\n * A key feature is its lifecycle management: a `ComputedSignal` **must** be destroyed when no longer\n * needed to prevent memory leaks from its subscriptions to dependency signals.\n *\n * @template T The type of the computed value.\n *\n * @param config The configuration for the computed signal.\n * @returns A new, read-only computed signal.\n *\n * @example\n * const firstName = createStateSignal({ signalId: 'firstName', initialValue: 'John' });\n * const lastName = createStateSignal({ signalId: 'lastName', initialValue: 'Doe' });\n *\n * const fullName = createComputedSignal({\n * signalId: 'fullName',\n * deps: [firstName, lastName],\n * get: () => `${firstName.get()} ${lastName.get()}`,\n * });\n *\n * console.log(fullName.get()); // \"John Doe\"\n *\n * // IMPORTANT: Always destroy a computed signal when no longer needed.\n * // fullName.destroy();\n */\nexport function createComputedSignal<T>(config: ComputedSignalConfig<T>): ComputedSignal<T> {\n return new ComputedSignal(config);\n}\n", "import {EffectSignal} from '../core/effect-signal.js';\n\nimport type {EffectSignalConfig} from '../type.js';\n\n/**\n * Creates a side-effect that runs in response to changes in dependency signals.\n *\n * `EffectSignal` is designed for running logic that interacts with the \"outside world\"—such as\n * logging, network requests, or DOM manipulation—whenever its dependencies are updated.\n * It encapsulates the subscription and cleanup logic, providing a robust and memory-safe\n * way to handle reactive side-effects.\n *\n * A key feature is its lifecycle management: an `EffectSignal` **must** be destroyed when no longer\n * needed to prevent memory leaks and stop the effect from running unnecessarily.\n *\n * @param config The configuration for the effect.\n * @returns An object with a `destroy` method to stop the effect.\n *\n * @example\n * // --- Create dependency signals ---\n * const counter = createStateSignal({ initialValue: 0, signalId: 'counter' });\n * const user = createStateSignal({ initialValue: 'guest', signalId: 'user' });\n *\n * // --- Create an effect ---\n * const analyticsEffect = createEffect({\n * deps: [counter, user],\n * run: () => {\n * console.log(`Analytics: User '${user.get()}' clicked ${counter.get()} times.`);\n * },\n * runImmediately: true, // Optional: run once on creation\n * });\n * // Immediately logs: \"Analytics: User 'guest' clicked 0 times.\"\n *\n * // --- Trigger the effect by updating a dependency ---\n * counter.set(1);\n * // After a macrotask, logs: \"Analytics: User 'guest' clicked 1 times.\"\n *\n * // --- IMPORTANT: Clean up when the effect is no longer needed ---\n * analyticsEffect.destroy();\n *\n * // Further updates will not trigger the effect.\n * counter.set(2); // Nothing is logged.\n */\nexport function createEffect(config: EffectSignalConfig): EffectSignal {\n return new EffectSignal(config);\n}\n", "import {createDebouncer} from '@alwatr/debounce';\n\nimport {StateSignal} from '../core/state-signal.js';\nimport {createComputedSignal} from '../creators/computed.js';\n\nimport type {ComputedSignal} from '../core/computed-signal.js';\nimport type {IReadonlySignal, DebounceSignalConfig} from '../type.js';\n\n/**\n * Creates a new computed signal that debounces updates from a source signal.\n *\n * The returned signal is a `ComputedSignal`, meaning it is read-only and its value is\n * derived from the source. It only updates its value after a specified period of\n * inactivity from the source signal.\n *\n * This operator is essential for handling high-frequency events, such as user input\n * in a search box, resizing a window, or any other event that fires rapidly.\n * By debouncing, you can ensure that expensive operations (like API calls or heavy\n * computations) are only executed once the events have settled.\n *\n * @template T The type of the signal's value.\n *\n * @param {IReadonlySignal<T>} sourceSignal The original signal to debounce.\n * It can be a `StateSignal`, `ComputedSignal`, or any signal implementing `IReadonlySignal`.\n * @param {DebounceSignalConfig} config Configuration object for the debouncer,\n * including `delay`, `leading`, and `trailing` options from `@alwatr/debounce`.\n *\n * @returns {IComputedSignal<T>} A new, read-only computed signal that emits debounced values.\n * Crucially, you **must** call `.destroy()` on this signal when it's no longer\n * needed to prevent memory leaks by cleaning up internal subscriptions and timers.\n *\n * @example\n * ```typescript\n * // Create a source signal for user input.\n * const searchInput = createStateSignal({\n * signalId: 'search-input',\n * initialValue: '',\n * });\n *\n * // Create a debounced signal that waits 300ms after the user stops typing.\n * const debouncedSearch = createDebouncedSignal(searchInput, { delay: 300 });\n *\n * // Use an effect to react to the debounced value.\n * createEffect({\n * deps: [debouncedSearch],\n * run: () => {\n * if (debouncedSearch.get()) {\n * console.log(`🚀 Sending API request for: \"${debouncedSearch.get()}\"`);\n * }\n * },\n * });\n *\n * searchInput.set('Alwatr');\n * searchInput.set('Alwatr Signal');\n * // (after 300ms of inactivity)\n * // Logs: \"🚀 Sending API request for: \"Alwatr Signal\"\"\n *\n * // IMPORTANT: Clean up when the component unmounts.\n * // debouncedSearch.destroy();\n * ```\n */\nexport function createDebouncedSignal<T>(sourceSignal: IReadonlySignal<T>, config: DebounceSignalConfig): ComputedSignal<T> {\n const signalId = config.signalId ?? `${sourceSignal.signalId}-debounced`;\n\n const internalSignal = new StateSignal<T>({\n signalId: `${signalId}-internal`,\n initialValue: sourceSignal.get(),\n });\n\n const debouncer = createDebouncer({\n ...config,\n func: (value: T): void => {\n internalSignal.set(value);\n },\n });\n\n const subscription = sourceSignal.subscribe(debouncer.trigger);\n\n return createComputedSignal({\n signalId,\n deps: [internalSignal],\n get: () => internalSignal.get(),\n onDestroy: () => {\n if (internalSignal.isDestroyed) return;\n subscription.unsubscribe();\n debouncer.cancel();\n internalSignal.destroy();\n config.onDestroy?.();\n config = null as unknown as DebounceSignalConfig;\n },\n });\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["import_delay", "import_logger", "import_delay", "import_logger", "import_delay", "import_logger"]
|
|
7
7
|
}
|
package/dist/main.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* @alwatr/signal v5.2.
|
|
1
|
+
/* @alwatr/signal v5.2.1 */
|
|
2
2
|
|
|
3
3
|
// src/core/signal-base.ts
|
|
4
4
|
var SignalBase = class {
|
|
@@ -202,9 +202,9 @@ var StateSignal = class extends SignalBase {
|
|
|
202
202
|
* @returns The current value.
|
|
203
203
|
*
|
|
204
204
|
* @example
|
|
205
|
-
* console.log(mySignal.
|
|
205
|
+
* console.log(mySignal.get());
|
|
206
206
|
*/
|
|
207
|
-
get
|
|
207
|
+
get() {
|
|
208
208
|
this.checkDestroyed_();
|
|
209
209
|
return this.value__;
|
|
210
210
|
}
|
|
@@ -221,7 +221,7 @@ var StateSignal = class extends SignalBase {
|
|
|
221
221
|
* mySignal.set(42);
|
|
222
222
|
*
|
|
223
223
|
* // For object types, it's best practice to set an immutable new object.
|
|
224
|
-
* mySignal.set({ ...mySignal.
|
|
224
|
+
* mySignal.set({ ...mySignal.get(), property: 'new-value' });
|
|
225
225
|
*/
|
|
226
226
|
set(newValue) {
|
|
227
227
|
this.logger_.logMethodArgs?.("set", { newValue });
|
|
@@ -301,7 +301,7 @@ var ComputedSignal = class {
|
|
|
301
301
|
this.logger_ = createLogger3(`computed-signal: ${this.signalId}`);
|
|
302
302
|
/**
|
|
303
303
|
* The internal `StateSignal` that holds the computed value.
|
|
304
|
-
* This is how the computed signal provides `.
|
|
304
|
+
* This is how the computed signal provides `.get()` and `.subscribe()` methods.
|
|
305
305
|
* @protected
|
|
306
306
|
*/
|
|
307
307
|
this.internalSignal_ = new StateSignal({
|
|
@@ -331,8 +331,8 @@ var ComputedSignal = class {
|
|
|
331
331
|
* @returns The current computed value.
|
|
332
332
|
* @throws {Error} If accessed after the signal has been destroyed.
|
|
333
333
|
*/
|
|
334
|
-
get
|
|
335
|
-
return this.internalSignal_.
|
|
334
|
+
get() {
|
|
335
|
+
return this.internalSignal_.get();
|
|
336
336
|
}
|
|
337
337
|
/**
|
|
338
338
|
* Indicates whether the computed signal has been destroyed.
|
|
@@ -368,7 +368,7 @@ var ComputedSignal = class {
|
|
|
368
368
|
* stopping future recalculations and allowing the signal to be garbage collected.
|
|
369
369
|
* Failure to call `destroy()` will result in memory leaks.
|
|
370
370
|
*
|
|
371
|
-
* After `destroy()` is called, any attempt to access `.
|
|
371
|
+
* After `destroy()` is called, any attempt to access `.get()` or `.subscribe()` will throw an error.
|
|
372
372
|
*/
|
|
373
373
|
destroy() {
|
|
374
374
|
this.logger_.logMethod?.("destroy");
|
|
@@ -450,12 +450,12 @@ var EffectSignal = class {
|
|
|
450
450
|
*/
|
|
451
451
|
this.isDestroyed__ = false;
|
|
452
452
|
this.logger_.logMethod?.("constructor");
|
|
453
|
-
this.
|
|
453
|
+
this.scheduleExecution_ = this.scheduleExecution_.bind(this);
|
|
454
454
|
for (const signal of config_.deps) {
|
|
455
|
-
this.dependencySubscriptions__.push(signal.subscribe(this.
|
|
455
|
+
this.dependencySubscriptions__.push(signal.subscribe(this.scheduleExecution_, { receivePrevious: false }));
|
|
456
456
|
}
|
|
457
457
|
if (config_.runImmediately === true) {
|
|
458
|
-
void this.
|
|
458
|
+
void this.scheduleExecution_();
|
|
459
459
|
}
|
|
460
460
|
}
|
|
461
461
|
/**
|
|
@@ -475,28 +475,28 @@ var EffectSignal = class {
|
|
|
475
475
|
* dependencies change simultaneously.
|
|
476
476
|
* @protected
|
|
477
477
|
*/
|
|
478
|
-
async
|
|
479
|
-
this.logger_.logMethod?.("
|
|
478
|
+
async scheduleExecution_() {
|
|
479
|
+
this.logger_.logMethod?.("scheduleExecution_");
|
|
480
480
|
if (this.isDestroyed__) {
|
|
481
|
-
this.logger_.incident?.("
|
|
481
|
+
this.logger_.incident?.("scheduleExecution_", "schedule_execution_on_destroyed_signal");
|
|
482
482
|
return;
|
|
483
483
|
}
|
|
484
484
|
if (this.isRunning__) {
|
|
485
|
-
this.logger_.logStep?.("
|
|
485
|
+
this.logger_.logStep?.("scheduleExecution_", "skipped_because_already_running");
|
|
486
486
|
return;
|
|
487
487
|
}
|
|
488
488
|
this.isRunning__ = true;
|
|
489
489
|
try {
|
|
490
490
|
await delay4.nextMacrotask();
|
|
491
491
|
if (this.isDestroyed__) {
|
|
492
|
-
this.logger_.incident?.("
|
|
492
|
+
this.logger_.incident?.("scheduleExecution_", "destroyed_during_delay");
|
|
493
493
|
this.isRunning__ = false;
|
|
494
494
|
return;
|
|
495
495
|
}
|
|
496
|
-
this.logger_.logStep?.("
|
|
496
|
+
this.logger_.logStep?.("scheduleExecution_", "executing_effect");
|
|
497
497
|
await this.config_.run();
|
|
498
498
|
} catch (err) {
|
|
499
|
-
this.logger_.error("
|
|
499
|
+
this.logger_.error("scheduleExecution_", "effect_failed", err);
|
|
500
500
|
}
|
|
501
501
|
this.isRunning__ = false;
|
|
502
502
|
}
|
|
@@ -549,7 +549,7 @@ function createDebouncedSignal(sourceSignal, config) {
|
|
|
549
549
|
const signalId = config.signalId ?? `${sourceSignal.signalId}-debounced`;
|
|
550
550
|
const internalSignal = new StateSignal({
|
|
551
551
|
signalId: `${signalId}-internal`,
|
|
552
|
-
initialValue: sourceSignal.
|
|
552
|
+
initialValue: sourceSignal.get()
|
|
553
553
|
});
|
|
554
554
|
const debouncer = createDebouncer({
|
|
555
555
|
...config,
|
|
@@ -561,7 +561,7 @@ function createDebouncedSignal(sourceSignal, config) {
|
|
|
561
561
|
return createComputedSignal({
|
|
562
562
|
signalId,
|
|
563
563
|
deps: [internalSignal],
|
|
564
|
-
get: () => internalSignal.
|
|
564
|
+
get: () => internalSignal.get(),
|
|
565
565
|
onDestroy: () => {
|
|
566
566
|
if (internalSignal.isDestroyed) return;
|
|
567
567
|
subscription.unsubscribe();
|
package/dist/main.mjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/core/signal-base.ts", "../src/core/event-signal.ts", "../src/core/state-signal.ts", "../src/core/computed-signal.ts", "../src/core/effect-signal.ts", "../src/creators/event.ts", "../src/creators/state.ts", "../src/creators/computed.ts", "../src/creators/effect.ts", "../src/operators/debounce.ts"],
|
|
4
|
-
"sourcesContent": ["import type {Observer_, SubscribeOptions, SubscribeResult, ListenerCallback, SignalConfig} from '../type.js';\nimport type {AlwatrLogger} from '@alwatr/logger';\nimport type {} from '@alwatr/nano-build';\n\n/**\n * An abstract base class for signal implementations.\n * It provides the core functionality for managing subscriptions (observers).\n *\n * @template T The type of data that the signal holds or dispatches.\n */\nexport abstract class SignalBase<T> {\n /**\n * The unique identifier for this signal instance.\n * Useful for debugging and logging.\n */\n public readonly signalId = this.config_.signalId;\n\n /**\n * The logger instance for this signal.\n * @protected\n */\n protected abstract logger_: AlwatrLogger;\n\n /**\n * The list of observers (listeners) subscribed to this signal.\n * @protected\n */\n protected readonly observers_: Observer_<T>[] = [];\n\n /**\n * A flag indicating whether the signal has been destroyed.\n * @private\n */\n private isDestroyed__ = false;\n\n /**\n * Indicates whether the signal has been destroyed.\n * A destroyed signal cannot be used and will throw an error if interacted with.\n *\n * @returns `true` if the signal is destroyed, `false` otherwise.\n */\n public get isDestroyed(): boolean {\n return this.isDestroyed__;\n }\n\n public constructor(protected config_: SignalConfig) {}\n\n /**\n * Removes a specific observer from the observers list.\n *\n * @param observer The observer instance to remove.\n * @protected\n */\n protected removeObserver_(observer: Observer_<T>): void {\n this.logger_.logMethod?.('removeObserver_');\n\n if (this.isDestroyed__) {\n this.logger_.incident?.('removeObserver_', 'remove_observer_on_destroyed_signal');\n return;\n }\n\n const index = this.observers_.indexOf(observer);\n if (index !== -1) {\n this.observers_.splice(index, 1);\n }\n }\n\n /**\n * Subscribes a listener function to this signal.\n *\n * The listener will be called whenever the signal is notified (e.g., when `dispatch` or `set` is called).\n *\n * @param callback The function to be called when the signal is dispatched.\n * @param options Subscription options to customize the behavior (e.g., `once`, `priority`).\n * @returns A `SubscribeResult` object with an `unsubscribe` method to remove the listener.\n */\n public subscribe(callback: ListenerCallback<T>, options?: SubscribeOptions): SubscribeResult {\n this.logger_.logMethodArgs?.('subscribe.base', {options});\n this.checkDestroyed_();\n\n const observer: Observer_<T> = {callback, options};\n\n if (options?.priority) {\n // High-priority observers are added to the front of the queue.\n this.observers_.unshift(observer);\n }\n else {\n this.observers_.push(observer);\n }\n\n // The returned unsubscribe function is a closure that calls the internal removal method.\n return {\n unsubscribe: (): void => this.removeObserver_(observer),\n };\n }\n\n /**\n * Notifies all registered observers about a new value.\n *\n * This method iterates through a snapshot of the current observers to prevent issues\n * with subscriptions changing during notification (e.g., an observer unsubscribing itself).\n *\n * @param value The new value to notify observers about.\n * @protected\n */\n protected notify_(value: T): void {\n this.logger_.logMethodArgs?.('notify_', value);\n\n if (this.isDestroyed__) {\n this.logger_.incident?.('notify_', 'notify_on_destroyed_signal');\n return;\n }\n\n // Create a snapshot of the observers array to iterate over.\n // This prevents issues if the observers_ array is modified during the loop.\n const currentObservers = [...this.observers_];\n\n for (const observer of currentObservers) {\n if (observer.options?.once) {\n this.removeObserver_(observer);\n }\n\n try {\n const result = observer.callback(value);\n if (result instanceof Promise) {\n result.catch((err) => this.logger_.error('notify_', 'async_callback_failed', err, {observer}));\n }\n }\n catch (err) {\n this.logger_.error('notify_', 'sync_callback_failed', err);\n }\n }\n }\n\n /**\n * Returns a Promise that resolves with the next value dispatched by the signal.\n * This provides an elegant way to wait for a single, future event using `async/await`.\n *\n * @returns A Promise that resolves with the next dispatched value.\n *\n * @example\n * async function onButtonClick() {\n * console.log('Waiting for the next signal...');\n * const nextValue = await mySignal.untilNext();\n * console.log('Signal received:', nextValue);\n * }\n */\n public untilNext(): Promise<T> {\n this.logger_.logMethod?.('untilNext');\n this.checkDestroyed_();\n return new Promise((resolve) => {\n this.subscribe(resolve, {\n once: true,\n priority: true, // Resolve the promise before other listeners are called.\n receivePrevious: false, // We only want the *next* value, not the current one.\n });\n });\n }\n\n /**\n * Destroys the signal, clearing all its listeners and making it inactive.\n *\n * After destruction, any interaction with the signal (like `subscribe` or `untilNext`)\n * will throw an error. This is crucial for preventing memory leaks by allowing\n * garbage collection of the signal and its observers.\n */\n public destroy(): void {\n this.logger_.logMethod?.('destroy');\n if (this.isDestroyed__) {\n this.logger_.incident?.('destroy_', 'double_destroy_attempt');\n return;\n }\n this.isDestroyed__ = true;\n this.observers_.length = 0; // Clear all observers.\n this.config_.onDestroy?.(); // Call the optional onDestroy callback.\n this.config_ = null as unknown as SignalConfig; // Help GC by breaking references.\n }\n\n /**\n * Throws an error if the signal has been destroyed.\n * This is a safeguard to prevent interaction with a defunct signal.\n * @protected\n */\n protected checkDestroyed_(): void {\n if (this.isDestroyed__) {\n this.logger_.accident('checkDestroyed_', 'attempt_to_use_destroyed_signal');\n throw new Error(`Cannot interact with a destroyed signal (id: ${this.signalId})`);\n }\n }\n}\n", "import {delay} from '@alwatr/delay';\nimport {createLogger} from '@alwatr/logger';\n\nimport {SignalBase} from './signal-base.js';\n\nimport type {SignalConfig} from '../type.js';\n\n/**\n * A stateless signal for dispatching transient events.\n *\n * `EventSignal` is ideal for broadcasting events that do not have a persistent state.\n * Unlike `StateSignal`, it does not hold a value. Listeners are only notified of new\n * events as they are dispatched. This makes it suitable for modeling user interactions,\n * system notifications, or any one-off message.\n *\n * @template T The type of the payload for the events. Defaults to `void` for events without a payload.\n *\n * @example\n * // Create a signal for user click events.\n * const onUserClick = new EventSignal<{ x: number, y: number }>({ signalId: 'on-user-click' });\n *\n * // Subscribe to the event.\n * onUserClick.subscribe(clickPosition => {\n * console.log(`User clicked at: ${clickPosition.x}, ${clickPosition.y}`);\n * });\n *\n * // Dispatch an event.\n * onUserClick.dispatch({ x: 100, y: 250 }); // Notifies the listener.\n *\n * // --- Example with no payload ---\n * const onAppReady = new EventSignal({ signalId: 'on-app-ready' });\n * onAppReady.subscribe(() => console.log('Application is ready!'));\n * onAppReady.dispatch(); // Notifies the listener.\n */\nexport class EventSignal<T = void> extends SignalBase<T> {\n /**\n * The logger instance for this signal.\n * @protected\n */\n protected logger_ = createLogger(`event-signal: ${this.signalId}`);\n\n public constructor(config: SignalConfig) {\n super(config);\n this.logger_.logMethod?.('constructor');\n }\n\n /**\n * Dispatches an event with an optional payload to all active listeners.\n * The notification is scheduled as a microtask to prevent blocking and ensure\n * a consistent, non-blocking flow.\n *\n * @param payload The data to send with the event.\n */\n public dispatch(payload: T): void {\n this.logger_.logMethodArgs?.('dispatch', {payload});\n this.checkDestroyed_();\n // Dispatch as a microtask to ensure consistent, non-blocking behavior.\n delay.nextMicrotask().then(() => this.notify_(payload));\n }\n}\n", "import {delay} from '@alwatr/delay';\nimport {createLogger} from '@alwatr/logger';\n\nimport {SignalBase} from './signal-base.js';\n\nimport type {StateSignalConfig, ListenerCallback, SubscribeOptions, SubscribeResult, IReadonlySignal} from '../type.js';\n\n/**\n * A stateful signal that holds a value and notifies listeners when the value changes.\n *\n * `StateSignal` is the core of the signal library, representing a piece of mutable state.\n * It always has a value, and new subscribers immediately receive the current value by default.\n *\n * @template T The type of the state it holds.\n * @implements {IReadonlySignal<T>}\n *\n * @example\n * // Create a new state signal with an initial value.\n * const counter = new StateSignal<number>({\n * signalId: 'counter-signal',\n * initialValue: 0,\n * });\n *\n * // Get the current value.\n * console.log(counter.value); // Outputs: 0\n *\n * // Subscribe to changes.\n * const subscription = counter.subscribe(newValue => {\n * console.log(`Counter changed to: ${newValue}`);\n * });\n *\n * // Set a new value, which triggers the notification.\n * counter.set(1); // Outputs: \"Counter changed to: 1\"\n *\n * // Update value based on the previous value.\n * counter.update(current => current + 1); // Outputs: \"Counter changed to: 2\"\n *\n * // Unsubscribe when no longer needed.\n * subscription.unsubscribe();\n */\nexport class StateSignal<T> extends SignalBase<T> implements IReadonlySignal<T> {\n /**\n * The current value of the signal.\n * @private\n */\n private value__: T;\n\n /**\n * The logger instance for this signal.\n * @protected\n */\n protected logger_ = createLogger(`state-signal: ${this.signalId}`);\n\n public constructor(config: StateSignalConfig<T>) {\n super(config);\n this.value__ = config.initialValue;\n this.logger_.logMethodArgs?.('constructor', {initialValue: this.value__});\n }\n\n /**\n * Retrieves the current value of the signal.\n *\n * @returns The current value.\n *\n * @example\n * console.log(mySignal.value);\n */\n public get value(): T {\n this.checkDestroyed_();\n return this.value__;\n }\n\n /**\n * Updates the signal's value and notifies all active listeners.\n *\n * The notification is scheduled as a microtask, which means the update is deferred\n * slightly to batch multiple synchronous changes.\n *\n * @param newValue The new value to set.\n *\n * @example\n * // For primitive types\n * mySignal.set(42);\n *\n * // For object types, it's best practice to set an immutable new object.\n * mySignal.set({ ...mySignal.value, property: 'new-value' });\n */\n public set(newValue: T): void {\n this.logger_.logMethodArgs?.('set', {newValue});\n this.checkDestroyed_();\n\n // For primitives (including null), do not notify if the value is the same.\n if (Object.is(this.value__, newValue) && (typeof newValue !== 'object' || newValue === null)) {\n return;\n }\n\n this.value__ = newValue;\n\n // Dispatch as a microtask to ensure consistent, non-blocking behavior.\n delay.nextMicrotask().then(() => this.notify_(newValue));\n }\n\n /**\n * Updates the signal's value based on its previous value.\n *\n * This method is particularly useful for state transitions that depend on the current value,\n * especially for objects or arrays, as it promotes an immutable update pattern.\n *\n * @param updater A function that receives the current value and returns the new value.\n *\n * @example\n * // For a counter\n * counterSignal.update(current => current + 1);\n *\n * // For an object state\n * userSignal.update(currentUser => ({ ...currentUser, loggedIn: true }));\n */\n public update(updater: (previousValue: T) => T): void {\n this.logger_.logMethod?.('update');\n this.checkDestroyed_();\n // The updater function is called with the current value to compute the new value,\n // which is then passed to the `set` method.\n this.set(updater(this.value__));\n }\n\n /**\n * Subscribes a listener to this signal.\n *\n * By default, the listener is immediately called with the signal's current value (`receivePrevious: true`).\n * This behavior can be customized via the `options` parameter.\n *\n * @param callback The function to be called when the signal's value changes.\n * @param options Subscription options, including `receivePrevious` and `once`.\n * @returns An object with an `unsubscribe` method to remove the listener.\n */\n public override subscribe(callback: ListenerCallback<T>, options: SubscribeOptions = {}): SubscribeResult {\n this.logger_.logMethodArgs?.('subscribe', {options});\n this.checkDestroyed_();\n\n // By default, new subscribers to a StateSignal should receive the current value.\n if (options.receivePrevious !== false) {\n // Immediately (but asynchronously) call the listener with the current value.\n // This is done in a microtask to ensure it happens after the subscription is fully registered.\n delay\n .nextMicrotask()\n .then(() => callback(this.value__))\n .catch((err) => this.logger_.error('subscribe', 'immediate_callback_failed', err));\n\n // If it's a 'once' subscription that receives the previous value, it's now fulfilled.\n // We don't need to add it to the observers list for future updates.\n if (options.once) {\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n return {unsubscribe: () => {}};\n }\n }\n\n return super.subscribe(callback, options);\n }\n\n /**\n * Destroys the signal, clearing its value and all listeners.\n * This is crucial for memory management to prevent leaks.\n */\n public override destroy(): void {\n this.value__ = null as T; // Clear the value to allow for garbage collection.\n super.destroy();\n }\n}\n", "import {delay} from '@alwatr/delay';\nimport {createLogger} from '@alwatr/logger';\n\nimport {StateSignal} from './state-signal.js';\n\nimport type {ComputedSignalConfig, IReadonlySignal, SubscribeResult, SubscribeOptions} from '../type.js';\n\n/**\n * A read-only signal that derives its value from a set of dependency signals.\n *\n * `ComputedSignal` is a powerful tool for creating values that reactively update when their underlying\n * data sources change. Its value is memoized, meaning the `get` function is only re-evaluated when\n * one of its dependencies has actually changed.\n *\n * A key feature is its lifecycle management: a `ComputedSignal` **must** be destroyed when no longer\n * needed to prevent memory leaks from its subscriptions to dependency signals.\n *\n * @template T The type of the computed value.\n *\n * @example\n * // --- Create dependency signals ---\n * const firstName = new StateSignal({ signalId: 'firstName', initialValue: 'John' });\n * const lastName = new StateSignal({ signalId: 'lastName', initialValue: 'Doe' });\n *\n * // --- Create a computed signal ---\n * const fullName = new ComputedSignal({\n * signalId: 'fullName',\n * deps: [firstName, lastName],\n * get: () => `${firstName.value} ${lastName.value}`,\n * });\n *\n * console.log(fullName.value); // Outputs: \"John Doe\"\n *\n * // --- Subscribe to the computed value ---\n * fullName.subscribe(newFullName => {\n * console.log(`Name changed to: ${newFullName}`);\n * });\n *\n * // --- Update a dependency ---\n * lastName.set('Smith'); // Recalculates and logs: \"Name changed to: John Smith\"\n * console.log(fullName.value); // Outputs: \"John Smith\"\n *\n * // --- IMPORTANT: Clean up when done ---\n * fullName.destroy();\n */\nexport class ComputedSignal<T> implements IReadonlySignal<T> {\n /**\n * The unique identifier for this signal instance.\n */\n public readonly signalId = this.config_.signalId;\n\n /**\n * The logger instance for this signal.\n * @protected\n */\n protected readonly logger_ = createLogger(`computed-signal: ${this.signalId}`);\n\n /**\n * The internal `StateSignal` that holds the computed value.\n * This is how the computed signal provides `.value` and `.subscribe()` methods.\n * @protected\n */\n protected readonly internalSignal_ = new StateSignal<T>({\n signalId: `${this.signalId}-internal`,\n initialValue: this.config_.get(),\n });\n\n /**\n * A list of subscriptions to dependency signals.\n * @private\n */\n\n private readonly dependencySubscriptions__: SubscribeResult[] = [];\n\n /**\n * A flag to prevent concurrent recalculations.\n * @private\n */\n private isRecalculating__ = false;\n\n public constructor(protected config_: ComputedSignalConfig<T>) {\n this.logger_.logMethod?.('constructor');\n this.recalculate_ = this.recalculate_.bind(this);\n\n // Subscribe to all dependencies to trigger recalculation on change.\n for (const signal of config_.deps) {\n this.dependencySubscriptions__.push(signal.subscribe(this.recalculate_, {receivePrevious: false}));\n }\n }\n\n /**\n * The current value of the computed signal.\n * Accessing this property returns the memoized value and does not trigger a recalculation.\n *\n * @returns The current computed value.\n * @throws {Error} If accessed after the signal has been destroyed.\n */\n public get value(): T {\n return this.internalSignal_.value;\n }\n\n /**\n * Indicates whether the computed signal has been destroyed.\n * A destroyed signal cannot be used and will throw an error if interacted with.\n * @returns `true` if the signal is destroyed, `false` otherwise.\n */\n public get isDestroyed(): boolean {\n return this.internalSignal_.isDestroyed;\n }\n\n /**\n * Subscribes a listener to this signal.\n * The listener will be called whenever the computed value changes.\n *\n * @param callback The function to be called with the new value.\n * @param options Subscription options.\n * @returns A `SubscribeResult` object with an `unsubscribe` method.\n */\n public subscribe(callback: (value: T) => void, options?: SubscribeOptions): SubscribeResult {\n return this.internalSignal_.subscribe(callback, options);\n }\n\n /**\n * Returns a Promise that resolves with the next computed value.\n *\n * @returns A Promise that resolves with the next value.\n */\n public untilNext(): Promise<T> {\n return this.internalSignal_.untilNext();\n }\n\n /**\n * Permanently disposes of the computed signal.\n *\n * This is a critical cleanup step. It unsubscribes from all dependency signals,\n * stopping future recalculations and allowing the signal to be garbage collected.\n * Failure to call `destroy()` will result in memory leaks.\n *\n * After `destroy()` is called, any attempt to access `.value` or `.subscribe()` will throw an error.\n */\n public destroy(): void {\n this.logger_.logMethod?.('destroy');\n /**\n * If already destroyed, log an incident and return early.\n */\n if (this.isDestroyed) {\n this.logger_.incident?.('destroy', 'already_destroyed');\n return;\n }\n\n // Unsubscribe from all upstream dependencies.\n for (const subscription of this.dependencySubscriptions__) {\n subscription.unsubscribe();\n }\n this.dependencySubscriptions__.length = 0; // Clear the array of subscriptions.\n\n this.internalSignal_.destroy(); // Destroy the internal signal.\n this.config_.onDestroy?.(); // Call the optional onDestroy callback.\n this.config_ = null as unknown as ComputedSignalConfig<T>; // Release config closure.\n }\n\n /**\n * Schedules a recalculation of the signal's value.\n *\n * This method batches updates using a macrotask (`delay.nextMacrotask`) to ensure the\n * `get` function runs only once per event loop tick, even if multiple dependencies\n * change in the same synchronous block of code.\n * @protected\n */\n protected async recalculate_(): Promise<void> {\n this.logger_.logMethod?.('recalculate_');\n\n if (this.internalSignal_.isDestroyed) {\n // This check is important in case a dependency fires after this signal is destroyed.\n this.logger_.incident?.('recalculate_', 'recalculate_on_destroyed_signal');\n return;\n }\n\n if (this.isRecalculating__) {\n // If a recalculation is already scheduled, do nothing.\n this.logger_.logStep?.('recalculate_', 'skipping_recalculation_already_scheduled');\n return;\n }\n\n this.isRecalculating__ = true;\n\n try {\n // Wait for the next macrotask to start the recalculation.\n // This batches all synchronous dependency updates in the current event loop.\n await delay.nextMacrotask();\n \n if (this.isDestroyed) {\n this.logger_.incident?.('recalculate_', 'destroyed_during_delay');\n this.isRecalculating__ = false;\n return;\n }\n\n this.logger_.logStep?.('recalculate_', 'recalculating_value');\n\n // Set the new value on the internal signal, which will notify our subscribers.\n this.internalSignal_.set(this.config_.get());\n }\n catch (err) {\n this.logger_.error('recalculate_', 'recalculation_failed', err);\n }\n\n // Allow the next recalculation to be scheduled.\n this.isRecalculating__ = false;\n }\n}\n", "import {delay} from '@alwatr/delay';\nimport {createLogger} from '@alwatr/logger';\n\nimport type {EffectSignalConfig, IEffectSignal, SubscribeResult} from '../type.js';\n\n/**\n * Manages a side-effect that runs in response to changes in dependency signals.\n *\n * `EffectSignal` is designed for running logic that interacts with the \"outside world\"—such as\n * logging, network requests, or DOM manipulation—whenever its dependencies are updated.\n * It encapsulates the subscription and cleanup logic, providing a robust and memory-safe\n * way to handle reactive side-effects.\n *\n * A key feature is its lifecycle management: an `EffectSignal` **must** be destroyed when no longer\n * needed to prevent memory leaks and stop the effect from running unnecessarily.\n *\n * @implements {IEffectSignal}\n *\n * @example\n * // --- Create dependency signals ---\n * const counter = new StateSignal({ initialValue: 0, signalId: 'counter' });\n * const user = new StateSignal({ initialValue: 'guest', signalId: 'user' });\n *\n * // --- Create an effect ---\n * const analyticsEffect = new EffectSignal({\n * signalId: 'analytics-effect',\n * deps: [counter, user],\n * run: () => {\n * console.log(`Analytics: User '${user.value}' clicked ${counter.value} times.`);\n * },\n * runImmediately: true, // Optional: run once on creation\n * });\n * // Immediately logs: \"Analytics: User 'guest' clicked 0 times.\"\n *\n * // --- Trigger the effect by updating a dependency ---\n * counter.set(1);\n * // After a macrotask, logs: \"Analytics: User 'guest' clicked 1 times.\"\n *\n * // --- IMPORTANT: Clean up when the effect is no longer needed ---\n * analyticsEffect.destroy();\n *\n * // Further updates will not trigger the effect.\n * counter.set(2); // Nothing is logged.\n */\nexport class EffectSignal implements IEffectSignal {\n /**\n * The unique identifier for this signal instance.\n */\n public readonly signalId = this.config_.signalId ? this.config_.signalId : `[${this.config_.deps.map((dep) => dep.signalId).join(', ')}]`;\n\n /**\n * The logger instance for this signal.\n * @protected\n */\n protected readonly logger_ = createLogger(`effect-signal: ${this.signalId}`);\n\n /**\n * A list of subscriptions to dependency signals.\n * @private\n */\n private readonly dependencySubscriptions__: SubscribeResult[] = [];\n\n /**\n * A flag to prevent concurrent executions of the effect.\n * @private\n */\n private isRunning__ = false;\n\n /**\n * A flag indicating whether the effect has been destroyed.\n * @private\n */\n private isDestroyed__ = false;\n\n /**\n * Indicates whether the effect signal has been destroyed.\n * A destroyed signal will no longer execute its effect and cannot be reused.\n *\n * @returns `true` if the signal is destroyed, `false` otherwise.\n */\n public get isDestroyed(): boolean {\n return this.isDestroyed__;\n }\n\n public constructor(protected config_: EffectSignalConfig) {\n this.logger_.logMethod?.('constructor');\n this.run_ = this.run_.bind(this);\n\n // Subscribe to all dependencies. We don't need the previous value,\n // as the `runImmediately` option controls the initial execution.\n for (const signal of config_.deps) {\n this.dependencySubscriptions__.push(signal.subscribe(this.run_, {receivePrevious: false}));\n }\n\n // Run the effect immediately if requested.\n if (config_.runImmediately === true) {\n // We don't need to await this, let it run in the background.\n void this.run_();\n }\n }\n\n /**\n * Schedules the execution of the effect's `run` function.\n *\n * This method batches updates using a macrotask (`delay.nextMacrotask`) to ensure the\n * `run` function executes only once per event loop tick, even if multiple\n * dependencies change simultaneously.\n * @protected\n */\n protected async run_(): Promise<void> {\n this.logger_.logMethod?.('run_');\n\n if (this.isDestroyed__) {\n this.logger_.incident?.('run_', 'run_on_destroyed_signal');\n return;\n }\n if (this.isRunning__) {\n // If an execution is already scheduled, do nothing.\n this.logger_.logStep?.('run_', 'skipped_because_already_running');\n return;\n }\n\n this.isRunning__ = true;\n\n try {\n // Wait for the next macrotask to batch simultaneous updates.\n await delay.nextMacrotask();\n if (this.isDestroyed__) {\n this.logger_.incident?.('run_', 'destroyed_during_delay');\n this.isRunning__ = false;\n return;\n }\n\n this.logger_.logStep?.('run_', 'executing_effect');\n await this.config_.run();\n }\n catch (err) {\n this.logger_.error('run_', 'effect_failed', err);\n }\n\n // Reset the flag after the current execution is complete.\n this.isRunning__ = false;\n }\n\n /**\n * Permanently disposes of the effect signal.\n *\n * This is a critical cleanup step. It unsubscribes from all dependency signals,\n * stopping any future executions of the effect and allowing it to be garbage collected.\n * Failure to call `destroy()` will result in memory leaks and potentially unwanted side effects.\n */\n public destroy(): void {\n this.logger_.logMethod?.('destroy');\n\n if (this.isDestroyed__) {\n this.logger_.incident?.('destroy', 'already_destroyed');\n return;\n }\n\n this.isDestroyed__ = true;\n\n // Unsubscribe from all upstream dependencies.\n for (const subscription of this.dependencySubscriptions__) {\n subscription.unsubscribe();\n }\n this.dependencySubscriptions__.length = 0; // Clear the array of subscriptions.\n\n this.config_.onDestroy?.(); // Call the optional onDestroy callback.\n this.config_ = null as unknown as EffectSignalConfig; // Release config closure.\n }\n}\n", "import {EventSignal} from '../core/event-signal.js';\n\nimport type {SignalConfig} from '../type.js';\n\n/**\n * Creates a stateless signal for dispatching transient events.\n *\n * `EventSignal` is ideal for broadcasting events that do not have a persistent state.\n * Unlike `StateSignal`, it does not hold a value. Listeners are only notified of new\n * events as they are dispatched. This makes it suitable for modeling user interactions,\n * system notifications, or any one-off message.\n *\n * @template T The type of the payload for the events.\n *\n * @param config The configuration for the event signal.\n * @returns A new instance of EventSignal.\n *\n * @example\n * const onUserClick = createEventSignal<{ x: number, y: number }>({\n * signalId: 'on-user-click'\n * });\n *\n * onUserClick.subscribe(pos => {\n * console.log(`User clicked at: ${pos.x}, ${pos.y}`);\n * });\n *\n * onUserClick.dispatch({ x: 100, y: 250 });\n */\nexport function createEventSignal<T = void>(config: SignalConfig): EventSignal<T> {\n return new EventSignal<T>(config);\n}\n", "import {StateSignal} from '../core/state-signal.js';\n\nimport type {StateSignalConfig} from '../type.js';\n\n/**\n * Creates a stateful signal that holds a value and notifies listeners when the value changes.\n *\n * `StateSignal` is the core of the signal library, representing a piece of mutable state.\n * It always has a value, and new subscribers immediately receive the current value by default.\n *\n * @template T The type of the state it holds.\n *\n * @param config The configuration for the state signal.\n * @returns A new instance of StateSignal.\n *\n * @example\n * const counter = createStateSignal({\n * signalId: 'counter-signal',\n * initialValue: 0,\n * });\n *\n * console.log(counter.value); // Outputs: 0\n * counter.set(1);\n * console.log(counter.value); // Outputs: 1\n */\nexport function createStateSignal<T>(config: StateSignalConfig<T>): StateSignal<T> {\n return new StateSignal(config);\n}\n", "import {ComputedSignal} from '../core/computed-signal.js';\n\nimport type {ComputedSignalConfig} from '../type.js';\n\n/**\n * Creates a read-only signal that derives its value from a set of dependency signals.\n *\n * `ComputedSignal` is a powerful tool for creating values that reactively update when their underlying\n * data sources change. Its value is memoized, meaning the `get` function is only re-evaluated when\n * one of its dependencies has actually changed.\n *\n * A key feature is its lifecycle management: a `ComputedSignal` **must** be destroyed when no longer\n * needed to prevent memory leaks from its subscriptions to dependency signals.\n *\n * @template T The type of the computed value.\n *\n * @param config The configuration for the computed signal.\n * @returns A new, read-only computed signal.\n *\n * @example\n * const firstName = createStateSignal({ signalId: 'firstName', initialValue: 'John' });\n * const lastName = createStateSignal({ signalId: 'lastName', initialValue: 'Doe' });\n *\n * const fullName = createComputedSignal({\n * signalId: 'fullName',\n * deps: [firstName, lastName],\n * get: () => `${firstName.value} ${lastName.value}`,\n * });\n *\n * console.log(fullName.value); // \"John Doe\"\n *\n * // IMPORTANT: Always destroy a computed signal when no longer needed.\n * // fullName.destroy();\n */\nexport function createComputedSignal<T>(config: ComputedSignalConfig<T>): ComputedSignal<T> {\n return new ComputedSignal(config);\n}\n", "import {EffectSignal} from '../core/effect-signal.js';\n\nimport type {EffectSignalConfig} from '../type.js';\n\n/**\n * Creates a side-effect that runs in response to changes in dependency signals.\n *\n * `EffectSignal` is designed for running logic that interacts with the \"outside world\"—such as\n * logging, network requests, or DOM manipulation—whenever its dependencies are updated.\n * It encapsulates the subscription and cleanup logic, providing a robust and memory-safe\n * way to handle reactive side-effects.\n *\n * A key feature is its lifecycle management: an `EffectSignal` **must** be destroyed when no longer\n * needed to prevent memory leaks and stop the effect from running unnecessarily.\n *\n * @param config The configuration for the effect.\n * @returns An object with a `destroy` method to stop the effect.\n *\n * @example\n * // --- Create dependency signals ---\n * const counter = createStateSignal({ initialValue: 0, signalId: 'counter' });\n * const user = createStateSignal({ initialValue: 'guest', signalId: 'user' });\n *\n * // --- Create an effect ---\n * const analyticsEffect = createEffect({\n * deps: [counter, user],\n * run: () => {\n * console.log(`Analytics: User '${user.value}' clicked ${counter.value} times.`);\n * },\n * runImmediately: true, // Optional: run once on creation\n * });\n * // Immediately logs: \"Analytics: User 'guest' clicked 0 times.\"\n *\n * // --- Trigger the effect by updating a dependency ---\n * counter.set(1);\n * // After a macrotask, logs: \"Analytics: User 'guest' clicked 1 times.\"\n *\n * // --- IMPORTANT: Clean up when the effect is no longer needed ---\n * analyticsEffect.destroy();\n *\n * // Further updates will not trigger the effect.\n * counter.set(2); // Nothing is logged.\n */\nexport function createEffect(config: EffectSignalConfig): EffectSignal {\n return new EffectSignal(config);\n}\n", "import {createDebouncer} from '@alwatr/debounce';\n\nimport {StateSignal} from '../core/state-signal.js';\nimport {createComputedSignal} from '../creators/computed.js';\n\nimport type {ComputedSignal} from '../core/computed-signal.js';\nimport type {IReadonlySignal, DebounceSignalConfig} from '../type.js';\n\n/**\n * Creates a new computed signal that debounces updates from a source signal.\n *\n * The returned signal is a `ComputedSignal`, meaning it is read-only and its value is\n * derived from the source. It only updates its value after a specified period of\n * inactivity from the source signal.\n *\n * This operator is essential for handling high-frequency events, such as user input\n * in a search box, resizing a window, or any other event that fires rapidly.\n * By debouncing, you can ensure that expensive operations (like API calls or heavy\n * computations) are only executed once the events have settled.\n *\n * @template T The type of the signal's value.\n *\n * @param {IReadonlySignal<T>} sourceSignal The original signal to debounce.\n * It can be a `StateSignal`, `ComputedSignal`, or any signal implementing `IReadonlySignal`.\n * @param {DebounceSignalConfig} config Configuration object for the debouncer,\n * including `delay`, `leading`, and `trailing` options from `@alwatr/debounce`.\n *\n * @returns {IComputedSignal<T>} A new, read-only computed signal that emits debounced values.\n * Crucially, you **must** call `.destroy()` on this signal when it's no longer\n * needed to prevent memory leaks by cleaning up internal subscriptions and timers.\n *\n * @example\n * ```typescript\n * // Create a source signal for user input.\n * const searchInput = createStateSignal({\n * signalId: 'search-input',\n * initialValue: '',\n * });\n *\n * // Create a debounced signal that waits 300ms after the user stops typing.\n * const debouncedSearch = createDebouncedSignal(searchInput, { delay: 300 });\n *\n * // Use an effect to react to the debounced value.\n * createEffect({\n * deps: [debouncedSearch],\n * run: () => {\n * if (debouncedSearch.value) {\n * console.log(`🚀 Sending API request for: \"${debouncedSearch.value}\"`);\n * }\n * },\n * });\n *\n * searchInput.set('Alwatr');\n * searchInput.set('Alwatr Signal');\n * // (after 300ms of inactivity)\n * // Logs: \"🚀 Sending API request for: \"Alwatr Signal\"\"\n *\n * // IMPORTANT: Clean up when the component unmounts.\n * // debouncedSearch.destroy();\n * ```\n */\nexport function createDebouncedSignal<T>(sourceSignal: IReadonlySignal<T>, config: DebounceSignalConfig): ComputedSignal<T> {\n const signalId = config.signalId ?? `${sourceSignal.signalId}-debounced`;\n\n const internalSignal = new StateSignal<T>({\n signalId: `${signalId}-internal`,\n initialValue: sourceSignal.value,\n });\n\n const debouncer = createDebouncer({\n ...config,\n func: (value: T): void => {\n internalSignal.set(value);\n },\n });\n\n const subscription = sourceSignal.subscribe(debouncer.trigger);\n\n return createComputedSignal({\n signalId,\n deps: [internalSignal],\n get: () => internalSignal.value,\n onDestroy: () => {\n if (internalSignal.isDestroyed) return;\n subscription.unsubscribe();\n debouncer.cancel();\n internalSignal.destroy();\n config.onDestroy?.();\n config = null as unknown as DebounceSignalConfig;\n },\n });\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import type {Observer_, SubscribeOptions, SubscribeResult, ListenerCallback, SignalConfig} from '../type.js';\nimport type {AlwatrLogger} from '@alwatr/logger';\nimport type {} from '@alwatr/nano-build';\n\n/**\n * An abstract base class for signal implementations.\n * It provides the core functionality for managing subscriptions (observers).\n *\n * @template T The type of data that the signal holds or dispatches.\n */\nexport abstract class SignalBase<T> {\n /**\n * The unique identifier for this signal instance.\n * Useful for debugging and logging.\n */\n public readonly signalId = this.config_.signalId;\n\n /**\n * The logger instance for this signal.\n * @protected\n */\n protected abstract logger_: AlwatrLogger;\n\n /**\n * The list of observers (listeners) subscribed to this signal.\n * @protected\n */\n protected readonly observers_: Observer_<T>[] = [];\n\n /**\n * A flag indicating whether the signal has been destroyed.\n * @private\n */\n private isDestroyed__ = false;\n\n /**\n * Indicates whether the signal has been destroyed.\n * A destroyed signal cannot be used and will throw an error if interacted with.\n *\n * @returns `true` if the signal is destroyed, `false` otherwise.\n */\n public get isDestroyed(): boolean {\n return this.isDestroyed__;\n }\n\n public constructor(protected config_: SignalConfig) {}\n\n /**\n * Removes a specific observer from the observers list.\n *\n * @param observer The observer instance to remove.\n * @protected\n */\n protected removeObserver_(observer: Observer_<T>): void {\n this.logger_.logMethod?.('removeObserver_');\n\n if (this.isDestroyed__) {\n this.logger_.incident?.('removeObserver_', 'remove_observer_on_destroyed_signal');\n return;\n }\n\n const index = this.observers_.indexOf(observer);\n if (index !== -1) {\n this.observers_.splice(index, 1);\n }\n }\n\n /**\n * Subscribes a listener function to this signal.\n *\n * The listener will be called whenever the signal is notified (e.g., when `dispatch` or `set` is called).\n *\n * @param callback The function to be called when the signal is dispatched.\n * @param options Subscription options to customize the behavior (e.g., `once`, `priority`).\n * @returns A `SubscribeResult` object with an `unsubscribe` method to remove the listener.\n */\n public subscribe(callback: ListenerCallback<T>, options?: SubscribeOptions): SubscribeResult {\n this.logger_.logMethodArgs?.('subscribe.base', {options});\n this.checkDestroyed_();\n\n const observer: Observer_<T> = {callback, options};\n\n if (options?.priority) {\n // High-priority observers are added to the front of the queue.\n this.observers_.unshift(observer);\n }\n else {\n this.observers_.push(observer);\n }\n\n // The returned unsubscribe function is a closure that calls the internal removal method.\n return {\n unsubscribe: (): void => this.removeObserver_(observer),\n };\n }\n\n /**\n * Notifies all registered observers about a new value.\n *\n * This method iterates through a snapshot of the current observers to prevent issues\n * with subscriptions changing during notification (e.g., an observer unsubscribing itself).\n *\n * @param value The new value to notify observers about.\n * @protected\n */\n protected notify_(value: T): void {\n this.logger_.logMethodArgs?.('notify_', value);\n\n if (this.isDestroyed__) {\n this.logger_.incident?.('notify_', 'notify_on_destroyed_signal');\n return;\n }\n\n // Create a snapshot of the observers array to iterate over.\n // This prevents issues if the observers_ array is modified during the loop.\n const currentObservers = [...this.observers_];\n\n for (const observer of currentObservers) {\n if (observer.options?.once) {\n this.removeObserver_(observer);\n }\n\n try {\n const result = observer.callback(value);\n if (result instanceof Promise) {\n result.catch((err) => this.logger_.error('notify_', 'async_callback_failed', err, {observer}));\n }\n }\n catch (err) {\n this.logger_.error('notify_', 'sync_callback_failed', err);\n }\n }\n }\n\n /**\n * Returns a Promise that resolves with the next value dispatched by the signal.\n * This provides an elegant way to wait for a single, future event using `async/await`.\n *\n * @returns A Promise that resolves with the next dispatched value.\n *\n * @example\n * async function onButtonClick() {\n * console.log('Waiting for the next signal...');\n * const nextValue = await mySignal.untilNext();\n * console.log('Signal received:', nextValue);\n * }\n */\n public untilNext(): Promise<T> {\n this.logger_.logMethod?.('untilNext');\n this.checkDestroyed_();\n return new Promise((resolve) => {\n this.subscribe(resolve, {\n once: true,\n priority: true, // Resolve the promise before other listeners are called.\n receivePrevious: false, // We only want the *next* value, not the current one.\n });\n });\n }\n\n /**\n * Destroys the signal, clearing all its listeners and making it inactive.\n *\n * After destruction, any interaction with the signal (like `subscribe` or `untilNext`)\n * will throw an error. This is crucial for preventing memory leaks by allowing\n * garbage collection of the signal and its observers.\n */\n public destroy(): void {\n this.logger_.logMethod?.('destroy');\n if (this.isDestroyed__) {\n this.logger_.incident?.('destroy_', 'double_destroy_attempt');\n return;\n }\n this.isDestroyed__ = true;\n this.observers_.length = 0; // Clear all observers.\n this.config_.onDestroy?.(); // Call the optional onDestroy callback.\n this.config_ = null as unknown as SignalConfig; // Help GC by breaking references.\n }\n\n /**\n * Throws an error if the signal has been destroyed.\n * This is a safeguard to prevent interaction with a defunct signal.\n * @protected\n */\n protected checkDestroyed_(): void {\n if (this.isDestroyed__) {\n this.logger_.accident('checkDestroyed_', 'attempt_to_use_destroyed_signal');\n throw new Error(`Cannot interact with a destroyed signal (id: ${this.signalId})`);\n }\n }\n}\n", "import {delay} from '@alwatr/delay';\nimport {createLogger} from '@alwatr/logger';\n\nimport {SignalBase} from './signal-base.js';\n\nimport type {SignalConfig} from '../type.js';\n\n/**\n * A stateless signal for dispatching transient events.\n *\n * `EventSignal` is ideal for broadcasting events that do not have a persistent state.\n * Unlike `StateSignal`, it does not hold a value. Listeners are only notified of new\n * events as they are dispatched. This makes it suitable for modeling user interactions,\n * system notifications, or any one-off message.\n *\n * @template T The type of the payload for the events. Defaults to `void` for events without a payload.\n *\n * @example\n * // Create a signal for user click events.\n * const onUserClick = new EventSignal<{ x: number, y: number }>({ signalId: 'on-user-click' });\n *\n * // Subscribe to the event.\n * onUserClick.subscribe(clickPosition => {\n * console.log(`User clicked at: ${clickPosition.x}, ${clickPosition.y}`);\n * });\n *\n * // Dispatch an event.\n * onUserClick.dispatch({ x: 100, y: 250 }); // Notifies the listener.\n *\n * // --- Example with no payload ---\n * const onAppReady = new EventSignal({ signalId: 'on-app-ready' });\n * onAppReady.subscribe(() => console.log('Application is ready!'));\n * onAppReady.dispatch(); // Notifies the listener.\n */\nexport class EventSignal<T = void> extends SignalBase<T> {\n /**\n * The logger instance for this signal.\n * @protected\n */\n protected logger_ = createLogger(`event-signal: ${this.signalId}`);\n\n public constructor(config: SignalConfig) {\n super(config);\n this.logger_.logMethod?.('constructor');\n }\n\n /**\n * Dispatches an event with an optional payload to all active listeners.\n * The notification is scheduled as a microtask to prevent blocking and ensure\n * a consistent, non-blocking flow.\n *\n * @param payload The data to send with the event.\n */\n public dispatch(payload: T): void {\n this.logger_.logMethodArgs?.('dispatch', {payload});\n this.checkDestroyed_();\n // Dispatch as a microtask to ensure consistent, non-blocking behavior.\n delay.nextMicrotask().then(() => this.notify_(payload));\n }\n}\n", "import {delay} from '@alwatr/delay';\nimport {createLogger} from '@alwatr/logger';\n\nimport {SignalBase} from './signal-base.js';\n\nimport type {StateSignalConfig, ListenerCallback, SubscribeOptions, SubscribeResult, IReadonlySignal} from '../type.js';\n\n/**\n * A stateful signal that holds a value and notifies listeners when the value changes.\n *\n * `StateSignal` is the core of the signal library, representing a piece of mutable state.\n * It always has a value, and new subscribers immediately receive the current value by default.\n *\n * @template T The type of the state it holds.\n * @implements {IReadonlySignal<T>}\n *\n * @example\n * // Create a new state signal with an initial value.\n * const counter = new StateSignal<number>({\n * signalId: 'counter-signal',\n * initialValue: 0,\n * });\n *\n * // Get the current value.\n * console.log(counter.get()); // Outputs: 0\n *\n * // Subscribe to changes.\n * const subscription = counter.subscribe(newValue => {\n * console.log(`Counter changed to: ${newValue}`);\n * });\n *\n * // Set a new value, which triggers the notification.\n * counter.set(1); // Outputs: \"Counter changed to: 1\"\n *\n * // Update value based on the previous value.\n * counter.update(current => current + 1); // Outputs: \"Counter changed to: 2\"\n *\n * // Unsubscribe when no longer needed.\n * subscription.unsubscribe();\n */\nexport class StateSignal<T> extends SignalBase<T> implements IReadonlySignal<T> {\n /**\n * The current value of the signal.\n * @private\n */\n private value__: T;\n\n /**\n * The logger instance for this signal.\n * @protected\n */\n protected logger_ = createLogger(`state-signal: ${this.signalId}`);\n\n public constructor(config: StateSignalConfig<T>) {\n super(config);\n this.value__ = config.initialValue;\n this.logger_.logMethodArgs?.('constructor', {initialValue: this.value__});\n }\n\n /**\n * Retrieves the current value of the signal.\n *\n * @returns The current value.\n *\n * @example\n * console.log(mySignal.get());\n */\n public get(): T {\n this.checkDestroyed_();\n return this.value__;\n }\n\n /**\n * Updates the signal's value and notifies all active listeners.\n *\n * The notification is scheduled as a microtask, which means the update is deferred\n * slightly to batch multiple synchronous changes.\n *\n * @param newValue The new value to set.\n *\n * @example\n * // For primitive types\n * mySignal.set(42);\n *\n * // For object types, it's best practice to set an immutable new object.\n * mySignal.set({ ...mySignal.get(), property: 'new-value' });\n */\n public set(newValue: T): void {\n this.logger_.logMethodArgs?.('set', {newValue});\n this.checkDestroyed_();\n\n // For primitives (including null), do not notify if the value is the same.\n if (Object.is(this.value__, newValue) && (typeof newValue !== 'object' || newValue === null)) {\n return;\n }\n\n this.value__ = newValue;\n\n // Dispatch as a microtask to ensure consistent, non-blocking behavior.\n delay.nextMicrotask().then(() => this.notify_(newValue));\n }\n\n /**\n * Updates the signal's value based on its previous value.\n *\n * This method is particularly useful for state transitions that depend on the current value,\n * especially for objects or arrays, as it promotes an immutable update pattern.\n *\n * @param updater A function that receives the current value and returns the new value.\n *\n * @example\n * // For a counter\n * counterSignal.update(current => current + 1);\n *\n * // For an object state\n * userSignal.update(currentUser => ({ ...currentUser, loggedIn: true }));\n */\n public update(updater: (previousValue: T) => T): void {\n this.logger_.logMethod?.('update');\n this.checkDestroyed_();\n // The updater function is called with the current value to compute the new value,\n // which is then passed to the `set` method.\n this.set(updater(this.value__));\n }\n\n /**\n * Subscribes a listener to this signal.\n *\n * By default, the listener is immediately called with the signal's current value (`receivePrevious: true`).\n * This behavior can be customized via the `options` parameter.\n *\n * @param callback The function to be called when the signal's value changes.\n * @param options Subscription options, including `receivePrevious` and `once`.\n * @returns An object with an `unsubscribe` method to remove the listener.\n */\n public override subscribe(callback: ListenerCallback<T>, options: SubscribeOptions = {}): SubscribeResult {\n this.logger_.logMethodArgs?.('subscribe', {options});\n this.checkDestroyed_();\n\n // By default, new subscribers to a StateSignal should receive the current value.\n if (options.receivePrevious !== false) {\n // Immediately (but asynchronously) call the listener with the current value.\n // This is done in a microtask to ensure it happens after the subscription is fully registered.\n delay\n .nextMicrotask()\n .then(() => callback(this.value__))\n .catch((err) => this.logger_.error('subscribe', 'immediate_callback_failed', err));\n\n // If it's a 'once' subscription that receives the previous value, it's now fulfilled.\n // We don't need to add it to the observers list for future updates.\n if (options.once) {\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n return {unsubscribe: () => {}};\n }\n }\n\n return super.subscribe(callback, options);\n }\n\n /**\n * Destroys the signal, clearing its value and all listeners.\n * This is crucial for memory management to prevent leaks.\n */\n public override destroy(): void {\n this.value__ = null as T; // Clear the value to allow for garbage collection.\n super.destroy();\n }\n}\n", "import {delay} from '@alwatr/delay';\nimport {createLogger} from '@alwatr/logger';\n\nimport {StateSignal} from './state-signal.js';\n\nimport type {ComputedSignalConfig, IReadonlySignal, SubscribeResult, SubscribeOptions} from '../type.js';\n\n/**\n * A read-only signal that derives its value from a set of dependency signals.\n *\n * `ComputedSignal` is a powerful tool for creating values that reactively update when their underlying\n * data sources change. Its value is memoized, meaning the `get` function is only re-evaluated when\n * one of its dependencies has actually changed.\n *\n * A key feature is its lifecycle management: a `ComputedSignal` **must** be destroyed when no longer\n * needed to prevent memory leaks from its subscriptions to dependency signals.\n *\n * @template T The type of the computed value.\n *\n * @example\n * // --- Create dependency signals ---\n * const firstName = new StateSignal({ signalId: 'firstName', initialValue: 'John' });\n * const lastName = new StateSignal({ signalId: 'lastName', initialValue: 'Doe' });\n *\n * // --- Create a computed signal ---\n * const fullName = new ComputedSignal({\n * signalId: 'fullName',\n * deps: [firstName, lastName],\n * get: () => `${firstName.get()} ${lastName.get()}`,\n * });\n *\n * console.log(fullName.get()); // Outputs: \"John Doe\"\n *\n * // --- Subscribe to the computed value ---\n * fullName.subscribe(newFullName => {\n * console.log(`Name changed to: ${newFullName}`);\n * });\n *\n * // --- Update a dependency ---\n * lastName.set('Smith'); // Recalculates and logs: \"Name changed to: John Smith\"\n * console.log(fullName.get()); // Outputs: \"John Smith\"\n *\n * // --- IMPORTANT: Clean up when done ---\n * fullName.destroy();\n */\nexport class ComputedSignal<T> implements IReadonlySignal<T> {\n /**\n * The unique identifier for this signal instance.\n */\n public readonly signalId = this.config_.signalId;\n\n /**\n * The logger instance for this signal.\n * @protected\n */\n protected readonly logger_ = createLogger(`computed-signal: ${this.signalId}`);\n\n /**\n * The internal `StateSignal` that holds the computed value.\n * This is how the computed signal provides `.get()` and `.subscribe()` methods.\n * @protected\n */\n protected readonly internalSignal_ = new StateSignal<T>({\n signalId: `${this.signalId}-internal`,\n initialValue: this.config_.get(),\n });\n\n /**\n * A list of subscriptions to dependency signals.\n * @private\n */\n\n private readonly dependencySubscriptions__: SubscribeResult[] = [];\n\n /**\n * A flag to prevent concurrent recalculations.\n * @private\n */\n private isRecalculating__ = false;\n\n public constructor(protected config_: ComputedSignalConfig<T>) {\n this.logger_.logMethod?.('constructor');\n this.recalculate_ = this.recalculate_.bind(this);\n\n // Subscribe to all dependencies to trigger recalculation on change.\n for (const signal of config_.deps) {\n this.dependencySubscriptions__.push(signal.subscribe(this.recalculate_, {receivePrevious: false}));\n }\n }\n\n /**\n * The current value of the computed signal.\n * Accessing this property returns the memoized value and does not trigger a recalculation.\n *\n * @returns The current computed value.\n * @throws {Error} If accessed after the signal has been destroyed.\n */\n public get(): T {\n return this.internalSignal_.get();\n }\n\n /**\n * Indicates whether the computed signal has been destroyed.\n * A destroyed signal cannot be used and will throw an error if interacted with.\n * @returns `true` if the signal is destroyed, `false` otherwise.\n */\n public get isDestroyed(): boolean {\n return this.internalSignal_.isDestroyed;\n }\n\n /**\n * Subscribes a listener to this signal.\n * The listener will be called whenever the computed value changes.\n *\n * @param callback The function to be called with the new value.\n * @param options Subscription options.\n * @returns A `SubscribeResult` object with an `unsubscribe` method.\n */\n public subscribe(callback: (value: T) => void, options?: SubscribeOptions): SubscribeResult {\n return this.internalSignal_.subscribe(callback, options);\n }\n\n /**\n * Returns a Promise that resolves with the next computed value.\n *\n * @returns A Promise that resolves with the next value.\n */\n public untilNext(): Promise<T> {\n return this.internalSignal_.untilNext();\n }\n\n /**\n * Permanently disposes of the computed signal.\n *\n * This is a critical cleanup step. It unsubscribes from all dependency signals,\n * stopping future recalculations and allowing the signal to be garbage collected.\n * Failure to call `destroy()` will result in memory leaks.\n *\n * After `destroy()` is called, any attempt to access `.get()` or `.subscribe()` will throw an error.\n */\n public destroy(): void {\n this.logger_.logMethod?.('destroy');\n /**\n * If already destroyed, log an incident and return early.\n */\n if (this.isDestroyed) {\n this.logger_.incident?.('destroy', 'already_destroyed');\n return;\n }\n\n // Unsubscribe from all upstream dependencies.\n for (const subscription of this.dependencySubscriptions__) {\n subscription.unsubscribe();\n }\n this.dependencySubscriptions__.length = 0; // Clear the array of subscriptions.\n\n this.internalSignal_.destroy(); // Destroy the internal signal.\n this.config_.onDestroy?.(); // Call the optional onDestroy callback.\n this.config_ = null as unknown as ComputedSignalConfig<T>; // Release config closure.\n }\n\n /**\n * Schedules a recalculation of the signal's value.\n *\n * This method batches updates using a macrotask (`delay.nextMacrotask`) to ensure the\n * `get` function runs only once per event loop tick, even if multiple dependencies\n * change in the same synchronous block of code.\n * @protected\n */\n protected async recalculate_(): Promise<void> {\n this.logger_.logMethod?.('recalculate_');\n\n if (this.internalSignal_.isDestroyed) {\n // This check is important in case a dependency fires after this signal is destroyed.\n this.logger_.incident?.('recalculate_', 'recalculate_on_destroyed_signal');\n return;\n }\n\n if (this.isRecalculating__) {\n // If a recalculation is already scheduled, do nothing.\n this.logger_.logStep?.('recalculate_', 'skipping_recalculation_already_scheduled');\n return;\n }\n\n this.isRecalculating__ = true;\n\n try {\n // Wait for the next macrotask to start the recalculation.\n // This batches all synchronous dependency updates in the current event loop.\n await delay.nextMacrotask();\n \n if (this.isDestroyed) {\n this.logger_.incident?.('recalculate_', 'destroyed_during_delay');\n this.isRecalculating__ = false;\n return;\n }\n\n this.logger_.logStep?.('recalculate_', 'recalculating_value');\n\n // Set the new value on the internal signal, which will notify our subscribers.\n this.internalSignal_.set(this.config_.get());\n }\n catch (err) {\n this.logger_.error('recalculate_', 'recalculation_failed', err);\n }\n\n // Allow the next recalculation to be scheduled.\n this.isRecalculating__ = false;\n }\n}\n", "import {delay} from '@alwatr/delay';\nimport {createLogger} from '@alwatr/logger';\n\nimport type {EffectSignalConfig, IEffectSignal, SubscribeResult} from '../type.js';\n\n/**\n * Manages a side-effect that runs in response to changes in dependency signals.\n *\n * `EffectSignal` is designed for running logic that interacts with the \"outside world\"—such as\n * logging, network requests, or DOM manipulation—whenever its dependencies are updated.\n * It encapsulates the subscription and cleanup logic, providing a robust and memory-safe\n * way to handle reactive side-effects.\n *\n * A key feature is its lifecycle management: an `EffectSignal` **must** be destroyed when no longer\n * needed to prevent memory leaks and stop the effect from running unnecessarily.\n *\n * @implements {IEffectSignal}\n *\n * @example\n * // --- Create dependency signals ---\n * const counter = new StateSignal({ initialValue: 0, signalId: 'counter' });\n * const user = new StateSignal({ initialValue: 'guest', signalId: 'user' });\n *\n * // --- Create an effect ---\n * const analyticsEffect = new EffectSignal({\n * signalId: 'analytics-effect',\n * deps: [counter, user],\n * run: () => {\n * console.log(`Analytics: User '${user.get()}' clicked ${counter.get()} times.`);\n * },\n * runImmediately: true, // Optional: run once on creation\n * });\n * // Immediately logs: \"Analytics: User 'guest' clicked 0 times.\"\n *\n * // --- Trigger the effect by updating a dependency ---\n * counter.set(1);\n * // After a macrotask, logs: \"Analytics: User 'guest' clicked 1 times.\"\n *\n * // --- IMPORTANT: Clean up when the effect is no longer needed ---\n * analyticsEffect.destroy();\n *\n * // Further updates will not trigger the effect.\n * counter.set(2); // Nothing is logged.\n */\nexport class EffectSignal implements IEffectSignal {\n /**\n * The unique identifier for this signal instance.\n */\n public readonly signalId = this.config_.signalId ? this.config_.signalId : `[${this.config_.deps.map((dep) => dep.signalId).join(', ')}]`;\n\n /**\n * The logger instance for this signal.\n * @protected\n */\n protected readonly logger_ = createLogger(`effect-signal: ${this.signalId}`);\n\n /**\n * A list of subscriptions to dependency signals.\n * @private\n */\n private readonly dependencySubscriptions__: SubscribeResult[] = [];\n\n /**\n * A flag to prevent concurrent executions of the effect.\n * @private\n */\n private isRunning__ = false;\n\n /**\n * A flag indicating whether the effect has been destroyed.\n * @private\n */\n private isDestroyed__ = false;\n\n /**\n * Indicates whether the effect signal has been destroyed.\n * A destroyed signal will no longer execute its effect and cannot be reused.\n *\n * @returns `true` if the signal is destroyed, `false` otherwise.\n */\n public get isDestroyed(): boolean {\n return this.isDestroyed__;\n }\n\n public constructor(protected config_: EffectSignalConfig) {\n this.logger_.logMethod?.('constructor');\n this.scheduleExecution_ = this.scheduleExecution_.bind(this);\n\n // Subscribe to all dependencies. We don't need the previous value,\n // as the `runImmediately` option controls the initial execution.\n for (const signal of config_.deps) {\n this.dependencySubscriptions__.push(signal.subscribe(this.scheduleExecution_, {receivePrevious: false}));\n }\n\n // Run the effect immediately if requested.\n if (config_.runImmediately === true) {\n // We don't need to await this, let it run in the background.\n void this.scheduleExecution_();\n }\n }\n\n /**\n * Schedules the execution of the effect's `run` function.\n *\n * This method batches updates using a macrotask (`delay.nextMacrotask`) to ensure the\n * `run` function executes only once per event loop tick, even if multiple\n * dependencies change simultaneously.\n * @protected\n */\n protected async scheduleExecution_(): Promise<void> {\n this.logger_.logMethod?.('scheduleExecution_');\n\n if (this.isDestroyed__) {\n this.logger_.incident?.('scheduleExecution_', 'schedule_execution_on_destroyed_signal');\n return;\n }\n if (this.isRunning__) {\n // If an execution is already scheduled, do nothing.\n this.logger_.logStep?.('scheduleExecution_', 'skipped_because_already_running');\n return;\n }\n\n this.isRunning__ = true;\n\n try {\n // Wait for the next macrotask to batch simultaneous updates.\n await delay.nextMacrotask();\n if (this.isDestroyed__) {\n this.logger_.incident?.('scheduleExecution_', 'destroyed_during_delay');\n this.isRunning__ = false;\n return;\n }\n\n this.logger_.logStep?.('scheduleExecution_', 'executing_effect');\n await this.config_.run();\n }\n catch (err) {\n this.logger_.error('scheduleExecution_', 'effect_failed', err);\n }\n\n // Reset the flag after the current execution is complete.\n this.isRunning__ = false;\n }\n\n /**\n * Permanently disposes of the effect signal.\n *\n * This is a critical cleanup step. It unsubscribes from all dependency signals,\n * stopping any future executions of the effect and allowing it to be garbage collected.\n * Failure to call `destroy()` will result in memory leaks and potentially unwanted side effects.\n */\n public destroy(): void {\n this.logger_.logMethod?.('destroy');\n\n if (this.isDestroyed__) {\n this.logger_.incident?.('destroy', 'already_destroyed');\n return;\n }\n\n this.isDestroyed__ = true;\n\n // Unsubscribe from all upstream dependencies.\n for (const subscription of this.dependencySubscriptions__) {\n subscription.unsubscribe();\n }\n this.dependencySubscriptions__.length = 0; // Clear the array of subscriptions.\n\n this.config_.onDestroy?.(); // Call the optional onDestroy callback.\n this.config_ = null as unknown as EffectSignalConfig; // Release config closure.\n }\n}\n", "import {EventSignal} from '../core/event-signal.js';\n\nimport type {SignalConfig} from '../type.js';\n\n/**\n * Creates a stateless signal for dispatching transient events.\n *\n * `EventSignal` is ideal for broadcasting events that do not have a persistent state.\n * Unlike `StateSignal`, it does not hold a value. Listeners are only notified of new\n * events as they are dispatched. This makes it suitable for modeling user interactions,\n * system notifications, or any one-off message.\n *\n * @template T The type of the payload for the events.\n *\n * @param config The configuration for the event signal.\n * @returns A new instance of EventSignal.\n *\n * @example\n * const onUserClick = createEventSignal<{ x: number, y: number }>({\n * signalId: 'on-user-click'\n * });\n *\n * onUserClick.subscribe(pos => {\n * console.log(`User clicked at: ${pos.x}, ${pos.y}`);\n * });\n *\n * onUserClick.dispatch({ x: 100, y: 250 });\n */\nexport function createEventSignal<T = void>(config: SignalConfig): EventSignal<T> {\n return new EventSignal<T>(config);\n}\n", "import {StateSignal} from '../core/state-signal.js';\n\nimport type {StateSignalConfig} from '../type.js';\n\n/**\n * Creates a stateful signal that holds a value and notifies listeners when the value changes.\n *\n * `StateSignal` is the core of the signal library, representing a piece of mutable state.\n * It always has a value, and new subscribers immediately receive the current value by default.\n *\n * @template T The type of the state it holds.\n *\n * @param config The configuration for the state signal.\n * @returns A new instance of StateSignal.\n *\n * @example\n * const counter = createStateSignal({\n * signalId: 'counter-signal',\n * initialValue: 0,\n * });\n *\n * console.log(counter.get()); // Outputs: 0\n * counter.set(1);\n * console.log(counter.get()); // Outputs: 1\n */\nexport function createStateSignal<T>(config: StateSignalConfig<T>): StateSignal<T> {\n return new StateSignal(config);\n}\n", "import {ComputedSignal} from '../core/computed-signal.js';\n\nimport type {ComputedSignalConfig} from '../type.js';\n\n/**\n * Creates a read-only signal that derives its value from a set of dependency signals.\n *\n * `ComputedSignal` is a powerful tool for creating values that reactively update when their underlying\n * data sources change. Its value is memoized, meaning the `get` function is only re-evaluated when\n * one of its dependencies has actually changed.\n *\n * A key feature is its lifecycle management: a `ComputedSignal` **must** be destroyed when no longer\n * needed to prevent memory leaks from its subscriptions to dependency signals.\n *\n * @template T The type of the computed value.\n *\n * @param config The configuration for the computed signal.\n * @returns A new, read-only computed signal.\n *\n * @example\n * const firstName = createStateSignal({ signalId: 'firstName', initialValue: 'John' });\n * const lastName = createStateSignal({ signalId: 'lastName', initialValue: 'Doe' });\n *\n * const fullName = createComputedSignal({\n * signalId: 'fullName',\n * deps: [firstName, lastName],\n * get: () => `${firstName.get()} ${lastName.get()}`,\n * });\n *\n * console.log(fullName.get()); // \"John Doe\"\n *\n * // IMPORTANT: Always destroy a computed signal when no longer needed.\n * // fullName.destroy();\n */\nexport function createComputedSignal<T>(config: ComputedSignalConfig<T>): ComputedSignal<T> {\n return new ComputedSignal(config);\n}\n", "import {EffectSignal} from '../core/effect-signal.js';\n\nimport type {EffectSignalConfig} from '../type.js';\n\n/**\n * Creates a side-effect that runs in response to changes in dependency signals.\n *\n * `EffectSignal` is designed for running logic that interacts with the \"outside world\"—such as\n * logging, network requests, or DOM manipulation—whenever its dependencies are updated.\n * It encapsulates the subscription and cleanup logic, providing a robust and memory-safe\n * way to handle reactive side-effects.\n *\n * A key feature is its lifecycle management: an `EffectSignal` **must** be destroyed when no longer\n * needed to prevent memory leaks and stop the effect from running unnecessarily.\n *\n * @param config The configuration for the effect.\n * @returns An object with a `destroy` method to stop the effect.\n *\n * @example\n * // --- Create dependency signals ---\n * const counter = createStateSignal({ initialValue: 0, signalId: 'counter' });\n * const user = createStateSignal({ initialValue: 'guest', signalId: 'user' });\n *\n * // --- Create an effect ---\n * const analyticsEffect = createEffect({\n * deps: [counter, user],\n * run: () => {\n * console.log(`Analytics: User '${user.get()}' clicked ${counter.get()} times.`);\n * },\n * runImmediately: true, // Optional: run once on creation\n * });\n * // Immediately logs: \"Analytics: User 'guest' clicked 0 times.\"\n *\n * // --- Trigger the effect by updating a dependency ---\n * counter.set(1);\n * // After a macrotask, logs: \"Analytics: User 'guest' clicked 1 times.\"\n *\n * // --- IMPORTANT: Clean up when the effect is no longer needed ---\n * analyticsEffect.destroy();\n *\n * // Further updates will not trigger the effect.\n * counter.set(2); // Nothing is logged.\n */\nexport function createEffect(config: EffectSignalConfig): EffectSignal {\n return new EffectSignal(config);\n}\n", "import {createDebouncer} from '@alwatr/debounce';\n\nimport {StateSignal} from '../core/state-signal.js';\nimport {createComputedSignal} from '../creators/computed.js';\n\nimport type {ComputedSignal} from '../core/computed-signal.js';\nimport type {IReadonlySignal, DebounceSignalConfig} from '../type.js';\n\n/**\n * Creates a new computed signal that debounces updates from a source signal.\n *\n * The returned signal is a `ComputedSignal`, meaning it is read-only and its value is\n * derived from the source. It only updates its value after a specified period of\n * inactivity from the source signal.\n *\n * This operator is essential for handling high-frequency events, such as user input\n * in a search box, resizing a window, or any other event that fires rapidly.\n * By debouncing, you can ensure that expensive operations (like API calls or heavy\n * computations) are only executed once the events have settled.\n *\n * @template T The type of the signal's value.\n *\n * @param {IReadonlySignal<T>} sourceSignal The original signal to debounce.\n * It can be a `StateSignal`, `ComputedSignal`, or any signal implementing `IReadonlySignal`.\n * @param {DebounceSignalConfig} config Configuration object for the debouncer,\n * including `delay`, `leading`, and `trailing` options from `@alwatr/debounce`.\n *\n * @returns {IComputedSignal<T>} A new, read-only computed signal that emits debounced values.\n * Crucially, you **must** call `.destroy()` on this signal when it's no longer\n * needed to prevent memory leaks by cleaning up internal subscriptions and timers.\n *\n * @example\n * ```typescript\n * // Create a source signal for user input.\n * const searchInput = createStateSignal({\n * signalId: 'search-input',\n * initialValue: '',\n * });\n *\n * // Create a debounced signal that waits 300ms after the user stops typing.\n * const debouncedSearch = createDebouncedSignal(searchInput, { delay: 300 });\n *\n * // Use an effect to react to the debounced value.\n * createEffect({\n * deps: [debouncedSearch],\n * run: () => {\n * if (debouncedSearch.get()) {\n * console.log(`🚀 Sending API request for: \"${debouncedSearch.get()}\"`);\n * }\n * },\n * });\n *\n * searchInput.set('Alwatr');\n * searchInput.set('Alwatr Signal');\n * // (after 300ms of inactivity)\n * // Logs: \"🚀 Sending API request for: \"Alwatr Signal\"\"\n *\n * // IMPORTANT: Clean up when the component unmounts.\n * // debouncedSearch.destroy();\n * ```\n */\nexport function createDebouncedSignal<T>(sourceSignal: IReadonlySignal<T>, config: DebounceSignalConfig): ComputedSignal<T> {\n const signalId = config.signalId ?? `${sourceSignal.signalId}-debounced`;\n\n const internalSignal = new StateSignal<T>({\n signalId: `${signalId}-internal`,\n initialValue: sourceSignal.get(),\n });\n\n const debouncer = createDebouncer({\n ...config,\n func: (value: T): void => {\n internalSignal.set(value);\n },\n });\n\n const subscription = sourceSignal.subscribe(debouncer.trigger);\n\n return createComputedSignal({\n signalId,\n deps: [internalSignal],\n get: () => internalSignal.get(),\n onDestroy: () => {\n if (internalSignal.isDestroyed) return;\n subscription.unsubscribe();\n debouncer.cancel();\n internalSignal.destroy();\n config.onDestroy?.();\n config = null as unknown as DebounceSignalConfig;\n },\n });\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["delay", "createLogger", "createLogger", "delay", "delay", "createLogger", "createLogger", "delay", "delay", "createLogger"]
|
|
7
7
|
}
|
|
@@ -38,8 +38,8 @@ import type { IReadonlySignal, DebounceSignalConfig } from '../type.js';
|
|
|
38
38
|
* createEffect({
|
|
39
39
|
* deps: [debouncedSearch],
|
|
40
40
|
* run: () => {
|
|
41
|
-
* if (debouncedSearch.
|
|
42
|
-
* console.log(`🚀 Sending API request for: "${debouncedSearch.
|
|
41
|
+
* if (debouncedSearch.get()) {
|
|
42
|
+
* console.log(`🚀 Sending API request for: "${debouncedSearch.get()}"`);
|
|
43
43
|
* }
|
|
44
44
|
* },
|
|
45
45
|
* });
|
|
@@ -32,8 +32,8 @@ import type { IReadonlySignal } from '../type.js';
|
|
|
32
32
|
* run: () => {
|
|
33
33
|
* // This effect only runs for even numbers.
|
|
34
34
|
* // The value can be `undefined` on the first run if initialValue is not even.
|
|
35
|
-
* if (evenNumberSignal.
|
|
36
|
-
* console.log(`Even number detected: ${evenNumberSignal.
|
|
35
|
+
* if (evenNumberSignal.get() !== undefined) {
|
|
36
|
+
* console.log(`Even number detected: ${evenNumberSignal.get()}`);
|
|
37
37
|
* }
|
|
38
38
|
* },
|
|
39
39
|
* runImmediately: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filter.d.ts","sourceRoot":"","sources":["../../src/operators/filter.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,4BAA4B,CAAC;AAC/D,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,YAAY,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EACpC,YAAY,EAAE,eAAe,CAAC,CAAC,CAAC,EAChC,SAAS,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,OAAO,EAChC,QAAQ,SAAsC,GAC7C,cAAc,CAAC,CAAC,GAAG,SAAS,CAAC,
|
|
1
|
+
{"version":3,"file":"filter.d.ts","sourceRoot":"","sources":["../../src/operators/filter.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,4BAA4B,CAAC;AAC/D,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,YAAY,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EACpC,YAAY,EAAE,eAAe,CAAC,CAAC,CAAC,EAChC,SAAS,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,OAAO,EAChC,QAAQ,SAAsC,GAC7C,cAAc,CAAC,CAAC,GAAG,SAAS,CAAC,CAwB/B"}
|
package/dist/operators/map.d.ts
CHANGED
|
@@ -27,10 +27,10 @@ import type { IReadonlySignal } from '../type.js';
|
|
|
27
27
|
* (user) => user.name,
|
|
28
28
|
* );
|
|
29
29
|
*
|
|
30
|
-
* console.log(userNameSignal.
|
|
30
|
+
* console.log(userNameSignal.get()); // Outputs: "John"
|
|
31
31
|
* // in next macro-task ...
|
|
32
32
|
* userSignal.set({ name: 'Jane', age: 32 });
|
|
33
|
-
* console.log(userNameSignal.
|
|
33
|
+
* console.log(userNameSignal.get()); // Outputs: "Jane"
|
|
34
34
|
*/
|
|
35
35
|
export declare function createMappedSignal<T, R>(sourceSignal: IReadonlySignal<T>, projectFunction: (value: T) => R, signalId?: string): ComputedSignal<R>;
|
|
36
36
|
//# sourceMappingURL=map.d.ts.map
|
package/dist/type.d.ts
CHANGED
|
@@ -126,7 +126,7 @@ export interface IReadonlySignal<T> {
|
|
|
126
126
|
/**
|
|
127
127
|
* The current value of the signal.
|
|
128
128
|
*/
|
|
129
|
-
|
|
129
|
+
get: () => T;
|
|
130
130
|
/**
|
|
131
131
|
* Indicates whether the signal has been destroyed.
|
|
132
132
|
* A destroyed signal cannot be used and will throw an error if interacted with.
|
|
@@ -189,7 +189,7 @@ export interface ComputedSignalConfig<T> extends SignalConfig {
|
|
|
189
189
|
* const counter = new StateSignal({initialValue: 0});
|
|
190
190
|
* const isEven = new ComputedSignal({
|
|
191
191
|
* deps: [counter],
|
|
192
|
-
* get: () => counter.
|
|
192
|
+
* get: () => counter.get() % 2 === 0,
|
|
193
193
|
* });
|
|
194
194
|
*/
|
|
195
195
|
get: () => T;
|
|
@@ -222,7 +222,7 @@ export interface EffectSignalConfig {
|
|
|
222
222
|
* const counter = new StateSignal({initialValue: 0});
|
|
223
223
|
* new EffectSignal({
|
|
224
224
|
* deps: [counter],
|
|
225
|
-
* run: () => console.log(`The counter is now: ${counter.
|
|
225
|
+
* run: () => console.log(`The counter is now: ${counter.get()}`),
|
|
226
226
|
* });
|
|
227
227
|
*/
|
|
228
228
|
run: () => Awaitable<void>;
|
package/dist/type.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"type.d.ts","sourceRoot":"","sources":["../src/type.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,kBAAkB,CAAC;AAGtD;;;;;;;GAOG;AACH,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,SAAS,CAAC,IAAI,CAAC,CAAC;AAEhE;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;;;OASG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IAEf;;;;;;;;;OASG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;;;;;;;;;;;;OAcG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;;;;OAQG;IACH,WAAW,EAAE,MAAM,IAAI,CAAC;CACzB;AAED;;;GAGG;AACH,MAAM,WAAW,SAAS,CAAC,CAAC;IAC1B;;OAEG;IACH,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAE9B;;OAEG;IACH,OAAO,CAAC,EAAE,gBAAgB,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;;;OAOG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB,CAAC,CAAC,CAAE,SAAQ,YAAY;IACxD;;OAEG;IACH,QAAQ,CAAC,YAAY,EAAE,CAAC,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE1B;;OAEG;IACH,
|
|
1
|
+
{"version":3,"file":"type.d.ts","sourceRoot":"","sources":["../src/type.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alwatr/signal",
|
|
3
3
|
"description": "Alwatr Signal is a powerful, lightweight, and modern reactive programming library. It is inspired by the best concepts from major reactive libraries but engineered to be faster and more efficient than all of them. It provides a robust and elegant way to manage application state through a system of signals, offering fine-grained reactivity, predictability, and excellent performance.",
|
|
4
|
-
"version": "5.2.
|
|
4
|
+
"version": "5.2.1",
|
|
5
5
|
"author": "S. Ali Mihandoost <ali.mihandoost@gmail.com>",
|
|
6
6
|
"bugs": "https://github.com/Alwatr/flux/issues",
|
|
7
7
|
"dependencies": {
|
|
@@ -68,5 +68,5 @@
|
|
|
68
68
|
},
|
|
69
69
|
"type": "module",
|
|
70
70
|
"types": "./dist/main.d.ts",
|
|
71
|
-
"gitHead": "
|
|
71
|
+
"gitHead": "f7e5cd521b2f961f1fa0efe236d7008fbc199c05"
|
|
72
72
|
}
|
|
@@ -17,7 +17,7 @@ describe('ComputedSignal', () => {
|
|
|
17
17
|
signal = new ComputedSignal({
|
|
18
18
|
signalId,
|
|
19
19
|
deps: [dep1, dep2],
|
|
20
|
-
get: () => dep1.
|
|
20
|
+
get: () => dep1.get() + dep2.get(),
|
|
21
21
|
});
|
|
22
22
|
});
|
|
23
23
|
|
|
@@ -31,14 +31,14 @@ describe('ComputedSignal', () => {
|
|
|
31
31
|
expect(ComputedSignal).toBeDefined();
|
|
32
32
|
expect(signal).toBeInstanceOf(ComputedSignal);
|
|
33
33
|
expect(signal.signalId).toBe(signalId);
|
|
34
|
-
expect(signal.
|
|
34
|
+
expect(signal.get()).toBe(3); // 1 + 2
|
|
35
35
|
});
|
|
36
36
|
|
|
37
37
|
it('should compute value from dependencies', async () => {
|
|
38
|
-
expect(signal.
|
|
38
|
+
expect(signal.get()).toBe(3);
|
|
39
39
|
dep1.set(5);
|
|
40
40
|
await signal.untilNext();
|
|
41
|
-
expect(signal.
|
|
41
|
+
expect(signal.get()).toBe(7); // 5 + 2
|
|
42
42
|
});
|
|
43
43
|
|
|
44
44
|
it('should notify subscribers when computed value changes', async () => {
|
|
@@ -115,7 +115,7 @@ describe('ComputedSignal', () => {
|
|
|
115
115
|
deps: [],
|
|
116
116
|
get: () => 42,
|
|
117
117
|
});
|
|
118
|
-
expect(noDepSignal.
|
|
118
|
+
expect(noDepSignal.get()).toBe(42);
|
|
119
119
|
noDepSignal.destroy();
|
|
120
120
|
});
|
|
121
121
|
|
|
@@ -135,7 +135,7 @@ describe('ComputedSignal', () => {
|
|
|
135
135
|
describe('destroyed signal', () => {
|
|
136
136
|
it('should throw error when accessing value after destroy', () => {
|
|
137
137
|
signal.destroy();
|
|
138
|
-
expect(() => signal.
|
|
138
|
+
expect(() => signal.get()).toThrow();
|
|
139
139
|
});
|
|
140
140
|
|
|
141
141
|
it('should not notify after destroy', async () => {
|
|
@@ -19,7 +19,7 @@ describe('StateSignal', () => {
|
|
|
19
19
|
expect(StateSignal).toBeDefined();
|
|
20
20
|
expect(signal).toBeInstanceOf(StateSignal);
|
|
21
21
|
expect(signal.signalId).toBe(signalId);
|
|
22
|
-
expect(signal.
|
|
22
|
+
expect(signal.get()).toBe(0);
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
it('should notify subscribers when value changes', async () => {
|
|
@@ -218,7 +218,7 @@ describe('StateSignal', () => {
|
|
|
218
218
|
});
|
|
219
219
|
|
|
220
220
|
it('should throw an error when accessing value on a destroyed signal', () => {
|
|
221
|
-
expect(() => signal.
|
|
221
|
+
expect(() => signal.get()).toThrow(`Cannot interact with a destroyed signal (id: ${signalId})`);
|
|
222
222
|
});
|
|
223
223
|
|
|
224
224
|
it('should not notify any listeners after being destroyed', async () => {
|