@alwatr/signal 9.11.2 → 9.13.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.
@@ -135,6 +135,8 @@ export abstract class SignalBase<T> {
135
135
  }
136
136
  }
137
137
 
138
+ private pendingRejects__ = new Set<(reason?: any) => void>();
139
+
138
140
  /**
139
141
  * Returns a Promise that resolves with the next value dispatched by the signal.
140
142
  * This provides an elegant way to wait for a single, future event using `async/await`.
@@ -151,12 +153,19 @@ export abstract class SignalBase<T> {
151
153
  public untilNext(): Promise<T> {
152
154
  this.logger_.logMethod?.('untilNext');
153
155
  this.checkDestroyed_();
154
- return new Promise((resolve) => {
155
- this.subscribe(resolve, {
156
- once: true,
157
- priority: true, // Resolve the promise before other listeners are called.
158
- receivePrevious: false, // We only want the *next* value, not the current one.
159
- });
156
+ return new Promise((resolve, reject) => {
157
+ this.pendingRejects__.add(reject);
158
+ this.subscribe(
159
+ (value) => {
160
+ this.pendingRejects__.delete(reject);
161
+ resolve(value);
162
+ },
163
+ {
164
+ once: true,
165
+ priority: true, // Resolve the promise before other listeners are called.
166
+ receivePrevious: false, // We only want the *next* value, not the current one.
167
+ },
168
+ );
160
169
  });
161
170
  }
162
171
 
@@ -173,7 +182,15 @@ export abstract class SignalBase<T> {
173
182
  this.logger_.incident?.('destroy_', 'double_destroy_attempt');
174
183
  return;
175
184
  }
176
- this.isDestroyed__ = true;
185
+ this.isDestroyed__ = true; // Mark the signal as destroyed.
186
+ // Reject all pending promises.
187
+ if (this.pendingRejects__.size) {
188
+ const error = new Error('signal_destroyed');
189
+ for (const reject of this.pendingRejects__) {
190
+ reject(error);
191
+ }
192
+ this.pendingRejects__.clear(); // Clear all pending rejects.
193
+ }
177
194
  this.priorityObservers_.clear(); // Clear all priority observers.
178
195
  this.observers_.clear(); // Clear all normal observers.
179
196
  this.config_.onDestroy?.(); // Call the optional onDestroy callback.
@@ -0,0 +1,44 @@
1
+ import {ChannelSignal} from '../core/channel-signal.js';
2
+
3
+ import type {ChannelSignalConfig} from '../core/channel-signal.js';
4
+
5
+ /**
6
+ * Creates a stateless multi-channel signal that acts as a typed message bus.
7
+ *
8
+ * `ChannelSignal` is ideal when you need a single signal to carry multiple
9
+ * distinct message types — each identified by a `name` — rather than creating
10
+ * a separate `EventSignal` for every event.
11
+ *
12
+ * The generic parameter `TMap` is a record that maps every valid message name
13
+ * to its payload type, giving you full type safety at both dispatch and subscribe sites.
14
+ *
15
+ * @template TMap A record mapping message names to their payload types.
16
+ *
17
+ * @param config The configuration for the channel signal.
18
+ * @returns A new instance of `ChannelSignal`.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * interface AppMessages {
23
+ * 'open-drawer': {panel: string};
24
+ * 'close-drawer': void;
25
+ * 'show-toast': {message: string; type: 'info' | 'error'};
26
+ * }
27
+ *
28
+ * const appChannel = createChannelSignal<AppMessages>({name: 'app-channel'});
29
+ *
30
+ * // Subscribe to a specific message
31
+ * appChannel.on('show-toast', (payload) => {
32
+ * toast.show(payload!.message, payload!.type);
33
+ * });
34
+ *
35
+ * // Dispatch a message
36
+ * appChannel.dispatch('show-toast', {message: 'Saved!', type: 'info'});
37
+ * appChannel.dispatch('close-drawer');
38
+ * ```
39
+ */
40
+ export function createChannelSignal<TMap extends Record<string, unknown>>(
41
+ config: ChannelSignalConfig,
42
+ ): ChannelSignal<TMap> {
43
+ return new ChannelSignal<TMap>(config);
44
+ }
package/src/main.ts CHANGED
@@ -5,6 +5,7 @@ export * from './core/computed-signal.js';
5
5
  export * from './core/effect-signal.js';
6
6
  export * from './core/persistent-state-signal.js';
7
7
  export * from './core/session-state-signal.js';
8
+ export * from './core/channel-signal.js';
8
9
 
9
10
  export * from './creators/event.js';
10
11
  export * from './creators/state.js';
@@ -12,6 +13,7 @@ export * from './creators/computed.js';
12
13
  export * from './creators/effect.js';
13
14
  export * from './creators/persistent-state.js';
14
15
  export * from './creators/session-state.js';
16
+ export * from './creators/channel.js';
15
17
 
16
18
  export * from './operators/debounce.js';
17
19
  export * from './operators/filter.js';