@solidjs/signals 2.0.0-beta.21 → 2.0.0-beta.23

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.
@@ -1,4 +1,4 @@
1
- import { NOT_PENDING, REACTIVE_DISPOSED, REACTIVE_DIRTY, REACTIVE_CHECK, unwrapOverride, REACTIVE_ZOMBIE, 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
3
  import { setSignal, prepareComputed, read, context, stale, currentOptimisticLane, tracking, setLatestReadActive, setContextInternal, optimisticComputed, setPendingCheckActive, latestReadActive, optimisticSignal, pendingCheckActive } from "./core.js";
4
4
 
@@ -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, activeTransition } 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.ye) {
35
+ if (!e.be) {
36
36
  // Start false, write true if pending - ensures reversion returns to false
37
- e.ye = optimisticSignal(false, {
37
+ e.be = optimisticSignal(false, {
38
38
  ownedWrite: true
39
39
  });
40
- e.ye.en = e;
41
- if (computePendingState(e)) setSignal(e.ye, true);
40
+ e.be.nn = e;
41
+ if (computePendingState(e)) setSignal(e.be, true);
42
42
  }
43
- return e.ye;
43
+ return e.be;
44
44
  }
45
45
 
46
46
  function collectPendingSources(e) {
47
47
  if (!pendingProbe) return;
48
48
  pendingProbe.sources.add(e);
49
- const t = e.rt || 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.m) {
65
- for (const t of e.m) if (!t.A) return false;
104
+ if (e.oe) {
105
+ for (const t of e.oe) if (!t._e) return false;
66
106
  return true;
67
107
  }
68
- return e.A;
108
+ return e._e;
69
109
  }
70
110
 
71
111
  function newQuestionInFlight(e) {
72
- return !!(e.i & STATUS_PENDING) && !(e.i & STATUS_UNINITIALIZED) && !quietPending(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.u & REACTIVE_DISPOSED) return false;
78
- if (e.M) return true;
79
- const n = e.rt;
80
- if (e.en) {
81
- const t = e.en;
82
- if (t.M) return true;
83
- const n = t.rt || t;
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.ge !== NOT_PENDING && !hasActiveOverride(e)) {
87
- return !!(n.u & REACTIVE_MANUAL_WRITE) || !n.Ae && !(n.i & STATUS_PENDING) || !!(n.i & STATUS_PENDING) && quietPending(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.ge !== NOT_PENDING && !(t.i & STATUS_UNINITIALIZED)) {
90
- if (hasActiveOverride(e)) return !e.Ge || !e.Ge(e.ge, unwrapOverride(e.be));
131
+ if (e.Ne !== NOT_PENDING && !(t.S & STATUS_UNINITIALIZED)) {
132
+ if (hasActiveOverride(e)) return !e.Re || !e.Re(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.ye) updatePendingSignal(e);
98
- if (e.pe) setSignal(e.pe, t);
139
+ if (e.be) updatePendingSignal(e);
140
+ if (e.ge) setSignal(e.ge, t);
99
141
  }
100
142
 
101
143
  function updatePendingSignal(e) {
102
- if (e.ye) {
103
- setSignal(e.ye, computePendingState(e));
144
+ if (e.be) {
145
+ setSignal(e.be, computePendingState(e));
104
146
  }
105
- if (e.pe) updatePendingSignal(e.pe);
147
+ if (e.ge) updatePendingSignal(e.ge);
106
148
  }
107
149
 
108
150
  function updateChildCompanions(e) {
109
- for (let t = e.G; t !== null; t = t.Ne) {
110
- if (t.ye || t.pe) updatePendingSignal(t);
151
+ for (let t = e.u; t !== null; t = t.fe) {
152
+ if (t.be || t.ge) updatePendingSignal(t);
111
153
  }
112
154
  }
113
155
 
114
- function repollDownstreamVerdicts(e) {
115
- const t = new Set;
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 (t.has(e)) return;
118
- t.add(e);
119
- if (e.ye || e.pe) updatePendingSignal(e);
120
- for (let t = e.p; t !== null; t = t.de) visit(t.Ee);
121
- for (let t = e.G ?? null; t !== null; t = t.Ne) {
168
+ if (i.has(e)) return;
169
+ i.add(e);
170
+ if (e.be || e.ge) 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.ye;
130
- if (t && (t.be === undefined || t.be === NOT_PENDING)) {
180
+ const t = e.be;
181
+ if (t && (t.Ee === undefined || t.Ee === NOT_PENDING)) {
131
182
  const n = computePendingState(e);
132
- if (t.Ue !== n || t.ge !== NOT_PENDING) {
133
- t.Ue = n;
134
- t.ge = NOT_PENDING;
135
- t.Ie = clock;
183
+ if (t.Pe !== n || t.Ne !== NOT_PENDING) {
184
+ t.Pe = 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.pe;
141
- if (n && !(n.u & REACTIVE_DISPOSED)) {
142
- if ((n.be === undefined || n.be === NOT_PENDING) && n.ge === NOT_PENDING && !Object.is(n.Ue, e.Ue) && !(n.u & (REACTIVE_DIRTY | REACTIVE_CHECK))) {
143
- n.u |= REACTIVE_DIRTY;
191
+ const n = e.ge;
192
+ if (n && !(n.se & REACTIVE_DISPOSED)) {
193
+ if ((n.Ee === undefined || n.Ee === NOT_PENDING) && n.Ne === NOT_PENDING && !Object.is(n.Pe, e.Pe) && !(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.pe) {
204
+ if (!e.ge) {
154
205
  const t = latestReadActive;
155
206
  setLatestReadActive(false);
156
207
  const n = pendingCheckActive;
@@ -158,21 +209,21 @@ 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.pe = optimisticComputed(() => read(e));
162
- e.pe.en = e;
212
+ e.ge = optimisticComputed(() => read(e));
213
+ e.ge.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.pe;
219
+ return e.ge;
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.be !== undefined && e.be !== NOT_PENDING ? unwrapOverride(e.be) : e.Ue;
226
+ const i = e.Ee !== undefined && e.Ee !== NOT_PENDING ? unwrapOverride(e.Ee) : e.Pe;
176
227
  let r;
177
228
  try {
178
229
  // An untracked latest() read has no reading context, so read() never
@@ -181,22 +232,22 @@ function getLatestValueComputed(e) {
181
232
  // until the flush (#2922). Mirror the tracked-read pull here: mark the
182
233
  // queued staleness through the graph, then bring the shadow up to date.
183
234
  const e = queueFor(t);
184
- if (t.qe >= e.Le && !(t.u & (REACTIVE_DISPOSED | REACTIVE_ZOMBIE))) {
235
+ if (t.He >= e.Fe && !(t.se & (REACTIVE_DISPOSED | REACTIVE_ZOMBIE))) {
185
236
  markHeap(e);
186
237
  prepareComputed(t, true);
187
238
  }
188
239
  r = read(t);
189
240
  } catch (t) {
190
- if (t instanceof NotReadyError && (!context || !(e.i & STATUS_UNINITIALIZED))) return i;
241
+ if (t instanceof NotReadyError && (!context || !(e.S & STATUS_UNINITIALIZED))) return i;
191
242
  throw t;
192
243
  } finally {
193
244
  setLatestReadActive(n);
194
245
  }
195
- if (t.i & STATUS_PENDING) return i;
196
- if (stale && currentOptimisticLane && t.Je) {
197
- const e = findLane(t.Je);
246
+ if (t.S & STATUS_PENDING) return i;
247
+ if (stale && currentOptimisticLane && t.je) {
248
+ const e = findLane(t.je);
198
249
  const n = findLane(currentOptimisticLane);
199
- if (e !== n && e.Pe.size > 0) {
250
+ if (e !== n && e.de.size > 0) {
200
251
  return i;
201
252
  }
202
253
  }
@@ -204,18 +255,18 @@ function getLatestValueComputed(e) {
204
255
  // speculative value in _pendingValue; a contextless read() only surfaces
205
256
  // _value. Overrides stay authoritative (A17), and stale readers keep the
206
257
  // other transition's committed view, matching read()'s own selection.
207
- if (t.ge !== NOT_PENDING && !hasActiveOverride(t) && !(stale && t.ve && activeTransition !== t.ve)) return t.ge;
258
+ if (t.Ne !== NOT_PENDING && !hasActiveOverride(t) && !(stale && t.Ue && activeTransition !== t.Ue)) return t.Ne;
208
259
  return r;
209
260
  }
210
261
 
211
262
  /** The isPending()-probe read path, installed as GlobalQueue._pendingCheck. */ function pendingCheckRead(e, t, n, i) {
212
263
  setPendingCheckActive(false);
213
- if (typeof e.xe === "function") prepareComputed(e, true);
214
- const r = n.i;
264
+ if (typeof e.ke === "function") prepareComputed(e, true);
265
+ const r = n.S;
215
266
  if (t && r & STATUS_PENDING && r & STATUS_UNINITIALIZED) {
216
267
  if (tracking && e !== t) link(e, t);
217
268
  setPendingCheckActive(true);
218
- throw n.k;
269
+ throw n._;
219
270
  }
220
271
  collectPendingSources(e);
221
272
  if (i) collectPendingSources(i);
@@ -223,14 +274,14 @@ function getLatestValueComputed(e) {
223
274
  }
224
275
 
225
276
  function recordFreshRead(e, t) {
226
- if (pendingProbe !== null && e.ge !== NOT_PENDING && t === e.ge) pendingProbe.freshReads.add(e);
277
+ if (pendingProbe !== null && e.Ne !== NOT_PENDING && t === e.Ne) pendingProbe.freshReads.add(e);
227
278
  }
228
279
 
229
280
  function applyReask(e, t) {
230
- const n = !!(e.i & STATUS_PENDING);
231
- const i = t && !(n && !e.A);
232
- const r = n && e.A !== i;
233
- e.A = i;
281
+ const n = !!(e.S & STATUS_PENDING);
282
+ const i = t && !(n && !e._e);
283
+ const r = n && e._e !== i;
284
+ e._e = i;
234
285
  return r;
235
286
  }
236
287
 
@@ -270,7 +321,7 @@ function isPending(e) {
270
321
  } catch (e) {
271
322
  collectPending();
272
323
  if (e instanceof NotReadyError) {
273
- const t = !!(e.source?.i & STATUS_UNINITIALIZED);
324
+ const t = !!(e.source?.S & STATUS_UNINITIALIZED);
274
325
  if (i.found && !t) return true;
275
326
  if (context && t) throw e;
276
327
  }
@@ -284,24 +335,24 @@ function isPending(e) {
284
335
  // Hook installation (same late-binding pattern as GlobalQueue._update /
285
336
  // _propagateAffects): core call sites fire these behind the same guards the
286
337
  // direct calls used, so behavior is identical once this module loads.
287
- GlobalQueue._e = syncCompanions;
338
+ GlobalQueue.Ie = syncCompanions;
288
339
 
289
- GlobalQueue.P = updatePendingSignal;
340
+ GlobalQueue.ae = updatePendingSignal;
290
341
 
291
- GlobalQueue.me = updateChildCompanions;
342
+ GlobalQueue.he = updateChildCompanions;
292
343
 
293
- GlobalQueue.R = snapCompanionsToState;
344
+ GlobalQueue.un = snapCompanionsToState;
294
345
 
295
- GlobalQueue.Gt = latestRead;
346
+ GlobalQueue.St = latestRead;
296
347
 
297
- GlobalQueue.Pt = pendingCheckRead;
348
+ GlobalQueue.dt = pendingCheckRead;
298
349
 
299
- GlobalQueue.kt = recordFreshRead;
350
+ GlobalQueue.Rt = recordFreshRead;
300
351
 
301
- GlobalQueue.tt = applyReask;
352
+ GlobalQueue.ze = applyReask;
302
353
 
303
- GlobalQueue.nt = repollDownstreamVerdicts;
354
+ GlobalQueue.k = repollDownstreamVerdicts;
304
355
 
305
- GlobalQueue.wt = witnessAffects;
356
+ GlobalQueue.Lt = witnessAffects;
306
357
 
307
358
  export { isPending, latest };