@alwatr/signal 4.1.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.mjs CHANGED
@@ -1,56 +1,459 @@
1
- /* @alwatr/signal v4.1.0 */
1
+ /* @alwatr/signal v5.0.0 */
2
2
 
3
- // src/trigger.ts
4
- import { AlwatrObservable } from "@alwatr/observable";
3
+ // src/signal-base.ts
4
+ import { packageTracer } from "@alwatr/package-tracer";
5
+ __dev_mode__: packageTracer.add("@alwatr/signal", "5.0.0");
6
+ var SignalBase = class {
7
+ /**
8
+ * Initializes a new `SignalBase`.
9
+ * @param config The configuration for the signal, containing its `signalId`.
10
+ */
11
+ constructor(config) {
12
+ /**
13
+ * The list of observers (listeners) subscribed to this signal.
14
+ * @protected
15
+ */
16
+ this.observers_ = [];
17
+ this.isDestroyed_ = false;
18
+ /**
19
+ * Throws an error if the signal has been destroyed.
20
+ * This is a safeguard to prevent interaction with a defunct signal.
21
+ * @protected
22
+ */
23
+ this.checkDestroyed_ = () => {
24
+ if (this.isDestroyed_) {
25
+ this.logger_.accident("checkDestroyed_", "attempt_to_use_destroyed_signal");
26
+ throw new Error(`Cannot interact with a destroyed signal (id: ${this.signalId})`);
27
+ }
28
+ };
29
+ this.signalId = config.signalId;
30
+ }
31
+ /**
32
+ * Indicates whether the signal has been destroyed.
33
+ * A destroyed signal cannot be used and will throw an error if interacted with.
34
+ * @returns `true` if the signal is destroyed, `false` otherwise.
35
+ */
36
+ get isDestroyed() {
37
+ return this.isDestroyed_;
38
+ }
39
+ /**
40
+ * Removes a specific observer from the observers list.
41
+ * @param observer The observer instance to remove.
42
+ * @protected
43
+ */
44
+ removeObserver_(observer) {
45
+ if (this.isDestroyed_) {
46
+ this.logger_.incident?.("removeObserver_", "remove_observer_on_destroyed_signal");
47
+ return;
48
+ }
49
+ this.logger_.logMethod?.("removeObserver_");
50
+ const index = this.observers_.indexOf(observer);
51
+ if (index !== -1) {
52
+ this.observers_.splice(index, 1);
53
+ }
54
+ }
55
+ /**
56
+ * Subscribes a listener function to this signal.
57
+ *
58
+ * The listener will be called whenever the signal is notified (e.g., when `dispatch` or `set` is called).
59
+ *
60
+ * @param callback The function to be called when the signal is dispatched.
61
+ * @param options Subscription options to customize the behavior (e.g., `once`, `priority`).
62
+ * @returns A `SubscribeResult` object with an `unsubscribe` method to remove the listener.
63
+ */
64
+ subscribe(callback, options) {
65
+ this.logger_.logMethodArgs?.("subscribe.base", { options });
66
+ this.checkDestroyed_();
67
+ const observer = { callback, options };
68
+ if (options?.priority) {
69
+ this.observers_.unshift(observer);
70
+ } else {
71
+ this.observers_.push(observer);
72
+ }
73
+ const unsubscribe = () => this.removeObserver_(observer);
74
+ return { unsubscribe };
75
+ }
76
+ /**
77
+ * Notifies all registered observers about a new value.
78
+ *
79
+ * This method iterates through a snapshot of the current observers to prevent issues
80
+ * with subscriptions changing during notification (e.g., an observer unsubscribing itself).
81
+ *
82
+ * @param value The new value to notify observers about.
83
+ * @protected
84
+ */
85
+ notify_(value) {
86
+ if (this.isDestroyed_) {
87
+ this.logger_.incident?.("notify_", "notify_on_destroyed_signal");
88
+ return;
89
+ }
90
+ this.logger_.logMethodArgs?.("notify_", value);
91
+ const currentObservers = [...this.observers_];
92
+ for (const observer of currentObservers) {
93
+ if (observer.options?.once) {
94
+ this.removeObserver_(observer);
95
+ }
96
+ try {
97
+ const result = observer.callback(value);
98
+ if (result instanceof Promise) {
99
+ result.catch((err) => {
100
+ this.logger_.error("notify_", "async_callback_failed", err, { observer });
101
+ });
102
+ }
103
+ } catch (err) {
104
+ this.logger_.error("notify_", "sync_callback_failed", err);
105
+ }
106
+ }
107
+ }
108
+ /**
109
+ * Returns a Promise that resolves with the next value dispatched by the signal.
110
+ * This provides an elegant way to wait for a single, future event using `async/await`.
111
+ *
112
+ * @returns A Promise that resolves with the next dispatched value.
113
+ *
114
+ * @example
115
+ * async function onButtonClick() {
116
+ * console.log('Waiting for the next signal...');
117
+ * const nextValue = await mySignal.untilNext();
118
+ * console.log('Signal received:', nextValue);
119
+ * }
120
+ */
121
+ untilNext() {
122
+ this.logger_.logMethod?.("untilNext");
123
+ this.checkDestroyed_();
124
+ return new Promise((resolve) => {
125
+ this.subscribe(resolve, {
126
+ once: true,
127
+ priority: true,
128
+ // Resolve the promise before other listeners are called.
129
+ receivePrevious: false
130
+ // We only want the *next* value, not the current one.
131
+ });
132
+ });
133
+ }
134
+ /**
135
+ * Destroys the signal, clearing all its listeners and making it inactive.
136
+ *
137
+ * After destruction, any interaction with the signal (like `subscribe` or `untilNext`)
138
+ * will throw an error. This is crucial for preventing memory leaks by allowing
139
+ * garbage collection of the signal and its observers.
140
+ */
141
+ destroy() {
142
+ this.logger_.logMethod?.("destroy");
143
+ this.isDestroyed_ = true;
144
+ this.observers_.length = 0;
145
+ }
146
+ };
5
147
 
6
- // src/logger.ts
7
- import { createLogger } from "@alwatr/nanolib";
8
- var logger = /* @__PURE__ */ createLogger("@alwatr/signal");
148
+ // src/event-signal.ts
149
+ import { delay } from "@alwatr/delay";
150
+ import { createLogger } from "@alwatr/logger";
151
+ var EventSignal = class extends SignalBase {
152
+ /**
153
+ * Initializes a new `EventSignal`.
154
+ * @param config The configuration for the signal, containing its `signalId`.
155
+ */
156
+ constructor(config) {
157
+ super(config);
158
+ this.logger_ = createLogger(`event-signal: ${this.signalId}`);
159
+ this.logger_.logMethod?.("constructor");
160
+ }
161
+ /**
162
+ * Dispatches an event with an optional payload to all active listeners.
163
+ * The notification is scheduled as a microtask to prevent blocking and ensure
164
+ * a consistent, non-blocking flow.
165
+ *
166
+ * @param payload The data to send with the event.
167
+ */
168
+ dispatch(payload) {
169
+ this.logger_.logMethodArgs?.("dispatch", payload);
170
+ this.checkDestroyed_();
171
+ delay.nextMicrotask().then(() => {
172
+ this.notify_(payload);
173
+ });
174
+ }
175
+ };
9
176
 
10
- // src/trigger.ts
11
- __dev_mode__: logger.logFileModule?.("trigger");
12
- var AlwatrTrigger = class extends AlwatrObservable {
177
+ // src/state-signal.ts
178
+ import { delay as delay2 } from "@alwatr/delay";
179
+ import { createLogger as createLogger2 } from "@alwatr/logger";
180
+ var StateSignal = class extends SignalBase {
181
+ /**
182
+ * Initializes a new `StateSignal`.
183
+ * @param config The configuration for the state signal, including `signalId` and `initialValue`.
184
+ */
13
185
  constructor(config) {
14
- config.loggerPrefix ??= "signal";
15
186
  super(config);
187
+ this.logger_ = createLogger2(`state-signal: ${this.signalId}`);
188
+ this.value__ = config.initialValue;
189
+ this.logger_.logMethodArgs?.("constructor", { initialValue: this.value__ });
190
+ }
191
+ /**
192
+ * Retrieves the current value of the signal.
193
+ *
194
+ * @returns The current value.
195
+ *
196
+ * @example
197
+ * console.log(mySignal.value);
198
+ */
199
+ get value() {
200
+ this.checkDestroyed_();
201
+ return this.value__;
202
+ }
203
+ /**
204
+ * Updates the signal's value and notifies all active listeners.
205
+ *
206
+ * The notification is scheduled as a microtask, which means the update is deferred
207
+ * slightly to batch multiple synchronous changes.
208
+ *
209
+ * @param newValue The new value to set.
210
+ *
211
+ * @example
212
+ * // For primitive types
213
+ * mySignal.set(42);
214
+ *
215
+ * // For object types, it's best practice to set an immutable new object.
216
+ * mySignal.set({ ...mySignal.value, property: 'new-value' });
217
+ */
218
+ set(newValue) {
219
+ this.logger_.logMethodArgs?.("set", { newValue });
220
+ this.checkDestroyed_();
221
+ if (Object.is(this.value__, newValue) && (typeof newValue !== "object" || newValue === null)) return;
222
+ this.value__ = newValue;
223
+ delay2.nextMicrotask().then(() => {
224
+ this.notify_(newValue);
225
+ });
16
226
  }
17
227
  /**
18
- * Dispatch an event to all listeners.
228
+ * Subscribes a listener to this signal.
229
+ *
230
+ * By default, the listener is immediately called with the signal's current value (`receivePrevious: true`).
231
+ * This behavior can be customized via the `options` parameter.
232
+ *
233
+ * @param callback The function to be called when the signal's value changes.
234
+ * @param options Subscription options, including `receivePrevious` and `once`.
235
+ * @returns An object with an `unsubscribe` method to remove the listener.
19
236
  */
20
- notify() {
21
- this.notify_({});
237
+ subscribe(callback, options = {}) {
238
+ this.logger_.logMethodArgs?.("subscribe", { options });
239
+ this.checkDestroyed_();
240
+ const receivePrevious = options.receivePrevious !== false;
241
+ if (receivePrevious) {
242
+ delay2.nextMicrotask().then(() => callback(this.value__)).catch((err) => {
243
+ this.logger_.error("subscribe", "run_callback_immediate_failed", err);
244
+ });
245
+ if (options.once) {
246
+ return { unsubscribe: () => {
247
+ } };
248
+ }
249
+ }
250
+ return super.subscribe(callback, options);
22
251
  }
23
252
  /**
24
- * Wait until next event signal.
253
+ * Destroys the signal, clearing its value and all listeners.
254
+ * This is crucial for memory management to prevent leaks.
25
255
  */
26
- async untilTriggered() {
27
- await super.untilNewNotify_();
256
+ destroy() {
257
+ super.destroy();
258
+ this.value__ = null;
28
259
  }
29
260
  };
30
261
 
31
- // src/signal.ts
32
- import { AlwatrObservable as AlwatrObservable2 } from "@alwatr/observable";
33
- __dev_mode__: logger.logFileModule?.("signal");
34
- var AlwatrSignal = class extends AlwatrObservable2 {
35
- constructor(config) {
36
- config.loggerPrefix ??= "signal";
37
- super(config);
262
+ // src/computed-signal.ts
263
+ import { delay as delay3 } from "@alwatr/delay";
264
+ import { createLogger as createLogger3 } from "@alwatr/logger";
265
+ var ComputedSignal = class {
266
+ /**
267
+ * Initializes a new `ComputedSignal`.
268
+ * @param config The configuration, including dependencies (`deps`) and the getter function (`get`).
269
+ */
270
+ constructor(config_) {
271
+ this.config_ = config_;
272
+ this.signalId = this.config_.signalId;
273
+ this.logger_ = createLogger3(`computed-signal: ${this.signalId}`);
274
+ /**
275
+ * The internal `StateSignal` that holds the computed value.
276
+ * This is how the computed signal provides `.value` and `.subscribe()` methods.
277
+ * @protected
278
+ */
279
+ this.internalSignal_ = new StateSignal({
280
+ signalId: this.signalId + "-internal",
281
+ initialValue: this.config_.get()
282
+ });
283
+ this.subscriptionList__ = [];
284
+ this.isRecalculating__ = false;
285
+ this.subscribe = this.internalSignal_.subscribe.bind(this.internalSignal_);
286
+ this.untilNext = this.internalSignal_.untilNext.bind(this.internalSignal_);
287
+ this.logger_.logMethod?.("constructor");
288
+ this.recalculate_ = this.recalculate_.bind(this);
289
+ for (const signal of config_.deps) {
290
+ this.subscriptionList__.push(signal.subscribe(this.recalculate_, { receivePrevious: false }));
291
+ }
292
+ }
293
+ /**
294
+ * The current value of the computed signal.
295
+ * Accessing this property returns the memoized value and does not trigger a recalculation.
296
+ *
297
+ * @returns The current computed value.
298
+ * @throws {Error} If accessed after the signal has been destroyed.
299
+ */
300
+ get value() {
301
+ return this.internalSignal_.value;
302
+ }
303
+ /**
304
+ * Indicates whether the computed signal has been destroyed.
305
+ * A destroyed signal cannot be used and will throw an error if interacted with.
306
+ * @returns `true` if the signal is destroyed, `false` otherwise.
307
+ */
308
+ get isDestroyed() {
309
+ return this.internalSignal_.isDestroyed;
310
+ }
311
+ /**
312
+ * Permanently disposes of the computed signal.
313
+ *
314
+ * This is a critical cleanup step. It unsubscribes from all dependency signals,
315
+ * stopping future recalculations and allowing the signal to be garbage collected.
316
+ * Failure to call `destroy()` will result in memory leaks.
317
+ *
318
+ * After `destroy()` is called, any attempt to access `.value` or `.subscribe()` will throw an error.
319
+ */
320
+ destroy() {
321
+ this.logger_.logMethod?.("destroy");
322
+ if (this.internalSignal_.isDestroyed) {
323
+ this.logger_.incident?.("destroy", "already_destroyed");
324
+ return;
325
+ }
326
+ for (const subscription of this.subscriptionList__) {
327
+ subscription.unsubscribe();
328
+ }
329
+ this.subscriptionList__.length = 0;
330
+ this.internalSignal_.destroy();
331
+ this.config_ = null;
332
+ }
333
+ /**
334
+ * Schedules a recalculation of the signal's value.
335
+ *
336
+ * This method batches updates using a macrotask (`delay.nextMacrotask`) to ensure the
337
+ * `get` function runs only once per event loop tick, even if multiple dependencies
338
+ * change in the same synchronous block of code.
339
+ * @protected
340
+ */
341
+ async recalculate_() {
342
+ if (this.internalSignal_.isDestroyed) {
343
+ this.logger_.incident?.("recalculate", "recalculate_on_destroyed_signal");
344
+ return;
345
+ }
346
+ if (this.isRecalculating__) {
347
+ this.logger_.logMethod?.("recalculate_//skipped");
348
+ return;
349
+ }
350
+ this.logger_.logMethod?.("recalculate_//scheduled");
351
+ this.isRecalculating__ = true;
352
+ try {
353
+ await delay3.nextMacrotask();
354
+ if (this.internalSignal_.isDestroyed) {
355
+ this.logger_.incident?.("recalculate", "destroyed_during_delay");
356
+ return;
357
+ }
358
+ this.logger_.logMethod?.("recalculate_//executing");
359
+ this.internalSignal_.set(this.config_.get());
360
+ } catch (err) {
361
+ this.logger_.error("recalculate_", "recalculation_failed", err);
362
+ }
363
+ this.isRecalculating__ = false;
364
+ }
365
+ };
366
+
367
+ // src/effect-signal.ts
368
+ import { delay as delay4 } from "@alwatr/delay";
369
+ import { createLogger as createLogger4 } from "@alwatr/logger";
370
+ var EffectSignal = class {
371
+ /**
372
+ * Initializes a new `EffectSignal`.
373
+ * @param config The configuration, including dependencies (`deps`) and the `run` function.
374
+ */
375
+ constructor(config_) {
376
+ this.config_ = config_;
377
+ this.logger_ = createLogger4(`effect-signal`);
378
+ this.subscriptionList__ = [];
379
+ this.isRunning__ = false;
380
+ this.isDestroyed__ = false;
381
+ this.logger_.logMethod?.("constructor");
382
+ this.run_ = this.run_.bind(this);
383
+ for (const signal of config_.deps) {
384
+ this.subscriptionList__.push(signal.subscribe(this.run_, { receivePrevious: false }));
385
+ }
386
+ if (config_.runImmediately === true) {
387
+ void this.run_();
388
+ }
389
+ }
390
+ /**
391
+ * Indicates whether the effect signal has been destroyed.
392
+ * A destroyed signal cannot be used and will throw an error if interacted with.
393
+ * @returns `true` if the signal is destroyed, `false` otherwise.
394
+ */
395
+ get isDestroyed() {
396
+ return this.isDestroyed__;
38
397
  }
39
398
  /**
40
- * Dispatch an event to all listeners.
399
+ * Schedules the execution of the effect's `run` function.
400
+ *
401
+ * This method batches updates using a macrotask (`delay.nextMacrotask`) to ensure the
402
+ * `run` function executes only once per event loop tick, even if multiple
403
+ * dependencies change simultaneously.
404
+ * @protected
41
405
  */
42
- notify(message) {
43
- this.notify_(message);
406
+ async run_() {
407
+ if (this.isDestroyed__) {
408
+ this.logger_.incident?.("run_", "run_on_destroyed_signal");
409
+ return;
410
+ }
411
+ if (this.isRunning__) {
412
+ this.logger_.logMethod?.("run_//skipped");
413
+ return;
414
+ }
415
+ this.logger_.logMethod?.("run_//scheduled");
416
+ this.isRunning__ = true;
417
+ try {
418
+ await delay4.nextMacrotask();
419
+ if (this.isDestroyed__) {
420
+ this.logger_.incident?.("run_", "destroyed_during_delay");
421
+ return;
422
+ }
423
+ this.logger_.logMethod?.("run_//executing");
424
+ await this.config_.run();
425
+ } catch (err) {
426
+ this.logger_.error("run_", "effect_failed", err);
427
+ } finally {
428
+ this.isRunning__ = false;
429
+ }
44
430
  }
45
431
  /**
46
- * Wait until next event.
432
+ * Permanently disposes of the effect signal.
433
+ *
434
+ * This is a critical cleanup step. It unsubscribes from all dependency signals,
435
+ * stopping any future executions of the effect and allowing it to be garbage collected.
436
+ * Failure to call `destroy()` will result in memory leaks and potentially unwanted side effects.
47
437
  */
48
- untilNewNotify() {
49
- return super.untilNewNotify_();
438
+ destroy() {
439
+ this.logger_.logMethod?.("destroy");
440
+ if (this.isDestroyed__) {
441
+ this.logger_.incident?.("destroy", "already_destroyed");
442
+ return;
443
+ }
444
+ this.isDestroyed__ = true;
445
+ for (const subscription of this.subscriptionList__) {
446
+ subscription.unsubscribe();
447
+ }
448
+ this.subscriptionList__.length = 0;
449
+ this.config_ = null;
50
450
  }
51
451
  };
52
452
  export {
53
- AlwatrSignal,
54
- AlwatrTrigger
453
+ ComputedSignal,
454
+ EffectSignal,
455
+ EventSignal,
456
+ SignalBase,
457
+ StateSignal
55
458
  };
56
459
  //# sourceMappingURL=main.mjs.map
package/dist/main.mjs.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/trigger.ts", "../src/logger.ts", "../src/signal.ts"],
4
- "sourcesContent": ["import {AlwatrObservable, type AlwatrObservableConfig} from '@alwatr/observable';\n\nimport {logger} from './logger.js';\n\n__dev_mode__: logger.logFileModule?.('trigger');\n\n/**\n * Alwatr event signal without any message (no event detail).\n */\nexport class AlwatrTrigger extends AlwatrObservable {\n constructor(config: AlwatrObservableConfig) {\n config.loggerPrefix ??= 'signal';\n super(config);\n }\n\n /**\n * Dispatch an event to all listeners.\n */\n notify(): void {\n this.notify_({});\n }\n\n /**\n * Wait until next event signal.\n */\n async untilTriggered(): Promise<void> {\n await super.untilNewNotify_();\n }\n}\n", "import {createLogger} from '@alwatr/nanolib';\n\nexport const logger = /* #__PURE__ */ createLogger(__package_name__);\n", "import {AlwatrObservable, type AlwatrObservableConfig} from '@alwatr/observable';\n\nimport {logger} from './logger.js';\n\n__dev_mode__: logger.logFileModule?.('signal');\n\n/**\n * Alwatr event signal with special message (event detail).\n */\nexport class AlwatrSignal<T extends DictionaryOpt = DictionaryOpt> extends AlwatrObservable<T> {\n constructor(config: AlwatrObservableConfig) {\n config.loggerPrefix ??= 'signal';\n super(config);\n }\n\n /**\n * Dispatch an event to all listeners.\n */\n notify(message: T): void {\n this.notify_(message);\n }\n\n /**\n * Wait until next event.\n */\n untilNewNotify(): Promise<T> {\n return super.untilNewNotify_();\n }\n}\n"],
5
- "mappings": ";;;AAAA,SAAQ,wBAAoD;;;ACA5D,SAAQ,oBAAmB;AAEpB,IAAM,SAAyB,6BAAa,gBAAgB;;;ADEnE,aAAc,QAAO,gBAAgB,SAAS;AAKvC,IAAM,gBAAN,cAA4B,iBAAiB;AAAA,EAClD,YAAY,QAAgC;AAC1C,WAAO,iBAAiB;AACxB,UAAM,MAAM;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,SAAe;AACb,SAAK,QAAQ,CAAC,CAAC;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,iBAAgC;AACpC,UAAM,MAAM,gBAAgB;AAAA,EAC9B;AACF;;;AE5BA,SAAQ,oBAAAA,yBAAoD;AAI5D,aAAc,QAAO,gBAAgB,QAAQ;AAKtC,IAAM,eAAN,cAAoEC,kBAAoB;AAAA,EAC7F,YAAY,QAAgC;AAC1C,WAAO,iBAAiB;AACxB,UAAM,MAAM;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,SAAkB;AACvB,SAAK,QAAQ,OAAO;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKA,iBAA6B;AAC3B,WAAO,MAAM,gBAAgB;AAAA,EAC/B;AACF;",
6
- "names": ["AlwatrObservable", "AlwatrObservable"]
3
+ "sources": ["../src/signal-base.ts", "../src/event-signal.ts", "../src/state-signal.ts", "../src/computed-signal.ts", "../src/effect-signal.ts"],
4
+ "sourcesContent": ["import {packageTracer} from '@alwatr/package-tracer';\n\nimport type {Observer_, SubscribeOptions, SubscribeResult, ListenerCallback, SignalConfig} from './type.js';\nimport type {AlwatrLogger} from '@alwatr/logger';\nimport type {} from '@alwatr/nano-build';\n\n__dev_mode__: packageTracer.add(__package_name__, __package_version__);\n\n/**\n * An abstract base class for signal implementations.\n *\n * `SignalBase` provides the core functionality for managing subscriptions (observers).\n * It handles adding, removing, and notifying listeners. The responsibility of *when* to notify\n * is left to the concrete subclasses (`StateSignal`, `EventSignal`, etc.).\n *\n * @template T The type of data the signal will handle.\n */\nexport abstract class SignalBase<T> {\n /**\n * The unique identifier for this signal instance. Useful for debugging.\n */\n public readonly signalId: string;\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 protected 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 * @returns `true` if the signal is destroyed, `false` otherwise.\n */\n public get isDestroyed(): boolean {\n return this.isDestroyed_;\n }\n\n /**\n * Initializes a new `SignalBase`.\n * @param config The configuration for the signal, containing its `signalId`.\n */\n public constructor(config: SignalConfig) {\n this.signalId = config.signalId;\n }\n\n /**\n * Removes a specific observer from the observers list.\n * @param observer The observer instance to remove.\n * @protected\n */\n protected removeObserver_(observer: Observer_<T>): void {\n if (this.isDestroyed_) {\n this.logger_.incident?.('removeObserver_', 'remove_observer_on_destroyed_signal');\n return;\n }\n this.logger_.logMethod?.('removeObserver_');\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 const unsubscribe = (): void => this.removeObserver_(observer);\n\n return {unsubscribe};\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 if (this.isDestroyed_) {\n this.logger_.incident?.('notify_', 'notify_on_destroyed_signal');\n return;\n }\n\n this.logger_.logMethodArgs?.('notify_', value);\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 // Fire the callback, but don't await it.\n // If the callback returns a promise, it's up to the callback's author to handle it.\n const result = observer.callback(value);\n if (result instanceof Promise) {\n // Log unhandled promise rejections from listeners.\n result.catch((err) => {\n this.logger_.error('notify_', 'async_callback_failed', err, {observer});\n });\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 this.isDestroyed_ = true;\n this.observers_.length = 0; // Clear all observers.\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 protected logger_ = createLogger(`event-signal: ${this.signalId}`);\n\n /**\n * Initializes a new `EventSignal`.\n * @param config The configuration for the signal, containing its `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(() => {\n this.notify_(payload);\n });\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 * // Unsubscribe when no longer needed.\n * subscription.unsubscribe();\n */\nexport class StateSignal<T> extends SignalBase<T> implements IReadonlySignal<T> {\n private value__: T;\n protected logger_ = createLogger(`state-signal: ${this.signalId}`);\n\n /**\n * Initializes a new `StateSignal`.\n * @param config The configuration for the state signal, including `signalId` and `initialValue`.\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)) return;\n\n this.value__ = newValue;\n\n // Dispatch as a microtask to ensure consistent, non-blocking behavior.\n delay.nextMicrotask().then(() => {\n this.notify_(newValue);\n });\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 const receivePrevious = options.receivePrevious !== false;\n\n if (receivePrevious) {\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) => {\n this.logger_.error('subscribe', 'run_callback_immediate_failed', err);\n });\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 super.destroy();\n // Clear the value to allow for garbage collection.\n this.value__ = null as T;\n }\n}\n", "import {delay} from '@alwatr/delay';\nimport {createLogger} from '@alwatr/logger';\n\nimport {StateSignal} from './state-signal.js';\n\nimport type {ComputedSignalConfig, IComputedSignal, SubscribeResult} 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 * @implements {IComputedSignal<T>}\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 IComputedSignal<T> {\n public readonly signalId = this.config_.signalId;\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 private readonly subscriptionList__: SubscribeResult[] = [];\n private isRecalculating__ = false;\n\n /**\n * Initializes a new `ComputedSignal`.\n * @param config The configuration, including dependencies (`deps`) and the getter function (`get`).\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.subscriptionList__.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 public readonly subscribe = this.internalSignal_.subscribe.bind(this.internalSignal_);\n\n public readonly untilNext = this.internalSignal_.untilNext.bind(this.internalSignal_);\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 (this.internalSignal_.isDestroyed) {\n this.logger_.incident?.('destroy', 'already_destroyed');\n return;\n }\n\n // Unsubscribe from all upstream dependencies.\n for (const subscription of this.subscriptionList__) {\n subscription.unsubscribe();\n }\n this.subscriptionList__.length = 0; // Clear the array of subscriptions.\n\n // Destroy the internal signal to clean up its resources and mark it as destroyed.\n this.internalSignal_.destroy();\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 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_.logMethod?.('recalculate_//skipped');\n return;\n }\n\n this.logger_.logMethod?.('recalculate_//scheduled');\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.internalSignal_.isDestroyed) {\n this.logger_.incident?.('recalculate', 'destroyed_during_delay');\n return;\n }\n\n this.logger_.logMethod?.('recalculate_//executing');\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 * 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 protected readonly logger_ = createLogger(`effect-signal`);\n\n private readonly subscriptionList__: SubscribeResult[] = [];\n private isRunning__ = false;\n private isDestroyed__ = false;\n\n /**\n * Indicates whether the effect 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.isDestroyed__;\n }\n\n /**\n * Initializes a new `EffectSignal`.\n * @param config The configuration, including dependencies (`deps`) and the `run` function.\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.subscriptionList__.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 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_.logMethod?.('run_//skipped');\n return;\n }\n\n this.logger_.logMethod?.('run_//scheduled');\n this.isRunning__ = true;\n\n try {\n // Wait for the next macrotask to batch simultaneous updates.\n await delay.nextMacrotask();\n\n if (this.isDestroyed__) {\n this.logger_.incident?.('run_', 'destroyed_during_delay');\n return;\n }\n\n this.logger_.logMethod?.('run_//executing');\n await this.config_.run();\n }\n catch (err) {\n this.logger_.error('run_', 'effect_failed', err);\n }\n finally {\n // Reset the flag after the current execution is complete.\n this.isRunning__ = false;\n }\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 this.isDestroyed__ = true;\n\n // Unsubscribe from all upstream dependencies.\n for (const subscription of this.subscriptionList__) {\n subscription.unsubscribe();\n }\n this.subscriptionList__.length = 0; // Clear the array of subscriptions.\n this.config_ = null as unknown as EffectSignalConfig; // Release config closure.\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["delay", "createLogger", "createLogger", "delay", "delay", "createLogger", "createLogger", "delay", "delay", "createLogger"]
7
7
  }
@@ -0,0 +1,90 @@
1
+ import type { Observer_, SubscribeOptions, SubscribeResult, ListenerCallback, SignalConfig } from './type.js';
2
+ import type { AlwatrLogger } from '@alwatr/logger';
3
+ /**
4
+ * An abstract base class for signal implementations.
5
+ *
6
+ * `SignalBase` provides the core functionality for managing subscriptions (observers).
7
+ * It handles adding, removing, and notifying listeners. The responsibility of *when* to notify
8
+ * is left to the concrete subclasses (`StateSignal`, `EventSignal`, etc.).
9
+ *
10
+ * @template T The type of data the signal will handle.
11
+ */
12
+ export declare abstract class SignalBase<T> {
13
+ /**
14
+ * The unique identifier for this signal instance. Useful for debugging.
15
+ */
16
+ readonly signalId: string;
17
+ protected abstract logger_: AlwatrLogger;
18
+ /**
19
+ * The list of observers (listeners) subscribed to this signal.
20
+ * @protected
21
+ */
22
+ protected readonly observers_: Observer_<T>[];
23
+ protected isDestroyed_: boolean;
24
+ /**
25
+ * Indicates whether the signal has been destroyed.
26
+ * A destroyed signal cannot be used and will throw an error if interacted with.
27
+ * @returns `true` if the signal is destroyed, `false` otherwise.
28
+ */
29
+ get isDestroyed(): boolean;
30
+ /**
31
+ * Initializes a new `SignalBase`.
32
+ * @param config The configuration for the signal, containing its `signalId`.
33
+ */
34
+ constructor(config: SignalConfig);
35
+ /**
36
+ * Removes a specific observer from the observers list.
37
+ * @param observer The observer instance to remove.
38
+ * @protected
39
+ */
40
+ protected removeObserver_(observer: Observer_<T>): void;
41
+ /**
42
+ * Subscribes a listener function to this signal.
43
+ *
44
+ * The listener will be called whenever the signal is notified (e.g., when `dispatch` or `set` is called).
45
+ *
46
+ * @param callback The function to be called when the signal is dispatched.
47
+ * @param options Subscription options to customize the behavior (e.g., `once`, `priority`).
48
+ * @returns A `SubscribeResult` object with an `unsubscribe` method to remove the listener.
49
+ */
50
+ subscribe(callback: ListenerCallback<T>, options?: SubscribeOptions): SubscribeResult;
51
+ /**
52
+ * Notifies all registered observers about a new value.
53
+ *
54
+ * This method iterates through a snapshot of the current observers to prevent issues
55
+ * with subscriptions changing during notification (e.g., an observer unsubscribing itself).
56
+ *
57
+ * @param value The new value to notify observers about.
58
+ * @protected
59
+ */
60
+ protected notify_(value: T): void;
61
+ /**
62
+ * Returns a Promise that resolves with the next value dispatched by the signal.
63
+ * This provides an elegant way to wait for a single, future event using `async/await`.
64
+ *
65
+ * @returns A Promise that resolves with the next dispatched value.
66
+ *
67
+ * @example
68
+ * async function onButtonClick() {
69
+ * console.log('Waiting for the next signal...');
70
+ * const nextValue = await mySignal.untilNext();
71
+ * console.log('Signal received:', nextValue);
72
+ * }
73
+ */
74
+ untilNext(): Promise<T>;
75
+ /**
76
+ * Destroys the signal, clearing all its listeners and making it inactive.
77
+ *
78
+ * After destruction, any interaction with the signal (like `subscribe` or `untilNext`)
79
+ * will throw an error. This is crucial for preventing memory leaks by allowing
80
+ * garbage collection of the signal and its observers.
81
+ */
82
+ destroy(): void;
83
+ /**
84
+ * Throws an error if the signal has been destroyed.
85
+ * This is a safeguard to prevent interaction with a defunct signal.
86
+ * @protected
87
+ */
88
+ protected checkDestroyed_: () => void;
89
+ }
90
+ //# sourceMappingURL=signal-base.d.ts.map