@solidjs/signals 2.0.0-rc.2 → 2.0.0-rc.4
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/dist/dev.js +1454 -118
- package/dist/node.cjs +2709 -1396
- package/dist/prod/affects.js +13 -12
- package/dist/prod/boundaries.js +39 -34
- package/dist/prod/core/action.js +3 -3
- package/dist/prod/core/async.js +48 -46
- package/dist/prod/core/core.js +99 -67
- package/dist/prod/core/effect.js +25 -28
- package/dist/prod/core/external.js +2 -2
- package/dist/prod/core/graph.js +85 -49
- package/dist/prod/core/heap.js +10 -10
- package/dist/prod/core/lanes.js +19 -19
- package/dist/prod/core/optimistic.js +66 -41
- package/dist/prod/core/owner.js +13 -13
- package/dist/prod/core/scheduler.js +131 -85
- package/dist/prod/core/verdict.js +36 -15
- package/dist/prod/index.js +4 -0
- package/dist/prod/map.js +101 -101
- package/dist/prod/signals.js +1 -1
- package/dist/prod/store/index.js +2 -0
- package/dist/prod/store/next/optimistic.js +65 -11
- package/dist/prod/store/next/patch-hooks.js +13 -0
- package/dist/prod/store/next/patch.js +614 -0
- package/dist/prod/store/next/projection.js +107 -45
- package/dist/prod/store/next/reconcile.js +307 -120
- package/dist/prod/store/next/store.js +321 -92
- package/dist/prod/store/next/target.js +13 -4
- package/dist/prod/store/store.js +5 -5
- package/dist/types/core/core.d.ts +15 -1
- package/dist/types/core/dev.d.ts +8 -0
- package/dist/types/core/graph.d.ts +22 -0
- package/dist/types/core/invariants.d.ts +1 -1
- package/dist/types/core/scheduler.d.ts +12 -0
- package/dist/types/store/index.d.ts +2 -0
- package/dist/types/store/next/patch-hooks.d.ts +41 -0
- package/dist/types/store/next/patch.d.ts +91 -0
- package/dist/types/store/next/reconcile.d.ts +14 -0
- package/dist/types/store/next/store.d.ts +30 -2
- package/dist/types/store/next/target.d.ts +58 -8
- package/dist/types-cjs/core/core.d.cts +15 -1
- package/dist/types-cjs/core/dev.d.cts +8 -0
- package/dist/types-cjs/core/graph.d.cts +22 -0
- package/dist/types-cjs/core/invariants.d.cts +1 -1
- package/dist/types-cjs/core/scheduler.d.cts +12 -0
- package/dist/types-cjs/store/index.d.cts +2 -0
- package/dist/types-cjs/store/next/patch-hooks.d.cts +41 -0
- package/dist/types-cjs/store/next/patch.d.cts +91 -0
- package/dist/types-cjs/store/next/reconcile.d.cts +14 -0
- package/dist/types-cjs/store/next/store.d.cts +30 -2
- package/dist/types-cjs/store/next/target.d.cts +58 -8
- package/package.json +14 -14
- package/dist/types/store/optimistic.d.ts +0 -45
- package/dist/types/store/projection.d.ts +0 -70
- package/dist/types/store/reconcile.d.ts +0 -46
- package/dist/types-cjs/store/optimistic.d.cts +0 -45
- package/dist/types-cjs/store/projection.d.cts +0 -70
- package/dist/types-cjs/store/reconcile.d.cts +0 -46
package/dist/prod/core/graph.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CONFIG_AUTO_DISPOSE,
|
|
1
|
+
import { CONFIG_AUTO_DISPOSE, STATUS_PENDING, REACTIVE_DISPOSED, REACTIVE_ZOMBIE, REACTIVE_RECOMPUTING_DEPS } from "./constants.js";
|
|
2
2
|
|
|
3
3
|
import { deleteFromHeap, queueFor } from "./heap.js";
|
|
4
4
|
|
|
@@ -8,36 +8,36 @@ import { bumpNotifyEpoch } from "./scheduler.js";
|
|
|
8
8
|
|
|
9
9
|
// https://github.com/stackblitz/alien-signals/blob/v2.0.3/src/system.ts#L100
|
|
10
10
|
function unlinkSubs(e) {
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
if (
|
|
16
|
-
if (
|
|
17
|
-
|
|
18
|
-
if (
|
|
19
|
-
|
|
11
|
+
const n = e.ut;
|
|
12
|
+
const l = e.it;
|
|
13
|
+
const o = e.ae;
|
|
14
|
+
const u = e.en;
|
|
15
|
+
if (o !== null) o.en = u; else n.rt = u;
|
|
16
|
+
if (u !== null) u.ae = o; else {
|
|
17
|
+
n.u = o;
|
|
18
|
+
if (o === null) {
|
|
19
|
+
n.o?.ft?.();
|
|
20
20
|
// No more subscribers; only tear down if CONFIG_AUTO_DISPOSE is set.
|
|
21
21
|
// A pending node is exempt: its in-flight async work (or the
|
|
22
22
|
// transition holding it) is an observer — tearing down would orphan
|
|
23
23
|
// the work and re-execute it on the next read. The settle path runs
|
|
24
24
|
// this same last-one-out check when that observer releases (the
|
|
25
|
-
// untracked-read
|
|
26
|
-
const e =
|
|
27
|
-
e.
|
|
25
|
+
// untracked-read dormancy sweep guards on pending identically).
|
|
26
|
+
const e = n;
|
|
27
|
+
e.oe && e.T & CONFIG_AUTO_DISPOSE && !(e.ie & REACTIVE_ZOMBIE) && !(e.S & STATUS_PENDING) && unobserved(e);
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
|
-
return
|
|
30
|
+
return l;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
function trimStaleDeps(e) {
|
|
34
|
-
const
|
|
35
|
-
let
|
|
36
|
-
if (
|
|
34
|
+
const n = e.Ye;
|
|
35
|
+
let l = n !== null ? n.it : e.nt;
|
|
36
|
+
if (l !== null) {
|
|
37
37
|
do {
|
|
38
|
-
|
|
39
|
-
} while (
|
|
40
|
-
if (
|
|
38
|
+
l = unlinkSubs(l);
|
|
39
|
+
} while (l !== null);
|
|
40
|
+
if (n !== null) n.it = null; else e.nt = null;
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
|
|
@@ -46,11 +46,11 @@ function trimStaleDeps(e) {
|
|
|
46
46
|
// and skipping early also avoids adding one (hidden-class churn) via the
|
|
47
47
|
// null-out below.
|
|
48
48
|
function clearDeps(e) {
|
|
49
|
-
let
|
|
50
|
-
if (!
|
|
49
|
+
let n = e.nt;
|
|
50
|
+
if (!n) return;
|
|
51
51
|
do {
|
|
52
|
-
|
|
53
|
-
} while (
|
|
52
|
+
n = unlinkSubs(n);
|
|
53
|
+
} while (n !== null);
|
|
54
54
|
e.nt = null;
|
|
55
55
|
e.Ye = null;
|
|
56
56
|
}
|
|
@@ -61,27 +61,63 @@ function unobserved(e) {
|
|
|
61
61
|
disposeChildren(e, true);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Deferred dormancy for never-observed auto-dispose computeds (#3078).
|
|
66
|
+
*
|
|
67
|
+
* An untracked top-level read of a subscriber-less observation-lifecycle memo
|
|
68
|
+
* used to call unobserved() inline at the end of read(). That kept the leak
|
|
69
|
+
* closed (the compute links the memo into its deps' sub lists — without a
|
|
70
|
+
* teardown point a never-observed memo is retained by its sources forever;
|
|
71
|
+
* upstream alien-signals has exactly this retention), but it made reads
|
|
72
|
+
* destructive: each read disposed the node, the next read revived it with a
|
|
73
|
+
* full recompute in whatever ambient transition/lane context happened to be
|
|
74
|
+
* current, so consecutive reads could return different answers with no write
|
|
75
|
+
* in between.
|
|
76
|
+
*
|
|
77
|
+
* Instead, reads queue the node here and the scheduler sweeps at the top of
|
|
78
|
+
* the next flush (before runHeap, so a same-tick dirtying is reclaimed
|
|
79
|
+
* instead of recomputed). Reads become idempotent within a tick (the node
|
|
80
|
+
* stays alive and serves its cache, uniform with observed memos) while
|
|
81
|
+
* reclamation still happens within one microtask — the enqueue site arms
|
|
82
|
+
* schedule(), so a flush is guaranteed even when no other work is queued.
|
|
83
|
+
*/ const dormantNodes = new Set;
|
|
84
|
+
|
|
85
|
+
function sweepDormant() {
|
|
86
|
+
if (dormantNodes.size === 0) return;
|
|
87
|
+
for (const e of dormantNodes) {
|
|
88
|
+
// Re-validate at sweep time: the node may have gained a subscriber (its
|
|
89
|
+
// lifecycle is the unlinkSubs cascade now), gone pending (in-flight async
|
|
90
|
+
// is an observer; the settle path re-runs last-one-out), lost its
|
|
91
|
+
// AUTO_DISPOSE bit (owner teardown strips it, #3024), or already been
|
|
92
|
+
// torn down.
|
|
93
|
+
if (!e.u && e.T & CONFIG_AUTO_DISPOSE && !(e.S & STATUS_PENDING) && !(e.ie & (REACTIVE_DISPOSED | REACTIVE_ZOMBIE))) {
|
|
94
|
+
unobserved(e);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
dormantNodes.clear();
|
|
98
|
+
}
|
|
99
|
+
|
|
64
100
|
// https://github.com/stackblitz/alien-signals/blob/v2.0.3/src/system.ts#L52
|
|
65
|
-
function link(e,
|
|
101
|
+
function link(e, n, l = false) {
|
|
66
102
|
// Repeat touches within one pass AND-combine `_pendingObserver`: a probe
|
|
67
103
|
// read (`isPending(() => x())`) beside a value read of the same dep must
|
|
68
104
|
// not relabel the value dependency as probe-only — the value read is what
|
|
69
105
|
// real-error propagation and affects() coverage key off, regardless of
|
|
70
106
|
// read order within the computation.
|
|
71
|
-
const
|
|
72
|
-
if (
|
|
73
|
-
|
|
107
|
+
const o = n.Ye;
|
|
108
|
+
if (o !== null && o.ut === e) {
|
|
109
|
+
o.me &&= l;
|
|
74
110
|
return;
|
|
75
111
|
}
|
|
76
|
-
let
|
|
77
|
-
const
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
-
if (
|
|
81
|
-
|
|
82
|
-
|
|
112
|
+
let u = null;
|
|
113
|
+
const t = n.ie & REACTIVE_RECOMPUTING_DEPS;
|
|
114
|
+
if (t) {
|
|
115
|
+
u = o !== null ? o.it : n.nt;
|
|
116
|
+
if (u !== null && u.ut === e) {
|
|
117
|
+
u.nn = n.Ze;
|
|
118
|
+
n.Ye = u;
|
|
83
119
|
// First touch of this pass: the previous pass's label is stale.
|
|
84
|
-
|
|
120
|
+
u.me = l;
|
|
85
121
|
return;
|
|
86
122
|
}
|
|
87
123
|
}
|
|
@@ -90,26 +126,26 @@ function link(e, l, n = false) {
|
|
|
90
126
|
// [deps.._depsTail] prefix — the O(1) equivalent of scanning the dep list
|
|
91
127
|
// (the old alien-signals `isValidLink` walk, O(n²) when a computation
|
|
92
128
|
// re-reads earlier deps non-consecutively, e.g. store leaf reads).
|
|
93
|
-
const
|
|
94
|
-
if (
|
|
129
|
+
const s = e.rt;
|
|
130
|
+
if (s !== null && s.ce === n && (!t || s.nn === n.Ze)) {
|
|
95
131
|
// Gen-matched during a recompute = repeat touch this pass (AND); outside
|
|
96
132
|
// a recompute there is no pass boundary, so the latest read labels it.
|
|
97
|
-
if (
|
|
133
|
+
if (t) s.me &&= l; else s.me = l;
|
|
98
134
|
return;
|
|
99
135
|
}
|
|
100
|
-
const
|
|
136
|
+
const r = n.Ye = e.rt = {
|
|
101
137
|
ut: e,
|
|
102
|
-
|
|
103
|
-
it:
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
me:
|
|
138
|
+
ce: n,
|
|
139
|
+
it: u,
|
|
140
|
+
en: s,
|
|
141
|
+
ae: null,
|
|
142
|
+
nn: n.Ze,
|
|
143
|
+
me: l
|
|
108
144
|
};
|
|
109
|
-
if (
|
|
110
|
-
if (
|
|
145
|
+
if (o !== null) o.it = r; else n.nt = r;
|
|
146
|
+
if (s !== null) s.ae = r; else e.u = r;
|
|
111
147
|
// New subscriber edge: staged-rewrite skips (§12d) must not miss it.
|
|
112
148
|
bumpNotifyEpoch();
|
|
113
149
|
}
|
|
114
150
|
|
|
115
|
-
export { clearDeps, link, trimStaleDeps, unlinkSubs, unobserved };
|
|
151
|
+
export { clearDeps, dormantNodes, link, sweepDormant, trimStaleDeps, unlinkSubs, unobserved };
|
package/dist/prod/core/heap.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "./core.js";
|
|
2
2
|
|
|
3
|
-
import { REACTIVE_IN_HEAP, REACTIVE_IN_HEAP_HEIGHT, EFFECT_TRACKED, EFFECT_USER, REACTIVE_RECOMPUTING_DEPS, REACTIVE_MANUAL_WRITE,
|
|
3
|
+
import { REACTIVE_IN_HEAP, REACTIVE_IN_HEAP_HEIGHT, EFFECT_TRACKED, EFFECT_USER, REACTIVE_ZOMBIE, REACTIVE_RECOMPUTING_DEPS, REACTIVE_MANUAL_WRITE, REACTIVE_CHECK, REACTIVE_DIRTY, CONFIG_FW_CHILDREN } from "./constants.js";
|
|
4
4
|
|
|
5
5
|
import { zombieQueue, dirtyQueue } from "./scheduler.js";
|
|
6
6
|
|
|
@@ -27,7 +27,7 @@ import { zombieQueue, dirtyQueue } from "./scheduler.js";
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
function actualInsertIntoHeap(e, E) {
|
|
30
|
-
const t = (e.
|
|
30
|
+
const t = (e.ke?.Ct ? e.ke.Ot?.Le : e.ke?.Le) ?? -1;
|
|
31
31
|
if (t >= e.Le) e.Le = t + 1;
|
|
32
32
|
const n = e.Le;
|
|
33
33
|
const I = E.eE[n];
|
|
@@ -97,16 +97,16 @@ function markNode(e, E = REACTIVE_DIRTY) {
|
|
|
97
97
|
const t = e.ie;
|
|
98
98
|
if ((t & (REACTIVE_CHECK | REACTIVE_DIRTY)) >= E) return;
|
|
99
99
|
e.ie = t & -4 | E;
|
|
100
|
-
for (let E = e.u; E !== null; E = E.
|
|
101
|
-
markNode(E.
|
|
100
|
+
for (let E = e.u; E !== null; E = E.ae) {
|
|
101
|
+
markNode(E.ce, REACTIVE_CHECK);
|
|
102
102
|
}
|
|
103
103
|
// Firewall children (projection machinery only): gate the cold-extension
|
|
104
104
|
// deref on the config bit — markNode runs per sub edge per write, and an
|
|
105
105
|
// unconditional _x chase here taxed every propagation (diamond -22%).
|
|
106
106
|
if (e.T & CONFIG_FW_CHILDREN) {
|
|
107
|
-
for (let E = e.o.
|
|
108
|
-
for (let e = E.u; e !== null; e = e.
|
|
109
|
-
markNode(e.
|
|
107
|
+
for (let E = e.o.i; E !== null; E = E.Se) {
|
|
108
|
+
for (let e = E.u; e !== null; e = e.ae) {
|
|
109
|
+
markNode(e.ce, REACTIVE_CHECK);
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
}
|
|
@@ -130,17 +130,17 @@ function adjustHeight(e, E) {
|
|
|
130
130
|
for (let E = e.nt; E; E = E.it) {
|
|
131
131
|
const e = E.ut;
|
|
132
132
|
const n = e.lt || e;
|
|
133
|
-
if (n.
|
|
133
|
+
if (n.oe && n.Le >= t) t = n.Le + 1;
|
|
134
134
|
}
|
|
135
135
|
if (e.Le !== t) {
|
|
136
136
|
e.Le = t;
|
|
137
|
-
for (let E = e.u; E !== null; E = E.
|
|
137
|
+
for (let E = e.u; E !== null; E = E.ae) {
|
|
138
138
|
// Route each subscriber by its own zombie flag, mirroring the
|
|
139
139
|
// post-recompute height-adjust path. Inserting into the running `heap`
|
|
140
140
|
// unconditionally can park a zombie in `dirtyQueue` (or a live node in
|
|
141
141
|
// `zombieQueue`), breaking the flag/queue invariant `deleteFromHeap`
|
|
142
142
|
// relies on — the same corruption class as #2759.
|
|
143
|
-
insertIntoHeapHeight(E.
|
|
143
|
+
insertIntoHeapHeight(E.ce, queueFor(E.ce));
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
}
|
package/dist/prod/core/lanes.js
CHANGED
|
@@ -20,16 +20,16 @@ const activeLanes = new Set;
|
|
|
20
20
|
}
|
|
21
21
|
// Detect parent lane: _parentSource chains from pendingSignal → pendingValueComputed → original.
|
|
22
22
|
// The child lane should not merge with the parent lane.
|
|
23
|
-
const i = n.o?.
|
|
23
|
+
const i = n.o?.Et;
|
|
24
24
|
const t = i?.o?.Be;
|
|
25
25
|
const r = t ? findLane(t) : null;
|
|
26
26
|
e = {
|
|
27
|
-
|
|
27
|
+
tn: n,
|
|
28
28
|
Ae: new Set,
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
rn: [ [], [] ],
|
|
30
|
+
an: null,
|
|
31
31
|
_e: activeTransition,
|
|
32
|
-
|
|
32
|
+
sn: r
|
|
33
33
|
};
|
|
34
34
|
signalLanes.set(n, e);
|
|
35
35
|
activeLanes.add(e);
|
|
@@ -51,13 +51,13 @@ function adoptCompanionLane(n, e) {
|
|
|
51
51
|
const t = findLane(i);
|
|
52
52
|
// Only the companion's own unmerged root is safely re-parentable: a root
|
|
53
53
|
// that absorbed other lanes carries work that is not a child of this owner.
|
|
54
|
-
if (t !== e && t.
|
|
54
|
+
if (t !== e && t.tn === n && !t.sn) t.sn = e;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* Union-find: find the root lane.
|
|
59
59
|
*/ function findLane(n) {
|
|
60
|
-
while (n.
|
|
60
|
+
while (n.an) n = n.an;
|
|
61
61
|
return n;
|
|
62
62
|
}
|
|
63
63
|
|
|
@@ -67,16 +67,16 @@ function adoptCompanionLane(n, e) {
|
|
|
67
67
|
n = findLane(n);
|
|
68
68
|
e = findLane(e);
|
|
69
69
|
if (n === e) return n;
|
|
70
|
-
e.
|
|
70
|
+
e.an = n;
|
|
71
71
|
// Move (not copy) the merged lane's work: after the merge all routing goes
|
|
72
72
|
// through findLane() to the root, so anything left behind here is dead —
|
|
73
73
|
// and anything *added* here later is a routing bug (INV-5).
|
|
74
74
|
for (const i of e.Ae) n.Ae.add(i);
|
|
75
75
|
e.Ae.clear();
|
|
76
|
-
n.
|
|
77
|
-
n.
|
|
78
|
-
e.
|
|
79
|
-
e.
|
|
76
|
+
n.rn[0].push(...e.rn[0]);
|
|
77
|
+
n.rn[1].push(...e.rn[1]);
|
|
78
|
+
e.rn[0].length = 0;
|
|
79
|
+
e.rn[1].length = 0;
|
|
80
80
|
return n;
|
|
81
81
|
}
|
|
82
82
|
|
|
@@ -97,10 +97,10 @@ function resolveTransition(n) {
|
|
|
97
97
|
// transactions (#2912) — the merged root's _transition would hand this
|
|
98
98
|
// node's override to whichever action wrote last through the shared
|
|
99
99
|
// reader. Chase merge chains; a dead owner settled through another path.
|
|
100
|
-
if (hasActiveOverride(n) && n.o?.
|
|
101
|
-
const e = ext(n).
|
|
102
|
-
if (e.
|
|
103
|
-
if (n.o !== null) n.o.
|
|
100
|
+
if (hasActiveOverride(n) && n.o?.Nt) {
|
|
101
|
+
const e = ext(n).Nt = currentTransition(n.o?.Nt);
|
|
102
|
+
if (e.fn !== true) return e;
|
|
103
|
+
if (n.o !== null) n.o.Nt = null;
|
|
104
104
|
}
|
|
105
105
|
return resolveLane(n)?._e ?? n._e;
|
|
106
106
|
}
|
|
@@ -122,7 +122,7 @@ function resolveTransition(n) {
|
|
|
122
122
|
// If the subscriber's lane was merged into another lane, it's stale —
|
|
123
123
|
// replace it with the new source lane instead of following the merge chain
|
|
124
124
|
// (which would incorrectly merge the new lane into the old group)
|
|
125
|
-
if (t.
|
|
125
|
+
if (t.an) {
|
|
126
126
|
ext(n).Be = e;
|
|
127
127
|
n.T |= CONFIG_HAS_LANE;
|
|
128
128
|
return;
|
|
@@ -132,10 +132,10 @@ function resolveTransition(n) {
|
|
|
132
132
|
if (r !== i && !hasActiveOverride(n)) {
|
|
133
133
|
// Parent-child lanes stay independent so isPending resolves without
|
|
134
134
|
// waiting for the parent's async. The child keeps ownership.
|
|
135
|
-
if (i.
|
|
135
|
+
if (i.sn && findLane(i.sn) === r) {
|
|
136
136
|
ext(n).Be = e;
|
|
137
137
|
n.T |= CONFIG_HAS_LANE;
|
|
138
|
-
} else if (r.
|
|
138
|
+
} else if (r.sn && findLane(r.sn) === i) ; else mergeLanes(i, r);
|
|
139
139
|
}
|
|
140
140
|
return;
|
|
141
141
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NOT_PENDING, unwrapOverride, STATUS_UNINITIALIZED, CONFIG_HAS_LANE, OVERRIDE_UNDEFINED, STATUS_PENDING, REACTIVE_MANUAL_WRITE, REACTIVE_OPTIMISTIC_DIRTY,
|
|
1
|
+
import { NOT_PENDING, unwrapOverride, STATUS_UNINITIALIZED, REACTIVE_DIRTY, REACTIVE_CHECK, CONFIG_HAS_LANE, OVERRIDE_UNDEFINED, STATUS_PENDING, EFFECT_RENDER, REACTIVE_MANUAL_WRITE, REACTIVE_OPTIMISTIC_DIRTY, EFFECT_USER } from "./constants.js";
|
|
2
2
|
|
|
3
3
|
import { ext, latestReadActive, currentOptimisticLane, stale } from "./core.js";
|
|
4
4
|
|
|
@@ -27,7 +27,14 @@ import { GlobalQueue, activeTransition, globalQueue, clock, insertSubs, schedule
|
|
|
27
27
|
const t = e.o?.De !== NOT_PENDING;
|
|
28
28
|
const i = t ? unwrapOverride(e.o?.De) : e.be;
|
|
29
29
|
if (typeof n === "function") n = n(i);
|
|
30
|
-
const u = !!(e.S & STATUS_UNINITIALIZED) ||
|
|
30
|
+
const u = !!(e.S & STATUS_UNINITIALIZED) ||
|
|
31
|
+
// A dirty node's _value is stale (its queued recompute hasn't run — e.g.
|
|
32
|
+
// a latest() shadow marked by the previous landing's companion snap), so
|
|
33
|
+
// equality against it must not swallow the write. Without this, a sync
|
|
34
|
+
// push returning the shadow to that stale value was dropped, the snap
|
|
35
|
+
// recompute then committed the parent's old value, and the banner showed
|
|
36
|
+
// the previous transition's target (#3041 follow-up).
|
|
37
|
+
!!((e.ie ?? 0) & (REACTIVE_DIRTY | REACTIVE_CHECK)) || !e.Ue || !e.Ue(i, n);
|
|
31
38
|
if (!u) {
|
|
32
39
|
// Same-value write with an active override still entangles the current
|
|
33
40
|
// action's transition — the hold must outlast all overlapping actions.
|
|
@@ -41,11 +48,11 @@ import { GlobalQueue, activeTransition, globalQueue, clock, insertSubs, schedule
|
|
|
41
48
|
// No revert target is stashed: while the override is active every reader
|
|
42
49
|
// sees it (A17), so authoritative arrivals commit silently into _value and
|
|
43
50
|
// reverting is just dropping the override — _value is already correct.
|
|
44
|
-
else globalQueue.
|
|
51
|
+
else globalQueue.m.Ke.push(e);
|
|
45
52
|
// Stamp ownership on the node (post-merge, so entangled writers share the
|
|
46
53
|
// joint root). resolveTransition prefers this over the lane's _transition,
|
|
47
54
|
// which a shared subscriber can merge across transactions (#2912).
|
|
48
|
-
ext(e).
|
|
55
|
+
ext(e).Nt = activeTransition;
|
|
49
56
|
const l = getOrCreateLane(e);
|
|
50
57
|
ext(e).Be = l;
|
|
51
58
|
e.T |= CONFIG_HAS_LANE;
|
|
@@ -56,7 +63,7 @@ import { GlobalQueue, activeTransition, globalQueue, clock, insertSubs, schedule
|
|
|
56
63
|
// syncCompanions only pokes _pendingSignal/_latestValueComputed — with
|
|
57
64
|
// neither companion present the call is a guaranteed no-op.
|
|
58
65
|
(e.o?.Ge !== undefined || e.o?.ge !== undefined) && GlobalQueue.Oe !== null && GlobalQueue.Oe(e, n);
|
|
59
|
-
if (e.
|
|
66
|
+
if (e.oe !== undefined) e.Te = clock;
|
|
60
67
|
// §12e: computed-only slot
|
|
61
68
|
insertSubs(e, true);
|
|
62
69
|
schedule();
|
|
@@ -93,7 +100,7 @@ function resolveOptimisticNodes(e) {
|
|
|
93
100
|
ext(n).De = NOT_PENDING;
|
|
94
101
|
if (i !== NOT_PENDING && n.be !== unwrapOverride(i)) insertSubs(n, true);
|
|
95
102
|
n._e = null;
|
|
96
|
-
if (n.o !== null) n.o.
|
|
103
|
+
if (n.o !== null) n.o.Nt = null;
|
|
97
104
|
}
|
|
98
105
|
// Settlement checkpoint (#2838): companions caught in this batch (or owned
|
|
99
106
|
// by a node in it) re-derive from committed state, so verdicts survive the
|
|
@@ -101,7 +108,7 @@ function resolveOptimisticNodes(e) {
|
|
|
101
108
|
for (let t = 0; t < n; t++) {
|
|
102
109
|
const n = e[t];
|
|
103
110
|
if (n.o?.Ge || n.o?.ge) GlobalQueue.un(n);
|
|
104
|
-
const i = n.o?.
|
|
111
|
+
const i = n.o?.Et;
|
|
105
112
|
if (i && (i.o?.Ge === n || i.o?.ge === n)) GlobalQueue.un(i);
|
|
106
113
|
}
|
|
107
114
|
e.splice(0, n);
|
|
@@ -115,29 +122,32 @@ function runQueue(e, n) {
|
|
|
115
122
|
* Run effects from all lanes that are ready (no pending async).
|
|
116
123
|
*/ function runLaneEffects(e) {
|
|
117
124
|
for (const n of activeLanes) {
|
|
118
|
-
if (n.
|
|
119
|
-
const t = n.
|
|
125
|
+
if (n.an || n.Ae.size > 0) continue;
|
|
126
|
+
const t = n.rn[e - 1];
|
|
120
127
|
if (t.length) {
|
|
121
|
-
n.
|
|
128
|
+
n.rn[e - 1] = [];
|
|
122
129
|
runQueue(t, e);
|
|
123
130
|
}
|
|
124
131
|
}
|
|
132
|
+
// Optimistic patch applications ride the same visibility slot as lane
|
|
133
|
+
// effects (in-flight DOM updates); no-op unless patches registered.
|
|
134
|
+
if (e === EFFECT_RENDER) GlobalQueue.ln?.();
|
|
125
135
|
}
|
|
126
136
|
|
|
127
137
|
function cleanupCompletedLanes(e) {
|
|
128
138
|
for (const n of activeLanes) {
|
|
129
139
|
const t = e ? n._e === e : !n._e;
|
|
130
140
|
if (!t) continue;
|
|
131
|
-
if (!n.
|
|
132
|
-
if (n.
|
|
133
|
-
if (n.
|
|
141
|
+
if (!n.an) {
|
|
142
|
+
if (n.rn[0].length) runQueue(n.rn[0], EFFECT_RENDER);
|
|
143
|
+
if (n.rn[1].length) runQueue(n.rn[1], EFFECT_USER);
|
|
134
144
|
}
|
|
135
|
-
if (n.
|
|
145
|
+
if (n.tn.o?.Be === n) if (n.tn.o !== null) n.tn.o.Be = undefined;
|
|
136
146
|
n.Ae.clear();
|
|
137
|
-
n.
|
|
138
|
-
n.
|
|
147
|
+
n.rn[0].length = 0;
|
|
148
|
+
n.rn[1].length = 0;
|
|
139
149
|
activeLanes.delete(n);
|
|
140
|
-
signalLanes.delete(n.
|
|
150
|
+
signalLanes.delete(n.tn);
|
|
141
151
|
}
|
|
142
152
|
}
|
|
143
153
|
|
|
@@ -155,10 +165,10 @@ function cleanupCompletedLanes(e) {
|
|
|
155
165
|
* that reads a pending mid-transition write sees the committed value; the sub
|
|
156
166
|
* is recorded for replay at commit.
|
|
157
167
|
*/ function gatedRead(e, n, t) {
|
|
158
|
-
if (latestReadActive || e.Pe === NOT_PENDING || e.
|
|
168
|
+
if (latestReadActive || e.Pe === NOT_PENDING || e.oe || n !== e && !(n.ie & REACTIVE_MANUAL_WRITE)) {
|
|
159
169
|
return false;
|
|
160
170
|
}
|
|
161
|
-
activeTransition.
|
|
171
|
+
activeTransition.cn.add(t);
|
|
162
172
|
return true;
|
|
163
173
|
}
|
|
164
174
|
|
|
@@ -166,16 +176,31 @@ function cleanupCompletedLanes(e) {
|
|
|
166
176
|
* read()'s value selection under a lane: return the committed `_value` for
|
|
167
177
|
* optimistic/lane-assigned signals, stale-mode reads, and pending owners.
|
|
168
178
|
*/ function laneReadsCommitted(e, n, t) {
|
|
169
|
-
if (e.o?.De !== undefined || !!e.o?.Be || !!(n.S & STATUS_PENDING))
|
|
170
|
-
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
if (e.Pe !== NOT_PENDING
|
|
179
|
+
if (e.o?.De !== undefined || !!e.o?.Be || !!(n.S & STATUS_PENDING)) {
|
|
180
|
+
// The committed view hides a staged in-flight value that will promote
|
|
181
|
+
// silently (commitPendingNode never re-notifies). gatedRead records plain
|
|
182
|
+
// signals for replay at commit; async memos are excluded from it by the
|
|
183
|
+
// `_fn` check and reach here instead — a lane-assigned source whose async
|
|
184
|
+
// already settled (laneAsyncSettled keeps _optimisticLane) served its
|
|
185
|
+
// committed value to a reader that never re-ran after the landing, so a
|
|
186
|
+
// pending-gated branch stayed one value behind permanently (#3041
|
|
187
|
+
// follow-up). Record the reader under the same replay contract.
|
|
188
|
+
if (e.Pe !== NOT_PENDING) (activeTransition ?? globalQueue.m).cn.add(t);
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
if (n === e && stale && t.o?.Et !== e) {
|
|
192
|
+
// The committed view can hide a staged write (a lane member — even just
|
|
193
|
+
// an isPending companion flip — puts the reader "under a lane"). The
|
|
194
|
+
// staged value commits with no re-delivery (commitPendingNode never
|
|
195
|
+
// re-notifies), so record the reader for replay at commit — the same
|
|
196
|
+
// contract gatedRead provides (#2963). gatedRead itself only covers
|
|
197
|
+
// signal reads where the reading computed differs from the source; the
|
|
198
|
+
// owner === el memo/self read lands here instead. With a transaction
|
|
199
|
+
// active the staged value promotes silently at ITS landing, so record
|
|
200
|
+
// into the transaction (#3041 follow-up: a pending-gated branch that
|
|
201
|
+
// first read its async source during the landing flush stayed one value
|
|
202
|
+
// behind permanently); with none, into the ambient batch.
|
|
203
|
+
if (e.Pe !== NOT_PENDING) (activeTransition ?? globalQueue.m).cn.add(t);
|
|
179
204
|
return true;
|
|
180
205
|
}
|
|
181
206
|
return false;
|
|
@@ -198,7 +223,7 @@ function cleanupCompletedLanes(e) {
|
|
|
198
223
|
// recompute() runs plain: the value stages and commits with the flush.
|
|
199
224
|
// (el's own override slot excludes companions themselves; a lane merged
|
|
200
225
|
// into a real optimistic lane resolves to a non-companion source.)
|
|
201
|
-
if (!globalQueue.
|
|
226
|
+
if (!globalQueue.En && !activeTransition && !n._e && n.tn.o?.Et !== undefined && e.o?.De === undefined) {
|
|
202
227
|
if (e.o !== null) e.o.Be = undefined;
|
|
203
228
|
return false;
|
|
204
229
|
}
|
|
@@ -220,11 +245,11 @@ function cleanupCompletedLanes(e) {
|
|
|
220
245
|
|
|
221
246
|
/** recompute()'s catch path: track pending async in the current lane. */ function laneAsyncPending(e) {
|
|
222
247
|
const n = findLane(currentOptimisticLane);
|
|
223
|
-
if (n.
|
|
248
|
+
if (n.tn !== e) {
|
|
224
249
|
n.Ae.add(e);
|
|
225
250
|
ext(e).Be = n;
|
|
226
251
|
e.T |= CONFIG_HAS_LANE;
|
|
227
|
-
GlobalQueue.de !== null && GlobalQueue.de(n.
|
|
252
|
+
GlobalQueue.de !== null && GlobalQueue.de(n.tn);
|
|
228
253
|
}
|
|
229
254
|
}
|
|
230
255
|
|
|
@@ -232,13 +257,13 @@ function cleanupCompletedLanes(e) {
|
|
|
232
257
|
const n = resolveLane(e);
|
|
233
258
|
if (n) {
|
|
234
259
|
n.Ae.delete(e);
|
|
235
|
-
GlobalQueue.de !== null && GlobalQueue.de(n.
|
|
260
|
+
GlobalQueue.de !== null && GlobalQueue.de(n.tn);
|
|
236
261
|
}
|
|
237
262
|
}
|
|
238
263
|
|
|
239
264
|
function trackOptimisticStore(e) {
|
|
240
265
|
// After initTransition, globalQueue._batch IS activeTransition (same reference)
|
|
241
|
-
globalQueue.
|
|
266
|
+
globalQueue.m.Tn.add(e);
|
|
242
267
|
schedule();
|
|
243
268
|
}
|
|
244
269
|
|
|
@@ -247,19 +272,19 @@ function trackOptimisticStore(e) {
|
|
|
247
272
|
* create optimistic state (verdict.ts at module top level, createOptimistic
|
|
248
273
|
* and createOptimisticStore at first call) BEFORE any optimistic node exists.
|
|
249
274
|
*/ function installOptimisticEngine() {
|
|
250
|
-
if (GlobalQueue.
|
|
251
|
-
GlobalQueue.
|
|
252
|
-
GlobalQueue.
|
|
253
|
-
GlobalQueue.
|
|
254
|
-
GlobalQueue.
|
|
255
|
-
GlobalQueue.
|
|
275
|
+
if (GlobalQueue.kt !== null) return;
|
|
276
|
+
GlobalQueue.kt = optimisticWrite;
|
|
277
|
+
GlobalQueue.dn = resolveOptimisticNodes;
|
|
278
|
+
GlobalQueue.In = transitionBlocked;
|
|
279
|
+
GlobalQueue.Nn = cleanupCompletedLanes;
|
|
280
|
+
GlobalQueue._n = runLaneEffects;
|
|
256
281
|
GlobalQueue.Ft = gatedRead;
|
|
257
282
|
GlobalQueue.ht = laneSuspends;
|
|
258
283
|
GlobalQueue.gt = laneReadsCommitted;
|
|
259
284
|
GlobalQueue.je = recomputeLane;
|
|
260
285
|
GlobalQueue.$e = laneAsyncPending;
|
|
261
286
|
GlobalQueue.ze = laneAsyncSettled;
|
|
262
|
-
GlobalQueue.
|
|
287
|
+
GlobalQueue.An = trackOptimisticStore;
|
|
263
288
|
}
|
|
264
289
|
|
|
265
290
|
export { installOptimisticEngine };
|
package/dist/prod/core/owner.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { REACTIVE_DISPOSED, REACTIVE_ZOMBIE, REACTIVE_IN_HEAP, REACTIVE_IN_HEAP_HEIGHT, CONFIG_TRANSPARENT, defaultContext
|
|
1
|
+
import { REACTIVE_DISPOSED, CONFIG_AUTO_DISPOSE, REACTIVE_ZOMBIE, REACTIVE_IN_HEAP, REACTIVE_IN_HEAP_HEIGHT, CONFIG_TRANSPARENT, defaultContext } from "./constants.js";
|
|
2
2
|
|
|
3
3
|
import { context, runWithOwner, pendingCheckActive, latestReadActive, tracking } from "./core.js";
|
|
4
4
|
|
|
@@ -12,7 +12,7 @@ const PENDING_OWNER = {};
|
|
|
12
12
|
|
|
13
13
|
// Dummy owner to trigger store's read() path
|
|
14
14
|
function markDisposal(e) {
|
|
15
|
-
let t = e.
|
|
15
|
+
let t = e.ve;
|
|
16
16
|
while (t) {
|
|
17
17
|
const e = t.ie;
|
|
18
18
|
t.ie = e | REACTIVE_ZOMBIE;
|
|
@@ -54,8 +54,8 @@ function disposeChildren(e, t = false, n) {
|
|
|
54
54
|
const t = e;
|
|
55
55
|
if (t.o?.Ge || t.o?.ge) GlobalQueue.un(t);
|
|
56
56
|
}
|
|
57
|
-
if (t && e.
|
|
58
|
-
let o = n ? e.o?.qe ?? null : e.
|
|
57
|
+
if (t && e.oe && e.o !== null) e.o.Ee = null;
|
|
58
|
+
let o = n ? e.o?.qe ?? null : e.ve;
|
|
59
59
|
while (o) {
|
|
60
60
|
const e = o.Ve;
|
|
61
61
|
const t = o;
|
|
@@ -82,17 +82,17 @@ function disposeChildren(e, t = false, n) {
|
|
|
82
82
|
if (n) {
|
|
83
83
|
if (e.o !== null) e.o.qe = null;
|
|
84
84
|
} else {
|
|
85
|
-
e.
|
|
85
|
+
e.ve = null;
|
|
86
86
|
e.Me = 0;
|
|
87
87
|
}
|
|
88
88
|
// O(1) splice out of parent's chain on individual dispose. Skipped during
|
|
89
89
|
// batch dispose (parent already disposed) and zombie disposal (node sits on
|
|
90
90
|
// parent's _pendingFirstChild). We leave node._nextSibling intact so outer
|
|
91
91
|
// walks that already advanced past us still reach later siblings.
|
|
92
|
-
if (t && !n && !(i & REACTIVE_ZOMBIE) && e.
|
|
92
|
+
if (t && !n && !(i & REACTIVE_ZOMBIE) && e.ke !== null && !(e.ke.ie & REACTIVE_DISPOSED)) {
|
|
93
93
|
const t = e.ct;
|
|
94
94
|
const n = e.Ve;
|
|
95
|
-
if (t !== null) t.Ve = n; else e.ve
|
|
95
|
+
if (t !== null) t.Ve = n; else e.ke.ve = n;
|
|
96
96
|
if (n !== null) n.ct = t;
|
|
97
97
|
e.ct = null;
|
|
98
98
|
}
|
|
@@ -124,7 +124,7 @@ function runDisposal(e, t) {
|
|
|
124
124
|
|
|
125
125
|
function childId(e, t) {
|
|
126
126
|
let n = e;
|
|
127
|
-
while (n.T & CONFIG_TRANSPARENT && n.
|
|
127
|
+
while (n.T & CONFIG_TRANSPARENT && n.ke) n = n.ke;
|
|
128
128
|
if (n.id != null) return formatId(n.id, t ? n.Me++ : n.Me);
|
|
129
129
|
throw new Error("");
|
|
130
130
|
}
|
|
@@ -248,7 +248,7 @@ function disposeRootSelf(e = true) {
|
|
|
248
248
|
T: n ? CONFIG_TRANSPARENT : 0,
|
|
249
249
|
Ct: true,
|
|
250
250
|
Ot: t?.Ct ? t.Ot : t,
|
|
251
|
-
|
|
251
|
+
ve: null,
|
|
252
252
|
Ve: null,
|
|
253
253
|
ct: null,
|
|
254
254
|
he: null,
|
|
@@ -256,17 +256,17 @@ function disposeRootSelf(e = true) {
|
|
|
256
256
|
we: t?.we || defaultContext,
|
|
257
257
|
Me: 0,
|
|
258
258
|
o: null,
|
|
259
|
-
|
|
259
|
+
ke: t,
|
|
260
260
|
dispose: disposeRootSelf
|
|
261
261
|
};
|
|
262
262
|
if (t) {
|
|
263
|
-
const e = t.
|
|
263
|
+
const e = t.ve;
|
|
264
264
|
if (e === null) {
|
|
265
|
-
t.
|
|
265
|
+
t.ve = i;
|
|
266
266
|
} else {
|
|
267
267
|
i.Ve = e;
|
|
268
268
|
e.ct = i;
|
|
269
|
-
t.
|
|
269
|
+
t.ve = i;
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
272
|
return i;
|