@solidjs/signals 2.0.0-rc.5 → 2.0.0-rc.6

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.
@@ -2,7 +2,9 @@ import { CONFIG_AUTO_DISPOSE, STATUS_PENDING, NOT_PENDING, CONFIG_OPTIMISTIC, CO
2
2
 
3
3
  import { computed, setSignal, isEqual } from "../../core/core.js";
4
4
 
5
- import { activeTransition, GlobalQueue, runAsTransitionBatch, globalQueue, currentTransition } from "../../core/scheduler.js";
5
+ import { getOwner } from "../../core/owner.js";
6
+
7
+ import { activeTransition, GlobalQueue, currentTransition, globalQueue, createTransition, runAsTransitionBatch } from "../../core/scheduler.js";
6
8
 
7
9
  import "../../core/invariants.js";
8
10
 
@@ -108,14 +110,22 @@ function installNextBlockedHalf() {
108
110
  const e = GlobalQueue.dn;
109
111
  GlobalQueue.dn = t => {
110
112
  for (const e of t.En) {
111
- const t = e?.[$TARGET];
112
- const n = t?.fam?.node;
113
+ const n = e?.[$TARGET];
114
+ const i = n?.fam;
115
+ const o = i?.node;
113
116
  // The hold exists to keep optimistic state alive until the store's own
114
117
  // truth lands (#2951). Once the family carries NO live overrides (a
115
118
  // landing consumed them, or they never existed), a pending firewall is
116
119
  // no reason to park the transaction — blocking then leaks it forever
117
120
  // when the in-flight question is never answered (undisposed fixtures).
118
- if (n != null && n.S & STATUS_PENDING && familyHasLiveOverrides(t.fam)) return true;
121
+ if (o == null || !(o.S & STATUS_PENDING)) continue;
122
+ // Ownership is declared (#3146): only the flight's OWN transaction
123
+ // parks on the flight (the #2951 anchor routed the bare write there).
124
+ // A transaction that merely brushed the store never waits for truth
125
+ // it does not carry.
126
+ const r = i.ft != null ? liveTransition(i.ft) : null;
127
+ if (r !== null && r !== currentTransition(t)) continue;
128
+ if (familyHasLiveOverrides(i)) return true;
119
129
  }
120
130
  return e(t);
121
131
  };
@@ -129,10 +139,10 @@ function familyHasLiveOverrides(e) {
129
139
  if (t === null) continue;
130
140
  for (const e of Reflect.ownKeys(t)) {
131
141
  const n = t[e];
132
- if (n.o?.Oe !== undefined && n.o?.Oe !== NOT_PENDING) return true;
142
+ if (n.o?.Pe !== undefined && n.o?.Pe !== NOT_PENDING) return true;
133
143
  }
134
144
  }
135
- if (e.k !== null && e.k.o?.Oe !== undefined && e.k.o?.Oe !== NOT_PENDING) return true;
145
+ if (e.k !== null && e.k.o?.Pe !== undefined && e.k.o?.Pe !== NOT_PENDING) return true;
136
146
  }
137
147
  t.clear();
138
148
  // nothing live — drop the bookkeeping
@@ -153,26 +163,47 @@ function createOptimisticStoreNext(e, t, n) {
153
163
  shallow: !!n?.shallow,
154
164
  opt: true
155
165
  };
156
- const s = wrapNext(o, null, null, r);
157
- r.px = s;
166
+ const l = wrapNext(o, null, null, r);
167
+ r.px = l;
158
168
  // Same key resolution the projection channels use ("id" default) — replay's
159
169
  // satisfaction rule reads it off the family.
160
- const l = n?.key === undefined ? "id" : n.key;
161
- r.key = typeof l === "function" ? l : l === null ? null : e => isWrappable(e) ? e[l] : undefined;
170
+ const s = n?.key === undefined ? "id" : n.key;
171
+ r.key = typeof s === "function" ? s : s === null ? null : e => isWrappable(e) ? e[s] : undefined;
162
172
  if (r.shallow) {
163
- s[$TARGET].s = true;
173
+ l[$TARGET].s = true;
164
174
  markRawIngest(o);
165
175
  }
166
176
  if (i) {
167
177
  const t = e;
178
+ // #3146: an async settle event belongs to the flight's OWN transaction.
179
+ // A live declared one re-enters (a merge if the generic settle path
180
+ // already entered a graph-stamped stranger — the landing supersedes any
181
+ // recompute deriving from that stranger's world); a dead one renews (per
182
+ // A18(1) each arrival reveals on its own schedule, so per-yield
183
+ // transactions die with their commit and the next settle event opens the
184
+ // flight's next one — still declared, never anonymous). An UNDECLARED
185
+ // flight (loading window) keeps the ambient reveal (#2933: the loading
186
+ // rail is transaction-invisible).
187
+ const enterFlightTransition = () => {
188
+ const e = r.ft;
189
+ if (e == null) return;
190
+ let t = liveTransition(e);
191
+ if (t === null) r.ft = t = createTransition();
192
+ r.node.Ae = t;
193
+ globalQueue.initTransition(t);
194
+ };
168
195
  // Landing router (#3164 fold ruling): while a transaction retains
169
196
  // optimistic edits on this family, truth landings stage INTO it and
170
197
  // reveal atomically at settle; with no retainer they commit immediately
171
198
  // under the authoritative posture (async commits land outside the
172
- // computed's sync body, so the posture is re-applied here).
199
+ // computed's sync body, so the posture is re-applied here) — inside the
200
+ // flight-owned transaction (#3146). Sync commits (the derive's body,
201
+ // owner is the firewall itself) reveal with their own recompute's flush.
173
202
  const wrapCommit = (e, t) => {
174
203
  const n = retainingTransition(r);
175
- if (n === null) runAuthoritative(e); else stageLanding(r, n, t);
204
+ if (n !== null) return void stageLanding(r, n, t);
205
+ if (getOwner() !== r.node) enterFlightTransition();
206
+ runAuthoritative(e);
176
207
  };
177
208
  // Draft writes (the derive mutating its draft, sync body and post-await
178
209
  // continuations alike) are the same truth channel per-operation: bind
@@ -184,24 +215,61 @@ function createOptimisticStoreNext(e, t, n) {
184
215
  const t = retainingTransition(r);
185
216
  if (t === null) e(); else runFolded(t, e);
186
217
  };
218
+ // Flight declaration (#3146): a recompute that registered a truth-flight
219
+ // OWNS its transaction. The ask's transaction is recorded on the family
220
+ // (created by the flight's own pending throw when none was ambient, the
221
+ // causing write's/refresh's when one was — graph-driven causality) and
222
+ // the firewall is stamped so every settle path resolves the flight's
223
+ // transaction by construction, not by whatever last brushed the node.
224
+ // When the ask took none (a dead stale stamp — the previous flight's,
225
+ // cleared nowhere — bare-returns the pre-throw entry), the flight opens
226
+ // its own here, same activation point as the pre-throw's creation: the
227
+ // ambient batch (the causing write, same-tick bare optimism) adopts into
228
+ // it exactly as it would have there, and the pending notification that
229
+ // follows this unwind registers observers against it. The flight
230
+ // also registers as its own async reporter: the transaction lives
231
+ // exactly as long as the question is unanswered, observed or not — the
232
+ // #2951 refetch-hold no longer depends on a tracked observer having
233
+ // happened to register one. Loading-window flights declare nothing
234
+ // (#2933: the loading rail is transaction-invisible); a sync run clears
235
+ // the declaration.
236
+ const declareFlight = e => {
237
+ if (e.o?.Ie == null) {
238
+ if (!e.Ne) r.ft = null;
239
+ return;
240
+ }
241
+ if (e.Ne) return;
242
+ let t = activeTransition;
243
+ if (t === null) globalQueue.initTransition(t = createTransition());
244
+ r.ft = t;
245
+ e.Ae = t;
246
+ let n = t._e.get(e);
247
+ if (n === undefined) t._e.set(e, n = new Set);
248
+ n.add(e);
249
+ };
187
250
  let i;
188
251
  if (n?.seedLoadingValue) i = {
189
252
  loadingValue: undefined
190
253
  };
191
254
  const o = computed(() => {
192
- runAuthoritative(() => runProjectionComputedNext(s, t, n?.key === undefined ? "id" : n.key, wrapCommit, aroundDraftWrite));
255
+ const e = getOwner();
256
+ try {
257
+ runAuthoritative(() => runProjectionComputedNext(l, t, n?.key === undefined ? "id" : n.key, wrapCommit, aroundDraftWrite));
258
+ } finally {
259
+ declareFlight(e);
260
+ }
193
261
  }, i);
194
262
  o.T &= ~CONFIG_AUTO_DISPOSE;
195
263
  r.node = o;
196
264
  }
197
- return [ s, e => {
265
+ return [ l, e => {
198
266
  // Retention ledger (#3164): record the owning transaction so landings
199
267
  // know to fold. Captured at entry — the action machinery has the
200
268
  // transaction ambient while user code runs; a bare write with no
201
269
  // transaction retains nothing (it rides the flight's own transition
202
270
  // per #2951 and dies with it).
203
271
  const t = activeTransition;
204
- storeSetterNext(s, e);
272
+ storeSetterNext(l, e);
205
273
  if (t !== null) (r.rt ??= new Set).add(t);
206
274
  } ];
207
275
  }
@@ -281,8 +349,8 @@ function createOptimisticStoreNext(e, t, n) {
281
349
  if (!isWrappable(i)) continue;
282
350
  const r = n(i);
283
351
  if (r === undefined) continue;
284
- const s = (o ??= new Map).get(r);
285
- if (s === undefined) o.set(r, [ i ]); else s.push(i);
352
+ const l = (o ??= new Map).get(r);
353
+ if (l === undefined) o.set(r, [ i ]); else l.push(i);
286
354
  }
287
355
  // Echo adoption: an optimistic structural add whose key the landing
288
356
  // confirms must keep its raw (and so its proxy — list drivers keep the
@@ -292,35 +360,35 @@ function createOptimisticStoreNext(e, t, n) {
292
360
  // keys; overlay rows only extend coverage. Adopted raws enter staged
293
361
  // truth; at settle the override reverts and the reveal re-seats the
294
362
  // same raw.
295
- const s = e[$TARGET]?.n;
296
- if (s != null) {
297
- for (const e of Reflect.ownKeys(s)) {
298
- const t = s[e];
363
+ const l = e[$TARGET]?.n;
364
+ if (l != null) {
365
+ for (const e of Reflect.ownKeys(l)) {
366
+ const t = l[e];
299
367
  if (!hasActiveOverride(t)) continue;
300
- const i = unwrapValue(unwrapOverride(t.o.Oe));
368
+ const i = unwrapValue(unwrapOverride(t.o.Pe));
301
369
  if (!isWrappable(i)) continue;
302
370
  const r = n(i);
303
371
  if (r === undefined) continue;
304
- const l = (o ??= new Map).get(r);
305
- if (l === undefined) o.set(r, [ i ]); else l.push(i);
372
+ const s = (o ??= new Map).get(r);
373
+ if (s === undefined) o.set(r, [ i ]); else s.push(i);
306
374
  }
307
375
  }
308
376
  for (let r = 0; r < i; r++) {
309
377
  const i = t[r];
310
- let s;
378
+ let l;
311
379
  if (isWrappable(i) && o !== null) {
312
380
  const e = n(i);
313
381
  if (e !== undefined) {
314
382
  for (const [t, n] of o) {
315
383
  if (!sameKey(t, e)) continue;
316
- s = n.shift();
384
+ l = n.shift();
317
385
  if (n.length === 0) o.delete(t);
318
386
  break;
319
387
  }
320
388
  }
321
389
  }
322
- if (s !== undefined) {
323
- if (unwrapValue(e[r]) !== s) e[r] = s;
390
+ if (l !== undefined) {
391
+ if (unwrapValue(e[r]) !== l) e[r] = l;
324
392
  stagedApply(e[r], i, n);
325
393
  } else {
326
394
  const t = unwrapValue(e[r]);
@@ -343,13 +411,13 @@ function createOptimisticStoreNext(e, t, n) {
343
411
  for (const o of Reflect.ownKeys(t)) {
344
412
  if (i && o === "length") continue;
345
413
  const r = t[o];
346
- const s = unwrapValue(e[o]);
347
- if (s === r) continue;
348
- if (isWrappable(r) && isWrappable(s) && Array.isArray(r) === Array.isArray(s)) {
414
+ const l = unwrapValue(e[o]);
415
+ if (l === r) continue;
416
+ if (isWrappable(r) && isWrappable(l) && Array.isArray(r) === Array.isArray(l)) {
349
417
  // Different-keyed entities never merge (tentative-channel parity):
350
418
  // the incoming object replaces the slot wholesale.
351
419
  if (n !== null) {
352
- const t = n(s);
420
+ const t = n(l);
353
421
  const i = n(r);
354
422
  if (t !== undefined && i !== undefined && !sameKey(t, i)) {
355
423
  e[o] = r;
@@ -357,7 +425,7 @@ function createOptimisticStoreNext(e, t, n) {
357
425
  }
358
426
  }
359
427
  stagedApply(e[o], r, n);
360
- } else if (!isEqual(s, r) && !targetsEqual(s, r)) {
428
+ } else if (!isEqual(l, r) && !targetsEqual(l, r)) {
361
429
  e[o] = r;
362
430
  }
363
431
  }
@@ -373,13 +441,18 @@ function createOptimisticStoreNext(e, t, n) {
373
441
  * overrides — the same view the draft was seeded from) and emit engine writes
374
442
  * for exactly the changed keys. Visible-view diffing keeps no-op writes from
375
443
  * entangling lanes (RUL-10 / opt R38). */ function notifyOptimisticWrites(e, t) {
376
- // A bare write while the store's own truth is in flight rides THAT
377
- // transaction (#2951, legacy parity): entangle the firewall's transition so
378
- // the override survives until the refetch settles instead of flash-reverting
444
+ // A bare write while the store's own truth is in flight rides the FLIGHT'S
445
+ // OWN transaction (#2951 via the #3146 declaration): entangle it so the
446
+ // override survives until the refetch settles instead of flash-reverting
379
447
  // at plain flush end. The blocked-check store-half keeps that transaction
380
- // from settling while the firewall is pending.
381
- const n = e.fam?.node;
382
- if (n?.Ae) globalQueue.initTransition(n.Ae);
448
+ // from settling while the firewall is pending. Declared ownership replaces
449
+ // the old circumstantial route through the firewall's `_transition` stamp,
450
+ // which was whatever last brushed the node.
451
+ const n = e.fam?.ft;
452
+ if (n != null) {
453
+ const e = liveTransition(n);
454
+ if (e !== null) globalQueue.initTransition(e);
455
+ }
383
456
  const i = e.v;
384
457
  // Patch channel (override-application site): the draft IS the intended
385
458
  // visible state; prev is the view before these overrides apply. Bypasses
@@ -398,26 +471,26 @@ function createOptimisticStoreNext(e, t, n) {
398
471
  }
399
472
  const visible = (t, n) => {
400
473
  const i = e.n?.[t];
401
- return i !== undefined && hasActiveOverride(i) ? unwrapOverride(i.o?.Oe) : n;
474
+ return i !== undefined && hasActiveOverride(i) ? unwrapOverride(i.o?.Pe) : n;
402
475
  };
403
476
  const visiblePresent = t => {
404
477
  const n = e.h?.[t];
405
- return n !== undefined && hasActiveOverride(n) ? !!unwrapOverride(n.o?.Oe) : t in i;
478
+ return n !== undefined && hasActiveOverride(n) ? !!unwrapOverride(n.o?.Pe) : t in i;
406
479
  };
407
480
  let o = false;
408
481
  const r = Array.isArray(t);
409
482
  for (const n of Reflect.ownKeys(t)) {
410
483
  if (r && n === "length") continue;
411
- const s = unwrapValue(t[n]);
484
+ const l = unwrapValue(t[n]);
412
485
  if (!visiblePresent(n)) {
413
486
  // Optimistic add: value node + presence node + membership bump.
414
- setSignal(getNode(e, n, i[n]), () => s);
487
+ setSignal(getNode(e, n, i[n]), () => l);
415
488
  setSignal(getHasNode(e, n, n in i), true);
416
489
  o = true;
417
490
  } else {
418
491
  const t = visible(n, i[n]);
419
- if (!isEqual(t, s) && !targetsEqual(t, s)) {
420
- setSignal(getNode(e, n, t), () => s);
492
+ if (!isEqual(t, l) && !targetsEqual(t, l)) {
493
+ setSignal(getNode(e, n, t), () => l);
421
494
  if (r) o = true;
422
495
  }
423
496
  }
@@ -467,7 +540,7 @@ function createOptimisticStoreNext(e, t, n) {
467
540
  for (const e of Reflect.ownKeys(i)) {
468
541
  const n = i[e];
469
542
  if (!hasActiveOverride(n)) continue;
470
- const o = unwrapOverride(n.o?.Oe);
543
+ const o = unwrapOverride(n.o?.Pe);
471
544
  if (e === "length" && Array.isArray(t)) {
472
545
  if (t.length !== o) ensure().length = o;
473
546
  } else if (!isEqual(t[e], o)) ensure()[e] = o;
@@ -478,7 +551,7 @@ function createOptimisticStoreNext(e, t, n) {
478
551
  for (const e of Reflect.ownKeys(o)) {
479
552
  const i = o[e];
480
553
  if (!hasActiveOverride(i)) continue;
481
- const r = !!unwrapOverride(i.o?.Oe);
554
+ const r = !!unwrapOverride(i.o?.Pe);
482
555
  if (!r && e in (n ?? t)) delete ensure()[e];
483
556
  }
484
557
  }
@@ -489,11 +562,11 @@ function applyTentative(e, t, n) {
489
562
  const i = e.pb ?? e.v;
490
563
  const o = optimisticView(e, i);
491
564
  const r = e.fam.map;
492
- const s = Array.isArray(t);
493
- if (Array.isArray(o) !== s) return;
565
+ const l = Array.isArray(t);
566
+ if (Array.isArray(o) !== l) return;
494
567
  // kind change at root: flat overrides below
495
- const l = [];
496
- const u = s ? [ ...t ] : shallowWithSymbols(t);
568
+ const s = [];
569
+ const u = l ? [ ...t ] : shallowWithSymbols(t);
497
570
  const match = (e, t) => {
498
571
  if (!isWrappable(e) || !isWrappable(t)) return null;
499
572
  if (rawValuesUsed && (isRawValue(e) || isRawValue(t))) return null;
@@ -507,13 +580,13 @@ function applyTentative(e, t, n) {
507
580
  }
508
581
  return r.get(unwrapValue(e)) ?? null;
509
582
  };
510
- if (s) {
583
+ if (l) {
511
584
  const e = o;
512
585
  let i = null;
513
586
  for (let o = 0; o < t.length; o++) {
514
587
  const r = t[o];
515
588
  if (!isWrappable(r)) continue;
516
- let s;
589
+ let l;
517
590
  if (n) {
518
591
  const t = n(r);
519
592
  if (t !== undefined) {
@@ -533,20 +606,20 @@ function applyTentative(e, t, n) {
533
606
  }
534
607
  }
535
608
  const o = i.get(t);
536
- if (o === undefined) s = undefined; else if (Array.isArray(o)) {
537
- s = unwrapValue(e[o.shift()]);
609
+ if (o === undefined) l = undefined; else if (Array.isArray(o)) {
610
+ l = unwrapValue(e[o.shift()]);
538
611
  if (o.length === 1) i.set(t, o[0]);
539
612
  } else {
540
- s = unwrapValue(e[o]);
613
+ l = unwrapValue(e[o]);
541
614
  i.delete(t);
542
615
  }
543
- } else s = unwrapValue(e[o]);
544
- } else s = unwrapValue(e[o]);
545
- const a = match(s, r);
616
+ } else l = unwrapValue(e[o]);
617
+ } else l = unwrapValue(e[o]);
618
+ const a = match(l, r);
546
619
  if (a !== null) {
547
620
  // Keep the existing row in the slot (identity preserved); recurse.
548
- u[o] = unwrapValue(s);
549
- l.push([ a, r ]);
621
+ u[o] = unwrapValue(l);
622
+ s.push([ a, r ]);
550
623
  }
551
624
  }
552
625
  } else {
@@ -556,7 +629,7 @@ function applyTentative(e, t, n) {
556
629
  const r = match(n, i);
557
630
  if (r !== null) {
558
631
  u[e] = n;
559
- l.push([ r, i ]);
632
+ s.push([ r, i ]);
560
633
  }
561
634
  }
562
635
  }
@@ -566,7 +639,7 @@ function applyTentative(e, t, n) {
566
639
  e.pb = null;
567
640
  notifyOptimisticWrites(e, u);
568
641
  e.pb = a;
569
- for (let e = 0; e < l.length; e++) applyTentative(l[e][0], unwrapValue(l[e][1]), n);
642
+ for (let e = 0; e < s.length; e++) applyTentative(s[e][0], unwrapValue(s[e][1]), n);
570
643
  }
571
644
 
572
645
  function shallowWithSymbols(e) {
@@ -12,11 +12,11 @@ import "../../core/effect.js";
12
12
 
13
13
  import { CONFIG_AUTO_DISPOSE } from "../../core/constants.js";
14
14
 
15
- import { $TARGET, markRawIngest, STORE_VALUE, setWriteOverride } from "../store.js";
15
+ import { $TARGET, STORE_VALUE, markRawIngest, setWriteOverride } from "../store.js";
16
16
 
17
17
  import { reconcileNextState } from "./reconcile.js";
18
18
 
19
- import { wrapNext, storeSetterNext } from "./store.js";
19
+ import { storeSetterNext, wrapNext } from "./store.js";
20
20
 
21
21
  /**
22
22
  * Store rewrite — projections (§7/§7b): a projection is a computed store.