@solidjs/signals 2.0.0-beta.20 → 2.0.0-beta.22
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/README.md +5 -1
- package/dist/dev.js +267 -307
- package/dist/node.cjs +1125 -1168
- package/dist/prod/affects.js +71 -163
- package/dist/prod/boundaries.js +141 -137
- package/dist/prod/core/action.js +50 -12
- package/dist/prod/core/async.js +83 -99
- package/dist/prod/core/core.js +255 -298
- package/dist/prod/core/effect.js +46 -46
- package/dist/prod/core/external.js +4 -4
- package/dist/prod/core/graph.js +54 -46
- package/dist/prod/core/heap.js +58 -50
- package/dist/prod/core/lanes.js +34 -34
- package/dist/prod/core/optimistic.js +64 -106
- package/dist/prod/core/owner.js +52 -52
- package/dist/prod/core/scheduler.js +217 -211
- package/dist/prod/core/verdict.js +144 -78
- package/dist/prod/map.js +101 -101
- package/dist/prod/signals.js +23 -5
- package/dist/prod/store/optimistic.js +11 -11
- package/dist/prod/store/projection.js +2 -2
- package/dist/prod/store/store.js +14 -14
- package/dist/types/affects.d.ts +9 -9
- package/dist/types/core/action.d.ts +16 -0
- package/dist/types/core/async.d.ts +1 -1
- package/dist/types/core/error.d.ts +7 -0
- package/dist/types/core/scheduler.d.ts +1 -6
- package/dist/types/core/types.d.ts +6 -11
- package/dist/types-cjs/affects.d.cts +9 -9
- package/dist/types-cjs/core/action.d.cts +16 -0
- package/dist/types-cjs/core/async.d.cts +1 -1
- package/dist/types-cjs/core/error.d.cts +7 -0
- package/dist/types-cjs/core/scheduler.d.cts +1 -6
- package/dist/types-cjs/core/types.d.cts +6 -11
- package/package.json +1 -1
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { NOT_PENDING, REACTIVE_DISPOSED, REACTIVE_DIRTY, REACTIVE_CHECK, unwrapOverride, STATUS_UNINITIALIZED, STATUS_PENDING, REACTIVE_MANUAL_WRITE } from "./constants.js";
|
|
1
|
+
import { NOT_PENDING, REACTIVE_DISPOSED, REACTIVE_DIRTY, REACTIVE_CHECK, unwrapOverride, REACTIVE_ZOMBIE, STATUS_UNINITIALIZED, STATUS_PENDING, REACTIVE_MANUAL_WRITE, STATUS_ERROR, REACTIVE_RECOMPUTING_DEPS } from "./constants.js";
|
|
2
2
|
|
|
3
|
-
import { setSignal, read, context, stale, currentOptimisticLane,
|
|
3
|
+
import { setSignal, prepareComputed, read, context, stale, currentOptimisticLane, tracking, setLatestReadActive, setContextInternal, optimisticComputed, setPendingCheckActive, latestReadActive, optimisticSignal, pendingCheckActive } from "./core.js";
|
|
4
4
|
|
|
5
5
|
import { NotReadyError } from "./error.js";
|
|
6
6
|
|
|
7
7
|
import { link } from "./graph.js";
|
|
8
8
|
|
|
9
|
-
import { insertIntoHeap, queueFor } from "./heap.js";
|
|
9
|
+
import { insertIntoHeap, queueFor, markHeap } from "./heap.js";
|
|
10
10
|
|
|
11
11
|
import "./invariants.js";
|
|
12
12
|
|
|
@@ -14,7 +14,7 @@ import { findLane, hasActiveOverride } from "./lanes.js";
|
|
|
14
14
|
|
|
15
15
|
import { installOptimisticEngine } from "./optimistic.js";
|
|
16
16
|
|
|
17
|
-
import { GlobalQueue, clock, insertSubs, schedule } from "./scheduler.js";
|
|
17
|
+
import { GlobalQueue, clock, insertSubs, schedule, activeTransition, activeAffectsMarks } from "./scheduler.js";
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* The isPending()/latest() verdict layer, moved out of core.ts. Importing this
|
|
@@ -32,21 +32,21 @@ let pendingProbe = null;
|
|
|
32
32
|
* Get or create the pending signal for a node (lazy).
|
|
33
33
|
* Used by isPending() to track pending state reactively.
|
|
34
34
|
*/ function getPendingSignal(e) {
|
|
35
|
-
if (!e.
|
|
35
|
+
if (!e.ge) {
|
|
36
36
|
// Start false, write true if pending - ensures reversion returns to false
|
|
37
|
-
e.
|
|
37
|
+
e.ge = optimisticSignal(false, {
|
|
38
38
|
ownedWrite: true
|
|
39
39
|
});
|
|
40
|
-
e.
|
|
41
|
-
if (computePendingState(e)) setSignal(e.
|
|
40
|
+
e.ge.nn = e;
|
|
41
|
+
if (computePendingState(e)) setSignal(e.ge, true);
|
|
42
42
|
}
|
|
43
|
-
return e.
|
|
43
|
+
return e.ge;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
function collectPendingSources(e) {
|
|
47
47
|
if (!pendingProbe) return;
|
|
48
48
|
pendingProbe.sources.add(e);
|
|
49
|
-
const t = e.
|
|
49
|
+
const t = e.nt || e;
|
|
50
50
|
if (t !== e) pendingProbe.sources.add(t);
|
|
51
51
|
}
|
|
52
52
|
|
|
@@ -60,65 +60,116 @@ function collectPendingSources(e) {
|
|
|
60
60
|
pendingProbe?.sources.add(e);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* The affects() coverage walk — the read half of the dedicated mark channel.
|
|
65
|
+
* A node is covered by a live mark iff it carries one (`_affectsCount`) or
|
|
66
|
+
* derives, through its CURRENT deps (hopping store firewalls), from a node
|
|
67
|
+
* that does. Pull-based coverage means graph rewires, mid-window recomputes,
|
|
68
|
+
* and probe-triggered recomputes can never strand or strip a mark — there is
|
|
69
|
+
* nothing stored downstream to corrupt. Probe-created links
|
|
70
|
+
* (`_pendingObserver`) are skipped so an `isPending` wrapper memo never
|
|
71
|
+
* inherits the coverage it reports on.
|
|
72
|
+
*/ function markWalk(e, t) {
|
|
73
|
+
if (e.t) return true;
|
|
74
|
+
// A real error outranks an inherited mark (A16/A24c): an errored node
|
|
75
|
+
// answers probes with its error, not a coverage verdict, and coverage does
|
|
76
|
+
// not flow through it — matching the rails' behavior, where propagation
|
|
77
|
+
// stopped at errored nodes. A DIRECT mark on an errored node still reads
|
|
78
|
+
// pending (the count check above), also matching.
|
|
79
|
+
if (e.S & STATUS_ERROR) return false;
|
|
80
|
+
if (t.has(e)) return false;
|
|
81
|
+
t.add(e);
|
|
82
|
+
const n = e.nt;
|
|
83
|
+
if (n && markWalk(n, t)) return true;
|
|
84
|
+
// Mid-recompute (the clearStatus companion poke runs before
|
|
85
|
+
// trimStaleDeps), only the validated prefix [_deps.._depsTail] is this
|
|
86
|
+
// pass's dependency set — walking past it would read dropped deps and
|
|
87
|
+
// latch a stale verdict on the companion.
|
|
88
|
+
const i = e;
|
|
89
|
+
const r = i.se & REACTIVE_RECOMPUTING_DEPS ? i.qe : undefined;
|
|
90
|
+
if (r !== null) {
|
|
91
|
+
for (let e = i.Xe ?? null; e !== null; e = e.et) {
|
|
92
|
+
if (!e.De && markWalk(e.tt, t)) return true;
|
|
93
|
+
if (e === r) break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Gated entry: apps with no live mark pay one integer compare. */ function markCovered(e) {
|
|
100
|
+
return activeAffectsMarks !== 0 && markWalk(e, new Set);
|
|
101
|
+
}
|
|
102
|
+
|
|
63
103
|
function quietPending(e) {
|
|
64
|
-
if (e.
|
|
65
|
-
for (const t of e.
|
|
104
|
+
if (e.oe) {
|
|
105
|
+
for (const t of e.oe) if (!t.be) return false;
|
|
66
106
|
return true;
|
|
67
107
|
}
|
|
68
|
-
return e.
|
|
108
|
+
return e.be;
|
|
69
109
|
}
|
|
70
110
|
|
|
71
111
|
function newQuestionInFlight(e) {
|
|
72
|
-
return !!(e.
|
|
112
|
+
return !!(e.S & STATUS_PENDING) && !(e.S & STATUS_UNINITIALIZED) && !quietPending(e);
|
|
73
113
|
}
|
|
74
114
|
|
|
75
115
|
function computePendingState(e) {
|
|
76
116
|
const t = e;
|
|
77
|
-
if (t.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
117
|
+
if (t.se & REACTIVE_DISPOSED) return false;
|
|
118
|
+
// Mark coverage is transitive by dep-graph reachability: a latest() shadow
|
|
119
|
+
// reaches its owner (and a store leaf its firewall) through its own deps,
|
|
120
|
+
// so the one walk covers direct marks, derivation, and companion chains.
|
|
121
|
+
if (markCovered(e)) return true;
|
|
122
|
+
const n = e.nt;
|
|
123
|
+
if (e.nn) {
|
|
124
|
+
const t = e.nn;
|
|
125
|
+
const n = t.nt || t;
|
|
84
126
|
return newQuestionInFlight(n);
|
|
85
127
|
}
|
|
86
|
-
if (n && e.
|
|
87
|
-
return !!(n.
|
|
128
|
+
if (n && e.Ne !== NOT_PENDING && !hasActiveOverride(e)) {
|
|
129
|
+
return !!(n.se & REACTIVE_MANUAL_WRITE) || !n.Te && !(n.S & STATUS_PENDING) || !!(n.S & STATUS_PENDING) && quietPending(n);
|
|
88
130
|
}
|
|
89
|
-
if (e.
|
|
90
|
-
if (hasActiveOverride(e)) return !e.
|
|
131
|
+
if (e.Ne !== NOT_PENDING && !(t.S & STATUS_UNINITIALIZED)) {
|
|
132
|
+
if (hasActiveOverride(e)) return !e.Pe || !e.Pe(e.Ne, unwrapOverride(e.Ee));
|
|
91
133
|
return true;
|
|
92
134
|
}
|
|
93
135
|
return newQuestionInFlight(t);
|
|
94
136
|
}
|
|
95
137
|
|
|
96
138
|
function syncCompanions(e, t) {
|
|
97
|
-
if (e.
|
|
98
|
-
if (e.
|
|
139
|
+
if (e.ge) updatePendingSignal(e);
|
|
140
|
+
if (e.he) setSignal(e.he, t);
|
|
99
141
|
}
|
|
100
142
|
|
|
101
143
|
function updatePendingSignal(e) {
|
|
102
|
-
if (e.
|
|
103
|
-
setSignal(e.
|
|
144
|
+
if (e.ge) {
|
|
145
|
+
setSignal(e.ge, computePendingState(e));
|
|
104
146
|
}
|
|
105
|
-
if (e.
|
|
147
|
+
if (e.he) updatePendingSignal(e.he);
|
|
106
148
|
}
|
|
107
149
|
|
|
108
150
|
function updateChildCompanions(e) {
|
|
109
|
-
for (let t = e.
|
|
110
|
-
if (t.
|
|
151
|
+
for (let t = e.u; t !== null; t = t.fe) {
|
|
152
|
+
if (t.ge || t.he) updatePendingSignal(t);
|
|
111
153
|
}
|
|
112
154
|
}
|
|
113
155
|
|
|
114
|
-
|
|
115
|
-
|
|
156
|
+
/**
|
|
157
|
+
* Re-derive every verdict companion downstream of `el` (subs + firewall
|
|
158
|
+
* children, dedup'd). The affects() channel's poke walk: registration and
|
|
159
|
+
* re-ask flips use the live write path (companion setSignal — its own lane
|
|
160
|
+
* lets the wake escape an incomplete transition's effect stash, #2887);
|
|
161
|
+
* mark release passes `snap` because it runs inside queue finalization,
|
|
162
|
+
* where companion writes must land committed (a setSignal there would open
|
|
163
|
+
* a fresh override window that nothing settles).
|
|
164
|
+
*/ function repollDownstreamVerdicts(e, t = false) {
|
|
165
|
+
const n = t ? snapCompanionsToState : updatePendingSignal;
|
|
166
|
+
const i = new Set;
|
|
116
167
|
const visit = e => {
|
|
117
|
-
if (
|
|
118
|
-
|
|
119
|
-
if (e.
|
|
120
|
-
for (let t = e.
|
|
121
|
-
for (let t = e.
|
|
168
|
+
if (i.has(e)) return;
|
|
169
|
+
i.add(e);
|
|
170
|
+
if (e.ge || e.he) n(e);
|
|
171
|
+
for (let t = e.o; t !== null; t = t.ue) visit(t.le);
|
|
172
|
+
for (let t = e.u ?? null; t !== null; t = t.fe) {
|
|
122
173
|
visit(t);
|
|
123
174
|
}
|
|
124
175
|
};
|
|
@@ -126,21 +177,21 @@ function repollDownstreamVerdicts(e) {
|
|
|
126
177
|
}
|
|
127
178
|
|
|
128
179
|
function snapCompanionsToState(e) {
|
|
129
|
-
const t = e.
|
|
130
|
-
if (t && (t.
|
|
180
|
+
const t = e.ge;
|
|
181
|
+
if (t && (t.Ee === undefined || t.Ee === NOT_PENDING)) {
|
|
131
182
|
const n = computePendingState(e);
|
|
132
|
-
if (t.
|
|
133
|
-
t.
|
|
134
|
-
t.
|
|
135
|
-
t.
|
|
183
|
+
if (t.Re !== n || t.Ne !== NOT_PENDING) {
|
|
184
|
+
t.Re = n;
|
|
185
|
+
t.Ne = NOT_PENDING;
|
|
186
|
+
t.ce = clock;
|
|
136
187
|
insertSubs(t);
|
|
137
188
|
schedule();
|
|
138
189
|
}
|
|
139
190
|
}
|
|
140
|
-
const n = e.
|
|
141
|
-
if (n && !(n.
|
|
142
|
-
if ((n.
|
|
143
|
-
n.
|
|
191
|
+
const n = e.he;
|
|
192
|
+
if (n && !(n.se & REACTIVE_DISPOSED)) {
|
|
193
|
+
if ((n.Ee === undefined || n.Ee === NOT_PENDING) && n.Ne === NOT_PENDING && !Object.is(n.Re, e.Re) && !(n.se & (REACTIVE_DIRTY | REACTIVE_CHECK))) {
|
|
194
|
+
n.se |= REACTIVE_DIRTY;
|
|
144
195
|
insertIntoHeap(n, queueFor(n));
|
|
145
196
|
insertSubs(n);
|
|
146
197
|
schedule();
|
|
@@ -150,7 +201,7 @@ function snapCompanionsToState(e) {
|
|
|
150
201
|
}
|
|
151
202
|
|
|
152
203
|
function getLatestValueComputed(e) {
|
|
153
|
-
if (!e.
|
|
204
|
+
if (!e.he) {
|
|
154
205
|
const t = latestReadActive;
|
|
155
206
|
setLatestReadActive(false);
|
|
156
207
|
const n = pendingCheckActive;
|
|
@@ -158,49 +209,64 @@ function getLatestValueComputed(e) {
|
|
|
158
209
|
const i = context;
|
|
159
210
|
setContextInternal(null);
|
|
160
211
|
// Detach from owner so it isn't disposed with effects
|
|
161
|
-
e.
|
|
162
|
-
e.
|
|
212
|
+
e.he = optimisticComputed(() => read(e));
|
|
213
|
+
e.he.nn = e;
|
|
163
214
|
// Parent-child lane relationship
|
|
164
215
|
setContextInternal(i);
|
|
165
216
|
setPendingCheckActive(n);
|
|
166
217
|
setLatestReadActive(t);
|
|
167
218
|
}
|
|
168
|
-
return e.
|
|
219
|
+
return e.he;
|
|
169
220
|
}
|
|
170
221
|
|
|
171
222
|
/** The latest()-mode read path, installed as GlobalQueue._latestRead. */ function latestRead(e) {
|
|
172
223
|
const t = getLatestValueComputed(e);
|
|
173
224
|
const n = latestReadActive;
|
|
174
225
|
setLatestReadActive(false);
|
|
175
|
-
const i = e.
|
|
226
|
+
const i = e.Ee !== undefined && e.Ee !== NOT_PENDING ? unwrapOverride(e.Ee) : e.Re;
|
|
176
227
|
let r;
|
|
177
228
|
try {
|
|
229
|
+
// An untracked latest() read has no reading context, so read() never
|
|
230
|
+
// performs its mid-tick pull — a plain write queued between two latest()
|
|
231
|
+
// calls left a still-subscribed shadow at its previous speculative value
|
|
232
|
+
// until the flush (#2922). Mirror the tracked-read pull here: mark the
|
|
233
|
+
// queued staleness through the graph, then bring the shadow up to date.
|
|
234
|
+
const e = queueFor(t);
|
|
235
|
+
if (t.He >= e.Fe && !(t.se & (REACTIVE_DISPOSED | REACTIVE_ZOMBIE))) {
|
|
236
|
+
markHeap(e);
|
|
237
|
+
prepareComputed(t, true);
|
|
238
|
+
}
|
|
178
239
|
r = read(t);
|
|
179
240
|
} catch (t) {
|
|
180
|
-
if (t instanceof NotReadyError && (!context || !(e.
|
|
241
|
+
if (t instanceof NotReadyError && (!context || !(e.S & STATUS_UNINITIALIZED))) return i;
|
|
181
242
|
throw t;
|
|
182
243
|
} finally {
|
|
183
244
|
setLatestReadActive(n);
|
|
184
245
|
}
|
|
185
|
-
if (t.
|
|
186
|
-
if (stale && currentOptimisticLane && t.
|
|
187
|
-
const e = findLane(t.
|
|
246
|
+
if (t.S & STATUS_PENDING) return i;
|
|
247
|
+
if (stale && currentOptimisticLane && t.je) {
|
|
248
|
+
const e = findLane(t.je);
|
|
188
249
|
const n = findLane(currentOptimisticLane);
|
|
189
|
-
if (e !== n && e.
|
|
250
|
+
if (e !== n && e.de.size > 0) {
|
|
190
251
|
return i;
|
|
191
252
|
}
|
|
192
253
|
}
|
|
254
|
+
// A shadow recomputed by the pull above (not at creation) holds its fresh
|
|
255
|
+
// speculative value in _pendingValue; a contextless read() only surfaces
|
|
256
|
+
// _value. Overrides stay authoritative (A17), and stale readers keep the
|
|
257
|
+
// other transition's committed view, matching read()'s own selection.
|
|
258
|
+
if (t.Ne !== NOT_PENDING && !hasActiveOverride(t) && !(stale && t.Ue && activeTransition !== t.Ue)) return t.Ne;
|
|
193
259
|
return r;
|
|
194
260
|
}
|
|
195
261
|
|
|
196
262
|
/** The isPending()-probe read path, installed as GlobalQueue._pendingCheck. */ function pendingCheckRead(e, t, n, i) {
|
|
197
263
|
setPendingCheckActive(false);
|
|
198
|
-
if (typeof e.
|
|
199
|
-
const r = n.
|
|
264
|
+
if (typeof e.ke === "function") prepareComputed(e, true);
|
|
265
|
+
const r = n.S;
|
|
200
266
|
if (t && r & STATUS_PENDING && r & STATUS_UNINITIALIZED) {
|
|
201
267
|
if (tracking && e !== t) link(e, t);
|
|
202
268
|
setPendingCheckActive(true);
|
|
203
|
-
throw n.
|
|
269
|
+
throw n._;
|
|
204
270
|
}
|
|
205
271
|
collectPendingSources(e);
|
|
206
272
|
if (i) collectPendingSources(i);
|
|
@@ -208,14 +274,14 @@ function getLatestValueComputed(e) {
|
|
|
208
274
|
}
|
|
209
275
|
|
|
210
276
|
function recordFreshRead(e, t) {
|
|
211
|
-
if (pendingProbe !== null && e.
|
|
277
|
+
if (pendingProbe !== null && e.Ne !== NOT_PENDING && t === e.Ne) pendingProbe.freshReads.add(e);
|
|
212
278
|
}
|
|
213
279
|
|
|
214
280
|
function applyReask(e, t) {
|
|
215
|
-
const n = !!(e.
|
|
216
|
-
const i = t && !(n && !e.
|
|
217
|
-
const r = n && e.
|
|
218
|
-
e.
|
|
281
|
+
const n = !!(e.S & STATUS_PENDING);
|
|
282
|
+
const i = t && !(n && !e.be);
|
|
283
|
+
const r = n && e.be !== i;
|
|
284
|
+
e.be = i;
|
|
219
285
|
return r;
|
|
220
286
|
}
|
|
221
287
|
|
|
@@ -255,7 +321,7 @@ function isPending(e) {
|
|
|
255
321
|
} catch (e) {
|
|
256
322
|
collectPending();
|
|
257
323
|
if (e instanceof NotReadyError) {
|
|
258
|
-
const t = !!(e.source?.
|
|
324
|
+
const t = !!(e.source?.S & STATUS_UNINITIALIZED);
|
|
259
325
|
if (i.found && !t) return true;
|
|
260
326
|
if (context && t) throw e;
|
|
261
327
|
}
|
|
@@ -269,24 +335,24 @@ function isPending(e) {
|
|
|
269
335
|
// Hook installation (same late-binding pattern as GlobalQueue._update /
|
|
270
336
|
// _propagateAffects): core call sites fire these behind the same guards the
|
|
271
337
|
// direct calls used, so behavior is identical once this module loads.
|
|
272
|
-
GlobalQueue.
|
|
338
|
+
GlobalQueue.Ie = syncCompanions;
|
|
273
339
|
|
|
274
|
-
GlobalQueue.
|
|
340
|
+
GlobalQueue.ae = updatePendingSignal;
|
|
275
341
|
|
|
276
|
-
GlobalQueue.
|
|
342
|
+
GlobalQueue._e = updateChildCompanions;
|
|
277
343
|
|
|
278
|
-
GlobalQueue.
|
|
344
|
+
GlobalQueue.un = snapCompanionsToState;
|
|
279
345
|
|
|
280
|
-
GlobalQueue.
|
|
346
|
+
GlobalQueue.St = latestRead;
|
|
281
347
|
|
|
282
|
-
GlobalQueue.
|
|
348
|
+
GlobalQueue.dt = pendingCheckRead;
|
|
283
349
|
|
|
284
|
-
GlobalQueue.
|
|
350
|
+
GlobalQueue.Rt = recordFreshRead;
|
|
285
351
|
|
|
286
|
-
GlobalQueue.
|
|
352
|
+
GlobalQueue.ze = applyReask;
|
|
287
353
|
|
|
288
|
-
GlobalQueue.
|
|
354
|
+
GlobalQueue.k = repollDownstreamVerdicts;
|
|
289
355
|
|
|
290
|
-
GlobalQueue.
|
|
356
|
+
GlobalQueue.Lt = witnessAffects;
|
|
291
357
|
|
|
292
358
|
export { isPending, latest };
|