@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 { STATUS_UNINITIALIZED, STATUS_ERROR, STATUS_PENDING, REACTIVE_DIRTY, REACTIVE_OPTIMISTIC_DIRTY, NOT_PENDING } from "./constants.js";
1
+ import { STATUS_UNINITIALIZED, STATUS_ERROR, STATUS_PENDING, REACTIVE_DIRTY, REACTIVE_OPTIMISTIC_DIRTY, NOT_PENDING, CONFIG_AUTO_DISPOSE } from "./constants.js";
2
2
 
3
3
  import { untrack, context, setSignal } from "./core.js";
4
4
 
@@ -6,7 +6,7 @@ import "./invariants.js";
6
6
 
7
7
  import { NotReadyError, StatusError } from "./error.js";
8
8
 
9
- import { trimStaleDeps } from "./graph.js";
9
+ import { trimStaleDeps, unobserved } from "./graph.js";
10
10
 
11
11
  import { enqueueSub } from "./heap.js";
12
12
 
@@ -22,79 +22,75 @@ import { globalQueue, GlobalQueue, clock, schedule, flush, queuePendingNode, ins
22
22
  // overlapping source landed beside the Set and removePendingSource refused
23
23
  // to clear it, stranding the Set members' pending forever (#2893).
24
24
  function addPendingSource(e, n) {
25
- if (e.m?.has(n)) return false;
26
- (e.m ??= new Set).add(n);
25
+ if (e.oe?.has(n)) return false;
26
+ (e.oe ??= new Set).add(n);
27
27
  return true;
28
28
  }
29
29
 
30
30
  function removePendingSource(e, n) {
31
- if (!e.m?.delete(n)) return false;
32
- if (e.m.size === 0) e.m = undefined;
31
+ if (!e.oe?.delete(n)) return false;
32
+ if (e.oe.size === 0) e.oe = undefined;
33
33
  return true;
34
34
  }
35
35
 
36
36
  function clearPendingSources(e) {
37
- e.m?.clear();
38
- e.m = undefined;
37
+ e.oe?.clear();
38
+ e.oe = undefined;
39
39
  }
40
40
 
41
41
  function setPendingError(e, n, r) {
42
42
  if (!n) {
43
- e.k = null;
43
+ e._ = null;
44
44
  return;
45
45
  }
46
46
  if (r instanceof NotReadyError && r.source === n) {
47
- e.k = r;
47
+ e._ = r;
48
48
  return;
49
49
  }
50
- const t = e.k;
50
+ const t = e._;
51
51
  if (!(t instanceof NotReadyError) || t.source !== n) {
52
- e.k = new NotReadyError(n);
52
+ e._ = new NotReadyError(n);
53
53
  }
54
54
  }
55
55
 
56
56
  function forEachDependent(e, n) {
57
- for (let r = e.p; r !== null; r = r.de) n(r.Ee, r);
57
+ for (let r = e.o; r !== null; r = r.ue) n(r.le, r);
58
58
  // `?? null`: affects() marks route plain signals (no `_child` slot) through here.
59
- for (let r = e.G ?? null; r !== null; r = r.Ne) {
60
- for (let e = r.p; e !== null; e = e.de) n(e.Ee, e);
59
+ for (let r = e.u ?? null; r !== null; r = r.fe) {
60
+ for (let e = r.o; e !== null; e = e.ue) n(e.le, e);
61
61
  }
62
62
  }
63
63
 
64
64
  // Queue a node to re-run on the next flush (used both when a pending source
65
65
  // settles and when an `isPending` observer must re-evaluate after a real error):
66
66
  // shared scheduling helper in heap.ts (tracked effects bypass the heap).
67
- function settlePendingSource(e, n = e,
68
- // Mark release runs inside queue finalization: companion writes must go
69
- // through the settlement snap (committed), because a setSignal here would
70
- // open a fresh transition-scoped override window that nothing reverts.
71
- r = false) {
72
- let t = false;
73
- const o = new Set;
74
- // Companion updates no-op without the verdict layer (null hooks).
75
- const u = r ? GlobalQueue.R : GlobalQueue.P;
76
- const settle = e => {
77
- if (o.has(e) || !removePendingSource(e, n)) return;
78
- o.add(e);
79
- e.Ie = clock;
80
- const r = e.m?.values().next().value;
81
- if (r) {
82
- setPendingError(e, r);
83
- u !== null && u(e);
67
+ function settlePendingSource(e) {
68
+ let n = false;
69
+ const r = new Set;
70
+ // Companion updates no-op without the verdict layer (null hook).
71
+ const t = GlobalQueue.ae;
72
+ const settle = o => {
73
+ if (r.has(o) || !removePendingSource(o, e)) return;
74
+ r.add(o);
75
+ o.ce = clock;
76
+ const u = o.oe?.values().next().value;
77
+ if (u) {
78
+ setPendingError(o, u);
79
+ t !== null && t(o);
84
80
  } else {
85
- e.i &= ~STATUS_PENDING;
86
- setPendingError(e);
87
- u !== null && u(e);
88
- if (e.Re) {
89
- enqueueSub(e);
90
- t = true;
81
+ o.S &= ~STATUS_PENDING;
82
+ setPendingError(o);
83
+ t !== null && t(o);
84
+ if (o.Se) {
85
+ enqueueSub(o);
86
+ n = true;
91
87
  }
92
- e.Re = false;
88
+ o.Se = false;
93
89
  }
94
- forEachDependent(e, settle);
90
+ forEachDependent(o, settle);
95
91
  };
96
92
  forEachDependent(e, settle);
97
- if (t) schedule();
93
+ if (n) schedule();
98
94
  }
99
95
 
100
96
  // Object-thenable detection (Promises/A+ shape).
@@ -112,34 +108,34 @@ function handleAsync(e, n, r) {
112
108
  });
113
109
  }
114
110
  if (!o && !t) {
115
- e.Ae = null;
111
+ e.Te = null;
116
112
  return n;
117
113
  }
118
- e.Ae = n;
114
+ e.Te = n;
119
115
  let u;
120
116
  const handleError = r => {
121
- if (e.Ae !== n) return;
117
+ if (e.Te !== n) return;
122
118
  globalQueue.initTransition(resolveTransition(e));
123
119
  // NotReadyError from rejected promises should be treated as pending, not error
124
120
  notifyStatus(e, r instanceof NotReadyError ? STATUS_PENDING : STATUS_ERROR, r);
125
- e.Ie = clock;
121
+ e.ce = clock;
126
122
  };
127
123
  const asyncWrite = (t, o) => {
128
- if (e.Ae !== n) return;
124
+ if (e.Te !== n) return;
129
125
  // If the node was dirtied by a newer write (optimistic override or regular),
130
126
  // skip this stale async result — the upcoming flush will recompute the node
131
127
  // with the new value, creating a fresh Promise that supersedes this one.
132
- if (e.u & (REACTIVE_DIRTY | REACTIVE_OPTIMISTIC_DIRTY)) return;
128
+ if (e.se & (REACTIVE_DIRTY | REACTIVE_OPTIMISTIC_DIRTY)) return;
133
129
  globalQueue.initTransition(resolveTransition(e));
134
- const u = !!(e.i & STATUS_UNINITIALIZED);
130
+ const u = !!(e.S & STATUS_UNINITIALIZED);
135
131
  trimStaleDeps(e);
136
132
  clearStatus(e);
137
133
  const l = resolveLane(e);
138
- if (l) l.Pe.delete(e);
134
+ if (l) l.de.delete(e);
139
135
  if (r) {
140
136
  r(t);
141
137
  if (u) clearStatus(e, true);
142
- } else if (e.be !== undefined) {
138
+ } else if (e.Ee !== undefined) {
143
139
  // Optimistic node — resting OR covered by an active override — holds
144
140
  // through the shared pending-node path, exactly like a plain async memo,
145
141
  // so the commit clears STATUS_UNINITIALIZED (#2806) and elevation to
@@ -149,8 +145,8 @@ function handleAsync(e, n, r) {
149
145
  // (A17 — every reader sees the override); the revert reveals whatever
150
146
  // has committed by then, so corrections reveal atomically with their
151
147
  // transition rather than escaping it.
152
- if (e.ge === NOT_PENDING) queuePendingNode(e);
153
- e.ge = t;
148
+ if (e.Ne === NOT_PENDING) queuePendingNode(e);
149
+ e.Ne = t;
154
150
  // The hold is a companion-visible write like any other (A13/A19): the
155
151
  // clearStatus() above computed its verdict before the hold existed, so
156
152
  // isPending must re-derive (the value is not final until commit — V1)
@@ -158,22 +154,22 @@ function handleAsync(e, n, r) {
158
154
  // only notified when the hold is visible to them: under an active
159
155
  // override every reader sees the override (A17), so waking subs would
160
156
  // re-show an unchanged view — the revert is the notification point.
161
- GlobalQueue._e !== null && GlobalQueue._e(e, t);
157
+ GlobalQueue.Ie !== null && GlobalQueue.Ie(e, t);
162
158
  if (!hasActiveOverride(e)) insertSubs(e);
163
- e.Ie = clock;
159
+ e.ce = clock;
164
160
  } else if (l) {
165
161
  // Route through lane's effect queue for independent flushing
166
- const n = e.De;
167
- const r = e.Ue;
168
- const o = e.Ge;
162
+ const n = e.Ae;
163
+ const r = e.Pe;
164
+ const o = e.Re;
169
165
  try {
170
166
  if (!n && u || !o || !o(t, r)) {
171
- e.Ue = t;
172
- e.Ie = clock;
167
+ e.Pe = t;
168
+ e.ce = clock;
173
169
  // The latest() shadow write gives latest() effects independent lanes; the
174
170
  // _pendingSignal update is a no-op repeat of the clearStatus() call above
175
171
  // (computePendingState doesn't read _value).
176
- GlobalQueue._e !== null && GlobalQueue._e(e, t);
172
+ GlobalQueue.Ie !== null && GlobalQueue.Ie(e, t);
177
173
  insertSubs(e, true);
178
174
  }
179
175
  } catch (n) {
@@ -197,18 +193,34 @@ function handleAsync(e, n, r) {
197
193
  flush();
198
194
  o?.();
199
195
  };
196
+ // A pending node's in-flight promise is an observer: `unlinkSubs` skips
197
+ // autodispose while STATUS_PENDING so subscriber churn can't orphan the
198
+ // work (a lazy async memo would otherwise tear down and re-execute — one
199
+ // fetch per suspended re-read). Settling is that observer's release, so
200
+ // it runs the same last-one-out check the other release sites run.
201
+ const settleAutodispose = () => {
202
+ if (e.T & CONFIG_AUTO_DISPOSE && !e.o && !(e.S & STATUS_PENDING)) {
203
+ unobserved(e);
204
+ }
205
+ };
200
206
  if (o) {
201
207
  let r = false, t = false, o, l = true;
202
208
  n.then(e => {
203
209
  if (l) {
204
210
  u = e;
205
211
  r = true;
206
- } else asyncWrite(e);
212
+ } else {
213
+ asyncWrite(e);
214
+ settleAutodispose();
215
+ }
207
216
  }, e => {
208
217
  if (l) {
209
218
  o = e;
210
219
  t = true;
211
- } else handleError(e);
220
+ } else {
221
+ handleError(e);
222
+ settleAutodispose();
223
+ }
212
224
  });
213
225
  l = false;
214
226
  if (t) {
@@ -242,7 +254,7 @@ function handleAsync(e, n, r) {
242
254
  i = r;
243
255
  f = true;
244
256
  if (r.done) o = true;
245
- } else if (e.Ae !== n) {
257
+ } else if (e.Te !== n) {
246
258
  return;
247
259
  } else if (!r.done) {
248
260
  t = true;
@@ -261,7 +273,7 @@ function handleAsync(e, n, r) {
261
273
  if (c) {
262
274
  s = r;
263
275
  a = true;
264
- } else if (e.Ae === n) {
276
+ } else if (e.Te === n) {
265
277
  o = true;
266
278
  handleError(r);
267
279
  }
@@ -293,86 +305,74 @@ function handleAsync(e, n, r) {
293
305
  }
294
306
 
295
307
  function clearStatus(e, n = false) {
296
- if (e.m) clearPendingSources(e);
297
- if (e.Re) e.Re = false;
308
+ if (e.oe) clearPendingSources(e);
309
+ if (e.Se) e.Se = false;
298
310
  // The pending window is over; its quiet classification dies with it.
299
311
  // (Unconditional: _reask is baked into the node literals, so this is a
300
312
  // plain store to an existing slot — no shape change.)
301
- e.A = false;
302
- e.i = n ? 0 : e.i & STATUS_UNINITIALIZED;
303
- if (e.k) setPendingError(e);
313
+ e._e = false;
314
+ e.S = n ? 0 : e.S & STATUS_UNINITIALIZED;
315
+ if (e._) setPendingError(e);
304
316
  // Update pending signal for isPending() reactivity (companions only exist
305
317
  // once the verdict layer created them, which installs the hooks).
306
- if (e.ye || e.pe) GlobalQueue.P(e);
307
- if (e.G && GlobalQueue.me !== null) GlobalQueue.me(e);
308
- if (e.C) e.C();
318
+ if (e.be || e.ge) GlobalQueue.ae(e);
319
+ if (e.u && GlobalQueue.he !== null) GlobalQueue.he(e);
320
+ if (e.i) e.i();
309
321
  }
310
322
 
311
323
  function notifyStatus(e, n, r, t, o) {
312
324
  // Wrap regular errors to track source node
313
325
  if (n === STATUS_ERROR && !(r instanceof StatusError) && !(r instanceof NotReadyError)) r = new StatusError(e, r);
314
326
  const u = n === STATUS_PENDING && r instanceof NotReadyError ? r.source : undefined;
315
- // Mark-sourced propagation must not capture subscribers into the marking
316
- // action's transaction (#2893): they carry no held value needing a
317
- // transition-scheduled commit, and stamping `_transition` on them would
318
- // freeze unrelated writes that share a downstream memo until the action
319
- // settles. Real async keeps queuing (its commits ride the transition).
320
- const l = u?.l !== undefined;
321
- // A real error is a settled verdict a mark must not erase (#2893): landing
322
- // STATUS_PENDING here would clobber `_error` with the sentinel's
323
- // NotReadyError, and unlike real async there is no arriving value whose
324
- // recompute would surface the error again. Descent stops too — everything
325
- // downstream holds the propagated error for the same reason.
326
- if (l && e.i & STATUS_ERROR) return;
327
- const i = u === e;
328
- const s = n === STATUS_PENDING && e.be !== undefined && !i;
329
- const f = s && hasActiveOverride(e);
327
+ const l = u === e;
328
+ const i = n === STATUS_PENDING && e.Ee !== undefined && !l;
329
+ const s = i && hasActiveOverride(e);
330
330
  if (!t) {
331
331
  if (n === STATUS_PENDING && u) {
332
332
  addPendingSource(e, u);
333
- e.i = STATUS_PENDING | e.i & STATUS_UNINITIALIZED;
333
+ e.S = STATUS_PENDING | e.S & STATUS_UNINITIALIZED;
334
334
  // Preserve the current source on this propagation so render-effect notification
335
335
  // can register every distinct pending source with the transition.
336
336
  setPendingError(e, u, r);
337
337
  } else {
338
338
  clearPendingSources(e);
339
- e.i = n | (n !== STATUS_ERROR ? e.i & STATUS_UNINITIALIZED : 0);
340
- e.k = r;
339
+ e.S = n | (n !== STATUS_ERROR ? e.S & STATUS_UNINITIALIZED : 0);
340
+ e._ = r;
341
341
  }
342
- GlobalQueue.P !== null && GlobalQueue.P(e);
343
- if (e.G && GlobalQueue.me !== null) GlobalQueue.me(e);
342
+ GlobalQueue.ae !== null && GlobalQueue.ae(e);
343
+ if (e.u && GlobalQueue.he !== null) GlobalQueue.he(e);
344
344
  }
345
345
  if (o && !t) {
346
346
  assignOrMergeLane(e, o);
347
347
  }
348
- const a = t || f;
349
- const c = t || s ? undefined : o;
350
- if (e.C) {
348
+ const f = t || s;
349
+ const a = t || i ? undefined : o;
350
+ if (e.i) {
351
351
  if (t && n === STATUS_PENDING) {
352
352
  return;
353
353
  }
354
- if (a) {
355
- e.C(n, r);
354
+ if (f) {
355
+ e.i(n, r);
356
356
  } else {
357
- e.C();
357
+ e.i();
358
358
  }
359
359
  return;
360
360
  }
361
361
  forEachDependent(e, (e, t) => {
362
- e.Ie = clock;
363
- if (n === STATUS_PENDING && u && !e.m?.has(u) || n !== STATUS_PENDING && (e.k !== r || e.m)) {
362
+ e.ce = clock;
363
+ if (n === STATUS_PENDING && u && !e.oe?.has(u) || n !== STATUS_PENDING && (e._ !== r || e.oe)) {
364
364
  // A pending-observer link is the subscription an `isPending` read created.
365
365
  // It exists so the observer re-runs when the source settles, but it must
366
366
  // not carry a real (non-NotReadyError) error — the synchronous `isPending`
367
367
  // read swallows those, and the async path must match. Re-run the observer
368
368
  // so `isPending` re-evaluates (to not-pending) instead of forwarding.
369
- if (t.Qe && n !== STATUS_PENDING && !(r instanceof NotReadyError)) {
369
+ if (t.De && n !== STATUS_PENDING && !(r instanceof NotReadyError)) {
370
370
  enqueueSub(e);
371
371
  schedule();
372
372
  return;
373
373
  }
374
- if (!a && !l && !e.ve) queuePendingNode(e);
375
- notifyStatus(e, n, r, a, c);
374
+ if (!f && !e.Ue) queuePendingNode(e);
375
+ notifyStatus(e, n, r, f, a);
376
376
  }
377
377
  });
378
378
  }