signalium 0.2.7 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +12 -0
- package/CHANGELOG.md +12 -0
- package/dist/cjs/config.d.ts +14 -5
- package/dist/cjs/config.d.ts.map +1 -1
- package/dist/cjs/config.js +23 -14
- package/dist/cjs/config.js.map +1 -1
- package/dist/cjs/debug.d.ts +3 -0
- package/dist/cjs/debug.d.ts.map +1 -0
- package/dist/cjs/debug.js +16 -0
- package/dist/cjs/debug.js.map +1 -0
- package/dist/cjs/hooks.d.ts +45 -0
- package/dist/cjs/hooks.d.ts.map +1 -0
- package/dist/cjs/hooks.js +260 -0
- package/dist/cjs/hooks.js.map +1 -0
- package/dist/cjs/index.d.ts +5 -3
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +21 -8
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/react/context.d.ts +4 -0
- package/dist/cjs/react/context.d.ts.map +1 -0
- package/dist/cjs/react/context.js +10 -0
- package/dist/cjs/react/context.js.map +1 -0
- package/dist/cjs/react/index.d.ts +5 -0
- package/dist/cjs/react/index.d.ts.map +1 -0
- package/dist/cjs/react/index.js +12 -0
- package/dist/cjs/react/index.js.map +1 -0
- package/dist/cjs/react/provider.d.ts +7 -0
- package/dist/cjs/react/provider.d.ts.map +1 -0
- package/dist/cjs/react/provider.js +13 -0
- package/dist/cjs/react/provider.js.map +1 -0
- package/dist/cjs/react/signal-value.d.ts +3 -0
- package/dist/cjs/react/signal-value.d.ts.map +1 -0
- package/dist/cjs/react/signal-value.js +42 -0
- package/dist/cjs/react/signal-value.js.map +1 -0
- package/dist/cjs/react/state.d.ts +3 -0
- package/dist/cjs/react/state.d.ts.map +1 -0
- package/dist/cjs/react/state.js +13 -0
- package/dist/cjs/react/state.js.map +1 -0
- package/dist/cjs/scheduling.d.ts +5 -0
- package/dist/cjs/scheduling.d.ts.map +1 -1
- package/dist/cjs/scheduling.js +59 -5
- package/dist/cjs/scheduling.js.map +1 -1
- package/dist/cjs/signals.d.ts +28 -65
- package/dist/cjs/signals.d.ts.map +1 -1
- package/dist/cjs/signals.js +223 -65
- package/dist/cjs/signals.js.map +1 -1
- package/dist/cjs/trace.d.ts +127 -0
- package/dist/cjs/trace.d.ts.map +1 -0
- package/dist/cjs/trace.js +319 -0
- package/dist/cjs/trace.js.map +1 -0
- package/dist/cjs/types.d.ts +66 -0
- package/dist/cjs/types.d.ts.map +1 -0
- package/dist/cjs/types.js +3 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/cjs/utils.d.ts +4 -0
- package/dist/cjs/utils.d.ts.map +1 -0
- package/dist/cjs/utils.js +80 -0
- package/dist/cjs/utils.js.map +1 -0
- package/dist/esm/config.d.ts +14 -5
- package/dist/esm/config.d.ts.map +1 -1
- package/dist/esm/config.js +19 -11
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/debug.d.ts +3 -0
- package/dist/esm/debug.d.ts.map +1 -0
- package/dist/esm/debug.js +3 -0
- package/dist/esm/debug.js.map +1 -0
- package/dist/esm/hooks.d.ts +45 -0
- package/dist/esm/hooks.d.ts.map +1 -0
- package/dist/esm/hooks.js +243 -0
- package/dist/esm/hooks.js.map +1 -0
- package/dist/esm/index.d.ts +5 -3
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +4 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/react/context.d.ts +4 -0
- package/dist/esm/react/context.d.ts.map +1 -0
- package/dist/esm/react/context.js +6 -0
- package/dist/esm/react/context.js.map +1 -0
- package/dist/esm/react/index.d.ts +5 -0
- package/dist/esm/react/index.d.ts.map +1 -0
- package/dist/esm/react/index.js +5 -0
- package/dist/esm/react/index.js.map +1 -0
- package/dist/esm/react/provider.d.ts +7 -0
- package/dist/esm/react/provider.d.ts.map +1 -0
- package/dist/esm/react/provider.js +10 -0
- package/dist/esm/react/provider.js.map +1 -0
- package/dist/esm/react/signal-value.d.ts +3 -0
- package/dist/esm/react/signal-value.d.ts.map +1 -0
- package/dist/esm/react/signal-value.js +38 -0
- package/dist/esm/react/signal-value.js.map +1 -0
- package/dist/esm/react/state.d.ts +3 -0
- package/dist/esm/react/state.d.ts.map +1 -0
- package/dist/esm/react/state.js +10 -0
- package/dist/esm/react/state.js.map +1 -0
- package/dist/esm/scheduling.d.ts +5 -0
- package/dist/esm/scheduling.d.ts.map +1 -1
- package/dist/esm/scheduling.js +51 -1
- package/dist/esm/scheduling.js.map +1 -1
- package/dist/esm/signals.d.ts +28 -65
- package/dist/esm/signals.d.ts.map +1 -1
- package/dist/esm/signals.js +215 -61
- package/dist/esm/signals.js.map +1 -1
- package/dist/esm/trace.d.ts +127 -0
- package/dist/esm/trace.d.ts.map +1 -0
- package/dist/esm/trace.js +311 -0
- package/dist/esm/trace.js.map +1 -0
- package/dist/esm/types.d.ts +66 -0
- package/dist/esm/types.d.ts.map +1 -0
- package/dist/esm/types.js +2 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/utils.d.ts +4 -0
- package/dist/esm/utils.d.ts.map +1 -0
- package/dist/esm/utils.js +75 -0
- package/dist/esm/utils.js.map +1 -0
- package/package.json +43 -2
- package/src/__tests__/hooks/async-computed.test.ts +190 -0
- package/src/__tests__/hooks/async-task.test.ts +227 -0
- package/src/__tests__/hooks/computed.test.ts +126 -0
- package/src/__tests__/hooks/context.test.ts +527 -0
- package/src/__tests__/hooks/nesting.test.ts +303 -0
- package/src/__tests__/hooks/params-and-state.test.ts +168 -0
- package/src/__tests__/hooks/subscription.test.ts +97 -0
- package/src/__tests__/signals/async.test.ts +416 -0
- package/src/__tests__/signals/basic.test.ts +399 -0
- package/src/__tests__/signals/subscription.test.ts +632 -0
- package/src/__tests__/signals/watcher.test.ts +253 -0
- package/src/__tests__/utils/async.ts +6 -0
- package/src/__tests__/utils/builders.ts +22 -0
- package/src/__tests__/utils/instrumented-hooks.ts +309 -0
- package/src/__tests__/utils/instrumented-signals.ts +281 -0
- package/src/__tests__/utils/permute.ts +74 -0
- package/src/config.ts +32 -17
- package/src/debug.ts +14 -0
- package/src/hooks.ts +429 -0
- package/src/index.ts +28 -3
- package/src/react/__tests__/react.test.tsx +135 -0
- package/src/react/context.ts +8 -0
- package/src/react/index.ts +4 -0
- package/src/react/provider.tsx +18 -0
- package/src/react/signal-value.ts +56 -0
- package/src/react/state.ts +13 -0
- package/src/scheduling.ts +69 -1
- package/src/signals.ts +331 -157
- package/src/trace.ts +449 -0
- package/src/types.ts +86 -0
- package/src/utils.ts +83 -0
- package/tsconfig.json +2 -1
- package/vitest.workspace.ts +24 -0
- package/src/__tests__/async.test.ts +0 -426
- package/src/__tests__/basic.test.ts +0 -378
- package/src/__tests__/subscription.test.ts +0 -645
- package/src/__tests__/utils/instrumented.ts +0 -326
package/src/signals.ts
CHANGED
@@ -1,12 +1,34 @@
|
|
1
|
-
import {
|
1
|
+
import {
|
2
|
+
scheduleConnect,
|
3
|
+
scheduleDirty,
|
4
|
+
scheduleDisconnect,
|
5
|
+
scheduleEffect,
|
6
|
+
schedulePull,
|
7
|
+
scheduleWatcher,
|
8
|
+
} from './scheduling.js';
|
2
9
|
import WeakRef from './weakref.js';
|
10
|
+
import { TRACER as TRACER, TracerEventType, VisualizerNodeType } from './trace.js';
|
11
|
+
import {
|
12
|
+
AsyncResult,
|
13
|
+
AsyncSignal,
|
14
|
+
AsyncTask,
|
15
|
+
Signal,
|
16
|
+
SignalAsyncCompute,
|
17
|
+
SignalCompute,
|
18
|
+
SignalEquals,
|
19
|
+
SignalOptions,
|
20
|
+
SignalOptionsWithInit,
|
21
|
+
SignalSubscribe,
|
22
|
+
SignalSubscription,
|
23
|
+
Watcher,
|
24
|
+
WatcherListenerOptions,
|
25
|
+
} from './types.js';
|
3
26
|
|
4
27
|
let CURRENT_ORD = 0;
|
5
28
|
let CURRENT_CONSUMER: ComputedSignal<any> | undefined;
|
6
29
|
let CURRENT_IS_WAITING: boolean = false;
|
7
30
|
|
8
|
-
|
9
|
-
|
31
|
+
// Should not leave the file so it doesn't become an interop issue
|
10
32
|
const enum SignalType {
|
11
33
|
Computed,
|
12
34
|
Subscription,
|
@@ -14,39 +36,6 @@ const enum SignalType {
|
|
14
36
|
Watcher,
|
15
37
|
}
|
16
38
|
|
17
|
-
export interface Signal<T = unknown> {
|
18
|
-
get(): T;
|
19
|
-
}
|
20
|
-
|
21
|
-
export interface WriteableSignal<T> extends Signal<T> {
|
22
|
-
set(value: T): void;
|
23
|
-
}
|
24
|
-
|
25
|
-
export type AsyncSignal<T> = Signal<AsyncResult<T>>;
|
26
|
-
|
27
|
-
export type SignalCompute<T> = (prev: T | undefined) => T;
|
28
|
-
|
29
|
-
export type SignalAsyncCompute<T> = (prev: T | undefined) => T | Promise<T>;
|
30
|
-
|
31
|
-
export type SignalWatcherEffect = () => void;
|
32
|
-
|
33
|
-
export type SignalEquals<T> = (prev: T, next: T) => boolean;
|
34
|
-
|
35
|
-
export type SignalSubscription = {
|
36
|
-
update?(): void;
|
37
|
-
unsubscribe?(): void;
|
38
|
-
};
|
39
|
-
|
40
|
-
export type SignalSubscribe<T> = (get: () => T, set: (value: T) => void) => SignalSubscription | undefined | void;
|
41
|
-
|
42
|
-
export interface SignalOptions<T> {
|
43
|
-
equals?: SignalEquals<T>;
|
44
|
-
}
|
45
|
-
|
46
|
-
export interface SignalOptionsWithInit<T> extends SignalOptions<T> {
|
47
|
-
initValue: T;
|
48
|
-
}
|
49
|
-
|
50
39
|
const SUBSCRIPTIONS = new WeakMap<ComputedSignal<any>, SignalSubscription | undefined | void>();
|
51
40
|
const ACTIVE_ASYNCS = new WeakMap<ComputedSignal<any>, Promise<unknown>>();
|
52
41
|
|
@@ -68,8 +57,28 @@ interface Link {
|
|
68
57
|
nextDirty: Link | undefined;
|
69
58
|
}
|
70
59
|
|
60
|
+
const FALSE_EQUALS: SignalEquals<unknown> = () => false;
|
61
|
+
|
62
|
+
export function signalTypeToVisualizerType(type: SignalType): VisualizerNodeType {
|
63
|
+
switch (type) {
|
64
|
+
case SignalType.Computed:
|
65
|
+
return VisualizerNodeType.Computed;
|
66
|
+
case SignalType.Subscription:
|
67
|
+
return VisualizerNodeType.Subscription;
|
68
|
+
case SignalType.Async:
|
69
|
+
return VisualizerNodeType.AsyncComputed;
|
70
|
+
case SignalType.Watcher:
|
71
|
+
return VisualizerNodeType.Watcher;
|
72
|
+
}
|
73
|
+
}
|
74
|
+
|
75
|
+
interface InternalSignalOptions<T> extends SignalOptions<T, unknown[]> {
|
76
|
+
equals: SignalEquals<T>;
|
77
|
+
id: string;
|
78
|
+
subscribers?: ((value: T) => void)[];
|
79
|
+
}
|
80
|
+
|
71
81
|
export class ComputedSignal<T> {
|
72
|
-
_id = ID++;
|
73
82
|
_type: SignalType;
|
74
83
|
|
75
84
|
_deps = new Map<ComputedSignal<any>, Link>();
|
@@ -81,21 +90,20 @@ export class ComputedSignal<T> {
|
|
81
90
|
_computedCount: number = 0;
|
82
91
|
_connectedCount: number = 0;
|
83
92
|
_currentValue: T | AsyncResult<T> | undefined;
|
84
|
-
_compute: SignalCompute<T> | SignalAsyncCompute<T> | SignalSubscribe<T
|
93
|
+
_compute: SignalCompute<T> | SignalAsyncCompute<T> | SignalSubscribe<T>;
|
85
94
|
|
86
|
-
|
95
|
+
_opts: InternalSignalOptions<T>;
|
87
96
|
_ref: WeakRef<ComputedSignal<T>> = new WeakRef(this);
|
88
97
|
|
89
98
|
constructor(
|
90
99
|
type: SignalType,
|
91
|
-
compute: SignalCompute<T> | SignalAsyncCompute<T> | SignalSubscribe<T
|
92
|
-
|
100
|
+
compute: SignalCompute<T> | SignalAsyncCompute<T> | SignalSubscribe<T>,
|
101
|
+
opts: InternalSignalOptions<T>,
|
93
102
|
initValue?: T,
|
94
103
|
) {
|
95
104
|
this._type = type;
|
96
105
|
this._compute = compute;
|
97
|
-
this.
|
98
|
-
this._connectedCount = type === SignalType.Watcher ? 1 : 0;
|
106
|
+
this._opts = opts;
|
99
107
|
|
100
108
|
this._currentValue =
|
101
109
|
type !== SignalType.Async
|
@@ -122,6 +130,11 @@ export class ComputedSignal<T> {
|
|
122
130
|
);
|
123
131
|
}
|
124
132
|
|
133
|
+
TRACER?.emit({
|
134
|
+
type: TracerEventType.StartLoading,
|
135
|
+
id: CURRENT_CONSUMER._opts.id,
|
136
|
+
});
|
137
|
+
|
125
138
|
const value = this._currentValue as AsyncResult<T>;
|
126
139
|
|
127
140
|
if (value.isPending) {
|
@@ -144,6 +157,17 @@ export class ComputedSignal<T> {
|
|
144
157
|
const { _deps: deps, _computedCount: computedCount, _connectedCount: connectedCount } = CURRENT_CONSUMER;
|
145
158
|
const prevLink = deps.get(this);
|
146
159
|
|
160
|
+
if (prevLink === undefined) {
|
161
|
+
TRACER?.emit({
|
162
|
+
type: TracerEventType.Connected,
|
163
|
+
id: CURRENT_CONSUMER._opts.id,
|
164
|
+
childId: this._opts.id,
|
165
|
+
name: this._opts.desc,
|
166
|
+
params: this._opts.params,
|
167
|
+
nodeType: signalTypeToVisualizerType(this._type),
|
168
|
+
});
|
169
|
+
}
|
170
|
+
|
147
171
|
const ord = CURRENT_ORD++;
|
148
172
|
|
149
173
|
this._check(!prevLink && connectedCount > 0);
|
@@ -164,7 +188,6 @@ export class ComputedSignal<T> {
|
|
164
188
|
prevLink.ord = ord;
|
165
189
|
prevLink.version = this._version;
|
166
190
|
prevLink.consumedAt = computedCount;
|
167
|
-
// prevLink.nextDirty = undefined;
|
168
191
|
this._subs.add(prevLink);
|
169
192
|
}
|
170
193
|
} else {
|
@@ -174,8 +197,7 @@ export class ComputedSignal<T> {
|
|
174
197
|
return this._currentValue!;
|
175
198
|
}
|
176
199
|
|
177
|
-
_check(shouldWatch = false): number {
|
178
|
-
// COUNTS.checks++;
|
200
|
+
_check(shouldWatch = false, connectCount = 1, immediate = false): number {
|
179
201
|
let state = this._state;
|
180
202
|
let connectedCount = this._connectedCount;
|
181
203
|
|
@@ -183,7 +205,7 @@ export class ComputedSignal<T> {
|
|
183
205
|
const shouldConnect = shouldWatch && !wasConnected;
|
184
206
|
|
185
207
|
if (shouldWatch) {
|
186
|
-
this._connectedCount = connectedCount = connectedCount +
|
208
|
+
this._connectedCount = connectedCount = connectedCount + connectCount;
|
187
209
|
}
|
188
210
|
|
189
211
|
if (shouldConnect) {
|
@@ -219,7 +241,7 @@ export class ComputedSignal<T> {
|
|
219
241
|
}
|
220
242
|
|
221
243
|
if (state === SignalState.Dirty) {
|
222
|
-
this._run(wasConnected, shouldConnect);
|
244
|
+
this._run(wasConnected, shouldConnect, immediate);
|
223
245
|
} else {
|
224
246
|
this._resetDirty();
|
225
247
|
}
|
@@ -230,7 +252,12 @@ export class ComputedSignal<T> {
|
|
230
252
|
return this._version;
|
231
253
|
}
|
232
254
|
|
233
|
-
_run(wasConnected: boolean, shouldConnect: boolean) {
|
255
|
+
_run(wasConnected: boolean, shouldConnect: boolean, immediate = false) {
|
256
|
+
TRACER?.emit({
|
257
|
+
type: TracerEventType.StartUpdate,
|
258
|
+
id: this._opts.id,
|
259
|
+
});
|
260
|
+
|
234
261
|
const { _type: type } = this;
|
235
262
|
|
236
263
|
const prevConsumer = CURRENT_CONSUMER;
|
@@ -243,13 +270,15 @@ export class ComputedSignal<T> {
|
|
243
270
|
|
244
271
|
switch (type) {
|
245
272
|
case SignalType.Computed: {
|
246
|
-
const
|
273
|
+
const version = this._version;
|
274
|
+
const prevValue = this._currentValue as T | undefined;
|
247
275
|
const nextValue = (this._compute as SignalCompute<T>)(prevValue);
|
248
276
|
|
249
|
-
if (!this.
|
277
|
+
if (version === 0 || !this._opts.equals(prevValue!, nextValue)) {
|
250
278
|
this._currentValue = nextValue;
|
251
|
-
this._version
|
279
|
+
this._version = version + 1;
|
252
280
|
}
|
281
|
+
|
253
282
|
break;
|
254
283
|
}
|
255
284
|
|
@@ -292,34 +321,47 @@ export class ComputedSignal<T> {
|
|
292
321
|
} else if (nextValue instanceof Promise) {
|
293
322
|
const currentVersion = ++this._version;
|
294
323
|
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
}
|
324
|
+
TRACER?.emit({
|
325
|
+
type: TracerEventType.StartLoading,
|
326
|
+
id: this._opts.id,
|
327
|
+
});
|
300
328
|
|
301
|
-
|
302
|
-
|
303
|
-
|
329
|
+
nextValue = nextValue
|
330
|
+
.then(
|
331
|
+
result => {
|
332
|
+
if (currentVersion !== this._version) {
|
333
|
+
return;
|
334
|
+
}
|
304
335
|
|
305
|
-
|
306
|
-
|
336
|
+
value.result = result;
|
337
|
+
value.isReady = true;
|
338
|
+
value.didResolve = true;
|
307
339
|
|
308
|
-
|
309
|
-
|
310
|
-
},
|
311
|
-
error => {
|
312
|
-
if (currentVersion !== this._version || error === WAITING) {
|
313
|
-
return;
|
314
|
-
}
|
340
|
+
value.isPending = false;
|
341
|
+
value.isSuccess = true;
|
315
342
|
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
343
|
+
this._version++;
|
344
|
+
scheduleDirty(this);
|
345
|
+
},
|
346
|
+
error => {
|
347
|
+
if (currentVersion !== this._version || error === WAITING) {
|
348
|
+
return;
|
349
|
+
}
|
350
|
+
|
351
|
+
value.error = error;
|
352
|
+
value.isPending = false;
|
353
|
+
value.isError = true;
|
354
|
+
this._version++;
|
355
|
+
scheduleDirty(this);
|
356
|
+
},
|
357
|
+
)
|
358
|
+
.finally(() => {
|
359
|
+
TRACER?.emit({
|
360
|
+
type: TracerEventType.EndLoading,
|
361
|
+
id: this._opts.id,
|
362
|
+
value: value,
|
363
|
+
});
|
364
|
+
});
|
323
365
|
|
324
366
|
ACTIVE_ASYNCS.set(this, nextValue);
|
325
367
|
|
@@ -334,6 +376,12 @@ export class ComputedSignal<T> {
|
|
334
376
|
value.isError = false;
|
335
377
|
|
336
378
|
this._version++;
|
379
|
+
|
380
|
+
TRACER?.emit({
|
381
|
+
type: TracerEventType.EndLoading,
|
382
|
+
id: this._opts.id,
|
383
|
+
value: value,
|
384
|
+
});
|
337
385
|
}
|
338
386
|
|
339
387
|
break;
|
@@ -344,12 +392,27 @@ export class ComputedSignal<T> {
|
|
344
392
|
const subscription = (this._compute as SignalSubscribe<T>)(
|
345
393
|
() => this._currentValue as T,
|
346
394
|
value => {
|
347
|
-
|
395
|
+
const version = this._version;
|
396
|
+
|
397
|
+
if (version !== 0 && this._opts.equals(value, this._currentValue as T)) {
|
348
398
|
return;
|
349
399
|
}
|
400
|
+
|
401
|
+
TRACER?.emit({
|
402
|
+
type: TracerEventType.StartUpdate,
|
403
|
+
id: this._opts.id,
|
404
|
+
});
|
405
|
+
|
350
406
|
this._currentValue = value;
|
351
|
-
this._version
|
407
|
+
this._version = version + 1;
|
352
408
|
this._dirtyConsumers();
|
409
|
+
|
410
|
+
TRACER?.emit({
|
411
|
+
type: TracerEventType.EndUpdate,
|
412
|
+
id: this._opts.id,
|
413
|
+
value: this._currentValue,
|
414
|
+
preserveChildren: true,
|
415
|
+
});
|
353
416
|
},
|
354
417
|
);
|
355
418
|
SUBSCRIPTIONS.set(this, subscription);
|
@@ -363,23 +426,52 @@ export class ComputedSignal<T> {
|
|
363
426
|
}
|
364
427
|
|
365
428
|
default: {
|
366
|
-
|
429
|
+
const version = this._version;
|
430
|
+
const prevValue = this._currentValue as T | undefined;
|
431
|
+
const nextValue = (this._compute as SignalCompute<T>)(prevValue);
|
432
|
+
|
433
|
+
if (version === 0 || !this._opts.equals(prevValue!, nextValue)) {
|
434
|
+
this._currentValue = nextValue;
|
435
|
+
this._version = version + 1;
|
436
|
+
|
437
|
+
if (immediate) {
|
438
|
+
this._runEffects();
|
439
|
+
} else {
|
440
|
+
scheduleEffect(this);
|
441
|
+
}
|
442
|
+
}
|
443
|
+
|
444
|
+
break;
|
367
445
|
}
|
368
446
|
}
|
369
447
|
} finally {
|
370
|
-
|
448
|
+
TRACER?.emit({
|
449
|
+
type: TracerEventType.EndUpdate,
|
450
|
+
id: this._opts.id,
|
451
|
+
value: this._currentValue,
|
452
|
+
});
|
371
453
|
|
372
|
-
|
373
|
-
|
454
|
+
if (this._type !== SignalType.Watcher) {
|
455
|
+
const deps = this._deps;
|
374
456
|
|
375
|
-
const
|
457
|
+
for (const link of deps.values()) {
|
458
|
+
if (link.consumedAt === this._computedCount) continue;
|
376
459
|
|
377
|
-
|
378
|
-
scheduleDisconnect(dep);
|
379
|
-
}
|
460
|
+
const dep = link.dep;
|
380
461
|
|
381
|
-
|
382
|
-
|
462
|
+
if (wasConnected) {
|
463
|
+
scheduleDisconnect(dep);
|
464
|
+
}
|
465
|
+
|
466
|
+
TRACER?.emit({
|
467
|
+
type: TracerEventType.Disconnected,
|
468
|
+
id: this._opts.id,
|
469
|
+
childId: dep._opts.id,
|
470
|
+
});
|
471
|
+
|
472
|
+
deps.delete(dep);
|
473
|
+
dep._subs.delete(link);
|
474
|
+
}
|
383
475
|
}
|
384
476
|
|
385
477
|
CURRENT_CONSUMER = prevConsumer;
|
@@ -388,10 +480,8 @@ export class ComputedSignal<T> {
|
|
388
480
|
|
389
481
|
_resetDirty() {
|
390
482
|
let dirty = this._dirtyDep;
|
391
|
-
// COUNTS.dirtyResetIterations++;
|
392
483
|
|
393
484
|
while (dirty !== undefined) {
|
394
|
-
// COUNTS.dirtyResetIterations++;
|
395
485
|
dirty.dep._subs.add(dirty);
|
396
486
|
|
397
487
|
let nextDirty = dirty.nextDirty;
|
@@ -430,7 +520,6 @@ export class ComputedSignal<T> {
|
|
430
520
|
} else {
|
431
521
|
let nextDirty = dirty!.nextDirty;
|
432
522
|
while (nextDirty !== undefined && nextDirty!.ord < ord) {
|
433
|
-
// COUNTS.dirtyInsertIterations++;
|
434
523
|
dirty = nextDirty;
|
435
524
|
nextDirty = dirty.nextDirty;
|
436
525
|
}
|
@@ -475,45 +564,63 @@ export class ComputedSignal<T> {
|
|
475
564
|
dep._disconnect();
|
476
565
|
}
|
477
566
|
}
|
478
|
-
}
|
479
567
|
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
}
|
568
|
+
_runEffects() {
|
569
|
+
for (const subscriber of this._opts.subscribers!) {
|
570
|
+
subscriber(this._currentValue as T);
|
571
|
+
}
|
572
|
+
}
|
484
573
|
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
isSuccess: boolean;
|
492
|
-
didResolve: boolean;
|
493
|
-
}
|
574
|
+
addListener(subscriber: (value: T) => void, opts?: WatcherListenerOptions) {
|
575
|
+
const subscribers = this._opts.subscribers!;
|
576
|
+
const index = subscribers.indexOf(subscriber);
|
577
|
+
|
578
|
+
if (index === -1) {
|
579
|
+
subscribers.push(subscriber);
|
494
580
|
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
581
|
+
if (opts?.immediate) {
|
582
|
+
this._check(true, 1, true);
|
583
|
+
} else {
|
584
|
+
scheduleConnect(this);
|
585
|
+
}
|
586
|
+
}
|
587
|
+
|
588
|
+
return () => {
|
589
|
+
const index = subscribers.indexOf(subscriber);
|
590
|
+
|
591
|
+
if (index !== -1) {
|
592
|
+
subscribers.splice(index, 1);
|
593
|
+
scheduleDisconnect(this);
|
594
|
+
}
|
595
|
+
};
|
596
|
+
}
|
503
597
|
}
|
504
598
|
|
505
|
-
|
599
|
+
let STATE_ID = 0;
|
506
600
|
|
507
|
-
class StateSignal<T> implements StateSignal<T> {
|
508
|
-
|
601
|
+
export class StateSignal<T> implements StateSignal<T> {
|
602
|
+
_subs: WeakRef<ComputedSignal<unknown>>[] = [];
|
603
|
+
_desc: string;
|
509
604
|
|
510
605
|
constructor(
|
511
606
|
private _value: T,
|
512
607
|
private _equals: SignalEquals<T> = (a, b) => a === b,
|
513
|
-
|
608
|
+
desc: string = 'state',
|
609
|
+
) {
|
610
|
+
this._desc = `${desc}${STATE_ID++}`;
|
611
|
+
}
|
514
612
|
|
515
613
|
get(): T {
|
516
614
|
if (CURRENT_CONSUMER !== undefined) {
|
615
|
+
TRACER?.emit({
|
616
|
+
type: TracerEventType.ConsumeState,
|
617
|
+
id: CURRENT_CONSUMER._opts.id,
|
618
|
+
childId: this._desc,
|
619
|
+
value: this._value,
|
620
|
+
setValue: (value: unknown) => {
|
621
|
+
this.set(value as T);
|
622
|
+
},
|
623
|
+
});
|
517
624
|
this._subs.push(CURRENT_CONSUMER._ref);
|
518
625
|
}
|
519
626
|
|
@@ -551,64 +658,132 @@ class StateSignal<T> implements StateSignal<T> {
|
|
551
658
|
}
|
552
659
|
}
|
553
660
|
|
554
|
-
|
555
|
-
|
661
|
+
let UNKNOWN_SIGNAL_ID = 0;
|
662
|
+
|
663
|
+
const normalizeOpts = <T>(
|
664
|
+
opts?: SignalOptions<T, unknown[]> & { subscribers?: ((value: T) => void)[] },
|
665
|
+
): InternalSignalOptions<T> => {
|
666
|
+
return {
|
667
|
+
equals: opts?.equals === false ? FALSE_EQUALS : (opts?.equals ?? ((a, b) => a === b)),
|
668
|
+
id: opts?.id ?? `unknownSignal${UNKNOWN_SIGNAL_ID++}`,
|
669
|
+
desc: opts?.desc,
|
670
|
+
params: opts?.params,
|
671
|
+
};
|
672
|
+
};
|
673
|
+
|
674
|
+
export function createStateSignal<T>(
|
675
|
+
initialValue: T,
|
676
|
+
opts?: Omit<SignalOptions<T, unknown[]>, 'paramKey'>,
|
677
|
+
): StateSignal<T> {
|
678
|
+
const equals = opts?.equals === false ? FALSE_EQUALS : (opts?.equals ?? ((a, b) => a === b));
|
679
|
+
|
680
|
+
return new StateSignal(initialValue, equals, opts?.desc);
|
556
681
|
}
|
557
682
|
|
558
|
-
export function
|
559
|
-
|
683
|
+
export function createComputedSignal<T>(
|
684
|
+
compute: (prev: T | undefined) => T,
|
685
|
+
opts?: SignalOptions<T, unknown[]>,
|
686
|
+
): Signal<T> {
|
687
|
+
return new ComputedSignal(SignalType.Computed, compute, normalizeOpts(opts)) as Signal<T>;
|
560
688
|
}
|
561
689
|
|
562
|
-
export function
|
563
|
-
|
690
|
+
export function createAsyncComputedSignal<T>(
|
691
|
+
compute: (prev: T | undefined) => Promise<T>,
|
692
|
+
opts?: SignalOptions<T, unknown[]>,
|
693
|
+
): AsyncSignal<T>;
|
694
|
+
export function createAsyncComputedSignal<T>(
|
564
695
|
compute: (prev: T | undefined) => Promise<T>,
|
565
|
-
opts: SignalOptionsWithInit<T>,
|
696
|
+
opts: SignalOptionsWithInit<T, unknown[]>,
|
566
697
|
): AsyncSignal<T>;
|
567
|
-
export function
|
698
|
+
export function createAsyncComputedSignal<T>(
|
568
699
|
compute: (prev: T | undefined) => Promise<T>,
|
569
|
-
opts?: Partial<SignalOptionsWithInit<T>>,
|
700
|
+
opts?: Partial<SignalOptionsWithInit<T, unknown[]>>,
|
570
701
|
): AsyncSignal<T> {
|
571
|
-
return new ComputedSignal(SignalType.Async, compute, opts
|
702
|
+
return new ComputedSignal(SignalType.Async, compute, normalizeOpts(opts), opts?.initValue) as AsyncSignal<T>;
|
572
703
|
}
|
573
704
|
|
574
|
-
export function
|
575
|
-
|
576
|
-
|
577
|
-
|
705
|
+
export function createSubscriptionSignal<T>(
|
706
|
+
subscribe: SignalSubscribe<T>,
|
707
|
+
opts?: SignalOptions<T, unknown[]>,
|
708
|
+
): Signal<T | undefined>;
|
709
|
+
export function createSubscriptionSignal<T>(
|
710
|
+
subscribe: SignalSubscribe<T>,
|
711
|
+
opts: SignalOptionsWithInit<T, unknown[]>,
|
712
|
+
): Signal<T>;
|
713
|
+
export function createSubscriptionSignal<T>(
|
714
|
+
subscribe: SignalSubscribe<T>,
|
715
|
+
opts?: Partial<SignalOptionsWithInit<T, unknown[]>>,
|
716
|
+
): Signal<T> {
|
717
|
+
return new ComputedSignal(SignalType.Subscription, subscribe, normalizeOpts(opts), opts?.initValue) as Signal<T>;
|
578
718
|
}
|
579
719
|
|
580
|
-
export
|
581
|
-
|
582
|
-
|
583
|
-
|
720
|
+
export function createAsyncTaskSignal<T, Args extends unknown[]>(
|
721
|
+
fn: (...args: Args) => Promise<T>,
|
722
|
+
): Signal<AsyncTask<T, Args>> {
|
723
|
+
let currentPromise: Promise<T> | undefined;
|
724
|
+
|
725
|
+
const task: AsyncTask<T, Args> = {
|
726
|
+
result: undefined,
|
727
|
+
error: undefined,
|
728
|
+
isPending: false,
|
729
|
+
isSuccess: false,
|
730
|
+
isError: false,
|
731
|
+
isReady: false,
|
732
|
+
|
733
|
+
async run(...params) {
|
734
|
+
if (!task.isPending) {
|
735
|
+
currentPromise = run(...params);
|
736
|
+
}
|
584
737
|
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
fn();
|
738
|
+
return currentPromise!;
|
739
|
+
},
|
740
|
+
};
|
589
741
|
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
});
|
742
|
+
const run = async (...params: Args) => {
|
743
|
+
try {
|
744
|
+
task.isPending = true;
|
745
|
+
task.isError = false;
|
746
|
+
task.isSuccess = false;
|
596
747
|
|
597
|
-
|
748
|
+
signal.set(task);
|
598
749
|
|
599
|
-
|
600
|
-
disconnect() {
|
601
|
-
scheduleDisconnect(watcher);
|
602
|
-
},
|
750
|
+
const result = await fn(...params);
|
603
751
|
|
604
|
-
|
605
|
-
|
752
|
+
task.result = result;
|
753
|
+
task.isSuccess = true;
|
754
|
+
task.isReady = true;
|
606
755
|
|
607
|
-
return
|
608
|
-
|
609
|
-
|
610
|
-
|
756
|
+
return result;
|
757
|
+
} catch (error) {
|
758
|
+
task.error = error;
|
759
|
+
task.isError = true;
|
760
|
+
|
761
|
+
throw error;
|
762
|
+
} finally {
|
763
|
+
task.isPending = false;
|
764
|
+
signal.set(task);
|
765
|
+
}
|
611
766
|
};
|
767
|
+
|
768
|
+
const signal = createStateSignal<AsyncTask<T, Args>>(task, { equals: false });
|
769
|
+
|
770
|
+
return signal;
|
771
|
+
}
|
772
|
+
|
773
|
+
export function createWatcherSignal<T>(fn: (prev: T | undefined) => T, opts?: SignalOptions<T, unknown[]>): Watcher<T> {
|
774
|
+
const normalizedOpts = normalizeOpts({
|
775
|
+
equals: FALSE_EQUALS,
|
776
|
+
subscribers: [],
|
777
|
+
...opts,
|
778
|
+
});
|
779
|
+
|
780
|
+
normalizedOpts.subscribers = [];
|
781
|
+
|
782
|
+
return new ComputedSignal(SignalType.Watcher, fn, normalizedOpts);
|
783
|
+
}
|
784
|
+
|
785
|
+
export function getCurrentConsumer(): ComputedSignal<any> | undefined {
|
786
|
+
return CURRENT_CONSUMER;
|
612
787
|
}
|
613
788
|
|
614
789
|
export function isTracking(): boolean {
|
@@ -620,7 +795,6 @@ export function untrack<T = void>(fn: () => T): T {
|
|
620
795
|
|
621
796
|
try {
|
622
797
|
CURRENT_CONSUMER = undefined;
|
623
|
-
// LAST_CONSUMED = undefined;
|
624
798
|
|
625
799
|
return fn();
|
626
800
|
} finally {
|