@alwatr/signal 9.26.0 → 9.29.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.
Files changed (52) hide show
  1. package/README.md +197 -603
  2. package/dist/core/channel-signal.d.ts +12 -53
  3. package/dist/core/channel-signal.d.ts.map +1 -1
  4. package/dist/core/computed-signal.d.ts +19 -33
  5. package/dist/core/computed-signal.d.ts.map +1 -1
  6. package/dist/core/derived-signal.d.ts +71 -0
  7. package/dist/core/derived-signal.d.ts.map +1 -0
  8. package/dist/core/effect-signal.d.ts +15 -1
  9. package/dist/core/effect-signal.d.ts.map +1 -1
  10. package/dist/core/event-signal.d.ts +11 -4
  11. package/dist/core/event-signal.d.ts.map +1 -1
  12. package/dist/core/persistent-state-signal.d.ts +21 -2
  13. package/dist/core/persistent-state-signal.d.ts.map +1 -1
  14. package/dist/core/session-state-signal.d.ts +19 -2
  15. package/dist/core/session-state-signal.d.ts.map +1 -1
  16. package/dist/core/signal-base.d.ts +58 -38
  17. package/dist/core/signal-base.d.ts.map +1 -1
  18. package/dist/core/state-signal.d.ts +33 -14
  19. package/dist/core/state-signal.d.ts.map +1 -1
  20. package/dist/creators/channel.d.ts +1 -1
  21. package/dist/creators/channel.d.ts.map +1 -1
  22. package/dist/creators/derived.d.ts +31 -0
  23. package/dist/creators/derived.d.ts.map +1 -0
  24. package/dist/main.d.ts +2 -1
  25. package/dist/main.d.ts.map +1 -1
  26. package/dist/main.js +3 -3
  27. package/dist/main.js.map +16 -15
  28. package/dist/operators/debounce.d.ts +2 -3
  29. package/dist/operators/debounce.d.ts.map +1 -1
  30. package/dist/operators/filter.d.ts +14 -13
  31. package/dist/operators/filter.d.ts.map +1 -1
  32. package/dist/type.d.ts +68 -3
  33. package/dist/type.d.ts.map +1 -1
  34. package/package.json +6 -6
  35. package/src/core/channel-signal.ts +25 -68
  36. package/src/core/computed-signal.ts +50 -74
  37. package/src/core/derived-signal.ts +166 -0
  38. package/src/core/effect-signal.ts +23 -11
  39. package/src/core/event-signal.ts +14 -9
  40. package/src/core/persistent-state-signal.ts +21 -4
  41. package/src/core/session-state-signal.ts +19 -4
  42. package/src/core/signal-base.ts +98 -61
  43. package/src/core/state-signal.ts +48 -29
  44. package/src/creators/channel.ts +1 -2
  45. package/src/creators/derived.ts +34 -0
  46. package/src/main.ts +2 -1
  47. package/src/operators/debounce.ts +13 -23
  48. package/src/operators/filter.ts +20 -26
  49. package/src/type.ts +71 -3
  50. package/dist/operators/map.d.ts +0 -36
  51. package/dist/operators/map.d.ts.map +0 -1
  52. package/src/operators/map.ts +0 -48
package/dist/type.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- import type { Awaitable } from '@alwatr/type-helper';
2
1
  import type { DebouncerConfig } from '@alwatr/debounce';
3
2
  import type { LocalStorageProviderConfig } from '@alwatr/local-storage';
4
3
  import type { SessionStorageProviderConfig } from '@alwatr/session-storage';
@@ -10,7 +9,7 @@ import type { SessionStorageProviderConfig } from '@alwatr/session-storage';
10
9
  *
11
10
  * @template T The type of the value that the signal holds or dispatches.
12
11
  */
13
- export type ListenerCallback<T> = (value: T) => Awaitable<void>;
12
+ export type ListenerCallback<T> = (value: T) => void;
14
13
  /**
15
14
  * Options for fine-tuning the behavior of a subscription to a signal.
16
15
  */
@@ -205,6 +204,21 @@ export interface ComputedSignalConfig<T> extends SignalConfig {
205
204
  */
206
205
  get: () => T;
207
206
  }
207
+ /**
208
+ * Configuration for creating a `DerivedSignal`.
209
+ * @template S The type of the source signal state.
210
+ * @template T The type of the projected derived state.
211
+ */
212
+ export interface DerivedSignalConfig<S, T> extends SignalConfig {
213
+ /**
214
+ * The single upstream readonly source signal.
215
+ */
216
+ readonly source: IReadonlySignal<S>;
217
+ /**
218
+ * Projection mapping function transforming source S to derived T.
219
+ */
220
+ readonly projector: (value: S) => T;
221
+ }
208
222
  /**
209
223
  * Configuration for creating an `EffectSignal`.
210
224
  */
@@ -236,7 +250,7 @@ export interface EffectSignalConfig {
236
250
  * run: () => console.log(`The counter is now: ${counter.get()}`),
237
251
  * });
238
252
  */
239
- run: () => Awaitable<void>;
253
+ run: () => void;
240
254
  /**
241
255
  * If `true`, the effect's `run` function will be executed once immediately upon initialization.
242
256
  * @default false
@@ -373,4 +387,55 @@ export interface SessionStateSignalConfig<T> extends StateSignalConfig<T>, Sessi
373
387
  */
374
388
  saveDebounceDelay?: number;
375
389
  }
390
+ /**
391
+ * Determines whether the payload argument for a given channel message is
392
+ * required or optional, based solely on the declared type in `TMap`.
393
+ *
394
+ * - `void | undefined` → payload is optional (second arg may be omitted).
395
+ * - anything else → payload is **required** (omitting it is a compile error).
396
+ *
397
+ * This is used to build the rest-parameter tuple for `dispatch()` so that
398
+ * TypeScript enforces the correct call signature at every dispatch site.
399
+ *
400
+ * @template TMap A record mapping message names to their payload types.
401
+ * @template K The specific message name key.
402
+ *
403
+ * @example
404
+ * ```ts
405
+ * // ActionRecord: { 'logout': void; 'add-to-cart': {productId: number} }
406
+ * type A = DispatchArgs<ActionRecord, 'logout'>; // [name: 'logout', payload?: void]
407
+ * type B = DispatchArgs<ActionRecord, 'add-to-cart'>; // [name: 'add-to-cart', payload: {productId: number}]
408
+ * ```
409
+ */
410
+ export type DispatchArgs<TMap extends object, K extends keyof TMap> = TMap[K] extends void | undefined ? [name: K, payload?: TMap[K]] : [name: K, payload: TMap[K]];
411
+ /**
412
+ * A single message dispatched through a `ChannelSignal`.
413
+ *
414
+ * `name` identifies the message type (e.g. `'open-drawer'`, `'add-to-cart'`).
415
+ * `payload` carries the associated data, whose type is determined by the generic `TMap` based on the `name`.
416
+ *
417
+ * @template TMap A record mapping message names to their payload types.
418
+ * @template K The specific message name key (inferred, not set manually).
419
+ */
420
+ export type ChannelMessage<TMap extends object, K extends keyof TMap = keyof TMap> = {
421
+ name: K;
422
+ payload: TMap[K];
423
+ };
424
+ /**
425
+ * A typed handler for a specific named message on a `ChannelSignal`.
426
+ * Receives only the `payload` — the name is already known at subscription time.
427
+ *
428
+ * The payload type mirrors `DispatchArgs`: it is `TMap[K] | undefined` only
429
+ * when the declared type is `void | undefined`; otherwise it is exactly `TMap[K]`
430
+ * (non-optional) so handlers do not need unnecessary null-guards.
431
+ *
432
+ * @template TMap A record mapping message names to their payload types.
433
+ * @template K The specific message name key.
434
+ */
435
+ export type ChannelHandler<TMap extends object, K extends keyof TMap = keyof TMap> = (payload: TMap[K]) => void;
436
+ /**
437
+ * Configuration for creating a `ChannelSignal`.
438
+ */
439
+ export interface ChannelSignalConfig extends SignalConfig {
440
+ }
376
441
  //# sourceMappingURL=type.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"type.d.ts","sourceRoot":"","sources":["../src/type.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"type.d.ts","sourceRoot":"","sources":["../src/type.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alwatr/signal",
3
- "version": "9.26.0",
3
+ "version": "9.29.0",
4
4
  "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.",
5
5
  "license": "MPL-2.0",
6
6
  "author": "S. Ali Mihandoost <ali.mihandoost@gmail.com> (https://ali.mihandoost.com)",
@@ -22,10 +22,10 @@
22
22
  "sideEffects": false,
23
23
  "dependencies": {
24
24
  "@alwatr/debounce": "9.26.0",
25
- "@alwatr/delay": "9.25.0",
26
- "@alwatr/local-storage": "9.25.0",
27
- "@alwatr/logger": "9.25.0",
28
- "@alwatr/session-storage": "9.25.0"
25
+ "@alwatr/delay": "9.29.0",
26
+ "@alwatr/local-storage": "9.29.0",
27
+ "@alwatr/logger": "9.29.0",
28
+ "@alwatr/session-storage": "9.29.0"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@alwatr/nano-build": "9.25.0",
@@ -66,5 +66,5 @@
66
66
  "signal",
67
67
  "typescript"
68
68
  ],
69
- "gitHead": "ef62969b649929413c640e69d241f4e99d6062e8"
69
+ "gitHead": "2f80c615d90e038dca634a2dfba8d900d3df6248"
70
70
  }
@@ -1,61 +1,15 @@
1
- import type {Awaitable} from '@alwatr/type-helper';
2
- import {delay} from '@alwatr/delay';
1
+ import {queueMicrotask} from '@alwatr/delay';
3
2
  import {createLogger, type AlwatrLogger} from '@alwatr/logger';
4
-
5
3
  import {SignalBase} from './signal-base.js';
6
-
7
- import type {SignalConfig, SubscribeOptions, SubscribeResult, ListenerCallback} from '../type.js';
8
-
9
- // ─── Types ────────────────────────────────────────────────────────────────────
10
-
11
- /**
12
- * Determines whether the payload argument for a given channel message is
13
- * required or optional, based solely on the declared type in `TMap`.
14
- *
15
- * - `void | undefined` → payload is optional (second arg may be omitted).
16
- * - anything else → payload is **required** (omitting it is a compile error).
17
- *
18
- * This is used to build the rest-parameter tuple for `dispatch()` so that
19
- * TypeScript enforces the correct call signature at every dispatch site.
20
- *
21
- * @template TMap A record mapping message names to their payload types.
22
- * @template K The specific message name key.
23
- *
24
- * @example
25
- * ```ts
26
- * // ActionRecord: { 'logout': void; 'add-to-cart': {productId: number} }
27
- * type A = DispatchArgs<ActionRecord, 'logout'>; // [name: 'logout', payload?: void]
28
- * type B = DispatchArgs<ActionRecord, 'add-to-cart'>; // [name: 'add-to-cart', payload: {productId: number}]
29
- * ```
30
- */
31
- export type DispatchArgs<TMap extends object, K extends keyof TMap> =
32
- TMap[K] extends void | undefined ? [name: K, payload?: TMap[K]] : [name: K, payload: TMap[K]];
33
-
34
- /**
35
- * A single message dispatched through a `ChannelSignal`.
36
- *
37
- * `name` identifies the message type (e.g. `'open-drawer'`, `'add-to-cart'`).
38
- * `payload` carries the associated data, whose type is determined by the generic `TMap` based on the `name`.
39
- *
40
- * @template TMap A record mapping message names to their payload types.
41
- * @template K The specific message name key (inferred, not set manually).
42
- */
43
- export type ChannelMessage<TMap extends object, K extends keyof TMap = keyof TMap> = {name: K; payload: TMap[K]};
44
-
45
- /**
46
- * A typed handler for a specific named message on a `ChannelSignal`.
47
- * Receives only the `payload` — the name is already known at subscription time.
48
- *
49
- * The payload type mirrors `DispatchArgs`: it is `TMap[K] | undefined` only
50
- * when the declared type is `void | undefined`; otherwise it is exactly `TMap[K]`
51
- * (non-optional) so handlers do not need unnecessary null-guards.
52
- *
53
- * @template TMap A record mapping message names to their payload types.
54
- * @template K The specific message name key.
55
- */
56
- export type ChannelHandler<TMap extends object, K extends keyof TMap = keyof TMap> = (
57
- payload: TMap[K],
58
- ) => Awaitable<void>;
4
+ import type {
5
+ SubscribeOptions,
6
+ SubscribeResult,
7
+ ListenerCallback,
8
+ ChannelHandler,
9
+ ChannelMessage,
10
+ ChannelSignalConfig,
11
+ DispatchArgs,
12
+ } from '../type.js';
59
13
 
60
14
  /**
61
15
  * Internal handler type used inside `namedHandlers__`.
@@ -68,12 +22,7 @@ export type ChannelHandler<TMap extends object, K extends keyof TMap = keyof TMa
68
22
  *
69
23
  * @internal
70
24
  */
71
- type InternalHandler = (payload: unknown) => Awaitable<void>;
72
-
73
- /**
74
- * Configuration for creating a `ChannelSignal`.
75
- */
76
- export interface ChannelSignalConfig extends SignalConfig {}
25
+ type InternalHandler = (payload: unknown) => void;
77
26
 
78
27
  // ─── Class ────────────────────────────────────────────────────────────────────
79
28
 
@@ -124,6 +73,7 @@ export interface ChannelSignalConfig extends SignalConfig {}
124
73
  export class ChannelSignal<TMap extends object> extends SignalBase<ChannelMessage<TMap>> {
125
74
  /**
126
75
  * The logger instance for this signal.
76
+ *
127
77
  * @protected
128
78
  */
129
79
  protected logger_: AlwatrLogger;
@@ -143,9 +93,14 @@ export class ChannelSignal<TMap extends object> extends SignalBase<ChannelMessag
143
93
  */
144
94
  private readonly namedHandlers__ = new Map<keyof TMap, Set<{handler: InternalHandler; once: boolean}>>();
145
95
 
96
+ /**
97
+ * Creates a new ChannelSignal instance.
98
+ *
99
+ * @param config Configuration options including the unique channel name and cleanup hook.
100
+ */
146
101
  constructor(config: ChannelSignalConfig) {
147
102
  super(config);
148
- this.logger_ = createLogger(`channel-signal:${this.name}`);
103
+ this.logger_ = createLogger(`channel_signal:${this.name}`);
149
104
  this.logger_.logMethod?.('constructor');
150
105
  }
151
106
 
@@ -172,6 +127,7 @@ export class ChannelSignal<TMap extends object> extends SignalBase<ChannelMessag
172
127
  * channel.dispatch('logout', undefined); // ✅ also fine
173
128
  * ```
174
129
  *
130
+ * @template K The specific message name key.
175
131
  * @param args Tuple of `[name, payload]` — payload optionality is enforced
176
132
  * by `DispatchArgs<TMap, K>` based on the declared type.
177
133
  */
@@ -179,7 +135,7 @@ export class ChannelSignal<TMap extends object> extends SignalBase<ChannelMessag
179
135
  const [name, payload] = args;
180
136
  this.logger_.logMethodArgs?.('dispatch', {name, payload});
181
137
  this.checkDestroyed_();
182
- delay.nextMicrotask().then(() => this.route__(name, payload));
138
+ queueMicrotask(() => this.route__(name, payload));
183
139
  }
184
140
 
185
141
  /**
@@ -192,6 +148,7 @@ export class ChannelSignal<TMap extends object> extends SignalBase<ChannelMessag
192
148
  * envelope) — since the name is already known at subscription time, passing
193
149
  * it again would be redundant.
194
150
  *
151
+ * @template K The specific message name key.
195
152
  * @param name The message name to listen for.
196
153
  * @param handler Callback invoked with the payload each time the named message
197
154
  * is dispatched.
@@ -272,6 +229,9 @@ export class ChannelSignal<TMap extends object> extends SignalBase<ChannelMessag
272
229
  * 2. Invokes each handler, removing `once` entries after their first call.
273
230
  * 3. Notifies raw-stream subscribers via `SignalBase.notify_()`.
274
231
  *
232
+ * @template K The specific message name key.
233
+ * @param name The message name to route.
234
+ * @param payload The payload associated with the message name.
275
235
  * @private
276
236
  */
277
237
  private route__<K extends keyof TMap>(name: K, payload: TMap[K] | undefined): void {
@@ -285,10 +245,7 @@ export class ChannelSignal<TMap extends object> extends SignalBase<ChannelMessag
285
245
  if (handlerSet.size === 0) this.namedHandlers__.delete(name);
286
246
  }
287
247
  try {
288
- const result = entry.handler(payload);
289
- if (result instanceof Promise) {
290
- result.catch((err) => this.logger_.error('route__', 'async_named_handler_failed', err));
291
- }
248
+ entry.handler(payload);
292
249
  } catch (err) {
293
250
  this.logger_.error('route__', 'sync_named_handler_failed', err);
294
251
  }
@@ -1,9 +1,13 @@
1
- import { delay } from '@alwatr/delay';
2
- import { createLogger, type AlwatrLogger } from '@alwatr/logger';
3
-
4
- import { StateSignal } from './state-signal.js';
5
-
6
- import type { ComputedSignalConfig, IReadonlySignal, SubscribeResult, SubscribeOptions } from '../type.js';
1
+ import {queueMicrotask} from '@alwatr/delay';
2
+ import {createLogger, type AlwatrLogger} from '@alwatr/logger';
3
+ import {StateSignal} from './state-signal.js';
4
+ import type {
5
+ ComputedSignalConfig,
6
+ IReadonlySignal,
7
+ SubscribeOptions,
8
+ SubscribeResult,
9
+ ListenerCallback,
10
+ } from '../type.js';
7
11
 
8
12
  /**
9
13
  * A read-only signal that derives its value from a set of dependency signals.
@@ -16,32 +20,6 @@ import type { ComputedSignalConfig, IReadonlySignal, SubscribeResult, SubscribeO
16
20
  * needed to prevent memory leaks from its subscriptions to dependency signals.
17
21
  *
18
22
  * @template T The type of the computed value.
19
- *
20
- * @example
21
- * // --- Create dependency signals ---
22
- * const firstName = new StateSignal({ name: 'firstName', initialValue: 'John' });
23
- * const lastName = new StateSignal({ name: 'lastName', initialValue: 'Doe' });
24
- *
25
- * // --- Create a computed signal ---
26
- * const fullName = new ComputedSignal({
27
- * name: 'fullName',
28
- * deps: [firstName, lastName],
29
- * get: () => `${firstName.get()} ${lastName.get()}`,
30
- * });
31
- *
32
- * console.log(fullName.get()); // Outputs: "John Doe"
33
- *
34
- * // --- Subscribe to the computed value ---
35
- * fullName.subscribe(newFullName => {
36
- * console.log(`Name changed to: ${newFullName}`);
37
- * });
38
- *
39
- * // --- Update a dependency ---
40
- * lastName.set('Smith'); // Recalculates and logs: "Name changed to: John Smith"
41
- * console.log(fullName.get()); // Outputs: "John Smith"
42
- *
43
- * // --- IMPORTANT: Clean up when done ---
44
- * fullName.destroy();
45
23
  */
46
24
  export class ComputedSignal<T> implements IReadonlySignal<T> {
47
25
  /**
@@ -51,6 +29,7 @@ export class ComputedSignal<T> implements IReadonlySignal<T> {
51
29
 
52
30
  /**
53
31
  * The logger instance for this signal.
32
+ *
54
33
  * @protected
55
34
  */
56
35
  protected readonly logger_: AlwatrLogger;
@@ -58,39 +37,51 @@ export class ComputedSignal<T> implements IReadonlySignal<T> {
58
37
  /**
59
38
  * The internal `StateSignal` that holds the computed value.
60
39
  * This is how the computed signal provides `.get()` and `.subscribe()` methods.
40
+ * Enforces COMPOSITION over inheritance.
41
+ *
61
42
  * @protected
62
43
  */
63
44
  protected readonly internalSignal_: StateSignal<T>;
64
45
 
65
46
  /**
66
47
  * A list of subscriptions to dependency signals.
48
+ * Used to unsubscribe from dependencies when this signal is destroyed.
49
+ *
67
50
  * @private
68
51
  */
69
-
70
52
  private readonly dependencySubscriptions__: SubscribeResult[] = [];
71
53
 
72
54
  /**
73
55
  * A flag to prevent concurrent recalculations.
56
+ * Avoids queuing multiple updates in the event loop.
57
+ *
74
58
  * @private
75
59
  */
76
60
  private isRecalculating__ = false;
77
61
 
62
+ /**
63
+ * Creates a new ComputedSignal instance.
64
+ * Subscribes to all dependency signals to trigger recalculations.
65
+ *
66
+ * @param config_ Configuration options including dependencies, evaluation getter, and cleanup hook.
67
+ */
78
68
  constructor(protected config_: ComputedSignalConfig<T>) {
79
69
  this.name = config_.name;
80
- this.logger_ = createLogger(`computed-signal:${this.name}`);
70
+ this.logger_ = createLogger(`computed_signal:${this.name}`);
81
71
  this.recalculate_ = this.recalculate_.bind(this);
82
72
 
83
73
  this.logger_.logMethod?.('constructor');
84
74
 
85
75
  this.internalSignal_ = new StateSignal<T>({
86
- name: `compute-${this.name}_`,
76
+ name: `compute_internal:${this.name}`,
87
77
  initialValue: this.config_.get(),
88
78
  });
89
79
 
90
80
  // Subscribe to all dependencies to trigger recalculation on change.
91
- for (const signal of config_.deps) {
92
- this.logger_.logStep?.('constructor', 'subscribing_to_dependency', { signal: signal.name });
93
- this.dependencySubscriptions__.push(signal.subscribe(this.recalculate_, { receivePrevious: false }));
81
+ for (let i = 0; i < this.config_.deps.length; i++) {
82
+ const signal = this.config_.deps[i];
83
+ this.logger_.logStep?.('constructor', 'subscribing_to_dependency', {signal: signal.name});
84
+ this.dependencySubscriptions__.push(signal.subscribe(this.recalculate_, {receivePrevious: false}));
94
85
  }
95
86
  }
96
87
 
@@ -108,6 +99,7 @@ export class ComputedSignal<T> implements IReadonlySignal<T> {
108
99
  /**
109
100
  * Indicates whether the computed signal has been destroyed.
110
101
  * A destroyed signal cannot be used and will throw an error if interacted with.
102
+ *
111
103
  * @returns `true` if the signal is destroyed, `false` otherwise.
112
104
  */
113
105
  public get isDestroyed(): boolean {
@@ -122,7 +114,7 @@ export class ComputedSignal<T> implements IReadonlySignal<T> {
122
114
  * @param options Subscription options.
123
115
  * @returns A `SubscribeResult` object with an `unsubscribe` method.
124
116
  */
125
- public subscribe(callback: (value: T) => void, options?: SubscribeOptions): SubscribeResult {
117
+ public subscribe(callback: ListenerCallback<T>, options?: SubscribeOptions): SubscribeResult {
126
118
  return this.internalSignal_.subscribe(callback, options);
127
119
  }
128
120
 
@@ -146,42 +138,30 @@ export class ComputedSignal<T> implements IReadonlySignal<T> {
146
138
  */
147
139
  public destroy(): void {
148
140
  this.logger_.logMethod?.('destroy');
149
- /**
150
- * If already destroyed, log an incident and return early.
151
- */
152
141
  if (this.isDestroyed) {
153
142
  this.logger_.incident?.('destroy', 'already_destroyed');
154
143
  return;
155
144
  }
156
145
 
157
- // Unsubscribe from all upstream dependencies.
158
- for (const subscription of this.dependencySubscriptions__) {
159
- subscription.unsubscribe();
146
+ for (let i = 0; i < this.dependencySubscriptions__.length; i++) {
147
+ this.dependencySubscriptions__[i].unsubscribe();
160
148
  }
161
- this.dependencySubscriptions__.length = 0; // Clear the array of subscriptions.
149
+ this.dependencySubscriptions__.length = 0;
162
150
 
163
- this.internalSignal_.destroy(); // Destroy the internal signal.
164
- this.config_.onDestroy?.(); // Call the optional onDestroy callback.
165
- this.config_ = null as unknown as ComputedSignalConfig<T>; // Release config closure.
151
+ this.internalSignal_.destroy();
152
+ this.config_.onDestroy?.();
153
+ this.config_ = null as unknown as ComputedSignalConfig<T>;
166
154
  }
167
155
 
168
156
  /**
169
- * Schedules a recalculation of the signal's value.
157
+ * Recalculates the derived value.
158
+ * Centralized microtask batcher coordination avoids internal loop crashes.
170
159
  *
171
- * This method batches updates using a macrotask (`delay.nextMacrotask`) to ensure the
172
- * `get` function runs only once per event loop tick, even if multiple dependencies
173
- * change in the same synchronous block of code.
174
160
  * @protected
175
161
  */
176
- protected async recalculate_(): Promise<void> {
162
+ protected recalculate_(): void {
177
163
  this.logger_.logMethod?.('recalculate_');
178
164
 
179
- if (this.internalSignal_.isDestroyed) {
180
- // This check is important in case a dependency fires after this signal is destroyed.
181
- this.logger_.incident?.('recalculate_', 'recalculate_on_destroyed_signal');
182
- return;
183
- }
184
-
185
165
  if (this.isRecalculating__) {
186
166
  // If a recalculation is already scheduled, do nothing.
187
167
  this.logger_.logStep?.('recalculate_', 'skipping_recalculation_already_scheduled');
@@ -190,11 +170,7 @@ export class ComputedSignal<T> implements IReadonlySignal<T> {
190
170
 
191
171
  this.isRecalculating__ = true;
192
172
 
193
- try {
194
- // Wait for the next macrotask to start the recalculation.
195
- // This batches all synchronous dependency updates in the current event loop.
196
- await delay.nextMacrotask();
197
-
173
+ queueMicrotask(() => {
198
174
  if (this.isDestroyed) {
199
175
  this.logger_.incident?.('recalculate_', 'destroyed_during_delay');
200
176
  this.isRecalculating__ = false;
@@ -202,15 +178,15 @@ export class ComputedSignal<T> implements IReadonlySignal<T> {
202
178
  }
203
179
 
204
180
  this.logger_.logStep?.('recalculate_', 'recalculating_value');
181
+ try {
182
+ // Set the new value on the internal signal, which will notify our subscribers.
183
+ this.internalSignal_.set(this.config_.get());
184
+ } catch (err) {
185
+ this.logger_.error('recalculate_', 'projection_evaluation_failed', err);
186
+ }
205
187
 
206
- // Set the new value on the internal signal, which will notify our subscribers.
207
- this.internalSignal_.set(this.config_.get());
208
- }
209
- catch (err) {
210
- this.logger_.error('recalculate_', 'recalculation_failed', err);
211
- }
212
-
213
- // Allow the next recalculation to be scheduled.
214
- this.isRecalculating__ = false;
188
+ // Allow the next recalculation to be scheduled.
189
+ this.isRecalculating__ = false;
190
+ });
215
191
  }
216
192
  }