signalium 0.2.1 → 0.2.2
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/CHANGELOG.md +6 -0
- package/dist/signals.d.ts +1 -0
- package/dist/signals.js +49 -2
- package/package.json +1 -1
- package/src/__tests__/async.test.ts +1 -1
- package/src/signals.ts +61 -4
package/CHANGELOG.md
CHANGED
package/dist/signals.d.ts
CHANGED
package/dist/signals.js
CHANGED
@@ -10,6 +10,41 @@ const SUBSCRIPTIONS = new WeakMap();
|
|
10
10
|
const ACTIVE_ASYNCS = new WeakMap();
|
11
11
|
const WAITING = Symbol();
|
12
12
|
let linkPool;
|
13
|
+
const checkForCircularLinks = (link) => {
|
14
|
+
if (!link)
|
15
|
+
return;
|
16
|
+
for (const key of ['nextDep', 'nextSub', 'prevSub', 'nextDirty']) {
|
17
|
+
let currentLink = link?.[key];
|
18
|
+
while (currentLink !== undefined) {
|
19
|
+
if (currentLink === link) {
|
20
|
+
throw new Error(`Circular link detected via ${key}. This is a bug, please report it to the Signalium maintainers.`);
|
21
|
+
}
|
22
|
+
currentLink = currentLink[key];
|
23
|
+
}
|
24
|
+
}
|
25
|
+
};
|
26
|
+
const typeToString = (type) => {
|
27
|
+
switch (type) {
|
28
|
+
case 0 /* SignalType.Computed */:
|
29
|
+
return 'Computed';
|
30
|
+
case 1 /* SignalType.Subscription */:
|
31
|
+
return 'Subscription';
|
32
|
+
case 2 /* SignalType.Async */:
|
33
|
+
return 'Async';
|
34
|
+
case 3 /* SignalType.Watcher */:
|
35
|
+
return 'Watcher';
|
36
|
+
}
|
37
|
+
};
|
38
|
+
const printComputed = (computed) => {
|
39
|
+
const type = typeToString(computed._type);
|
40
|
+
return `ComputedSignal<${type}:${computed.id}>`;
|
41
|
+
};
|
42
|
+
const printLink = (link) => {
|
43
|
+
const sub = link.sub.deref();
|
44
|
+
const subStr = sub === undefined ? 'undefined' : printComputed(sub);
|
45
|
+
const depStr = printComputed(link.dep);
|
46
|
+
return `Link<${link.id}> sub(${subStr}) -> dep(${depStr})`;
|
47
|
+
};
|
13
48
|
function linkNewDep(dep, sub, nextDep, depsTail, ord) {
|
14
49
|
let newLink;
|
15
50
|
if (linkPool !== undefined) {
|
@@ -22,6 +57,7 @@ function linkNewDep(dep, sub, nextDep, depsTail, ord) {
|
|
22
57
|
}
|
23
58
|
else {
|
24
59
|
newLink = {
|
60
|
+
id: id++,
|
25
61
|
dep,
|
26
62
|
sub: sub._ref,
|
27
63
|
ord,
|
@@ -175,6 +211,8 @@ export class ComputedSignal {
|
|
175
211
|
}
|
176
212
|
this._check(CURRENT_IS_WATCHED && !prevTracked);
|
177
213
|
CURRENT_DEP_TAIL = newLink ?? linkNewDep(this, CURRENT_CONSUMER, nextDep, CURRENT_DEP_TAIL, ord);
|
214
|
+
if (process.env.NODE_ENV !== 'production')
|
215
|
+
checkForCircularLinks(CURRENT_DEP_TAIL);
|
178
216
|
CURRENT_DEP_TAIL.version = this._version;
|
179
217
|
CURRENT_SEEN.add(this);
|
180
218
|
}
|
@@ -197,6 +235,8 @@ export class ComputedSignal {
|
|
197
235
|
}
|
198
236
|
else {
|
199
237
|
let link = this._deps;
|
238
|
+
if (process.env.NODE_ENV !== 'production')
|
239
|
+
checkForCircularLinks(link);
|
200
240
|
while (link !== undefined) {
|
201
241
|
const dep = link.dep;
|
202
242
|
if (link.version !== dep._check(true)) {
|
@@ -206,7 +246,6 @@ export class ComputedSignal {
|
|
206
246
|
link = link.nextDep;
|
207
247
|
}
|
208
248
|
}
|
209
|
-
this._resetDirty();
|
210
249
|
}
|
211
250
|
if (state === 0 /* SignalState.Clean */) {
|
212
251
|
return this._version;
|
@@ -269,8 +308,8 @@ export class ComputedSignal {
|
|
269
308
|
value.isPending = false;
|
270
309
|
value.isError = true;
|
271
310
|
this._version++;
|
311
|
+
break;
|
272
312
|
}
|
273
|
-
break;
|
274
313
|
}
|
275
314
|
if (CURRENT_IS_WAITING) {
|
276
315
|
if (!value.isPending) {
|
@@ -372,13 +411,19 @@ export class ComputedSignal {
|
|
372
411
|
}
|
373
412
|
else {
|
374
413
|
dirty.nextSub = oldHead;
|
414
|
+
dirty.prevSub = undefined;
|
375
415
|
oldHead.prevSub = dirty;
|
376
416
|
dep._subs = dirty;
|
377
417
|
}
|
418
|
+
if (process.env.NODE_ENV !== 'production') {
|
419
|
+
checkForCircularLinks(this._dirtyDep);
|
420
|
+
}
|
378
421
|
let nextDirty = dirty.nextDirty;
|
379
422
|
dirty.nextDirty = undefined;
|
380
423
|
dirty = nextDirty;
|
381
424
|
}
|
425
|
+
if (process.env.NODE_ENV !== 'production')
|
426
|
+
checkForCircularLinks(this._dirtyDep);
|
382
427
|
}
|
383
428
|
_dirty() {
|
384
429
|
if (this._type === 1 /* SignalType.Subscription */) {
|
@@ -397,6 +442,8 @@ export class ComputedSignal {
|
|
397
442
|
}
|
398
443
|
_dirtyConsumers() {
|
399
444
|
let link = this._subs;
|
445
|
+
if (process.env.NODE_ENV !== 'production')
|
446
|
+
checkForCircularLinks(link);
|
400
447
|
while (link !== undefined) {
|
401
448
|
const consumer = link.sub.deref();
|
402
449
|
if (consumer === undefined) {
|
package/package.json
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import { describe, expect, test } from 'vitest';
|
2
|
-
import { state,
|
2
|
+
import { state, asyncComputed } from './utils/instrumented.js';
|
3
3
|
import { AsyncResult } from '../signals';
|
4
4
|
|
5
5
|
const sleep = (ms = 0) => new Promise(r => setTimeout(r, ms));
|
package/src/signals.ts
CHANGED
@@ -61,6 +61,7 @@ const enum SignalState {
|
|
61
61
|
const WAITING = Symbol();
|
62
62
|
|
63
63
|
interface Link {
|
64
|
+
id: number;
|
64
65
|
sub: WeakRef<ComputedSignal<any>>;
|
65
66
|
dep: ComputedSignal<any>;
|
66
67
|
ord: number;
|
@@ -75,6 +76,51 @@ interface Link {
|
|
75
76
|
|
76
77
|
let linkPool: Link | undefined;
|
77
78
|
|
79
|
+
const checkForCircularLinks = (link: Link | undefined) => {
|
80
|
+
if (!link) return;
|
81
|
+
|
82
|
+
for (const key of ['nextDep', 'nextSub', 'prevSub', 'nextDirty'] as const) {
|
83
|
+
let currentLink: Link | undefined = link?.[key];
|
84
|
+
|
85
|
+
while (currentLink !== undefined) {
|
86
|
+
if (currentLink === link) {
|
87
|
+
throw new Error(
|
88
|
+
`Circular link detected via ${key}. This is a bug, please report it to the Signalium maintainers.`,
|
89
|
+
);
|
90
|
+
}
|
91
|
+
|
92
|
+
currentLink = currentLink[key];
|
93
|
+
}
|
94
|
+
}
|
95
|
+
};
|
96
|
+
|
97
|
+
const typeToString = (type: SignalType) => {
|
98
|
+
switch (type) {
|
99
|
+
case SignalType.Computed:
|
100
|
+
return 'Computed';
|
101
|
+
case SignalType.Subscription:
|
102
|
+
return 'Subscription';
|
103
|
+
case SignalType.Async:
|
104
|
+
return 'Async';
|
105
|
+
case SignalType.Watcher:
|
106
|
+
return 'Watcher';
|
107
|
+
}
|
108
|
+
};
|
109
|
+
|
110
|
+
const printComputed = (computed: ComputedSignal<any>) => {
|
111
|
+
const type = typeToString(computed._type);
|
112
|
+
|
113
|
+
return `ComputedSignal<${type}:${computed.id}>`;
|
114
|
+
};
|
115
|
+
|
116
|
+
const printLink = (link: Link) => {
|
117
|
+
const sub = link.sub.deref();
|
118
|
+
const subStr = sub === undefined ? 'undefined' : printComputed(sub);
|
119
|
+
const depStr = printComputed(link.dep);
|
120
|
+
|
121
|
+
return `Link<${link.id}> sub(${subStr}) -> dep(${depStr})`;
|
122
|
+
};
|
123
|
+
|
78
124
|
function linkNewDep(
|
79
125
|
dep: ComputedSignal<any>,
|
80
126
|
sub: ComputedSignal<any>,
|
@@ -93,6 +139,7 @@ function linkNewDep(
|
|
93
139
|
newLink.ord = ord;
|
94
140
|
} else {
|
95
141
|
newLink = {
|
142
|
+
id: id++,
|
96
143
|
dep,
|
97
144
|
sub: sub._ref,
|
98
145
|
ord,
|
@@ -288,6 +335,8 @@ export class ComputedSignal<T> {
|
|
288
335
|
|
289
336
|
CURRENT_DEP_TAIL = newLink ?? linkNewDep(this, CURRENT_CONSUMER, nextDep, CURRENT_DEP_TAIL, ord);
|
290
337
|
|
338
|
+
if (process.env.NODE_ENV !== 'production') checkForCircularLinks(CURRENT_DEP_TAIL);
|
339
|
+
|
291
340
|
CURRENT_DEP_TAIL.version = this._version;
|
292
341
|
CURRENT_SEEN!.add(this);
|
293
342
|
} else {
|
@@ -314,6 +363,8 @@ export class ComputedSignal<T> {
|
|
314
363
|
} else {
|
315
364
|
let link = this._deps;
|
316
365
|
|
366
|
+
if (process.env.NODE_ENV !== 'production') checkForCircularLinks(link);
|
367
|
+
|
317
368
|
while (link !== undefined) {
|
318
369
|
const dep = link.dep;
|
319
370
|
|
@@ -325,8 +376,6 @@ export class ComputedSignal<T> {
|
|
325
376
|
link = link.nextDep;
|
326
377
|
}
|
327
378
|
}
|
328
|
-
|
329
|
-
this._resetDirty();
|
330
379
|
}
|
331
380
|
|
332
381
|
if (state === SignalState.Clean) {
|
@@ -403,9 +452,8 @@ export class ComputedSignal<T> {
|
|
403
452
|
value.isPending = false;
|
404
453
|
value.isError = true;
|
405
454
|
this._version++;
|
455
|
+
break;
|
406
456
|
}
|
407
|
-
|
408
|
-
break;
|
409
457
|
}
|
410
458
|
|
411
459
|
if (CURRENT_IS_WAITING) {
|
@@ -527,14 +575,21 @@ export class ComputedSignal<T> {
|
|
527
575
|
dirty.prevSub = undefined;
|
528
576
|
} else {
|
529
577
|
dirty.nextSub = oldHead;
|
578
|
+
dirty.prevSub = undefined;
|
530
579
|
oldHead.prevSub = dirty;
|
531
580
|
dep._subs = dirty;
|
532
581
|
}
|
533
582
|
|
583
|
+
if (process.env.NODE_ENV !== 'production') {
|
584
|
+
checkForCircularLinks(this._dirtyDep);
|
585
|
+
}
|
586
|
+
|
534
587
|
let nextDirty = dirty.nextDirty;
|
535
588
|
dirty.nextDirty = undefined;
|
536
589
|
dirty = nextDirty;
|
537
590
|
}
|
591
|
+
|
592
|
+
if (process.env.NODE_ENV !== 'production') checkForCircularLinks(this._dirtyDep);
|
538
593
|
}
|
539
594
|
|
540
595
|
_dirty() {
|
@@ -556,6 +611,8 @@ export class ComputedSignal<T> {
|
|
556
611
|
_dirtyConsumers() {
|
557
612
|
let link = this._subs;
|
558
613
|
|
614
|
+
if (process.env.NODE_ENV !== 'production') checkForCircularLinks(link);
|
615
|
+
|
559
616
|
while (link !== undefined) {
|
560
617
|
const consumer = link.sub.deref();
|
561
618
|
|