@tstdl/base 0.90.13 → 0.90.14

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 (35) hide show
  1. package/package.json +4 -4
  2. package/rxjs/index.d.ts +1 -0
  3. package/rxjs/index.js +1 -0
  4. package/rxjs/untrack.d.ts +2 -0
  5. package/rxjs/untrack.js +11 -0
  6. package/signals/implementation/api.d.ts +1 -18
  7. package/signals/implementation/api.js +1 -21
  8. package/signals/implementation/asserts.d.ts +16 -0
  9. package/signals/implementation/asserts.js +23 -0
  10. package/signals/implementation/computed.d.ts +33 -1
  11. package/signals/implementation/computed.js +20 -10
  12. package/signals/implementation/effect.d.ts +23 -12
  13. package/signals/implementation/effect.js +58 -40
  14. package/signals/implementation/equality.d.ts +15 -0
  15. package/signals/implementation/equality.js +13 -0
  16. package/signals/implementation/graph.d.ts +10 -0
  17. package/signals/implementation/graph.js +12 -0
  18. package/signals/implementation/index.d.ts +2 -0
  19. package/signals/implementation/index.js +2 -0
  20. package/signals/implementation/signal.d.ts +23 -9
  21. package/signals/implementation/signal.js +45 -27
  22. package/signals/implementation/to-signal.d.ts +14 -62
  23. package/signals/implementation/to-signal.js +28 -9
  24. package/signals/implementation/watch.d.ts +11 -1
  25. package/signals/implementation/watch.js +4 -1
  26. package/signals/index.d.ts +1 -1
  27. package/signals/index.js +1 -1
  28. package/signals/pipe.js +4 -4
  29. package/signals/symbol.d.ts +6 -0
  30. package/signals/symbol.js +6 -0
  31. package/signals/to-lazy-signal.d.ts +23 -0
  32. package/signals/to-lazy-signal.js +20 -0
  33. package/utils/reactive-value-to-signal.d.ts +4 -4
  34. package/signals/to-signal-2.d.ts +0 -19
  35. package/signals/to-signal-2.js +0 -44
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tstdl/base",
3
- "version": "0.90.13",
3
+ "version": "0.90.14",
4
4
  "author": "Patrick Hein",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -109,7 +109,7 @@
109
109
  "luxon": "^3.4",
110
110
  "reflect-metadata": "^0.1",
111
111
  "rxjs": "^7.8",
112
- "type-fest": "^4.4"
112
+ "type-fest": "^4.5"
113
113
  },
114
114
  "devDependencies": {
115
115
  "@mxssfd/typedoc-theme": "1.1",
@@ -120,8 +120,8 @@
120
120
  "@types/mjml": "4.7",
121
121
  "@types/node": "20",
122
122
  "@types/nodemailer": "6.4",
123
- "@typescript-eslint/eslint-plugin": "6.7",
124
- "@typescript-eslint/parser": "6.7",
123
+ "@typescript-eslint/eslint-plugin": "6.8",
124
+ "@typescript-eslint/parser": "6.8",
125
125
  "concurrently": "8.2",
126
126
  "esbuild": "0.19",
127
127
  "eslint": "8.51",
package/rxjs/index.d.ts CHANGED
@@ -11,3 +11,4 @@ export * from './start-with-provider.js';
11
11
  export * from './teardown.js';
12
12
  export * from './timing.js';
13
13
  export * from './touch.js';
14
+ export * from './untrack.js';
package/rxjs/index.js CHANGED
@@ -11,3 +11,4 @@ export * from './start-with-provider.js';
11
11
  export * from './teardown.js';
12
12
  export * from './timing.js';
13
13
  export * from './touch.js';
14
+ export * from './untrack.js';
@@ -0,0 +1,2 @@
1
+ import type { MonoTypeOperatorFunction } from 'rxjs';
2
+ export declare function untrack<T>(): MonoTypeOperatorFunction<T>;
@@ -0,0 +1,11 @@
1
+ import { Observable } from 'rxjs';
2
+ import { untracked } from '../signals/api.js';
3
+ export function untrack() {
4
+ return function untrack(source) {
5
+ return new Observable((subscriber) => source.subscribe({
6
+ next: (state) => untracked(() => subscriber.next(state)),
7
+ complete: () => untracked(() => subscriber.complete()),
8
+ error: (error) => untracked(() => subscriber.error(error))
9
+ }));
10
+ };
11
+ }
@@ -5,12 +5,7 @@
5
5
  * Use of this source code is governed by an MIT-style license that can be
6
6
  * found in the LICENSE file at https://angular.io/license
7
7
  */
8
- /**
9
- * Symbol used to tell `Signal`s apart from other functions.
10
- *
11
- * This can be used to auto-unwrap signals in various cases, or to auto-wrap non-signal values.
12
- */
13
- export declare const SIGNAL: unique symbol;
8
+ import { SIGNAL } from '../symbol.js';
14
9
  /**
15
10
  * A reactive value which notifies consumers of any changes.
16
11
  *
@@ -27,15 +22,3 @@ export interface Signal<T> {
27
22
  * Checks if the given `value` is a reactive `Signal`.
28
23
  */
29
24
  export declare function isSignal(value: unknown): value is Signal<unknown>;
30
- /**
31
- * A comparison function which can determine if two values are equal.
32
- */
33
- export type ValueEqualityFn<T> = (a: T, b: T) => boolean;
34
- /**
35
- * The default equality function used for `signal` and `computed`, which treats objects and arrays
36
- * as never equal, and all other primitive values using identity semantics.
37
- *
38
- * This allows signals to hold non-primitive values (arrays, objects, other collections) and still
39
- * propagate change notification upon explicit mutation without identity change.
40
- */
41
- export declare function defaultEquals<T>(a: T, b: T): boolean;
@@ -5,30 +5,10 @@
5
5
  * Use of this source code is governed by an MIT-style license that can be
6
6
  * found in the LICENSE file at https://angular.io/license
7
7
  */
8
- /**
9
- * Symbol used to tell `Signal`s apart from other functions.
10
- *
11
- * This can be used to auto-unwrap signals in various cases, or to auto-wrap non-signal values.
12
- */
13
- export const SIGNAL = Symbol('SIGNAL');
8
+ import { SIGNAL } from '../symbol.js';
14
9
  /**
15
10
  * Checks if the given `value` is a reactive `Signal`.
16
11
  */
17
12
  export function isSignal(value) {
18
13
  return typeof value === 'function' && value[SIGNAL] !== undefined;
19
14
  }
20
- /**
21
- * The default equality function used for `signal` and `computed`, which treats objects and arrays
22
- * as never equal, and all other primitive values using identity semantics.
23
- *
24
- * This allows signals to hold non-primitive values (arrays, objects, other collections) and still
25
- * propagate change notification upon explicit mutation without identity change.
26
- */
27
- export function defaultEquals(a, b) {
28
- // `Object.is` compares two values using identity semantics which is desired behavior for
29
- // primitive values. If `Object.is` determines two values to be equal we need to make sure that
30
- // those don't represent objects (we want to make sure that 2 objects are always considered
31
- // "unequal"). The null check is needed for the special case of JavaScript reporting null values
32
- // as objects (`typeof null === 'object'`).
33
- return (a === null || typeof a !== 'object') && Object.is(a, b);
34
- }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @license
3
+ * Copyright Google LLC All Rights Reserved.
4
+ *
5
+ * Use of this source code is governed by an MIT-style license that can be
6
+ * found in the LICENSE file at https://angular.io/license
7
+ */
8
+ /**
9
+ * Asserts that the current stack frame is not within a reactive context. Useful
10
+ * to disallow certain code from running inside a reactive context (see {@link toSignal}).
11
+ *
12
+ * @param debugFn a reference to the function making the assertion (used for the error message).
13
+ *
14
+ * @publicApi
15
+ */
16
+ export declare function assertNotInReactiveContext(debugFn: Function, extraContext?: string): void;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @license
3
+ * Copyright Google LLC All Rights Reserved.
4
+ *
5
+ * Use of this source code is governed by an MIT-style license that can be
6
+ * found in the LICENSE file at https://angular.io/license
7
+ */
8
+ import { getActiveConsumer } from './graph.js';
9
+ /**
10
+ * Asserts that the current stack frame is not within a reactive context. Useful
11
+ * to disallow certain code from running inside a reactive context (see {@link toSignal}).
12
+ *
13
+ * @param debugFn a reference to the function making the assertion (used for the error message).
14
+ *
15
+ * @publicApi
16
+ */
17
+ export function assertNotInReactiveContext(debugFn, extraContext) {
18
+ // Taking a `Function` instead of a string name here prevents the un-minified name of the function
19
+ // from being retained in the bundle regardless of minification.
20
+ if (getActiveConsumer() !== null) {
21
+ throw new Error(`${debugFn.name}() cannot be called from within a reactive context.${extraContext ? ` ${extraContext}` : ''}`);
22
+ }
23
+ }
@@ -5,7 +5,39 @@
5
5
  * Use of this source code is governed by an MIT-style license that can be
6
6
  * found in the LICENSE file at https://angular.io/license
7
7
  */
8
- import type { Signal, ValueEqualityFn } from './api.js';
8
+ import { SIGNAL } from '../symbol.js';
9
+ import type { Signal } from './api.js';
10
+ import type { ValueEqualityFn } from './equality.js';
11
+ import type { ReactiveNode } from './graph.js';
12
+ /**
13
+ * A computation, which derives a value from a declarative reactive expression.
14
+ *
15
+ * `Computed`s are both producers and consumers of reactivity.
16
+ */
17
+ export interface ComputedNode<T> extends ReactiveNode {
18
+ /**
19
+ * Current value of the computation, or one of the sentinel values above (`UNSET`, `COMPUTING`,
20
+ * `ERROR`).
21
+ */
22
+ value: T;
23
+ /**
24
+ * If `value` is `ERRORED`, the error caught from the last computation attempt which will
25
+ * be re-thrown.
26
+ */
27
+ error: unknown;
28
+ /**
29
+ * The computation function which will produce a new value.
30
+ */
31
+ computation: () => T;
32
+ equal: ValueEqualityFn<T>;
33
+ }
34
+ export type ComputedGetter<T> = (() => T) & {
35
+ [SIGNAL]: ComputedNode<T>;
36
+ };
37
+ /**
38
+ * Create a computed signal which derives a reactive value from an expression.
39
+ */
40
+ export declare function createComputed<T>(computation: () => T): ComputedGetter<T>;
9
41
  /**
10
42
  * Options passed to the `computed` creation function.
11
43
  */
@@ -5,15 +5,15 @@
5
5
  * Use of this source code is governed by an MIT-style license that can be
6
6
  * found in the LICENSE file at https://angular.io/license
7
7
  */
8
- import { SIGNAL, defaultEquals } from './api.js';
9
- import { REACTIVE_NODE, consumerAfterComputation, consumerBeforeComputation, producerAccessed, producerUpdateValueVersion } from './graph.js';
8
+ import { SIGNAL } from '../symbol.js';
9
+ import { defaultEquals } from './equality.js';
10
+ import { consumerAfterComputation, consumerBeforeComputation, producerAccessed, producerUpdateValueVersion, REACTIVE_NODE } from './graph.js';
10
11
  /**
11
- * Create a computed `Signal` which derives a reactive value from an expression.
12
+ * Create a computed signal which derives a reactive value from an expression.
12
13
  */
13
- export function computed(computation, options) {
14
+ export function createComputed(computation) {
14
15
  const node = Object.create(COMPUTED_NODE);
15
16
  node.computation = computation;
16
- options?.equal && (node.equal = options.equal);
17
17
  const computed = () => {
18
18
  // Check if the value needs updating before returning it.
19
19
  producerUpdateValueVersion(node);
@@ -31,19 +31,19 @@ export function computed(computation, options) {
31
31
  * A dedicated symbol used before a computed value has been calculated for the first time.
32
32
  * Explicitly typed as `any` so we can use it as signal's value.
33
33
  */
34
- const UNSET = Symbol('UNSET');
34
+ const UNSET = /* @__PURE__ */ Symbol('UNSET');
35
35
  /**
36
36
  * A dedicated symbol used in place of a computed signal value to indicate that a given computation
37
37
  * is in progress. Used to detect cycles in computation chains.
38
38
  * Explicitly typed as `any` so we can use it as signal's value.
39
39
  */
40
- const COMPUTING = Symbol('COMPUTING');
40
+ const COMPUTING = /* @__PURE__ */ Symbol('COMPUTING');
41
41
  /**
42
42
  * A dedicated symbol used in place of a computed signal value to indicate that a given computation
43
43
  * failed. The thrown error is cached until the computation gets dirty again.
44
44
  * Explicitly typed as `any` so we can use it as signal's value.
45
45
  */
46
- const ERRORED = Symbol('ERRORED');
46
+ const ERRORED = /* @__PURE__ */ Symbol('ERRORED');
47
47
  const COMPUTED_NODE = {
48
48
  ...REACTIVE_NODE,
49
49
  value: UNSET,
@@ -51,8 +51,8 @@ const COMPUTED_NODE = {
51
51
  error: null,
52
52
  equal: defaultEquals,
53
53
  producerMustRecompute(node) {
54
- // Force a recomputation if there's no current value, or if the current value is in the process
55
- // of being calculated (which should throw an error).
54
+ // Force a recomputation if there's no current value, or if the current value is in the
55
+ // process of being calculated (which should throw an error).
56
56
  return node.value === UNSET || node.value === COMPUTING;
57
57
  },
58
58
  producerRecomputeValue(node) {
@@ -85,3 +85,13 @@ const COMPUTED_NODE = {
85
85
  node.version++;
86
86
  },
87
87
  };
88
+ /**
89
+ * Create a computed `Signal` which derives a reactive value from an expression.
90
+ */
91
+ export function computed(computation, options) {
92
+ const getter = createComputed(computation);
93
+ if (options?.equal) {
94
+ getter[SIGNAL].equal = options.equal;
95
+ }
96
+ return getter;
97
+ }
@@ -1,10 +1,3 @@
1
- /**
2
- * @license
3
- * Copyright Google LLC All Rights Reserved.
4
- *
5
- * Use of this source code is governed by an MIT-style license that can be
6
- * found in the LICENSE file at https://angular.io/license
7
- */
8
1
  /**
9
2
  * An effect can, optionally, register a cleanup function. If registered, the cleanup is executed
10
3
  * before the next effect run. The cleanup function makes it possible to "cancel" any work that the
@@ -15,16 +8,34 @@ export type EffectCleanupFn = () => void;
15
8
  * A callback passed to the effect function that makes it possible to register cleanup logic.
16
9
  */
17
10
  export type EffectCleanupRegisterFn = (cleanupFn: EffectCleanupFn) => void;
11
+ export interface SchedulableEffect {
12
+ run(): void;
13
+ }
18
14
  /**
19
- * Tracks all effects registered within a given application and runs them via `flush`.
15
+ * A scheduler which manages the execution of effects.
20
16
  */
21
- export declare class EffectManager {
22
- private readonly all;
17
+ export declare abstract class EffectScheduler {
18
+ /**
19
+ * Schedule the given effect to be executed at a later time.
20
+ *
21
+ * It is an error to attempt to execute any effects synchronously during a scheduling operation.
22
+ */
23
+ abstract scheduleEffect(e: SchedulableEffect): void;
24
+ }
25
+ /**
26
+ * Interface to an `EffectScheduler` capable of running scheduled effects synchronously.
27
+ */
28
+ export interface FlushableEffectRunner {
29
+ /**
30
+ * Run any scheduled effects.
31
+ */
32
+ flush(): void;
33
+ }
34
+ export declare class TstdlEffectScheduler implements EffectScheduler, FlushableEffectRunner {
23
35
  private readonly queue;
24
36
  private pendingFlush;
25
- create(effectFn: (onCleanup: (cleanupFn: EffectCleanupFn) => void) => void, allowSignalWrites: boolean): EffectRef;
37
+ scheduleEffect(effect: SchedulableEffect): void;
26
38
  flush(): void;
27
- get isQueueEmpty(): boolean;
28
39
  }
29
40
  /**
30
41
  * A global reactive effect, which can be manually destroyed.
@@ -1,3 +1,4 @@
1
+ /* eslint-disable */
1
2
  /**
2
3
  * @license
3
4
  * Copyright Google LLC All Rights Reserved.
@@ -5,57 +6,74 @@
5
6
  * Use of this source code is governed by an MIT-style license that can be
6
7
  * found in the LICENSE file at https://angular.io/license
7
8
  */
8
- import { watch } from './watch.js';
9
+ import { assertNotInReactiveContext } from './asserts.js';
10
+ import { createWatch } from './watch.js';
9
11
  /**
10
- * Tracks all effects registered within a given application and runs them via `flush`.
12
+ * A scheduler which manages the execution of effects.
11
13
  */
12
- export class EffectManager {
13
- all = new Set();
14
+ export class EffectScheduler {
15
+ }
16
+ export class TstdlEffectScheduler {
14
17
  queue = new Set();
15
- pendingFlush;
16
- create(effectFn, allowSignalWrites) {
17
- const w = watch(effectFn, () => {
18
- if (!this.all.has(w)) {
19
- return;
20
- }
21
- this.queue.add(w);
22
- if (!this.pendingFlush) {
23
- this.pendingFlush = true;
24
- queueMicrotask(() => {
25
- this.pendingFlush = false;
26
- this.flush();
27
- });
28
- }
29
- }, allowSignalWrites);
30
- this.all.add(w);
31
- // Effects start dirty.
32
- w.notify();
33
- const destroy = () => {
34
- w.cleanup();
35
- this.all.delete(w);
36
- this.queue.delete(w);
37
- };
38
- return {
39
- destroy
40
- };
18
+ pendingFlush = false;
19
+ scheduleEffect(effect) {
20
+ this.queue.add(effect);
21
+ if (!this.pendingFlush) {
22
+ this.pendingFlush = true;
23
+ queueMicrotask(() => {
24
+ this.pendingFlush = false;
25
+ this.flush();
26
+ });
27
+ }
41
28
  }
42
29
  flush() {
43
- if (this.queue.size === 0) {
44
- return;
45
- }
46
- for (const w of this.queue) {
47
- this.queue.delete(w);
48
- w.run();
30
+ for (const effect of this.queue) {
31
+ this.queue.delete(effect);
32
+ effect.run();
49
33
  }
50
34
  }
51
- get isQueueEmpty() {
52
- return this.queue.size === 0;
35
+ }
36
+ /**
37
+ * Core reactive node for an Angular effect.
38
+ *
39
+ * `EffectHandle` combines the reactive graph's `Watch` base node for effects with the framework's
40
+ * scheduling abstraction (`EffectScheduler`) as well as automatic cleanup via `DestroyRef` if
41
+ * available/requested.
42
+ */
43
+ class EffectHandle {
44
+ scheduler;
45
+ effectFn;
46
+ watcher;
47
+ constructor(scheduler, effectFn, allowSignalWrites) {
48
+ this.scheduler = scheduler;
49
+ this.effectFn = effectFn;
50
+ this.watcher = createWatch((onCleanup) => this.runEffect(onCleanup), () => this.schedule(), allowSignalWrites);
51
+ }
52
+ runEffect(onCleanup) {
53
+ this.effectFn(onCleanup);
54
+ }
55
+ run() {
56
+ this.watcher.run();
57
+ }
58
+ schedule() {
59
+ this.scheduler.scheduleEffect(this);
60
+ }
61
+ notify() {
62
+ this.watcher.notify();
63
+ }
64
+ destroy() {
65
+ this.watcher.destroy();
66
+ // Note: if the effect is currently scheduled, it's not un-scheduled, and so the scheduler will
67
+ // retain a reference to it. Attempting to execute it will be a no-op.
53
68
  }
54
69
  }
55
- const effectManager = new EffectManager();
70
+ const effectScheduler = new TstdlEffectScheduler();
56
71
  /**
57
72
  * Create a global `Effect` for the given reactive function.
58
73
  */
59
74
  export function effect(effectFn, options) {
60
- return effectManager.create(effectFn, options?.allowSignalWrites ?? false);
75
+ assertNotInReactiveContext(effect, 'Call `effect` outside of a reactive context. For example, schedule the effect inside the component constructor.');
76
+ const handle = new EffectHandle(effectScheduler, effectFn, options?.allowSignalWrites ?? false);
77
+ handle.notify();
78
+ return handle;
61
79
  }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @license
3
+ * Copyright Google LLC All Rights Reserved.
4
+ *
5
+ * Use of this source code is governed by an MIT-style license that can be
6
+ * found in the LICENSE file at https://angular.io/license
7
+ */
8
+ /**
9
+ * A comparison function which can determine if two values are equal.
10
+ */
11
+ export type ValueEqualityFn<T> = (a: T, b: T) => boolean;
12
+ /**
13
+ * The default equality function used for `signal` and `computed`, which uses referential equality.
14
+ */
15
+ export declare function defaultEquals<T>(a: T, b: T): boolean;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @license
3
+ * Copyright Google LLC All Rights Reserved.
4
+ *
5
+ * Use of this source code is governed by an MIT-style license that can be
6
+ * found in the LICENSE file at https://angular.io/license
7
+ */
8
+ /**
9
+ * The default equality function used for `signal` and `computed`, which uses referential equality.
10
+ */
11
+ export function defaultEquals(a, b) {
12
+ return Object.is(a, b);
13
+ }
@@ -5,11 +5,17 @@
5
5
  * Use of this source code is governed by an MIT-style license that can be
6
6
  * found in the LICENSE file at https://angular.io/license
7
7
  */
8
+ import { SIGNAL } from '../symbol.js';
8
9
  type Version = number & {
9
10
  __brand: 'Version';
10
11
  };
11
12
  export declare function setActiveConsumer(consumer: ReactiveNode | null): ReactiveNode | null;
13
+ export declare function getActiveConsumer(): ReactiveNode | null;
12
14
  export declare function isInNotificationPhase(): boolean;
15
+ export interface Reactive {
16
+ [SIGNAL]: ReactiveNode;
17
+ }
18
+ export declare function isReactive(value: unknown): value is Reactive;
13
19
  export declare const REACTIVE_NODE: ReactiveNode;
14
20
  /**
15
21
  * A producer and/or consumer which participates in the reactive graph.
@@ -95,6 +101,10 @@ export interface ReactiveNode {
95
101
  producerMustRecompute(node: unknown): boolean;
96
102
  producerRecomputeValue(node: unknown): void;
97
103
  consumerMarkedDirty(node: unknown): void;
104
+ /**
105
+ * Called when a signal is read within this consumer.
106
+ */
107
+ consumerOnSignalRead(node: unknown): void;
98
108
  }
99
109
  /**
100
110
  * Called by implementations when a producer's signal is read.
@@ -5,6 +5,7 @@
5
5
  * Use of this source code is governed by an MIT-style license that can be
6
6
  * found in the LICENSE file at https://angular.io/license
7
7
  */
8
+ import { SIGNAL } from '../symbol.js';
8
9
  /**
9
10
  * The currently active consumer `ReactiveNode`, if running code in a reactive context.
10
11
  *
@@ -17,9 +18,15 @@ export function setActiveConsumer(consumer) {
17
18
  activeConsumer = consumer;
18
19
  return prev;
19
20
  }
21
+ export function getActiveConsumer() {
22
+ return activeConsumer;
23
+ }
20
24
  export function isInNotificationPhase() {
21
25
  return inNotificationPhase;
22
26
  }
27
+ export function isReactive(value) {
28
+ return value[SIGNAL] !== undefined;
29
+ }
23
30
  export const REACTIVE_NODE = {
24
31
  version: 0,
25
32
  dirty: false,
@@ -34,6 +41,7 @@ export const REACTIVE_NODE = {
34
41
  producerMustRecompute: () => false,
35
42
  producerRecomputeValue: () => { },
36
43
  consumerMarkedDirty: () => { },
44
+ consumerOnSignalRead: () => { },
37
45
  };
38
46
  /**
39
47
  * Called by implementations when a producer's signal is read.
@@ -46,6 +54,7 @@ export function producerAccessed(node) {
46
54
  // Accessed outside of a reactive context, so nothing to record.
47
55
  return;
48
56
  }
57
+ activeConsumer.consumerOnSignalRead(node);
49
58
  // This producer is the `idx`th dependency of `activeConsumer`.
50
59
  const idx = activeConsumer.nextProducerIndex++;
51
60
  assertConsumerNode(activeConsumer);
@@ -232,6 +241,9 @@ function producerAddLiveConsumer(node, consumer, indexOfThis) {
232
241
  function producerRemoveLiveConsumerAtIndex(node, idx) {
233
242
  assertProducerNode(node);
234
243
  assertConsumerNode(node);
244
+ if (idx >= node.liveConsumerNode.length) {
245
+ throw new Error(`Assertion error: active consumer index ${idx} is out of bounds of ${node.liveConsumerNode.length} consumers)`);
246
+ }
235
247
  if (node.liveConsumerNode.length === 1) {
236
248
  // When removing the last live consumer, we will no longer be live. We need to remove
237
249
  // ourselves from our producers' tracking (which may cause consumer-producers to lose
@@ -1,7 +1,9 @@
1
1
  export * from './api.js';
2
+ export * from './asserts.js';
2
3
  export * from './computed.js';
3
4
  export * from './configure.js';
4
5
  export * from './effect.js';
6
+ export * from './equality.js';
5
7
  export * from './graph.js';
6
8
  export * from './signal.js';
7
9
  export * from './to-observable.js';
@@ -1,7 +1,9 @@
1
1
  export * from './api.js';
2
+ export * from './asserts.js';
2
3
  export * from './computed.js';
3
4
  export * from './configure.js';
4
5
  export * from './effect.js';
6
+ export * from './equality.js';
5
7
  export * from './graph.js';
6
8
  export * from './signal.js';
7
9
  export * from './to-observable.js';
@@ -5,25 +5,40 @@
5
5
  * Use of this source code is governed by an MIT-style license that can be
6
6
  * found in the LICENSE file at https://angular.io/license
7
7
  */
8
- import type { Signal, ValueEqualityFn } from './api.js';
8
+ import { SIGNAL } from '../symbol.js';
9
+ import { Signal } from './api.js';
10
+ import type { ValueEqualityFn } from './equality.js';
11
+ import type { ReactiveNode } from './graph.js';
12
+ export interface SignalNode<T> extends ReactiveNode {
13
+ value: T;
14
+ equal: ValueEqualityFn<T>;
15
+ readonly [SIGNAL]: SignalNode<T>;
16
+ }
17
+ export type SignalBaseGetter<T> = (() => T) & {
18
+ readonly [SIGNAL]: unknown;
19
+ };
20
+ export interface SignalGetter<T> extends SignalBaseGetter<T> {
21
+ readonly [SIGNAL]: SignalNode<T>;
22
+ }
9
23
  /**
10
- * A `Signal` with a value that can be mutated via a setter interface.
24
+ * Create a `Signal` that can be set or updated directly.
11
25
  */
26
+ export declare function createSignal<T>(initialValue: T): SignalGetter<T>;
27
+ export declare function setPostSignalSetFn(fn: (() => void) | null): (() => void) | null;
28
+ export declare function signalGetFn<T>(this: SignalNode<T>): T;
29
+ export declare function signalSetFn<T>(node: SignalNode<T>, newValue: T): void;
30
+ export declare function signalUpdateFn<T>(node: SignalNode<T>, updater: (value: T) => T): void;
31
+ export declare function signalMutateFn<T>(node: SignalNode<T>, mutator: (value: T) => void): void;
12
32
  export interface WritableSignal<T> extends Signal<T> {
13
33
  /**
14
34
  * Directly set the signal to a new value, and notify any dependents.
15
35
  */
16
36
  set(value: T): void;
17
37
  /**
18
- * Update the value of the signal based on its current value, and
38
+ * Update the value of the signal based on itfs current value, and
19
39
  * notify any dependents.
20
40
  */
21
41
  update(updateFn: (value: T) => T): void;
22
- /**
23
- * Update the current value by mutating it in-place, and
24
- * notify any dependents.
25
- */
26
- mutate(mutatorFn: (value: T) => void): void;
27
42
  /**
28
43
  * Returns a readonly version of this signal. Readonly signals can be accessed to read their value
29
44
  * but can't be changed using set, update or mutate methods. The readonly signals do _not_ have
@@ -44,4 +59,3 @@ export interface CreateSignalOptions<T> {
44
59
  * Create a `Signal` that can be set or updated directly.
45
60
  */
46
61
  export declare function signal<T>(initialValue: T, options?: CreateSignalOptions<T>): WritableSignal<T>;
47
- export declare function setPostSignalSetFn(fn: (() => void) | null): (() => void) | null;
@@ -1,3 +1,4 @@
1
+ /* eslint-disable */
1
2
  /**
2
3
  * @license
3
4
  * Copyright Google LLC All Rights Reserved.
@@ -5,9 +6,10 @@
5
6
  * Use of this source code is governed by an MIT-style license that can be
6
7
  * found in the LICENSE file at https://angular.io/license
7
8
  */
8
- import { SIGNAL, defaultEquals } from './api.js';
9
+ import { SIGNAL } from '../symbol.js';
10
+ import { defaultEquals } from './equality.js';
9
11
  import { throwInvalidWriteToSignalError } from './errors.js';
10
- import { REACTIVE_NODE, producerAccessed, producerNotifyConsumers, producerUpdatesAllowed } from './graph.js';
12
+ import { producerAccessed, producerNotifyConsumers, producerUpdatesAllowed, REACTIVE_NODE } from './graph.js';
11
13
  /**
12
14
  * If set, called after `WritableSignal`s are updated.
13
15
  *
@@ -18,38 +20,26 @@ let postSignalSetFn = null;
18
20
  /**
19
21
  * Create a `Signal` that can be set or updated directly.
20
22
  */
21
- export function signal(initialValue, options) {
23
+ export function createSignal(initialValue) {
22
24
  const node = Object.create(SIGNAL_NODE);
23
25
  node.value = initialValue;
24
- options?.equal && (node.equal = options.equal);
25
- function signalFn() {
26
+ const getter = (() => {
26
27
  producerAccessed(node);
27
28
  return node.value;
28
- }
29
- signalFn.set = signalSetFn;
30
- signalFn.update = signalUpdateFn;
31
- signalFn.mutate = signalMutateFn;
32
- signalFn.asReadonly = signalAsReadonlyFn;
33
- signalFn[SIGNAL] = node;
34
- return signalFn;
29
+ });
30
+ getter[SIGNAL] = node;
31
+ return getter;
35
32
  }
36
33
  export function setPostSignalSetFn(fn) {
37
34
  const prev = postSignalSetFn;
38
35
  postSignalSetFn = fn;
39
36
  return prev;
40
37
  }
41
- const SIGNAL_NODE = {
42
- ...REACTIVE_NODE,
43
- equal: defaultEquals,
44
- readonlyFn: undefined,
45
- };
46
- function signalValueChanged(node) {
47
- node.version++;
48
- producerNotifyConsumers(node);
49
- postSignalSetFn?.();
38
+ export function signalGetFn() {
39
+ producerAccessed(this);
40
+ return this.value;
50
41
  }
51
- function signalSetFn(newValue) {
52
- const node = this[SIGNAL];
42
+ export function signalSetFn(node, newValue) {
53
43
  if (!producerUpdatesAllowed()) {
54
44
  throwInvalidWriteToSignalError();
55
45
  }
@@ -58,14 +48,13 @@ function signalSetFn(newValue) {
58
48
  signalValueChanged(node);
59
49
  }
60
50
  }
61
- function signalUpdateFn(updater) {
51
+ export function signalUpdateFn(node, updater) {
62
52
  if (!producerUpdatesAllowed()) {
63
53
  throwInvalidWriteToSignalError();
64
54
  }
65
- signalSetFn.call(this, updater(this[SIGNAL].value));
55
+ signalSetFn(node, updater(node.value));
66
56
  }
67
- function signalMutateFn(mutator) {
68
- const node = this[SIGNAL];
57
+ export function signalMutateFn(node, mutator) {
69
58
  if (!producerUpdatesAllowed()) {
70
59
  throwInvalidWriteToSignalError();
71
60
  }
@@ -73,6 +62,35 @@ function signalMutateFn(mutator) {
73
62
  mutator(node.value);
74
63
  signalValueChanged(node);
75
64
  }
65
+ // Note: Using an IIFE here to ensure that the spread assignment is not considered
66
+ // a side-effect, ending up preserving `COMPUTED_NODE` and `REACTIVE_NODE`.
67
+ // TODO: remove when https://github.com/evanw/esbuild/issues/3392 is resolved.
68
+ const SIGNAL_NODE = /* @__PURE__ */ (() => {
69
+ return {
70
+ ...REACTIVE_NODE,
71
+ equal: defaultEquals,
72
+ value: undefined,
73
+ };
74
+ })();
75
+ function signalValueChanged(node) {
76
+ node.version++;
77
+ producerNotifyConsumers(node);
78
+ postSignalSetFn?.();
79
+ }
80
+ /**
81
+ * Create a `Signal` that can be set or updated directly.
82
+ */
83
+ export function signal(initialValue, options) {
84
+ const signalFn = createSignal(initialValue);
85
+ const node = signalFn[SIGNAL];
86
+ if (options?.equal) {
87
+ node.equal = options.equal;
88
+ }
89
+ signalFn.set = (newValue) => signalSetFn(node, newValue);
90
+ signalFn.update = (updateFn) => signalUpdateFn(node, updateFn);
91
+ signalFn.asReadonly = signalAsReadonlyFn.bind(signalFn);
92
+ return signalFn;
93
+ }
76
94
  function signalAsReadonlyFn() {
77
95
  const node = this[SIGNAL];
78
96
  if (node.readonlyFn === undefined) {
@@ -1,22 +1,15 @@
1
- /**
2
- * @license
3
- * Copyright Google LLC All Rights Reserved.
4
- *
5
- * Use of this source code is governed by an MIT-style license that can be
6
- * found in the LICENSE file at https://angular.io/license
7
- */
8
1
  import type { Observable, Subscribable } from 'rxjs';
9
- import type { Signal } from './api.js';
2
+ import { Signal } from './api.js';
10
3
  /**
11
4
  * Options for `toSignal`.
12
5
  */
13
- export interface ToSignalOptions<T> {
6
+ export interface ToSignalOptions {
14
7
  /**
15
8
  * Initial value for the signal produced by `toSignal`.
16
9
  *
17
10
  * This will be the value of the signal until the observable emits its first value.
18
11
  */
19
- initialValue?: T;
12
+ initialValue?: unknown;
20
13
  /**
21
14
  * Whether to require that the observable emits synchronously when `toSignal` subscribes.
22
15
  *
@@ -27,61 +20,20 @@ export interface ToSignalOptions<T> {
27
20
  */
28
21
  requireSync?: boolean;
29
22
  }
30
- /**
31
- * Get the current value of an `Observable` as a reactive `Signal`.
32
- *
33
- * `toSignal` returns a `Signal` which provides synchronous reactive access to values produced
34
- * by the given `Observable`, by subscribing to that `Observable`. The returned `Signal` will always
35
- * have the most recent value emitted by the subscription, and will throw an error if the
36
- * `Observable` errors.
37
- *
38
- * Before the `Observable` emits its first value, the `Signal` will return `undefined`. To avoid
39
- * this, either an `initialValue` can be passed or the `requireSync` option enabled.
40
- */
41
23
  export declare function toSignal<T>(source: Observable<T> | Subscribable<T>): Signal<T | undefined>;
42
- /**
43
- * Get the current value of an `Observable` as a reactive `Signal`.
44
- *
45
- * `toSignal` returns a `Signal` which provides synchronous reactive access to values produced
46
- * by the given `Observable`, by subscribing to that `Observable`. The returned `Signal` will always
47
- * have the most recent value emitted by the subscription, and will throw an error if the
48
- * `Observable` errors.
49
- *
50
- * Before the `Observable` emits its first value, the `Signal` will return the configured
51
- * `initialValue`, or `undefined` if no `initialValue` is provided. If the `Observable` is
52
- * guaranteed to emit synchronously, then the `requireSync` option can be passed instead.
53
- */
54
- export declare function toSignal<T>(source: Observable<T> | Subscribable<T>, options?: ToSignalOptions<undefined> & {
24
+ export declare function toSignal<T>(source: Observable<T> | Subscribable<T>, options: ToSignalOptions & {
25
+ initialValue?: undefined;
55
26
  requireSync?: false;
56
27
  }): Signal<T | undefined>;
57
- /**
58
- * Get the current value of an `Observable` as a reactive `Signal`.
59
- *
60
- * `toSignal` returns a `Signal` which provides synchronous reactive access to values produced
61
- * by the given `Observable`, by subscribing to that `Observable`. The returned `Signal` will always
62
- * have the most recent value emitted by the subscription, and will throw an error if the
63
- * `Observable` errors.
64
- *
65
- * Before the `Observable` emits its first value, the `Signal` will return the configured
66
- * `initialValue`. If the `Observable` is guaranteed to emit synchronously, then the `requireSync`
67
- * option can be passed instead.
68
- */
69
- export declare function toSignal<T, U extends T | null | undefined>(source: Observable<T> | Subscribable<T>, options: ToSignalOptions<U> & {
70
- initialValue: U;
28
+ export declare function toSignal<T>(source: Observable<T> | Subscribable<T>, options: ToSignalOptions & {
29
+ initialValue?: null;
71
30
  requireSync?: false;
72
- }): Signal<T | U>;
73
- /**
74
- * Get the current value of an `Observable` as a reactive `Signal`.
75
- *
76
- * `toSignal` returns a `Signal` which provides synchronous reactive access to values produced
77
- * by the given `Observable`, by subscribing to that `Observable`. The returned `Signal` will always
78
- * have the most recent value emitted by the subscription, and will throw an error if the
79
- * `Observable` errors.
80
- *
81
- * With `requireSync` set to `true`, `toSignal` will assert that the `Observable` produces a value
82
- * immediately upon subscription. No `initialValue` is needed in this case, and the returned signal
83
- * does not include an `undefined` type.
84
- */
85
- export declare function toSignal<T>(source: Observable<T> | Subscribable<T>, options: ToSignalOptions<undefined> & {
31
+ }): Signal<T | null>;
32
+ export declare function toSignal<T>(source: Observable<T> | Subscribable<T>, options: ToSignalOptions & {
33
+ initialValue?: undefined;
86
34
  requireSync: true;
87
35
  }): Signal<T>;
36
+ export declare function toSignal<T, const U extends T>(source: Observable<T> | Subscribable<T>, options: ToSignalOptions & {
37
+ initialValue: U;
38
+ requireSync?: false;
39
+ }): Signal<T | U>;
@@ -1,3 +1,4 @@
1
+ /* eslint-disable */
1
2
  /**
2
3
  * @license
3
4
  * Copyright Google LLC All Rights Reserved.
@@ -5,18 +6,24 @@
5
6
  * Use of this source code is governed by an MIT-style license that can be
6
7
  * found in the LICENSE file at https://angular.io/license
7
8
  */
8
- import { isDevMode } from '../../core.js';
9
9
  import { registerFinalization } from '../../memory/finalization.js';
10
+ import { assertNotInReactiveContext } from './asserts.js';
10
11
  import { computed } from './computed.js';
11
12
  import { signal } from './signal.js';
12
- import { untracked } from './untracked.js';
13
- var StateKind;
14
- (function (StateKind) {
15
- StateKind[StateKind["NoValue"] = 0] = "NoValue";
16
- StateKind[StateKind["Value"] = 1] = "Value";
17
- StateKind[StateKind["Error"] = 2] = "Error";
18
- })(StateKind || (StateKind = {}));
13
+ /**
14
+ * Get the current value of an `Observable` as a reactive `Signal`.
15
+ *
16
+ * `toSignal` returns a `Signal` which provides synchronous reactive access to values produced
17
+ * by the given `Observable`, by subscribing to that `Observable`. The returned `Signal` will always
18
+ * have the most recent value emitted by the subscription, and will throw an error if the
19
+ * `Observable` errors.
20
+ *
21
+ * With `requireSync` set to `true`, `toSignal` will assert that the `Observable` produces a value
22
+ * immediately upon subscription. No `initialValue` is needed in this case, and the returned signal
23
+ * does not include an `undefined` type.
24
+ */
19
25
  export function toSignal(source, options) {
26
+ assertNotInReactiveContext(toSignal, 'Invoking `toSignal` causes new subscriptions every time. Consider moving `toSignal` outside of the reactive context and read the signal value where needed.');
20
27
  // Note: T is the Observable value type, and U is the initial value type. They don't have to be
21
28
  // the same - the returned signal gives values of type `T`.
22
29
  let state;
@@ -28,13 +35,19 @@ export function toSignal(source, options) {
28
35
  // If an initial value was passed, use it. Otherwise, use `undefined` as the initial value.
29
36
  state = signal({ kind: StateKind.Value, value: options?.initialValue });
30
37
  }
38
+ // Note: This code cannot run inside a reactive context (see assertion above). If we'd support
39
+ // this, we would subscribe to the observable outside of the current reactive context, avoiding
40
+ // that side-effect signal reads/writes are attribute to the current consumer. The current
41
+ // consumer only needs to be notified when the `state` signal changes through the observable
42
+ // subscription. Additional context (related to async pipe):
43
+ // https://github.com/angular/angular/pull/50522.
31
44
  const sub = source.subscribe({
32
45
  next: value => state.set({ kind: StateKind.Value, value }),
33
46
  error: error => state.set({ kind: StateKind.Error, error }),
34
47
  // Completion of the Observable is meaningless to the signal. Signals don't have a concept of
35
48
  // "complete".
36
49
  });
37
- if (isDevMode() && options?.requireSync && untracked(state).kind === StateKind.NoValue) {
50
+ if (options?.requireSync && state().kind === StateKind.NoValue) {
38
51
  throw new Error('`toSignal()` called with `requireSync` but `Observable` did not emit synchronously.');
39
52
  }
40
53
  // The actual returned signal is a `computed` of the `State` signal, which maps the various states
@@ -53,3 +66,9 @@ export function toSignal(source, options) {
53
66
  registerFinalization(result, () => sub.unsubscribe());
54
67
  return result;
55
68
  }
69
+ var StateKind;
70
+ (function (StateKind) {
71
+ StateKind[StateKind["NoValue"] = 0] = "NoValue";
72
+ StateKind[StateKind["Value"] = 1] = "Value";
73
+ StateKind[StateKind["Error"] = 2] = "Error";
74
+ })(StateKind || (StateKind = {}));
@@ -5,6 +5,8 @@
5
5
  * Use of this source code is governed by an MIT-style license that can be
6
6
  * found in the LICENSE file at https://angular.io/license
7
7
  */
8
+ import { SIGNAL } from '../symbol.js';
9
+ import type { ReactiveNode } from './graph.js';
8
10
  /**
9
11
  * A cleanup function that can be optionally registered from the watch logic. If registered, the
10
12
  * cleanup logic runs before the next watch execution.
@@ -30,5 +32,13 @@ export interface Watch {
30
32
  * - mark it as destroyed so subsequent run and notify operations are noop.
31
33
  */
32
34
  destroy(): void;
35
+ [SIGNAL]: WatchNode;
33
36
  }
34
- export declare function watch(fn: (onCleanup: WatchCleanupRegisterFn) => void, schedule: (watch: Watch) => void, allowSignalWrites: boolean): Watch;
37
+ export interface WatchNode extends ReactiveNode {
38
+ hasRun: boolean;
39
+ fn: ((onCleanup: WatchCleanupRegisterFn) => void) | null;
40
+ schedule: ((watch: Watch) => void) | null;
41
+ cleanupFn: WatchCleanupFn;
42
+ ref: Watch;
43
+ }
44
+ export declare function createWatch(fn: (onCleanup: WatchCleanupRegisterFn) => void, schedule: (watch: Watch) => void, allowSignalWrites: boolean): Watch;
@@ -1,3 +1,4 @@
1
+ /* eslint-disable */
1
2
  /**
2
3
  * @license
3
4
  * Copyright Google LLC All Rights Reserved.
@@ -5,8 +6,9 @@
5
6
  * Use of this source code is governed by an MIT-style license that can be
6
7
  * found in the LICENSE file at https://angular.io/license
7
8
  */
9
+ import { SIGNAL } from '../symbol.js';
8
10
  import { consumerAfterComputation, consumerBeforeComputation, consumerDestroy, consumerMarkDirty, consumerPollProducersForChange, isInNotificationPhase, REACTIVE_NODE } from './graph.js';
9
- export function watch(fn, schedule, allowSignalWrites) {
11
+ export function createWatch(fn, schedule, allowSignalWrites) {
10
12
  const node = Object.create(WATCH_NODE);
11
13
  if (allowSignalWrites) {
12
14
  node.consumerAllowSignalWrites = true;
@@ -57,6 +59,7 @@ export function watch(fn, schedule, allowSignalWrites) {
57
59
  run,
58
60
  cleanup: () => node.cleanupFn(),
59
61
  destroy: () => destroyWatchNode(node),
62
+ [SIGNAL]: node,
60
63
  };
61
64
  return node.ref;
62
65
  }
@@ -5,6 +5,6 @@ export * from './lazylize.js';
5
5
  export * from './pipe.js';
6
6
  export * from './switch-map.js';
7
7
  export * from './to-observable-2.js';
8
- export * from './to-signal-2.js';
8
+ export * from './to-lazy-signal.js';
9
9
  export * from './types.js';
10
10
  export * from './untracked-operator.js';
package/signals/index.js CHANGED
@@ -5,6 +5,6 @@ export * from './lazylize.js';
5
5
  export * from './pipe.js';
6
6
  export * from './switch-map.js';
7
7
  export * from './to-observable-2.js';
8
- export * from './to-signal-2.js';
8
+ export * from './to-lazy-signal.js';
9
9
  export * from './types.js';
10
10
  export * from './untracked-operator.js';
package/signals/pipe.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { distinctUntilChanged } from 'rxjs';
2
2
  import { startWithProvider } from '../rxjs/start-with-provider.js';
3
- import { toObservable } from './api.js';
4
- import { toSignal2 } from './to-signal-2.js';
3
+ import { untrack } from '../rxjs/untrack.js';
4
+ import { toObservable, toSignal } from './api.js';
5
5
  export function pipe(source, ...operators) {
6
6
  return rawPipe(source, startWithProvider(source), distinctUntilChanged(), ...operators);
7
7
  }
8
8
  export function rawPipe(source, ...operators) {
9
- const piped = toObservable(source).pipe(...operators);
10
- return toSignal2(piped, { requireSync: true });
9
+ const piped = toObservable(source).pipe(...operators, untrack());
10
+ return toSignal(piped, { requireSync: true });
11
11
  }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Symbol used to tell `Signal`s apart from other functions.
3
+ *
4
+ * This can be used to auto-unwrap signals in various cases, or to auto-wrap non-signal values.
5
+ */
6
+ export declare const SIGNAL: unique symbol;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Symbol used to tell `Signal`s apart from other functions.
3
+ *
4
+ * This can be used to auto-unwrap signals in various cases, or to auto-wrap non-signal values.
5
+ */
6
+ export const SIGNAL = /* @__PURE__ */ Symbol('SIGNAL');
@@ -0,0 +1,23 @@
1
+ import type { Observable } from 'rxjs';
2
+ import type { Signal, ToSignalOptions } from './api.js';
3
+ /**
4
+ * Like `toSignal`, except that it uses untracked internal operations (required for some scenarios, but might be less safe in terms of bugs catching) and has the ability to subscribe lazily.
5
+ * Subscription to observable is cleaned up using finalization (garbage collection) of the signal.
6
+ */
7
+ export declare function toLazySignal<T>(source: Observable<T>): Signal<T | undefined>;
8
+ export declare function toLazySignal<T>(source: Observable<T>, options: ToSignalOptions & {
9
+ initialValue?: undefined;
10
+ requireSync?: false;
11
+ }): Signal<T | undefined>;
12
+ export declare function toLazySignal<T>(source: Observable<T>, options: ToSignalOptions & {
13
+ initialValue?: null;
14
+ requireSync?: false;
15
+ }): Signal<T | null>;
16
+ export declare function toLazySignal<T>(source: Observable<T>, options: ToSignalOptions & {
17
+ initialValue?: undefined;
18
+ requireSync: true;
19
+ }): Signal<T>;
20
+ export declare function toLazySignal<T, const I extends T>(source: Observable<T>, options: ToSignalOptions & {
21
+ initialValue: I;
22
+ requireSync?: false;
23
+ }): Signal<T | I>;
@@ -0,0 +1,20 @@
1
+ import { Subject, switchMap } from 'rxjs';
2
+ import { computed, toSignal } from './api.js';
3
+ const LAZY = Symbol('LAZY');
4
+ export function toLazySignal(source, options) {
5
+ const subscribe$ = new Subject();
6
+ const lazySource = subscribe$.pipe(switchMap(() => source));
7
+ const signal = toSignal(lazySource, { initialValue: LAZY, ...options }); // eslint-disable-line @typescript-eslint/no-unsafe-argument
8
+ let subscribed = false;
9
+ return computed(() => {
10
+ if (!subscribed) {
11
+ subscribed = true;
12
+ subscribe$.next();
13
+ subscribe$.complete();
14
+ if (signal() == LAZY) {
15
+ throw new Error('`toLazySignal()` called with `requireSync` but `Observable` did not emit synchronously.');
16
+ }
17
+ }
18
+ return signal();
19
+ });
20
+ }
@@ -1,13 +1,13 @@
1
1
  import type { Signal, ToSignalOptions } from '../signals/api.js';
2
2
  import type { ReactiveValue } from '../types.js';
3
- export type ReactiveValueToSignalOptions<T> = ToSignalOptions<T>;
4
- export declare function reactiveValueToSignal<T>(source: ReactiveValue<T>, options?: ReactiveValueToSignalOptions<undefined> & {
3
+ export type ReactiveValueToSignalOptions = ToSignalOptions;
4
+ export declare function reactiveValueToSignal<T>(source: ReactiveValue<T>, options?: ReactiveValueToSignalOptions & {
5
5
  requireSync?: false;
6
6
  }): Signal<T | undefined>;
7
- export declare function reactiveValueToSignal<T, I>(source: ReactiveValue<T>, options: ReactiveValueToSignalOptions<I> & {
7
+ export declare function reactiveValueToSignal<T, I>(source: ReactiveValue<T>, options: ReactiveValueToSignalOptions & {
8
8
  initialValue: I;
9
9
  requireSync?: false;
10
10
  }): Signal<T | I>;
11
- export declare function reactiveValueToSignal<T>(source: ReactiveValue<T>, options: ReactiveValueToSignalOptions<undefined> & {
11
+ export declare function reactiveValueToSignal<T>(source: ReactiveValue<T>, options: ReactiveValueToSignalOptions & {
12
12
  requireSync: true;
13
13
  }): Signal<T>;
@@ -1,19 +0,0 @@
1
- import type { Observable, Subscribable } from 'rxjs';
2
- import type { Signal, ToSignalOptions } from './api.js';
3
- type ToSignalInput<T> = Observable<T> | Subscribable<T>;
4
- export type ToSignal2Options<T> = ToSignalOptions<T> & {
5
- /** defer subscription until signal is used */
6
- lazy?: boolean;
7
- };
8
- /**
9
- * Like `toSignal`, except that it uses untracked internal operations (required for some scenarios, but might be less safe in terms of bugs catching) and has the ability to subscribe lazily.
10
- * Subscription to observable is cleaned up using finalization (garbage collection) of the signal.
11
- */
12
- export declare function toSignal2<T>(source: ToSignalInput<T>): Signal<T | undefined>;
13
- export declare function toSignal2<T>(source: ToSignalInput<T>, options: ToSignal2Options<undefined> & {
14
- requireSync: true;
15
- }): Signal<T>;
16
- export declare function toSignal2<T, const I = undefined>(source: ToSignalInput<T>, options: ToSignal2Options<I> & {
17
- requireSync?: false;
18
- }): Signal<T | I>;
19
- export {};
@@ -1,44 +0,0 @@
1
- import { isDevMode } from '../core.js';
2
- import { registerFinalization } from '../memory/finalization.js';
3
- import { isUndefined } from '../utils/type-guards.js';
4
- import { computed, signal, untracked } from './api.js';
5
- var StateKind;
6
- (function (StateKind) {
7
- StateKind[StateKind["NoValue"] = 0] = "NoValue";
8
- StateKind[StateKind["Value"] = 1] = "Value";
9
- StateKind[StateKind["Error"] = 2] = "Error";
10
- })(StateKind || (StateKind = {}));
11
- export function toSignal2(source, options) {
12
- const initialState = (options?.requireSync == true) ? { kind: StateKind.NoValue } : { kind: StateKind.Value, value: options?.initialValue };
13
- const state = signal(initialState);
14
- let subscription;
15
- if (options?.lazy != true) {
16
- subscription = subscribe(source, state, options);
17
- }
18
- const result = computed(() => {
19
- if (isUndefined(subscription)) {
20
- subscription = subscribe(source, state, options);
21
- }
22
- const current = state();
23
- switch (current.kind) { // eslint-disable-line default-case
24
- case StateKind.Value:
25
- return current.value;
26
- case StateKind.Error:
27
- throw current.error;
28
- case StateKind.NoValue:
29
- throw new Error('`toSignalLazy()` called with `requireSync` but `Observable` did not emit synchronously.');
30
- }
31
- });
32
- registerFinalization(result, () => subscription?.unsubscribe());
33
- return result;
34
- }
35
- function subscribe(source, state, options) {
36
- const subscription = source.subscribe({
37
- next: (value) => untracked(() => state.set({ kind: StateKind.Value, value })),
38
- error: (error) => untracked(() => state.set({ kind: StateKind.Error, error }))
39
- });
40
- if (isDevMode() && (options?.requireSync == true) && untracked(state).kind == StateKind.NoValue) {
41
- throw new Error('`toSignal()` called with `requireSync` but `Observable` did not emit synchronously.');
42
- }
43
- return subscription;
44
- }