@tstdl/base 0.90.12 → 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.
- package/intl/index.d.ts +1 -0
- package/intl/index.js +1 -0
- package/package.json +6 -5
- package/rxjs/index.d.ts +1 -0
- package/rxjs/index.js +1 -0
- package/rxjs/untrack.d.ts +2 -0
- package/rxjs/untrack.js +11 -0
- package/signals/implementation/api.d.ts +1 -18
- package/signals/implementation/api.js +1 -21
- package/signals/implementation/asserts.d.ts +16 -0
- package/signals/implementation/asserts.js +23 -0
- package/signals/implementation/computed.d.ts +33 -1
- package/signals/implementation/computed.js +20 -10
- package/signals/implementation/effect.d.ts +23 -12
- package/signals/implementation/effect.js +58 -40
- package/signals/implementation/equality.d.ts +15 -0
- package/signals/implementation/equality.js +13 -0
- package/signals/implementation/graph.d.ts +10 -0
- package/signals/implementation/graph.js +12 -0
- package/signals/implementation/index.d.ts +2 -0
- package/signals/implementation/index.js +2 -0
- package/signals/implementation/signal.d.ts +23 -9
- package/signals/implementation/signal.js +45 -27
- package/signals/implementation/to-signal.d.ts +14 -62
- package/signals/implementation/to-signal.js +28 -9
- package/signals/implementation/watch.d.ts +11 -1
- package/signals/implementation/watch.js +4 -1
- package/signals/index.d.ts +1 -1
- package/signals/index.js +1 -1
- package/signals/pipe.js +4 -4
- package/signals/symbol.d.ts +6 -0
- package/signals/symbol.js +6 -0
- package/signals/to-lazy-signal.d.ts +23 -0
- package/signals/to-lazy-signal.js +20 -0
- package/utils/reactive-value-to-signal.d.ts +4 -4
- package/signals/to-signal-2.d.ts +0 -19
- package/signals/to-signal-2.js +0 -44
package/intl/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './number-parser.js';
|
package/intl/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './number-parser.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tstdl/base",
|
|
3
|
-
"version": "0.90.
|
|
3
|
+
"version": "0.90.14",
|
|
4
4
|
"author": "Patrick Hein",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -47,10 +47,11 @@
|
|
|
47
47
|
"./errors": "./errors/index.js",
|
|
48
48
|
"./function": "./function/index.js",
|
|
49
49
|
"./http": "./http/index.js",
|
|
50
|
-
"./http/undici": "./http/client/adapters/undici.adapter.js",
|
|
51
50
|
"./http/node": "./http/server/node/index.js",
|
|
51
|
+
"./http/undici": "./http/client/adapters/undici.adapter.js",
|
|
52
52
|
"./image-service": "./image-service/index.js",
|
|
53
53
|
"./injector": "./injector/index.js",
|
|
54
|
+
"./intl": "./intl/index.js",
|
|
54
55
|
"./json-path": "./json-path/index.js",
|
|
55
56
|
"./key-value-store": "./key-value-store/index.js",
|
|
56
57
|
"./lock": "./lock/index.js",
|
|
@@ -108,7 +109,7 @@
|
|
|
108
109
|
"luxon": "^3.4",
|
|
109
110
|
"reflect-metadata": "^0.1",
|
|
110
111
|
"rxjs": "^7.8",
|
|
111
|
-
"type-fest": "^4.
|
|
112
|
+
"type-fest": "^4.5"
|
|
112
113
|
},
|
|
113
114
|
"devDependencies": {
|
|
114
115
|
"@mxssfd/typedoc-theme": "1.1",
|
|
@@ -119,8 +120,8 @@
|
|
|
119
120
|
"@types/mjml": "4.7",
|
|
120
121
|
"@types/node": "20",
|
|
121
122
|
"@types/nodemailer": "6.4",
|
|
122
|
-
"@typescript-eslint/eslint-plugin": "6.
|
|
123
|
-
"@typescript-eslint/parser": "6.
|
|
123
|
+
"@typescript-eslint/eslint-plugin": "6.8",
|
|
124
|
+
"@typescript-eslint/parser": "6.8",
|
|
124
125
|
"concurrently": "8.2",
|
|
125
126
|
"esbuild": "0.19",
|
|
126
127
|
"eslint": "8.51",
|
package/rxjs/index.d.ts
CHANGED
package/rxjs/index.js
CHANGED
package/rxjs/untrack.js
ADDED
|
@@ -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
|
|
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
|
|
9
|
-
import {
|
|
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
|
|
12
|
+
* Create a computed signal which derives a reactive value from an expression.
|
|
12
13
|
*/
|
|
13
|
-
export function
|
|
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
|
|
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
|
-
*
|
|
15
|
+
* A scheduler which manages the execution of effects.
|
|
20
16
|
*/
|
|
21
|
-
export declare class
|
|
22
|
-
|
|
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
|
-
|
|
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 {
|
|
9
|
+
import { assertNotInReactiveContext } from './asserts.js';
|
|
10
|
+
import { createWatch } from './watch.js';
|
|
9
11
|
/**
|
|
10
|
-
*
|
|
12
|
+
* A scheduler which manages the execution of effects.
|
|
11
13
|
*/
|
|
12
|
-
export class
|
|
13
|
-
|
|
14
|
+
export class EffectScheduler {
|
|
15
|
+
}
|
|
16
|
+
export class TstdlEffectScheduler {
|
|
14
17
|
queue = new Set();
|
|
15
|
-
pendingFlush;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
44
|
-
|
|
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
|
-
|
|
52
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
|
9
|
+
import { SIGNAL } from '../symbol.js';
|
|
10
|
+
import { defaultEquals } from './equality.js';
|
|
9
11
|
import { throwInvalidWriteToSignalError } from './errors.js';
|
|
10
|
-
import {
|
|
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
|
|
23
|
+
export function createSignal(initialValue) {
|
|
22
24
|
const node = Object.create(SIGNAL_NODE);
|
|
23
25
|
node.value = initialValue;
|
|
24
|
-
|
|
25
|
-
function signalFn() {
|
|
26
|
+
const getter = (() => {
|
|
26
27
|
producerAccessed(node);
|
|
27
28
|
return node.value;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
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
|
|
2
|
+
import { Signal } from './api.js';
|
|
10
3
|
/**
|
|
11
4
|
* Options for `toSignal`.
|
|
12
5
|
*/
|
|
13
|
-
export interface ToSignalOptions
|
|
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?:
|
|
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
|
-
|
|
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
|
-
|
|
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 |
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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 (
|
|
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
|
|
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
|
|
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
|
}
|
package/signals/index.d.ts
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
|
|
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
|
|
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 {
|
|
4
|
-
import {
|
|
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
|
|
9
|
+
const piped = toObservable(source).pipe(...operators, untrack());
|
|
10
|
+
return toSignal(piped, { requireSync: true });
|
|
11
11
|
}
|
|
@@ -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
|
|
4
|
-
export declare function reactiveValueToSignal<T>(source: ReactiveValue<T>, options?: ReactiveValueToSignalOptions
|
|
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
|
|
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
|
|
11
|
+
export declare function reactiveValueToSignal<T>(source: ReactiveValue<T>, options: ReactiveValueToSignalOptions & {
|
|
12
12
|
requireSync: true;
|
|
13
13
|
}): Signal<T>;
|
package/signals/to-signal-2.d.ts
DELETED
|
@@ -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 {};
|
package/signals/to-signal-2.js
DELETED
|
@@ -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
|
-
}
|