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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # signalium
2
2
 
3
+ ## 0.2.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 0376187: Fix a circular ref and add logs to detect circular refs in dev
8
+
3
9
  ## 0.2.1
4
10
 
5
11
  ### Patch Changes
package/dist/signals.d.ts CHANGED
@@ -32,6 +32,7 @@ declare const enum SignalState {
32
32
  Dirty = 2
33
33
  }
34
34
  interface Link {
35
+ id: number;
35
36
  sub: WeakRef<ComputedSignal<any>>;
36
37
  dep: ComputedSignal<any>;
37
38
  ord: number;
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,6 +1,6 @@
1
1
  {
2
2
  "name": "signalium",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "type": "module",
5
5
  "repository": "https://github.com/pzuraq/signalium",
6
6
  "description": "Chain-reactivity at critical mass",
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, test } from 'vitest';
2
- import { state, computed, asyncComputed } from './utils/instrumented.js';
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