@tstdl/base 0.88.0-alpha1 → 0.88.0-alpha2

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 (76) hide show
  1. package/authentication/index.d.ts +1 -0
  2. package/authentication/index.js +1 -0
  3. package/browser/index.d.ts +1 -0
  4. package/browser/index.js +1 -0
  5. package/data-structures/index.d.ts +1 -0
  6. package/data-structures/index.js +1 -0
  7. package/http/client/adapters/index.d.ts +1 -0
  8. package/http/client/adapters/index.js +1 -0
  9. package/http/client/index.d.ts +2 -0
  10. package/http/client/index.js +2 -0
  11. package/mail/clients/index.d.ts +1 -0
  12. package/mail/clients/index.js +1 -0
  13. package/mail/index.d.ts +2 -0
  14. package/mail/index.js +2 -0
  15. package/mail/mail.service.d.ts +1 -1
  16. package/mail/mail.service.js +1 -1
  17. package/mail/module.d.ts +1 -1
  18. package/mail/module.js +1 -1
  19. package/mail/repositories/index.d.ts +1 -0
  20. package/mail/repositories/index.js +1 -0
  21. package/mail/repositories/mail-log.repository.d.ts +4 -0
  22. package/mail/{mail-log.repository.js → repositories/mail-log.repository.js} +1 -1
  23. package/mail/repositories/mongo/index.d.ts +1 -0
  24. package/mail/repositories/mongo/index.js +1 -0
  25. package/mail/repositories/{mongo-mail-log.repository.d.ts → mongo/mongo-mail-log.repository.d.ts} +6 -6
  26. package/mail/repositories/{mongo-mail-log.repository.js → mongo/mongo-mail-log.repository.js} +3 -3
  27. package/package.json +90 -2
  28. package/random/number-generator/index.d.ts +2 -0
  29. package/random/number-generator/index.js +2 -0
  30. package/random/number-generator/utils.d.ts +1 -0
  31. package/random/number-generator/utils.js +5 -0
  32. package/rpc/endpoints/index.d.ts +1 -0
  33. package/rpc/endpoints/index.js +1 -0
  34. package/rpc/endpoints/message-port.rpc-endpoint.js +1 -1
  35. package/signals/implementation/api.d.ts +1 -18
  36. package/signals/implementation/api.js +1 -10
  37. package/signals/implementation/computed.js +42 -94
  38. package/signals/implementation/effect.d.ts +1 -1
  39. package/signals/implementation/effect.js +16 -15
  40. package/signals/implementation/graph.d.ts +118 -73
  41. package/signals/implementation/graph.js +235 -156
  42. package/signals/implementation/signal.js +58 -81
  43. package/signals/implementation/watch.d.ts +2 -18
  44. package/signals/implementation/watch.js +37 -54
  45. package/sse/index.d.ts +1 -0
  46. package/sse/index.js +1 -0
  47. package/templates/providers/index.d.ts +2 -0
  48. package/templates/providers/index.js +2 -0
  49. package/templates/renderers/index.d.ts +4 -0
  50. package/templates/renderers/index.js +4 -0
  51. package/templates/resolvers/index.d.ts +3 -0
  52. package/templates/resolvers/index.js +3 -0
  53. package/templates/types/index.d.ts +1 -0
  54. package/templates/types/index.js +1 -0
  55. package/text/dynamic-text.model.js +3 -5
  56. package/theme/adapters/index.d.ts +2 -0
  57. package/theme/adapters/index.js +2 -0
  58. package/async-iterator-symbol.d.ts +0 -1
  59. package/async-iterator-symbol.js +0 -6
  60. package/mail/mail-log.repository.d.ts +0 -4
  61. package/notification/api.d.ts +0 -15
  62. package/notification/api.js +0 -28
  63. package/notification/models/index.d.ts +0 -2
  64. package/notification/models/index.js +0 -2
  65. package/notification/models/notification-channel-job.model.d.ts +0 -3
  66. package/notification/models/notification-channel-job.model.js +0 -1
  67. package/notification/models/notification.model.d.ts +0 -45
  68. package/notification/models/notification.model.js +0 -103
  69. package/notification/module.d.ts +0 -9
  70. package/notification/module.js +0 -10
  71. package/notification/notification-channel.service.d.ts +0 -5
  72. package/notification/notification-channel.service.js +0 -2
  73. package/notification/notification.repository.d.ts +0 -5
  74. package/notification/notification.repository.js +0 -2
  75. package/notification/notification.service.d.ts +0 -8
  76. package/notification/notification.service.js +0 -36
@@ -6,182 +6,261 @@
6
6
  * found in the LICENSE file at https://angular.io/license
7
7
  */
8
8
  /**
9
- * Counter tracking the next `ProducerId` or `ConsumerId`.
10
- */
11
- let _nextReactiveId = 0;
12
- /**
13
- * Tracks the currently active reactive consumer (or `null` if there is no active
14
- * consumer).
9
+ * The currently active consumer `ReactiveNode`, if running code in a reactive context.
10
+ *
11
+ * Change this via `setActiveConsumer`.
15
12
  */
16
13
  let activeConsumer = null;
17
- /**
18
- * Whether the graph is currently propagating change notifications.
19
- */
20
14
  let inNotificationPhase = false;
21
15
  export function setActiveConsumer(consumer) {
22
16
  const prev = activeConsumer;
23
17
  activeConsumer = consumer;
24
18
  return prev;
25
19
  }
20
+ export const REACTIVE_NODE = {
21
+ version: 0,
22
+ dirty: false,
23
+ producerNode: undefined,
24
+ producerLastReadVersion: undefined,
25
+ producerIndexOfThis: undefined,
26
+ nextProducerIndex: 0,
27
+ liveConsumerNode: undefined,
28
+ liveConsumerIndexOfThis: undefined,
29
+ consumerAllowSignalWrites: false,
30
+ consumerIsAlwaysLive: false,
31
+ producerMustRecompute: () => false,
32
+ producerRecomputeValue: () => { },
33
+ consumerMarkedDirty: () => { },
34
+ };
26
35
  /**
27
- * A node in the reactive graph.
28
- *
29
- * Nodes can be producers of reactive values, consumers of other reactive values, or both.
30
- *
31
- * Producers are nodes that produce values, and can be depended upon by consumer nodes.
32
- *
33
- * Producers expose a monotonic `valueVersion` counter, and are responsible for incrementing this
34
- * version when their value semantically changes. Some producers may produce their values lazily and
35
- * thus at times need to be polled for potential updates to their value (and by extension their
36
- * `valueVersion`). This is accomplished via the `onProducerUpdateValueVersion` method for
37
- * implemented by producers, which should perform whatever calculations are necessary to ensure
38
- * `valueVersion` is up to date.
39
- *
40
- * Consumers are nodes that depend on the values of producers and are notified when those values
41
- * might have changed.
42
- *
43
- * Consumers do not wrap the reads they consume themselves, but rather can be set as the active
44
- * reader via `setActiveConsumer`. Reads of producers that happen while a consumer is active will
45
- * result in those producers being added as dependencies of that consumer node.
46
- *
47
- * The set of dependencies of a consumer is dynamic. Implementers expose a monotonically increasing
48
- * `trackingVersion` counter, which increments whenever the consumer is about to re-run any reactive
49
- * reads it needs and establish a new set of dependencies as a result.
50
- *
51
- * Producers store the last `trackingVersion` they've seen from `Consumer`s which have read them.
52
- * This allows a producer to identify whether its record of the dependency is current or stale, by
53
- * comparing the consumer's `trackingVersion` to the version at which the dependency was
54
- * last observed.
36
+ * Called by implementations when a producer's signal is read.
55
37
  */
56
- export class ReactiveNode {
57
- id = _nextReactiveId++;
58
- /**
59
- * A cached weak reference to this node, which will be used in `ReactiveEdge`s.
60
- */
61
- ref = new WeakRef(this);
62
- /**
63
- * Edges to producers on which this node depends (in its consumer capacity).
64
- */
65
- producers = new Map();
66
- /**
67
- * Edges to consumers on which this node depends (in its producer capacity).
68
- */
69
- consumers = new Map();
70
- /**
71
- * Monotonically increasing counter representing a version of this `Consumer`'s
72
- * dependencies.
73
- */
74
- trackingVersion = 0;
75
- /**
76
- * Monotonically increasing counter which increases when the value of this `Producer`
77
- * semantically changes.
78
- */
79
- valueVersion = 0;
80
- /**
81
- * Polls dependencies of a consumer to determine if they have actually changed.
82
- *
83
- * If this returns `false`, then even though the consumer may have previously been notified of a
84
- * change, the values of its dependencies have not actually changed and the consumer should not
85
- * rerun any reactions.
86
- */
87
- consumerPollProducersForChange() {
88
- for (const [producerId, edge] of this.producers) {
89
- const producer = edge.producerNode.deref();
90
- // On Safari < 16.1 deref can return null, we need to check for null also.
91
- // See https://github.com/WebKit/WebKit/commit/44c15ba58912faab38b534fef909dd9e13e095e0
92
- if (producer == null || edge.atTrackingVersion !== this.trackingVersion) {
93
- // This dependency edge is stale, so remove it.
94
- this.producers.delete(producerId);
95
- producer?.consumers.delete(this.id);
96
- continue;
97
- }
98
- if (producer.producerPollStatus(edge.seenValueVersion)) {
99
- // One of the dependencies reports a real value change.
100
- return true;
101
- }
38
+ export function producerAccessed(node) {
39
+ if (inNotificationPhase) {
40
+ throw new Error('Assertion error: signal read during notification phase');
41
+ }
42
+ if (activeConsumer === null) {
43
+ // Accessed outside of a reactive context, so nothing to record.
44
+ return;
45
+ }
46
+ // This producer is the `idx`th dependency of `activeConsumer`.
47
+ const idx = activeConsumer.nextProducerIndex++;
48
+ assertConsumerNode(activeConsumer);
49
+ if (idx < activeConsumer.producerNode.length && activeConsumer.producerNode[idx] !== node) {
50
+ // There's been a change in producers since the last execution of `activeConsumer`.
51
+ // `activeConsumer.producerNode[idx]` holds a stale dependency which will be be removed and
52
+ // replaced with `this`.
53
+ //
54
+ // If `activeConsumer` isn't live, then this is a no-op, since we can replace the producer in
55
+ // `activeConsumer.producerNode` directly. However, if `activeConsumer` is live, then we need
56
+ // to remove it from the stale producer's `liveConsumer`s.
57
+ if (consumerIsLive(activeConsumer)) {
58
+ const staleProducer = activeConsumer.producerNode[idx];
59
+ producerRemoveLiveConsumerAtIndex(staleProducer, activeConsumer.producerIndexOfThis[idx]);
60
+ // At this point, the only record of `staleProducer` is the reference at
61
+ // `activeConsumer.producerNode[idx]` which will be overwritten below.
102
62
  }
103
- // No dependency reported a real value change, so the `Consumer` has also not been
104
- // impacted.
105
- return false;
106
- }
107
- /**
108
- * Notify all consumers of this producer that its value may have changed.
109
- */
110
- producerMayHaveChanged() {
111
- // Prevent signal reads when we're updating the graph
112
- const prev = inNotificationPhase;
113
- inNotificationPhase = true;
114
- try {
115
- for (const [consumerId, edge] of this.consumers) {
116
- const consumer = edge.consumerNode.deref();
117
- // On Safari < 16.1 deref can return null, we need to check for null also.
118
- // See https://github.com/WebKit/WebKit/commit/44c15ba58912faab38b534fef909dd9e13e095e0
119
- if (consumer == null || consumer.trackingVersion !== edge.atTrackingVersion) {
120
- this.consumers.delete(consumerId);
121
- consumer?.producers.delete(this.id);
122
- continue;
123
- }
124
- consumer.onConsumerDependencyMayHaveChanged();
63
+ }
64
+ if (activeConsumer.producerNode[idx] !== node) {
65
+ // We're a new dependency of the consumer (at `idx`).
66
+ activeConsumer.producerNode[idx] = node;
67
+ // If the active consumer is live, then add it as a live consumer. If not, then use 0 as a
68
+ // placeholder value.
69
+ activeConsumer.producerIndexOfThis[idx] =
70
+ consumerIsLive(activeConsumer) ? producerAddLiveConsumer(node, activeConsumer, idx) : 0;
71
+ }
72
+ activeConsumer.producerLastReadVersion[idx] = node.version;
73
+ }
74
+ /**
75
+ * Ensure this producer's `version` is up-to-date.
76
+ */
77
+ export function producerUpdateValueVersion(node) {
78
+ if (consumerIsLive(node) && !node.dirty) {
79
+ // A live consumer will be marked dirty by producers, so a clean state means that its version
80
+ // is guaranteed to be up-to-date.
81
+ return;
82
+ }
83
+ if (!node.producerMustRecompute(node) && !consumerPollProducersForChange(node)) {
84
+ // None of our producers report a change since the last time they were read, so no
85
+ // recomputation of our value is necessary, and we can consider ourselves clean.
86
+ node.dirty = false;
87
+ return;
88
+ }
89
+ node.producerRecomputeValue(node);
90
+ // After recomputing the value, we're no longer dirty.
91
+ node.dirty = false;
92
+ }
93
+ /**
94
+ * Propagate a dirty notification to live consumers of this producer.
95
+ */
96
+ export function producerNotifyConsumers(node) {
97
+ if (node.liveConsumerNode === undefined) {
98
+ return;
99
+ }
100
+ // Prevent signal reads when we're updating the graph
101
+ const prev = inNotificationPhase;
102
+ inNotificationPhase = true;
103
+ try {
104
+ for (const consumer of node.liveConsumerNode) {
105
+ if (!consumer.dirty) {
106
+ consumerMarkDirty(consumer);
125
107
  }
126
108
  }
127
- finally {
128
- inNotificationPhase = prev;
109
+ }
110
+ finally {
111
+ inNotificationPhase = prev;
112
+ }
113
+ }
114
+ /**
115
+ * Whether this `ReactiveNode` in its producer capacity is currently allowed to initiate updates,
116
+ * based on the current consumer context.
117
+ */
118
+ export function producerUpdatesAllowed() {
119
+ return activeConsumer?.consumerAllowSignalWrites !== false;
120
+ }
121
+ export function consumerMarkDirty(node) {
122
+ node.dirty = true;
123
+ producerNotifyConsumers(node);
124
+ node.consumerMarkedDirty?.(node);
125
+ }
126
+ /**
127
+ * Prepare this consumer to run a computation in its reactive context.
128
+ *
129
+ * Must be called by subclasses which represent reactive computations, before those computations
130
+ * begin.
131
+ */
132
+ export function consumerBeforeComputation(node) {
133
+ node && (node.nextProducerIndex = 0);
134
+ return setActiveConsumer(node);
135
+ }
136
+ /**
137
+ * Finalize this consumer's state after a reactive computation has run.
138
+ *
139
+ * Must be called by subclasses which represent reactive computations, after those computations
140
+ * have finished.
141
+ */
142
+ export function consumerAfterComputation(node, prevConsumer) {
143
+ setActiveConsumer(prevConsumer);
144
+ if (!node || node.producerNode === undefined || node.producerIndexOfThis === undefined ||
145
+ node.producerLastReadVersion === undefined) {
146
+ return;
147
+ }
148
+ if (consumerIsLive(node)) {
149
+ // For live consumers, we need to remove the producer -> consumer edge for any stale producers
150
+ // which weren't dependencies after the recomputation.
151
+ for (let i = node.nextProducerIndex; i < node.producerNode.length; i++) {
152
+ producerRemoveLiveConsumerAtIndex(node.producerNode[i], node.producerIndexOfThis[i]);
129
153
  }
130
154
  }
131
- /**
132
- * Mark that this producer node has been accessed in the current reactive context.
133
- */
134
- producerAccessed() {
135
- if (inNotificationPhase) {
136
- throw new Error(`Assertion error: signal read during notification phase`);
155
+ // Truncate the producer tracking arrays.
156
+ for (let i = node.nextProducerIndex; i < node.producerNode.length; i++) {
157
+ node.producerNode.pop();
158
+ node.producerLastReadVersion.pop();
159
+ node.producerIndexOfThis.pop();
160
+ }
161
+ }
162
+ /**
163
+ * Determine whether this consumer has any dependencies which have changed since the last time
164
+ * they were read.
165
+ */
166
+ export function consumerPollProducersForChange(node) {
167
+ assertConsumerNode(node);
168
+ // Poll producers for change.
169
+ for (let i = 0; i < node.producerNode.length; i++) {
170
+ const producer = node.producerNode[i];
171
+ const seenVersion = node.producerLastReadVersion[i];
172
+ // First check the versions. A mismatch means that the producer's value is known to have
173
+ // changed since the last time we read it.
174
+ if (seenVersion !== producer.version) {
175
+ return true;
137
176
  }
138
- if (activeConsumer === null) {
139
- return;
177
+ // The producer's version is the same as the last time we read it, but it might itself be
178
+ // stale. Force the producer to recompute its version (calculating a new value if necessary).
179
+ producerUpdateValueVersion(producer);
180
+ // Now when we do this check, `producer.version` is guaranteed to be up to date, so if the
181
+ // versions still match then it has not changed since the last time we read it.
182
+ if (seenVersion !== producer.version) {
183
+ return true;
140
184
  }
141
- // Either create or update the dependency `Edge` in both directions.
142
- let edge = activeConsumer.producers.get(this.id);
143
- if (edge === undefined) {
144
- edge = {
145
- consumerNode: activeConsumer.ref,
146
- producerNode: this.ref,
147
- seenValueVersion: this.valueVersion,
148
- atTrackingVersion: activeConsumer.trackingVersion,
149
- };
150
- activeConsumer.producers.set(this.id, edge);
151
- this.consumers.set(activeConsumer.id, edge);
185
+ }
186
+ return false;
187
+ }
188
+ /**
189
+ * Disconnect this consumer from the graph.
190
+ */
191
+ export function consumerDestroy(node) {
192
+ assertConsumerNode(node);
193
+ if (consumerIsLive(node)) {
194
+ // Drop all connections from the graph to this node.
195
+ for (let i = 0; i < node.producerNode.length; i++) {
196
+ producerRemoveLiveConsumerAtIndex(node.producerNode[i], node.producerIndexOfThis[i]);
152
197
  }
153
- else {
154
- edge.seenValueVersion = this.valueVersion;
155
- edge.atTrackingVersion = activeConsumer.trackingVersion;
198
+ }
199
+ // Truncate all the arrays to drop all connection from this node to the graph.
200
+ node.producerNode.length = node.producerLastReadVersion.length = node.producerIndexOfThis.length =
201
+ 0;
202
+ if (node.liveConsumerNode) {
203
+ node.liveConsumerNode.length = node.liveConsumerIndexOfThis.length = 0;
204
+ }
205
+ }
206
+ /**
207
+ * Add `consumer` as a live consumer of this node.
208
+ *
209
+ * Note that this operation is potentially transitive. If this node becomes live, then it becomes
210
+ * a live consumer of all of its current producers.
211
+ */
212
+ function producerAddLiveConsumer(node, consumer, indexOfThis) {
213
+ assertProducerNode(node);
214
+ assertConsumerNode(node);
215
+ if (node.liveConsumerNode.length === 0) {
216
+ // When going from 0 to 1 live consumers, we become a live consumer to our producers.
217
+ for (let i = 0; i < node.producerNode.length; i++) {
218
+ node.producerIndexOfThis[i] = producerAddLiveConsumer(node.producerNode[i], node, i);
156
219
  }
157
220
  }
158
- /**
159
- * Whether this consumer currently has any producers registered.
160
- */
161
- get hasProducers() {
162
- return this.producers.size > 0;
163
- }
164
- /**
165
- * Whether this `ReactiveNode` in its producer capacity is currently allowed to initiate updates,
166
- * based on the current consumer context.
167
- */
168
- get producerUpdatesAllowed() {
169
- return activeConsumer?.consumerAllowSignalWrites !== false;
170
- }
171
- /**
172
- * Checks if a `Producer` has a current value which is different than the value
173
- * last seen at a specific version by a `Consumer` which recorded a dependency on
174
- * this `Producer`.
175
- */
176
- producerPollStatus(lastSeenValueVersion) {
177
- // `producer.valueVersion` may be stale, but a mismatch still means that the value
178
- // last seen by the `Consumer` is also stale.
179
- if (this.valueVersion !== lastSeenValueVersion) {
180
- return true;
221
+ node.liveConsumerIndexOfThis.push(indexOfThis);
222
+ return node.liveConsumerNode.push(consumer) - 1;
223
+ }
224
+ /**
225
+ * Remove the live consumer at `idx`.
226
+ */
227
+ function producerRemoveLiveConsumerAtIndex(node, idx) {
228
+ assertProducerNode(node);
229
+ assertConsumerNode(node);
230
+ if (node.liveConsumerNode.length === 1) {
231
+ // When removing the last live consumer, we will no longer be live. We need to remove
232
+ // ourselves from our producers' tracking (which may cause consumer-producers to lose
233
+ // liveness as well).
234
+ for (let i = 0; i < node.producerNode.length; i++) {
235
+ producerRemoveLiveConsumerAtIndex(node.producerNode[i], node.producerIndexOfThis[i]);
181
236
  }
182
- // Trigger the `Producer` to update its `valueVersion` if necessary.
183
- this.onProducerUpdateValueVersion();
184
- // At this point, we can trust `producer.valueVersion`.
185
- return this.valueVersion !== lastSeenValueVersion;
186
237
  }
238
+ // Move the last value of `liveConsumers` into `idx`. Note that if there's only a single
239
+ // live consumer, this is a no-op.
240
+ const lastIdx = node.liveConsumerNode.length - 1;
241
+ node.liveConsumerNode[idx] = node.liveConsumerNode[lastIdx];
242
+ node.liveConsumerIndexOfThis[idx] = node.liveConsumerIndexOfThis[lastIdx];
243
+ // Truncate the array.
244
+ node.liveConsumerNode.length--;
245
+ node.liveConsumerIndexOfThis.length--;
246
+ // If the index is still valid, then we need to fix the index pointer from the producer to this
247
+ // consumer, and update it from `lastIdx` to `idx` (accounting for the move above).
248
+ if (idx < node.liveConsumerNode.length) {
249
+ const idxProducer = node.liveConsumerIndexOfThis[idx];
250
+ const consumer = node.liveConsumerNode[idx];
251
+ assertConsumerNode(consumer);
252
+ consumer.producerIndexOfThis[idxProducer] = idx;
253
+ }
254
+ }
255
+ function consumerIsLive(node) {
256
+ return node.consumerIsAlwaysLive || (node?.liveConsumerNode?.length ?? 0) > 0;
257
+ }
258
+ function assertConsumerNode(node) {
259
+ node.producerNode ??= [];
260
+ node.producerIndexOfThis ??= [];
261
+ node.producerLastReadVersion ??= [];
262
+ }
263
+ function assertProducerNode(node) {
264
+ node.liveConsumerNode ??= [];
265
+ node.liveConsumerIndexOfThis ??= [];
187
266
  }
@@ -5,9 +5,9 @@
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 { createSignalFromFunction, defaultEquals } from './api.js';
8
+ import { SIGNAL, defaultEquals } from './api.js';
9
9
  import { throwInvalidWriteToSignalError } from './errors.js';
10
- import { ReactiveNode } from './graph.js';
10
+ import { REACTIVE_NODE, producerAccessed, producerNotifyConsumers, producerUpdatesAllowed } from './graph.js';
11
11
  /**
12
12
  * If set, called after `WritableSignal`s are updated.
13
13
  *
@@ -15,89 +15,22 @@ import { ReactiveNode } from './graph.js';
15
15
  * of setting a signal.
16
16
  */
17
17
  let postSignalSetFn = null;
18
- class WritableSignalImpl extends ReactiveNode {
19
- value;
20
- equal;
21
- readonlySignal;
22
- consumerAllowSignalWrites = false;
23
- constructor(value, equal) {
24
- super();
25
- this.value = value;
26
- this.equal = equal;
27
- }
28
- onConsumerDependencyMayHaveChanged() {
29
- // This never happens for writable signals as they're not consumers.
30
- }
31
- onProducerUpdateValueVersion() {
32
- // Writable signal value versions are always up to date.
33
- }
34
- /**
35
- * Directly update the value of the signal to a new value, which may or may not be
36
- * equal to the previous.
37
- *
38
- * In the event that `newValue` is semantically equal to the current value, `set` is
39
- * a no-op.
40
- */
41
- set(newValue) {
42
- if (!this.producerUpdatesAllowed) {
43
- throwInvalidWriteToSignalError();
44
- }
45
- if (!this.equal(this.value, newValue)) {
46
- this.value = newValue;
47
- this.valueVersion++;
48
- this.producerMayHaveChanged();
49
- postSignalSetFn?.();
50
- }
51
- }
52
- /**
53
- * Derive a new value for the signal from its current value using the `updater` function.
54
- *
55
- * This is equivalent to calling `set` on the result of running `updater` on the current
56
- * value.
57
- */
58
- update(updater) {
59
- if (!this.producerUpdatesAllowed) {
60
- throwInvalidWriteToSignalError();
61
- }
62
- this.set(updater(this.value));
63
- }
64
- /**
65
- * Calls `mutator` on the current value and assumes that it has been mutated.
66
- */
67
- mutate(mutator) {
68
- if (!this.producerUpdatesAllowed) {
69
- throwInvalidWriteToSignalError();
70
- }
71
- // Mutate bypasses equality checks as it's by definition changing the value.
72
- mutator(this.value);
73
- this.valueVersion++;
74
- this.producerMayHaveChanged();
75
- postSignalSetFn?.();
76
- }
77
- asReadonly() {
78
- if (this.readonlySignal === undefined) {
79
- this.readonlySignal = createSignalFromFunction(this, () => this.signal());
80
- }
81
- return this.readonlySignal;
82
- }
83
- signal() {
84
- this.producerAccessed();
85
- return this.value;
86
- }
87
- }
88
18
  /**
89
19
  * Create a `Signal` that can be set or updated directly.
90
20
  */
91
21
  export function signal(initialValue, options) {
92
- const signalNode = new WritableSignalImpl(initialValue, options?.equal ?? defaultEquals);
93
- // Casting here is required for g3, as TS inference behavior is slightly different between our
94
- // version/options and g3's.
95
- const signalFn = createSignalFromFunction(signalNode, signalNode.signal.bind(signalNode), {
96
- set: signalNode.set.bind(signalNode),
97
- update: signalNode.update.bind(signalNode),
98
- mutate: signalNode.mutate.bind(signalNode),
99
- asReadonly: signalNode.asReadonly.bind(signalNode)
100
- });
22
+ const node = Object.create(SIGNAL_NODE);
23
+ node.value = initialValue;
24
+ options?.equal && (node.equal = options.equal);
25
+ function signalFn() {
26
+ producerAccessed(node);
27
+ return node.value;
28
+ }
29
+ signalFn.set = signalSetFn;
30
+ signalFn.update = signalUpdateFn;
31
+ signalFn.mutate = signalMutateFn;
32
+ signalFn.asReadonly = signalAsReadonlyFn;
33
+ signalFn[SIGNAL] = node;
101
34
  return signalFn;
102
35
  }
103
36
  export function setPostSignalSetFn(fn) {
@@ -105,3 +38,47 @@ export function setPostSignalSetFn(fn) {
105
38
  postSignalSetFn = fn;
106
39
  return prev;
107
40
  }
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?.();
50
+ }
51
+ function signalSetFn(newValue) {
52
+ const node = this[SIGNAL];
53
+ if (!producerUpdatesAllowed()) {
54
+ throwInvalidWriteToSignalError();
55
+ }
56
+ if (!node.equal(node.value, newValue)) {
57
+ node.value = newValue;
58
+ signalValueChanged(node);
59
+ }
60
+ }
61
+ function signalUpdateFn(updater) {
62
+ if (!producerUpdatesAllowed()) {
63
+ throwInvalidWriteToSignalError();
64
+ }
65
+ signalSetFn.call(this, updater(this[SIGNAL].value));
66
+ }
67
+ function signalMutateFn(mutator) {
68
+ const node = this[SIGNAL];
69
+ if (!producerUpdatesAllowed()) {
70
+ throwInvalidWriteToSignalError();
71
+ }
72
+ // Mutate bypasses equality checks as it's by definition changing the value.
73
+ mutator(node.value);
74
+ signalValueChanged(node);
75
+ }
76
+ function signalAsReadonlyFn() {
77
+ const node = this[SIGNAL];
78
+ if (node.readonlyFn === undefined) {
79
+ const readonlyFn = () => this();
80
+ readonlyFn[SIGNAL] = node;
81
+ node.readonlyFn = readonlyFn;
82
+ }
83
+ return node.readonlyFn;
84
+ }
@@ -5,7 +5,6 @@
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 { ReactiveNode } from './graph.js';
9
8
  /**
10
9
  * A cleanup function that can be optionally registered from the watch logic. If registered, the
11
10
  * cleanup logic runs before the next watch execution.
@@ -15,24 +14,8 @@ export type WatchCleanupFn = () => void;
15
14
  * A callback passed to the watch function that makes it possible to register cleanup logic.
16
15
  */
17
16
  export type WatchCleanupRegisterFn = (cleanupFn: WatchCleanupFn) => void;
18
- /**
19
- * Watches a reactive expression and allows it to be scheduled to re-run
20
- * when any dependencies notify of a change.
21
- *
22
- * `Watch` doesn't run reactive expressions itself, but relies on a consumer-
23
- * provided scheduling operation to coordinate calling `Watch.run()`.
24
- */
25
- export declare class Watch extends ReactiveNode {
26
- private watch;
27
- private schedule;
28
- protected readonly consumerAllowSignalWrites: boolean;
29
- private dirty;
30
- private cleanupFn;
31
- private registerOnCleanup;
32
- constructor(watch: (onCleanup: WatchCleanupRegisterFn) => void, schedule: (watch: Watch) => void, allowSignalWrites: boolean);
17
+ export interface Watch {
33
18
  notify(): void;
34
- protected onConsumerDependencyMayHaveChanged(): void;
35
- protected onProducerUpdateValueVersion(): void;
36
19
  /**
37
20
  * Execute the reactive expression in the context of this `Watch` consumer.
38
21
  *
@@ -42,3 +25,4 @@ export declare class Watch extends ReactiveNode {
42
25
  run(): void;
43
26
  cleanup(): void;
44
27
  }
28
+ export declare function watch(fn: (onCleanup: WatchCleanupRegisterFn) => void, schedule: (watch: Watch) => void, allowSignalWrites: boolean): Watch;