@solidjs/signals 2.0.0-beta.8 → 2.0.0-rc.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.
Files changed (95) hide show
  1. package/README.md +13 -9
  2. package/dist/dev.js +6078 -1440
  3. package/dist/node.cjs +7684 -3248
  4. package/dist/prod/affects.js +126 -0
  5. package/dist/prod/boundaries.js +572 -0
  6. package/dist/prod/core/action.js +139 -0
  7. package/dist/prod/core/async.js +553 -0
  8. package/dist/prod/core/constants.js +92 -0
  9. package/dist/prod/core/context.js +67 -0
  10. package/dist/prod/core/core.js +817 -0
  11. package/dist/prod/core/dev.js +3 -0
  12. package/dist/prod/core/effect.js +145 -0
  13. package/dist/prod/core/error.js +68 -0
  14. package/dist/prod/core/external.js +98 -0
  15. package/dist/prod/core/graph.js +104 -0
  16. package/dist/prod/core/heap.js +140 -0
  17. package/dist/prod/core/invariants.js +42 -0
  18. package/dist/prod/core/lanes.js +140 -0
  19. package/dist/prod/core/optimistic.js +245 -0
  20. package/dist/prod/core/owner.js +302 -0
  21. package/dist/prod/core/scheduler.js +749 -0
  22. package/dist/prod/core/verdict.js +369 -0
  23. package/dist/prod/index.js +45 -0
  24. package/dist/prod/map.js +317 -0
  25. package/dist/prod/signals.js +375 -0
  26. package/dist/prod/store/optimistic.js +217 -0
  27. package/dist/prod/store/projection.js +231 -0
  28. package/dist/prod/store/reconcile.js +707 -0
  29. package/dist/prod/store/store.js +1081 -0
  30. package/dist/prod/store/storePath.js +103 -0
  31. package/dist/prod/store/utils.js +328 -0
  32. package/dist/types/affects.d.ts +47 -0
  33. package/dist/types/boundaries.d.ts +93 -11
  34. package/dist/types/core/action.d.ts +63 -0
  35. package/dist/types/core/async.d.ts +16 -1
  36. package/dist/types/core/constants.d.ts +46 -0
  37. package/dist/types/core/context.d.ts +10 -2
  38. package/dist/types/core/core.d.ts +111 -17
  39. package/dist/types/core/dev.d.ts +17 -2
  40. package/dist/types/core/effect.d.ts +1 -2
  41. package/dist/types/core/error.d.ts +33 -0
  42. package/dist/types/core/external.d.ts +0 -11
  43. package/dist/types/core/graph.d.ts +2 -1
  44. package/dist/types/core/heap.d.ts +8 -0
  45. package/dist/types/core/index.d.ts +3 -2
  46. package/dist/types/core/invariants.d.ts +59 -0
  47. package/dist/types/core/lanes.d.ts +2 -0
  48. package/dist/types/core/optimistic.d.ts +6 -0
  49. package/dist/types/core/owner.d.ts +101 -3
  50. package/dist/types/core/scheduler.d.ts +97 -9
  51. package/dist/types/core/types.d.ts +68 -6
  52. package/dist/types/core/verdict.d.ts +2 -0
  53. package/dist/types/index.d.ts +4 -3
  54. package/dist/types/map.d.ts +50 -5
  55. package/dist/types/signals.d.ts +424 -19
  56. package/dist/types/store/index.d.ts +1 -1
  57. package/dist/types/store/optimistic.d.ts +39 -13
  58. package/dist/types/store/projection.d.ts +48 -18
  59. package/dist/types/store/reconcile.d.ts +46 -1
  60. package/dist/types/store/store.d.ts +164 -19
  61. package/dist/types/store/storePath.d.ts +28 -0
  62. package/dist/types/store/utils.d.ts +78 -7
  63. package/dist/types-cjs/affects.d.cts +47 -0
  64. package/dist/types-cjs/boundaries.d.cts +93 -11
  65. package/dist/types-cjs/core/action.d.cts +63 -0
  66. package/dist/types-cjs/core/async.d.cts +16 -1
  67. package/dist/types-cjs/core/constants.d.cts +46 -0
  68. package/dist/types-cjs/core/context.d.cts +10 -2
  69. package/dist/types-cjs/core/core.d.cts +111 -17
  70. package/dist/types-cjs/core/dev.d.cts +17 -2
  71. package/dist/types-cjs/core/effect.d.cts +1 -2
  72. package/dist/types-cjs/core/error.d.cts +33 -0
  73. package/dist/types-cjs/core/external.d.cts +0 -11
  74. package/dist/types-cjs/core/graph.d.cts +2 -1
  75. package/dist/types-cjs/core/heap.d.cts +8 -0
  76. package/dist/types-cjs/core/index.d.cts +3 -2
  77. package/dist/types-cjs/core/invariants.d.cts +59 -0
  78. package/dist/types-cjs/core/lanes.d.cts +2 -0
  79. package/dist/types-cjs/core/optimistic.d.cts +6 -0
  80. package/dist/types-cjs/core/owner.d.cts +101 -3
  81. package/dist/types-cjs/core/scheduler.d.cts +97 -9
  82. package/dist/types-cjs/core/types.d.cts +68 -6
  83. package/dist/types-cjs/core/verdict.d.cts +2 -0
  84. package/dist/types-cjs/index.d.cts +4 -3
  85. package/dist/types-cjs/map.d.cts +50 -5
  86. package/dist/types-cjs/signals.d.cts +424 -19
  87. package/dist/types-cjs/store/index.d.cts +1 -1
  88. package/dist/types-cjs/store/optimistic.d.cts +39 -13
  89. package/dist/types-cjs/store/projection.d.cts +48 -18
  90. package/dist/types-cjs/store/reconcile.d.cts +46 -1
  91. package/dist/types-cjs/store/store.d.cts +164 -19
  92. package/dist/types-cjs/store/storePath.d.cts +28 -0
  93. package/dist/types-cjs/store/utils.d.cts +78 -7
  94. package/package.json +14 -11
  95. package/dist/prod.js +0 -3577
@@ -0,0 +1,3 @@
1
+ const DEV = undefined;
2
+
3
+ export { DEV };
@@ -0,0 +1,145 @@
1
+ import { REACTIVE_DISPOSED, STATUS_ERROR, EFFECT_USER, CONFIG_AUTO_DISPOSE, CONFIG_CHILDREN_FORBIDDEN, EFFECT_TRACKED, STATUS_PENDING, EFFECT_RENDER } from "./constants.js";
2
+
3
+ import { createEffectNode, recompute, computed, staleValues } from "./core.js";
4
+
5
+ import { unwrapStatusError, StatusError } from "./error.js";
6
+
7
+ import { GlobalQueue, haltReactivity } from "./scheduler.js";
8
+
9
+ /**
10
+ * Effects are the leaf nodes of our reactive graph. When their sources change, they are
11
+ * automatically added to the queue of effects to re-execute, which will cause them to fetch their
12
+ * sources and recompute
13
+ */ function effect(t, E, e, R) {
14
+ const r = !!R?.user;
15
+ const f = createEffectNode(t, E, e, r ? EFFECT_USER : EFFECT_RENDER, notifyEffectStatus, R);
16
+ recompute(f, true);
17
+ !R?.defer && (f.Re === EFFECT_USER || R?.schedule ? f.C.enqueue(f.Re, runEffect.bind(null, f)) : runEffect(f));
18
+ }
19
+
20
+ function notifyEffectStatus(t, E) {
21
+ // Use passed values if provided, otherwise read from node
22
+ const e = t !== undefined ? t : this.S;
23
+ const R = E !== undefined ? E : this._;
24
+ if (e & STATUS_ERROR) {
25
+ this.C.notify(this, STATUS_PENDING, 0);
26
+ if (this.Re === EFFECT_USER) {
27
+ // The error handler is the error arm of the effect phase (#2840 ruling):
28
+ // queue it like the effect function. It runs in the same imperative,
29
+ // writable scope, throws escalate the same way, and a held transition
30
+ // (or optimistic lane) defers it exactly as it defers the success arm.
31
+ // No payload is queued — the node already carries `_statusFlags`/`_error`,
32
+ // and the runner dispatches on them, so a recovery before the effect
33
+ // phase takes the success arm instead. Blocked forwards (explicit
34
+ // `status` arg without node-state writes) don't queue: the status
35
+ // re-propagates unblocked at commit.
36
+ if (this.S & STATUS_ERROR) {
37
+ this.Je = true;
38
+ this.C.enqueue(this.Re, this.Xe ??= runEffect.bind(null, this));
39
+ }
40
+ return;
41
+ }
42
+ if (!this.C.notify(this, STATUS_ERROR, STATUS_ERROR)) {
43
+ haltReactivity(unwrapStatusError(R));
44
+ throw R;
45
+ }
46
+ } else if (this.Re === EFFECT_RENDER) {
47
+ this.C.notify(this, STATUS_PENDING | STATUS_ERROR, e, R);
48
+ }
49
+ }
50
+
51
+ function runEffect(t) {
52
+ if (!t.Je || t.se & REACTIVE_DISPOSED) return;
53
+ // Error arm (#2840), user effects only: a compute-phase error that is still
54
+ // the node's settled state at effect time runs the bundle's error handler in
55
+ // this same imperative, writable scope. Unwrap the StatusError used for
56
+ // source tracking — user code gets the error it threw, as boundaries do. No
57
+ // handler: log and keep the system alive (the run was skipped). A handler
58
+ // (or logging) consumes the error; a handler throw falls to the shared
59
+ // catch below and escalates boundary-or-halt like any effect-phase throw.
60
+ // Render effects bypass: their errors route to boundaries synchronously in
61
+ // notifyEffectStatus, and a runner queued by an earlier valueChanged in the
62
+ // same flush must not be hijacked by a later-arriving error status.
63
+ if (t.S & STATUS_ERROR && t.Re === EFFECT_USER) {
64
+ const E = unwrapStatusError(t._);
65
+ t._t = t.Ue;
66
+ t.Je = false;
67
+ try {
68
+ t.Et ? t.Et(E, () => {
69
+ const E = t.Tt;
70
+ t.Tt = undefined;
71
+ E?.();
72
+ }) : console.error(E);
73
+ } catch (E) {
74
+ if (!t.C.notify(t, STATUS_ERROR, STATUS_ERROR)) {
75
+ haltReactivity(E);
76
+ throw E;
77
+ }
78
+ }
79
+ return;
80
+ }
81
+ const E = t.Tt;
82
+ t.Tt = undefined;
83
+ try {
84
+ E?.();
85
+ const e = t.ft(t.Ue, t._t);
86
+ if (false && e !== undefined && typeof e !== "function") ;
87
+ // The final cleanup is invoked by disposeChildren at true disposal.
88
+ t.Tt = e;
89
+ } catch (E) {
90
+ t._ = new StatusError(t, E);
91
+ t.S |= STATUS_ERROR;
92
+ if (!t.C.notify(t, STATUS_ERROR, STATUS_ERROR)) {
93
+ haltReactivity(E);
94
+ throw E;
95
+ }
96
+ } finally {
97
+ t._t = t.Ue;
98
+ t.Je = false;
99
+ }
100
+ }
101
+
102
+ GlobalQueue.et = runEffect;
103
+
104
+ /**
105
+ * Internal tracked effect - bypasses heap, goes directly to effect queue.
106
+ * Runs as a leaf owner: child primitives and onCleanup are forbidden (false throws).
107
+ * Uses stale reads.
108
+ */ function trackedEffect(t, E) {
109
+ const run = () => {
110
+ if (!e.Je || e.se & REACTIVE_DISPOSED) return;
111
+ try {
112
+ e.Je = false;
113
+ recompute(e);
114
+ } finally {}
115
+ };
116
+ const e = computed(() => {
117
+ const E = e.Tt;
118
+ e.Tt = undefined;
119
+ E?.();
120
+ const R = staleValues(t);
121
+ e.Tt = R;
122
+ }, {
123
+ ...E,
124
+ lazy: true
125
+ });
126
+ e.Tt = undefined;
127
+ e.T = e.T & ~CONFIG_AUTO_DISPOSE | CONFIG_CHILDREN_FORBIDDEN;
128
+ e.Je = true;
129
+ e.Re = EFFECT_TRACKED;
130
+ e.i = (t, E) => {
131
+ const R = t !== undefined ? t : e.S;
132
+ if (R & STATUS_ERROR) {
133
+ e.C.notify(e, STATUS_PENDING, 0);
134
+ const t = E !== undefined ? E : e._;
135
+ if (!e.C.notify(e, STATUS_ERROR, STATUS_ERROR)) {
136
+ haltReactivity(unwrapStatusError(t));
137
+ throw t;
138
+ }
139
+ }
140
+ };
141
+ e.Ft = run;
142
+ e.C.enqueue(EFFECT_USER, run);
143
+ }
144
+
145
+ export { effect, trackedEffect };
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Thrown by a tracked read whose value is currently pending (an async memo /
3
+ * `createSignal(asyncFn)` / projection / store derivation that hasn't settled
4
+ * yet). Surfacing through the reactive graph is what suspends the consumer
5
+ * scope — the nearest enclosing `<Loading>` boundary catches the throw and
6
+ * renders its fallback until the source resolves.
7
+ *
8
+ * App code rarely catches this directly; `<Loading>` is the canonical
9
+ * handler. The error type is exposed for advanced cases — e.g. interop layers
10
+ * that bridge Solid's pending-throw protocol to a different async strategy,
11
+ * or tests that want to assert on the suspension shape.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * // Advanced: distinguish "not ready yet" from a real error in custom
16
+ * // boundary plumbing. App code should rely on `<Loading>` / `<Errored>`.
17
+ * try {
18
+ * const value = readReactiveSource();
19
+ * } catch (err) {
20
+ * if (err instanceof NotReadyError) throw err; // re-throw to suspend
21
+ * reportError(err);
22
+ * }
23
+ * ```
24
+ */
25
+ class NotReadyError extends Error {
26
+ source;
27
+ constructor(r) {
28
+ // Control-flow throw: it happens on every read of a pending source, so in
29
+ // production skip V8's eager stack capture (proportional to stack depth —
30
+ // real cost under SSR) by zeroing the V8-specific stackTraceLimit around
31
+ // super(). Dev keeps the stack for debuggability; non-V8 engines (no
32
+ // stackTraceLimit) take the plain path.
33
+ const o = Error;
34
+ const t = o.stackTraceLimit;
35
+ if (t !== undefined) o.stackTraceLimit = 0;
36
+ super();
37
+ if (t !== undefined) o.stackTraceLimit = t;
38
+ this.source = r;
39
+ }
40
+ }
41
+
42
+ class StatusError extends Error {
43
+ source;
44
+ constructor(r, o) {
45
+ super(o instanceof Error ? o.message : String(o), {
46
+ cause: o
47
+ });
48
+ this.source = r;
49
+ }
50
+ }
51
+
52
+ /** Return the user's error from an internal status wrapper. */ function unwrapStatusError(r) {
53
+ return r instanceof StatusError ? r.cause : r;
54
+ }
55
+
56
+ class NoOwnerError extends Error {
57
+ constructor() {
58
+ super("");
59
+ }
60
+ }
61
+
62
+ class ContextNotFoundError extends Error {
63
+ constructor() {
64
+ super("");
65
+ }
66
+ }
67
+
68
+ export { ContextNotFoundError, NoOwnerError, NotReadyError, StatusError, unwrapStatusError };
@@ -0,0 +1,98 @@
1
+ import { signal, setSignal, read } from "./core.js";
2
+
3
+ import { cleanup } from "./owner.js";
4
+
5
+ import { GlobalQueue } from "./scheduler.js";
6
+
7
+ let externalSourceConfig = null;
8
+
9
+ /**
10
+ * Registers a factory that bridges external reactive systems (e.g. MobX, Vue refs)
11
+ * into Solid's tracking graph. Every computation will be wrapped so that the
12
+ * external library can track its own dependencies alongside Solid's.
13
+ *
14
+ * Multiple calls pipe together: each new factory wraps the previous one.
15
+ *
16
+ * @param config.factory receives `(fn, trigger)` — wrap fn execution in external tracking,
17
+ * call trigger when external deps change. Return `{ track, dispose }`.
18
+ * @param config.untrack optional wrapper for `untrack` — disables external tracking too.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * // Bridge an external "subscribe / notify" library into Solid's graph.
23
+ * // `factory` wraps every Solid compute so the external library can attach
24
+ * // its own dependency tracker; `trigger` re-runs the compute on external
25
+ * // change. `untrack` mirrors Solid's `untrack()` into the external library
26
+ * // so that reads inside `untrack(...)` don't get tracked twice.
27
+ * enableExternalSource({
28
+ * factory: (compute, trigger) => {
29
+ * const sub = externalLib.subscribe(trigger);
30
+ * return {
31
+ * track: prev => externalLib.run(() => compute(prev)),
32
+ * dispose: () => sub.unsubscribe()
33
+ * };
34
+ * },
35
+ * untrack: fn => externalLib.untracked(fn)
36
+ * });
37
+ * ```
38
+ */
39
+ // Wires a freshly created computed through the active external-source bridge.
40
+ // Lives here (installed on GlobalQueue while a config is active) rather than
41
+ // inline in core: esbuild cannot literal-track the mutable config binding the
42
+ // way rollup does, so an inline `if (externalSourceConfig)` block ships in
43
+ // every bundle even though only enableExternalSource() can make it reachable.
44
+ function wireExternalSource(e) {
45
+ const n = signal(undefined, {
46
+ equals: false,
47
+ ownedWrite: true
48
+ });
49
+ const r = externalSourceConfig.factory(e.ce, () => {
50
+ setSignal(n, undefined);
51
+ });
52
+ cleanup(() => r.dispose());
53
+ e.ce = e => {
54
+ read(n);
55
+ return r.track(e);
56
+ };
57
+ }
58
+
59
+ function externalUntrack(e) {
60
+ return externalSourceConfig.untrack(e);
61
+ }
62
+
63
+ // The hooks mirror the config's liveness exactly (installed on enable,
64
+ // removed on reset) so core's null checks stay equivalent to the old
65
+ // `externalSourceConfig` truthiness checks.
66
+ function syncExternalHooks() {
67
+ GlobalQueue.It = externalSourceConfig ? wireExternalSource : null;
68
+ GlobalQueue.St = externalSourceConfig ? externalUntrack : null;
69
+ }
70
+
71
+ function enableExternalSource(e) {
72
+ const {factory: n, untrack: r = e => e()} = e;
73
+ if (externalSourceConfig) {
74
+ const {factory: e, untrack: o} = externalSourceConfig;
75
+ externalSourceConfig = {
76
+ factory: (r, o) => {
77
+ const t = e(r, o);
78
+ const a = n(e => t.track(e), o);
79
+ return {
80
+ track: e => a.track(e),
81
+ dispose() {
82
+ a.dispose();
83
+ t.dispose();
84
+ }
85
+ };
86
+ },
87
+ untrack: e => o(() => r(e))
88
+ };
89
+ } else {
90
+ externalSourceConfig = {
91
+ factory: n,
92
+ untrack: r
93
+ };
94
+ }
95
+ syncExternalHooks();
96
+ }
97
+
98
+ export { enableExternalSource, externalSourceConfig };
@@ -0,0 +1,104 @@
1
+ import { CONFIG_AUTO_DISPOSE, REACTIVE_ZOMBIE, STATUS_PENDING, REACTIVE_RECOMPUTING_DEPS } from "./constants.js";
2
+
3
+ import { deleteFromHeap, queueFor } from "./heap.js";
4
+
5
+ import { disposeChildren } from "./owner.js";
6
+
7
+ import "./scheduler.js";
8
+
9
+ // https://github.com/stackblitz/alien-signals/blob/v2.0.3/src/system.ts#L100
10
+ function unlinkSubs(l) {
11
+ const n = l.it;
12
+ const e = l.nt;
13
+ const u = l.le;
14
+ const s = l.ll;
15
+ if (u !== null) u.ll = s; else n.rt = s;
16
+ if (s !== null) s.le = u; else {
17
+ n.o = u;
18
+ if (u === null) {
19
+ n.ut?.();
20
+ // No more subscribers; only tear down if CONFIG_AUTO_DISPOSE is set.
21
+ // A pending node is exempt: its in-flight async work (or the
22
+ // transition holding it) is an observer — tearing down would orphan
23
+ // the work and re-execute it on the next read. The settle path runs
24
+ // this same last-one-out check when that observer releases (the
25
+ // untracked-read dispose in core.ts guards on pending identically).
26
+ const l = n;
27
+ l.ce && l.T & CONFIG_AUTO_DISPOSE && !(l.se & REACTIVE_ZOMBIE) && !(l.S & STATUS_PENDING) && unobserved(l);
28
+ }
29
+ }
30
+ return e;
31
+ }
32
+
33
+ function trimStaleDeps(l) {
34
+ const n = l.Ye;
35
+ let e = n !== null ? n.nt : l.tt;
36
+ if (e !== null) {
37
+ do {
38
+ e = unlinkSubs(e);
39
+ } while (e !== null);
40
+ if (n !== null) n.nt = null; else l.tt = null;
41
+ }
42
+ }
43
+
44
+ function unobserved(l) {
45
+ deleteFromHeap(l, queueFor(l));
46
+ let n = l.tt;
47
+ while (n !== null) {
48
+ n = unlinkSubs(n);
49
+ }
50
+ l.tt = null;
51
+ l.Ye = null;
52
+ disposeChildren(l, true);
53
+ }
54
+
55
+ // https://github.com/stackblitz/alien-signals/blob/v2.0.3/src/system.ts#L52
56
+ function link(l, n, e = false) {
57
+ // Repeat touches within one pass AND-combine `_pendingObserver`: a probe
58
+ // read (`isPending(() => x())`) beside a value read of the same dep must
59
+ // not relabel the value dependency as probe-only — the value read is what
60
+ // real-error propagation and affects() coverage key off, regardless of
61
+ // read order within the computation.
62
+ const u = n.Ye;
63
+ if (u !== null && u.it === l) {
64
+ u.Ge &&= e;
65
+ return;
66
+ }
67
+ let s = null;
68
+ const t = n.se & REACTIVE_RECOMPUTING_DEPS;
69
+ if (t) {
70
+ s = u !== null ? u.nt : n.tt;
71
+ if (s !== null && s.it === l) {
72
+ s.nl = n.Ze;
73
+ n.Ye = s;
74
+ // First touch of this pass: the previous pass's label is stale.
75
+ s.Ge = e;
76
+ return;
77
+ }
78
+ }
79
+ // A link stamped with the current pass generation was created or reused
80
+ // in-order during this recompute, i.e. it already sits in the validated
81
+ // [deps.._depsTail] prefix — the O(1) equivalent of scanning the dep list
82
+ // (the old alien-signals `isValidLink` walk, O(n²) when a computation
83
+ // re-reads earlier deps non-consecutively, e.g. store leaf reads).
84
+ const i = l.rt;
85
+ if (i !== null && i.fe === n && (!t || i.nl === n.Ze)) {
86
+ // Gen-matched during a recompute = repeat touch this pass (AND); outside
87
+ // a recompute there is no pass boundary, so the latest read labels it.
88
+ if (t) i.Ge &&= e; else i.Ge = e;
89
+ return;
90
+ }
91
+ const o = n.Ye = l.rt = {
92
+ it: l,
93
+ fe: n,
94
+ nt: s,
95
+ ll: i,
96
+ le: null,
97
+ nl: n.Ze,
98
+ Ge: e
99
+ };
100
+ if (u !== null) u.nt = o; else n.tt = o;
101
+ if (i !== null) i.le = o; else l.o = o;
102
+ }
103
+
104
+ export { link, trimStaleDeps, unlinkSubs, unobserved };
@@ -0,0 +1,140 @@
1
+ import { REACTIVE_IN_HEAP, REACTIVE_IN_HEAP_HEIGHT, EFFECT_TRACKED, EFFECT_USER, REACTIVE_RECOMPUTING_DEPS, REACTIVE_MANUAL_WRITE, REACTIVE_ZOMBIE, REACTIVE_CHECK, REACTIVE_DIRTY } from "./constants.js";
2
+
3
+ import { zombieQueue, dirtyQueue } from "./scheduler.js";
4
+
5
+ /** The queue a node belongs to, picked from its own zombie flag. */ function queueFor(e) {
6
+ return e.se & REACTIVE_ZOMBIE ? zombieQueue : dirtyQueue;
7
+ }
8
+
9
+ /**
10
+ * Schedule one subscriber to re-run on the next flush: tracked effects bypass
11
+ * the heap and go directly to their effect queue; everything else is inserted
12
+ * into its own (zombie-flag-routed) heap with the `_min` cursor pulled down.
13
+ */ function enqueueSub(e) {
14
+ if (e.Re === EFFECT_TRACKED) {
15
+ const E = e;
16
+ if (!E.Je) {
17
+ E.Je = true;
18
+ E.C.enqueue(EFFECT_USER, E.Ft);
19
+ }
20
+ return;
21
+ }
22
+ const E = queueFor(e);
23
+ if (E.Ve > e.xe) E.Ve = e.xe;
24
+ insertIntoHeap(e, E);
25
+ }
26
+
27
+ function actualInsertIntoHeap(e, E) {
28
+ const t = (e.ke?.Nt ? e.ke.dt?.xe : e.ke?.xe) ?? -1;
29
+ if (t >= e.xe) e.xe = t + 1;
30
+ const n = e.xe;
31
+ const I = E.eE[n];
32
+ if (I === undefined) E.eE[n] = e; else {
33
+ const E = I.st;
34
+ E.ot = e;
35
+ e.st = E;
36
+ I.st = e;
37
+ }
38
+ if (n > E.EE) E.EE = n;
39
+ }
40
+
41
+ function insertIntoHeap(e, E) {
42
+ let t = e.se;
43
+ if (t & (REACTIVE_IN_HEAP | REACTIVE_RECOMPUTING_DEPS | REACTIVE_MANUAL_WRITE)) return;
44
+ if (t & REACTIVE_CHECK) {
45
+ e.se = t & -4 | REACTIVE_DIRTY | REACTIVE_IN_HEAP;
46
+ } else {
47
+ e.se = t | REACTIVE_IN_HEAP;
48
+ // An unmarked node entering a marked heap invalidates the markHeap memo:
49
+ // `_marked` is only reset by runHeap, so a write between two mid-tick
50
+ // pulls (read-time markHeap + updateIfNecessary) would otherwise leave
51
+ // this node unmarked and every downstream pull stale until the next
52
+ // flush (#2922: the second `latest()` returned the first write's value).
53
+ if (E.tE && !(t & REACTIVE_DIRTY)) E.tE = false;
54
+ }
55
+ if (!(t & REACTIVE_IN_HEAP_HEIGHT)) actualInsertIntoHeap(e, E);
56
+ }
57
+
58
+ function insertIntoHeapHeight(e, E) {
59
+ let t = e.se;
60
+ if (t & (REACTIVE_IN_HEAP | REACTIVE_RECOMPUTING_DEPS | REACTIVE_IN_HEAP_HEIGHT | REACTIVE_MANUAL_WRITE)) return;
61
+ e.se = t | REACTIVE_IN_HEAP_HEIGHT;
62
+ actualInsertIntoHeap(e, E);
63
+ }
64
+
65
+ function deleteFromHeap(e, E) {
66
+ const t = e.se;
67
+ if (!(t & (REACTIVE_IN_HEAP | REACTIVE_IN_HEAP_HEIGHT))) return;
68
+ e.se = t & -25;
69
+ const n = e.xe;
70
+ if (e.st === e) E.eE[n] = undefined; else {
71
+ const t = e.ot;
72
+ const I = E.eE[n];
73
+ const o = t ?? I;
74
+ if (e === I) E.eE[n] = t; else e.st.ot = t;
75
+ o.st = e.st;
76
+ }
77
+ e.st = e;
78
+ e.ot = undefined;
79
+ }
80
+
81
+ function markHeap(e) {
82
+ if (e.tE) return;
83
+ e.tE = true;
84
+ for (let E = 0; E <= e.EE; E++) {
85
+ for (let t = e.eE[E]; t !== undefined; t = t.ot) {
86
+ if (t.se & REACTIVE_IN_HEAP) markNode(t);
87
+ }
88
+ }
89
+ }
90
+
91
+ function markNode(e, E = REACTIVE_DIRTY) {
92
+ const t = e.se;
93
+ if ((t & (REACTIVE_CHECK | REACTIVE_DIRTY)) >= E) return;
94
+ e.se = t & -4 | E;
95
+ for (let E = e.o; E !== null; E = E.le) {
96
+ markNode(E.fe, REACTIVE_CHECK);
97
+ }
98
+ if (e.u !== null) {
99
+ for (let E = e.u; E !== null; E = E.ae) {
100
+ for (let e = E.o; e !== null; e = e.le) {
101
+ markNode(e.fe, REACTIVE_CHECK);
102
+ }
103
+ }
104
+ }
105
+ }
106
+
107
+ function runHeap(e, E) {
108
+ e.tE = false;
109
+ for (e.Ve = 0; e.Ve <= e.EE; e.Ve++) {
110
+ let t = e.eE[e.Ve];
111
+ while (t !== undefined) {
112
+ if (t.se & REACTIVE_IN_HEAP) E(t); else adjustHeight(t, e);
113
+ t = e.eE[e.Ve];
114
+ }
115
+ }
116
+ e.EE = 0;
117
+ }
118
+
119
+ function adjustHeight(e, E) {
120
+ deleteFromHeap(e, E);
121
+ let t = e.xe;
122
+ for (let E = e.tt; E; E = E.nt) {
123
+ const e = E.it;
124
+ const n = e.lt || e;
125
+ if (n.ce && n.xe >= t) t = n.xe + 1;
126
+ }
127
+ if (e.xe !== t) {
128
+ e.xe = t;
129
+ for (let E = e.o; E !== null; E = E.le) {
130
+ // Route each subscriber by its own zombie flag, mirroring the
131
+ // post-recompute height-adjust path. Inserting into the running `heap`
132
+ // unconditionally can park a zombie in `dirtyQueue` (or a live node in
133
+ // `zombieQueue`), breaking the flag/queue invariant `deleteFromHeap`
134
+ // relies on — the same corruption class as #2759.
135
+ insertIntoHeapHeight(E.fe, queueFor(E.fe));
136
+ }
137
+ }
138
+ }
139
+
140
+ export { deleteFromHeap, enqueueSub, insertIntoHeap, insertIntoHeapHeight, markHeap, markNode, queueFor, runHeap };
@@ -0,0 +1,42 @@
1
+ /**
2
+ * INV-2: a node with an *active* override must be registered for reversion in
3
+ * the queue's or a transition's `_optimisticNodes`. An unregistered active
4
+ * override would survive transition completion forever. Runs at the end of
5
+ * every flush (not just quiescence — the invariant holds mid-transition).
6
+ * (There is no revert-target requirement: authoritative values commit
7
+ * silently into `_value` under the override mask — A17 — so reverting is
8
+ * just dropping the override.)
9
+ */
10
+ function devCheckActiveOverrides(e) {
11
+ return;
12
+ }
13
+
14
+ /** INV-1: an isPending() probe must never leak past its own call. */ function devCheckFlushStart() {
15
+ return;
16
+ }
17
+
18
+ /**
19
+ * Companion-vs-oracle census (#2838 pre-work). A NON-ASSERTING diff logger:
20
+ * at the end of every flush it compares each live companion's cached state
21
+ * against a fresh oracle and logs every distinct divergence fingerprint once
22
+ * (console, `[census]` prefix). Legit lane-scoped windows will show up too —
23
+ * the census exists to enumerate the full taxonomy of mid-flight divergence
24
+ * so the write-driven redesign knows every case it must produce, not to
25
+ * judge them. Enabled only when the COMPANION_CENSUS env var is set (in
26
+ * addition to `false`), so normal test runs pay one boolean check.
27
+ */ typeof globalThis !== "undefined" && !!globalThis.process?.env?.COMPANION_CENSUS;
28
+
29
+ function devCensusCompanions(e) {
30
+ return;
31
+ }
32
+
33
+ /**
34
+ * Quiescence checks. Run only when the system is fully drained: nothing
35
+ * scheduled, no active/stashed transitions, no live lanes. At that point no
36
+ * transition-scoped state may survive, and the lazily-created companions must
37
+ * agree with a fresh computation of their owner's state.
38
+ */ function devCheckQuiescent(e) {
39
+ return;
40
+ }
41
+
42
+ export { devCensusCompanions, devCheckActiveOverrides, devCheckFlushStart, devCheckQuiescent };