@alwatr/signal 9.26.0 → 9.30.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
@@ -1,7 +1,4 @@
1
- import {createComputedSignal} from '../creators/computed.js';
2
1
  import {createStateSignal} from '../creators/state.js';
3
-
4
- import type {ComputedSignal} from '../core/computed-signal.js';
5
2
  import type {IReadonlySignal} from '../type.js';
6
3
 
7
4
  /**
@@ -24,40 +21,45 @@ import type {IReadonlySignal} from '../type.js';
24
21
  * @returns A new computed signal that emits filtered values.
25
22
  *
26
23
  * @example
24
+ * ```typescript
27
25
  * const numberSignal = createStateSignal({ name: 'number', initialValue: 0 });
28
26
  *
29
27
  * const evenNumberSignal = createFilteredSignal(
30
- * numberSignal,
31
- * (num) => num % 2 === 0,
28
+ * numberSignal,
29
+ * (num) => num % 2 === 0,
32
30
  * );
33
31
  *
34
32
  * createEffect({
35
- * deps: [evenNumberSignal],
36
- * run: () => {
37
- * // This effect only runs for even numbers.
38
- * // The value can be `undefined` on the first run if initialValue is not even.
39
- * if (evenNumberSignal.get() !== undefined) {
40
- * console.log(`Even number detected: ${evenNumberSignal.get()}`);
41
- * }
42
- * },
43
- * runImmediately: true,
33
+ * deps: [evenNumberSignal],
34
+ * run: () => {
35
+ * // This effect only runs for even numbers.
36
+ * // The value can be `undefined` on the first run if initialValue is not even.
37
+ * if (evenNumberSignal.get() !== undefined) {
38
+ * console.log(`Even number detected: ${evenNumberSignal.get()}`);
39
+ * }
40
+ * },
41
+ * runImmediately: true,
44
42
  * });
45
43
  * // Logs: "Even number detected: 0"
46
44
  *
47
45
  * numberSignal.set(1); // Effect does not run
48
46
  * numberSignal.set(2); // Logs: "Even number detected: 2"
47
+ * ```
49
48
  */
50
49
  export function createFilteredSignal<T>(
51
50
  sourceSignal: IReadonlySignal<T>,
52
51
  predicate: (value: T) => boolean,
53
- name = `${sourceSignal.name}-filtered`,
54
- ): ComputedSignal<T | undefined> {
52
+ name = `${sourceSignal.name}_filtered`,
53
+ ): IReadonlySignal<T | undefined> {
55
54
  const sourceValue = sourceSignal.get();
56
55
  const initialValue = predicate(sourceValue) ? sourceValue : undefined;
57
56
 
58
57
  const internalSignal = createStateSignal({
59
- name: `${name}-internal`,
58
+ name,
60
59
  initialValue,
60
+ onDestroy() {
61
+ subscription.unsubscribe();
62
+ },
61
63
  });
62
64
 
63
65
  const subscription = sourceSignal.subscribe((newValue) => {
@@ -66,13 +68,5 @@ export function createFilteredSignal<T>(
66
68
  }
67
69
  });
68
70
 
69
- return createComputedSignal({
70
- name: name,
71
- deps: [internalSignal],
72
- get: () => internalSignal.get(),
73
- onDestroy: () => {
74
- subscription.unsubscribe();
75
- internalSignal.destroy();
76
- },
77
- });
71
+ return internalSignal;
78
72
  }
package/src/type.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';
@@ -11,7 +10,7 @@ import type {SessionStorageProviderConfig} from '@alwatr/session-storage';
11
10
  *
12
11
  * @template T The type of the value that the signal holds or dispatches.
13
12
  */
14
- export type ListenerCallback<T> = (value: T) => Awaitable<void>;
13
+ export type ListenerCallback<T> = (value: T) => void;
15
14
 
16
15
  /**
17
16
  * Options for fine-tuning the behavior of a subscription to a signal.
@@ -225,6 +224,23 @@ export interface ComputedSignalConfig<T> extends SignalConfig {
225
224
  get: () => T;
226
225
  }
227
226
 
227
+ /**
228
+ * Configuration for creating a `DerivedSignal`.
229
+ * @template S The type of the source signal state.
230
+ * @template T The type of the projected derived state.
231
+ */
232
+ export interface DerivedSignalConfig<S, T> extends SignalConfig {
233
+ /**
234
+ * The single upstream readonly source signal.
235
+ */
236
+ readonly source: IReadonlySignal<S>;
237
+
238
+ /**
239
+ * Projection mapping function transforming source S to derived T.
240
+ */
241
+ readonly projector: (value: S) => T;
242
+ }
243
+
228
244
  /**
229
245
  * Configuration for creating an `EffectSignal`.
230
246
  */
@@ -258,7 +274,7 @@ export interface EffectSignalConfig {
258
274
  * run: () => console.log(`The counter is now: ${counter.get()}`),
259
275
  * });
260
276
  */
261
- run: () => Awaitable<void>;
277
+ run: () => void;
262
278
 
263
279
  /**
264
280
  * If `true`, the effect's `run` function will be executed once immediately upon initialization.
@@ -406,3 +422,55 @@ export interface SessionStateSignalConfig<T> extends StateSignalConfig<T>, Sessi
406
422
  */
407
423
  saveDebounceDelay?: number;
408
424
  }
425
+
426
+ /**
427
+ * Determines whether the payload argument for a given channel message is
428
+ * required or optional, based solely on the declared type in `TMap`.
429
+ *
430
+ * - `void | undefined` → payload is optional (second arg may be omitted).
431
+ * - anything else → payload is **required** (omitting it is a compile error).
432
+ *
433
+ * This is used to build the rest-parameter tuple for `dispatch()` so that
434
+ * TypeScript enforces the correct call signature at every dispatch site.
435
+ *
436
+ * @template TMap A record mapping message names to their payload types.
437
+ * @template K The specific message name key.
438
+ *
439
+ * @example
440
+ * ```ts
441
+ * // ActionRecord: { 'logout': void; 'add-to-cart': {productId: number} }
442
+ * type A = DispatchArgs<ActionRecord, 'logout'>; // [name: 'logout', payload?: void]
443
+ * type B = DispatchArgs<ActionRecord, 'add-to-cart'>; // [name: 'add-to-cart', payload: {productId: number}]
444
+ * ```
445
+ */
446
+ export type DispatchArgs<TMap extends object, K extends keyof TMap> =
447
+ TMap[K] extends void | undefined ? [name: K, payload?: TMap[K]] : [name: K, payload: TMap[K]];
448
+
449
+ /**
450
+ * A single message dispatched through a `ChannelSignal`.
451
+ *
452
+ * `name` identifies the message type (e.g. `'open-drawer'`, `'add-to-cart'`).
453
+ * `payload` carries the associated data, whose type is determined by the generic `TMap` based on the `name`.
454
+ *
455
+ * @template TMap A record mapping message names to their payload types.
456
+ * @template K The specific message name key (inferred, not set manually).
457
+ */
458
+ export type ChannelMessage<TMap extends object, K extends keyof TMap = keyof TMap> = {name: K; payload: TMap[K]};
459
+
460
+ /**
461
+ * A typed handler for a specific named message on a `ChannelSignal`.
462
+ * Receives only the `payload` — the name is already known at subscription time.
463
+ *
464
+ * The payload type mirrors `DispatchArgs`: it is `TMap[K] | undefined` only
465
+ * when the declared type is `void | undefined`; otherwise it is exactly `TMap[K]`
466
+ * (non-optional) so handlers do not need unnecessary null-guards.
467
+ *
468
+ * @template TMap A record mapping message names to their payload types.
469
+ * @template K The specific message name key.
470
+ */
471
+ export type ChannelHandler<TMap extends object, K extends keyof TMap = keyof TMap> = (payload: TMap[K]) => void;
472
+
473
+ /**
474
+ * Configuration for creating a `ChannelSignal`.
475
+ */
476
+ export interface ChannelSignalConfig extends SignalConfig {}
@@ -1,36 +0,0 @@
1
- import type { ComputedSignal } from '../core/computed-signal.js';
2
- import type { IReadonlySignal } from '../type.js';
3
- /**
4
- * Creates a new read-only computed signal that transforms the value of a source
5
- * signal using a projection function.
6
- *
7
- * This operator is analogous to `Array.prototype.map`. It applies a function to
8
- * each value emitted by the source signal and emits the result.
9
- *
10
- * @template T The type of the source signal's value.
11
- * @template R The type of the projected value.
12
- *
13
- * @param sourceSignal The original signal to transform.
14
- * @param projectFunction A function to apply to each value from the source signal.
15
- * @param [name] An optional, unique identifier for the new signal for debugging. default: `${sourceSignal.name}-mapped`
16
- *
17
- * @returns A new, read-only computed signal with the transformed values.
18
- *
19
- * @example
20
- * const userSignal = createStateSignal({
21
- * name: 'user',
22
- * initialValue: { name: 'John', age: 30 },
23
- * });
24
- *
25
- * const userNameSignal = createMappedSignal(
26
- * userSignal,
27
- * (user) => user.name,
28
- * );
29
- *
30
- * console.log(userNameSignal.get()); // Outputs: "John"
31
- * // in next macro-task ...
32
- * userSignal.set({ name: 'Jane', age: 32 });
33
- * console.log(userNameSignal.get()); // Outputs: "Jane"
34
- */
35
- export declare function createMappedSignal<T, R>(sourceSignal: IReadonlySignal<T>, projectFunction: (value: T) => R, name?: string): ComputedSignal<R>;
36
- //# sourceMappingURL=map.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"map.d.ts","sourceRoot":"","sources":["../../src/operators/map.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,4BAA4B,CAAC;AAC/D,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,YAAY,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,CAAC,EACrC,YAAY,EAAE,eAAe,CAAC,CAAC,CAAC,EAChC,eAAe,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,EAChC,IAAI,SAAgC,GACnC,cAAc,CAAC,CAAC,CAAC,CAMnB"}
@@ -1,48 +0,0 @@
1
- import {createComputedSignal} from '../creators/computed.js';
2
-
3
- import type {ComputedSignal} from '../core/computed-signal.js';
4
- import type {IReadonlySignal} from '../type.js';
5
-
6
- /**
7
- * Creates a new read-only computed signal that transforms the value of a source
8
- * signal using a projection function.
9
- *
10
- * This operator is analogous to `Array.prototype.map`. It applies a function to
11
- * each value emitted by the source signal and emits the result.
12
- *
13
- * @template T The type of the source signal's value.
14
- * @template R The type of the projected value.
15
- *
16
- * @param sourceSignal The original signal to transform.
17
- * @param projectFunction A function to apply to each value from the source signal.
18
- * @param [name] An optional, unique identifier for the new signal for debugging. default: `${sourceSignal.name}-mapped`
19
- *
20
- * @returns A new, read-only computed signal with the transformed values.
21
- *
22
- * @example
23
- * const userSignal = createStateSignal({
24
- * name: 'user',
25
- * initialValue: { name: 'John', age: 30 },
26
- * });
27
- *
28
- * const userNameSignal = createMappedSignal(
29
- * userSignal,
30
- * (user) => user.name,
31
- * );
32
- *
33
- * console.log(userNameSignal.get()); // Outputs: "John"
34
- * // in next macro-task ...
35
- * userSignal.set({ name: 'Jane', age: 32 });
36
- * console.log(userNameSignal.get()); // Outputs: "Jane"
37
- */
38
- export function createMappedSignal<T, R>(
39
- sourceSignal: IReadonlySignal<T>,
40
- projectFunction: (value: T) => R,
41
- name = `${sourceSignal.name}-mapped`,
42
- ): ComputedSignal<R> {
43
- return createComputedSignal({
44
- name: name,
45
- deps: [sourceSignal],
46
- get: () => projectFunction(sourceSignal.get()),
47
- });
48
- }