@solidjs/signals 2.0.0-rc.3 → 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 +1334 -94
- package/dist/node.cjs +2589 -1367
- 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 +36 -33
- package/dist/prod/core/owner.js +13 -13
- package/dist/prod/core/scheduler.js +131 -85
- package/dist/prod/core/verdict.js +29 -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/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/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/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 +2 -2
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, REACTIVE_DIRTY, REACTIVE_CHECK, 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
|
|
|
@@ -48,11 +48,11 @@ import { GlobalQueue, activeTransition, globalQueue, clock, insertSubs, schedule
|
|
|
48
48
|
// No revert target is stashed: while the override is active every reader
|
|
49
49
|
// sees it (A17), so authoritative arrivals commit silently into _value and
|
|
50
50
|
// reverting is just dropping the override — _value is already correct.
|
|
51
|
-
else globalQueue.
|
|
51
|
+
else globalQueue.m.Ke.push(e);
|
|
52
52
|
// Stamp ownership on the node (post-merge, so entangled writers share the
|
|
53
53
|
// joint root). resolveTransition prefers this over the lane's _transition,
|
|
54
54
|
// which a shared subscriber can merge across transactions (#2912).
|
|
55
|
-
ext(e).
|
|
55
|
+
ext(e).Nt = activeTransition;
|
|
56
56
|
const l = getOrCreateLane(e);
|
|
57
57
|
ext(e).Be = l;
|
|
58
58
|
e.T |= CONFIG_HAS_LANE;
|
|
@@ -63,7 +63,7 @@ import { GlobalQueue, activeTransition, globalQueue, clock, insertSubs, schedule
|
|
|
63
63
|
// syncCompanions only pokes _pendingSignal/_latestValueComputed — with
|
|
64
64
|
// neither companion present the call is a guaranteed no-op.
|
|
65
65
|
(e.o?.Ge !== undefined || e.o?.ge !== undefined) && GlobalQueue.Oe !== null && GlobalQueue.Oe(e, n);
|
|
66
|
-
if (e.
|
|
66
|
+
if (e.oe !== undefined) e.Te = clock;
|
|
67
67
|
// §12e: computed-only slot
|
|
68
68
|
insertSubs(e, true);
|
|
69
69
|
schedule();
|
|
@@ -100,7 +100,7 @@ function resolveOptimisticNodes(e) {
|
|
|
100
100
|
ext(n).De = NOT_PENDING;
|
|
101
101
|
if (i !== NOT_PENDING && n.be !== unwrapOverride(i)) insertSubs(n, true);
|
|
102
102
|
n._e = null;
|
|
103
|
-
if (n.o !== null) n.o.
|
|
103
|
+
if (n.o !== null) n.o.Nt = null;
|
|
104
104
|
}
|
|
105
105
|
// Settlement checkpoint (#2838): companions caught in this batch (or owned
|
|
106
106
|
// by a node in it) re-derive from committed state, so verdicts survive the
|
|
@@ -108,7 +108,7 @@ function resolveOptimisticNodes(e) {
|
|
|
108
108
|
for (let t = 0; t < n; t++) {
|
|
109
109
|
const n = e[t];
|
|
110
110
|
if (n.o?.Ge || n.o?.ge) GlobalQueue.un(n);
|
|
111
|
-
const i = n.o?.
|
|
111
|
+
const i = n.o?.Et;
|
|
112
112
|
if (i && (i.o?.Ge === n || i.o?.ge === n)) GlobalQueue.un(i);
|
|
113
113
|
}
|
|
114
114
|
e.splice(0, n);
|
|
@@ -122,29 +122,32 @@ function runQueue(e, n) {
|
|
|
122
122
|
* Run effects from all lanes that are ready (no pending async).
|
|
123
123
|
*/ function runLaneEffects(e) {
|
|
124
124
|
for (const n of activeLanes) {
|
|
125
|
-
if (n.
|
|
126
|
-
const t = n.
|
|
125
|
+
if (n.an || n.Ae.size > 0) continue;
|
|
126
|
+
const t = n.rn[e - 1];
|
|
127
127
|
if (t.length) {
|
|
128
|
-
n.
|
|
128
|
+
n.rn[e - 1] = [];
|
|
129
129
|
runQueue(t, e);
|
|
130
130
|
}
|
|
131
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?.();
|
|
132
135
|
}
|
|
133
136
|
|
|
134
137
|
function cleanupCompletedLanes(e) {
|
|
135
138
|
for (const n of activeLanes) {
|
|
136
139
|
const t = e ? n._e === e : !n._e;
|
|
137
140
|
if (!t) continue;
|
|
138
|
-
if (!n.
|
|
139
|
-
if (n.
|
|
140
|
-
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);
|
|
141
144
|
}
|
|
142
|
-
if (n.
|
|
145
|
+
if (n.tn.o?.Be === n) if (n.tn.o !== null) n.tn.o.Be = undefined;
|
|
143
146
|
n.Ae.clear();
|
|
144
|
-
n.
|
|
145
|
-
n.
|
|
147
|
+
n.rn[0].length = 0;
|
|
148
|
+
n.rn[1].length = 0;
|
|
146
149
|
activeLanes.delete(n);
|
|
147
|
-
signalLanes.delete(n.
|
|
150
|
+
signalLanes.delete(n.tn);
|
|
148
151
|
}
|
|
149
152
|
}
|
|
150
153
|
|
|
@@ -162,10 +165,10 @@ function cleanupCompletedLanes(e) {
|
|
|
162
165
|
* that reads a pending mid-transition write sees the committed value; the sub
|
|
163
166
|
* is recorded for replay at commit.
|
|
164
167
|
*/ function gatedRead(e, n, t) {
|
|
165
|
-
if (latestReadActive || e.Pe === NOT_PENDING || e.
|
|
168
|
+
if (latestReadActive || e.Pe === NOT_PENDING || e.oe || n !== e && !(n.ie & REACTIVE_MANUAL_WRITE)) {
|
|
166
169
|
return false;
|
|
167
170
|
}
|
|
168
|
-
activeTransition.
|
|
171
|
+
activeTransition.cn.add(t);
|
|
169
172
|
return true;
|
|
170
173
|
}
|
|
171
174
|
|
|
@@ -182,10 +185,10 @@ function cleanupCompletedLanes(e) {
|
|
|
182
185
|
// committed value to a reader that never re-ran after the landing, so a
|
|
183
186
|
// pending-gated branch stayed one value behind permanently (#3041
|
|
184
187
|
// follow-up). Record the reader under the same replay contract.
|
|
185
|
-
if (e.Pe !== NOT_PENDING) (activeTransition ?? globalQueue.
|
|
188
|
+
if (e.Pe !== NOT_PENDING) (activeTransition ?? globalQueue.m).cn.add(t);
|
|
186
189
|
return true;
|
|
187
190
|
}
|
|
188
|
-
if (n === e && stale && t.o?.
|
|
191
|
+
if (n === e && stale && t.o?.Et !== e) {
|
|
189
192
|
// The committed view can hide a staged write (a lane member — even just
|
|
190
193
|
// an isPending companion flip — puts the reader "under a lane"). The
|
|
191
194
|
// staged value commits with no re-delivery (commitPendingNode never
|
|
@@ -197,7 +200,7 @@ function cleanupCompletedLanes(e) {
|
|
|
197
200
|
// into the transaction (#3041 follow-up: a pending-gated branch that
|
|
198
201
|
// first read its async source during the landing flush stayed one value
|
|
199
202
|
// behind permanently); with none, into the ambient batch.
|
|
200
|
-
if (e.Pe !== NOT_PENDING) (activeTransition ?? globalQueue.
|
|
203
|
+
if (e.Pe !== NOT_PENDING) (activeTransition ?? globalQueue.m).cn.add(t);
|
|
201
204
|
return true;
|
|
202
205
|
}
|
|
203
206
|
return false;
|
|
@@ -220,7 +223,7 @@ function cleanupCompletedLanes(e) {
|
|
|
220
223
|
// recompute() runs plain: the value stages and commits with the flush.
|
|
221
224
|
// (el's own override slot excludes companions themselves; a lane merged
|
|
222
225
|
// into a real optimistic lane resolves to a non-companion source.)
|
|
223
|
-
if (!globalQueue.
|
|
226
|
+
if (!globalQueue.En && !activeTransition && !n._e && n.tn.o?.Et !== undefined && e.o?.De === undefined) {
|
|
224
227
|
if (e.o !== null) e.o.Be = undefined;
|
|
225
228
|
return false;
|
|
226
229
|
}
|
|
@@ -242,11 +245,11 @@ function cleanupCompletedLanes(e) {
|
|
|
242
245
|
|
|
243
246
|
/** recompute()'s catch path: track pending async in the current lane. */ function laneAsyncPending(e) {
|
|
244
247
|
const n = findLane(currentOptimisticLane);
|
|
245
|
-
if (n.
|
|
248
|
+
if (n.tn !== e) {
|
|
246
249
|
n.Ae.add(e);
|
|
247
250
|
ext(e).Be = n;
|
|
248
251
|
e.T |= CONFIG_HAS_LANE;
|
|
249
|
-
GlobalQueue.de !== null && GlobalQueue.de(n.
|
|
252
|
+
GlobalQueue.de !== null && GlobalQueue.de(n.tn);
|
|
250
253
|
}
|
|
251
254
|
}
|
|
252
255
|
|
|
@@ -254,13 +257,13 @@ function cleanupCompletedLanes(e) {
|
|
|
254
257
|
const n = resolveLane(e);
|
|
255
258
|
if (n) {
|
|
256
259
|
n.Ae.delete(e);
|
|
257
|
-
GlobalQueue.de !== null && GlobalQueue.de(n.
|
|
260
|
+
GlobalQueue.de !== null && GlobalQueue.de(n.tn);
|
|
258
261
|
}
|
|
259
262
|
}
|
|
260
263
|
|
|
261
264
|
function trackOptimisticStore(e) {
|
|
262
265
|
// After initTransition, globalQueue._batch IS activeTransition (same reference)
|
|
263
|
-
globalQueue.
|
|
266
|
+
globalQueue.m.Tn.add(e);
|
|
264
267
|
schedule();
|
|
265
268
|
}
|
|
266
269
|
|
|
@@ -269,19 +272,19 @@ function trackOptimisticStore(e) {
|
|
|
269
272
|
* create optimistic state (verdict.ts at module top level, createOptimistic
|
|
270
273
|
* and createOptimisticStore at first call) BEFORE any optimistic node exists.
|
|
271
274
|
*/ function installOptimisticEngine() {
|
|
272
|
-
if (GlobalQueue.
|
|
273
|
-
GlobalQueue.
|
|
274
|
-
GlobalQueue.
|
|
275
|
-
GlobalQueue.
|
|
276
|
-
GlobalQueue.
|
|
277
|
-
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;
|
|
278
281
|
GlobalQueue.Ft = gatedRead;
|
|
279
282
|
GlobalQueue.ht = laneSuspends;
|
|
280
283
|
GlobalQueue.gt = laneReadsCommitted;
|
|
281
284
|
GlobalQueue.je = recomputeLane;
|
|
282
285
|
GlobalQueue.$e = laneAsyncPending;
|
|
283
286
|
GlobalQueue.ze = laneAsyncSettled;
|
|
284
|
-
GlobalQueue.
|
|
287
|
+
GlobalQueue.An = trackOptimisticStore;
|
|
285
288
|
}
|
|
286
289
|
|
|
287
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;
|