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

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.
Files changed (66) hide show
  1. package/dist/dev.js +2467 -1322
  2. package/dist/node.cjs +2037 -2428
  3. package/dist/node.dev.cjs +13724 -0
  4. package/dist/prod/boundaries.js +3 -1
  5. package/dist/prod/core/async.js +47 -20
  6. package/dist/prod/core/attribution-hooks.js +3 -0
  7. package/dist/prod/core/constants.js +9 -1
  8. package/dist/prod/core/context.js +10 -16
  9. package/dist/prod/core/core.js +260 -188
  10. package/dist/prod/core/dev.js +2 -0
  11. package/dist/prod/core/effect.js +41 -32
  12. package/dist/prod/core/external.js +2 -2
  13. package/dist/prod/core/graph.js +35 -31
  14. package/dist/prod/core/heap.js +34 -40
  15. package/dist/prod/core/lanes.js +44 -30
  16. package/dist/prod/core/optimistic.js +55 -45
  17. package/dist/prod/core/owner.js +35 -35
  18. package/dist/prod/core/scheduler.js +176 -177
  19. package/dist/prod/core/verdict.js +53 -52
  20. package/dist/prod/index.js +0 -2
  21. package/dist/prod/map.js +104 -94
  22. package/dist/prod/signals.js +119 -35
  23. package/dist/prod/store/next/optimistic.js +217 -163
  24. package/dist/prod/store/next/projection.js +2 -2
  25. package/dist/prod/store/next/reconcile.js +140 -288
  26. package/dist/prod/store/next/store.js +338 -252
  27. package/dist/prod/store/store.js +2 -2
  28. package/dist/types/core/attribution-hooks.d.ts +75 -0
  29. package/dist/types/core/attribution.d.ts +313 -9
  30. package/dist/types/core/constants.d.ts +8 -0
  31. package/dist/types/core/core.d.ts +12 -3
  32. package/dist/types/core/dev.d.ts +56 -9
  33. package/dist/types/core/heap.d.ts +5 -3
  34. package/dist/types/core/invariants.d.ts +1 -1
  35. package/dist/types/core/lanes.d.ts +10 -0
  36. package/dist/types/core/scheduler.d.ts +14 -4
  37. package/dist/types/signals.d.ts +10 -11
  38. package/dist/types/store/index.d.ts +3 -6
  39. package/dist/types/store/next/optimistic.d.ts +4 -2
  40. package/dist/types/store/next/reconcile.d.ts +5 -12
  41. package/dist/types/store/next/store.d.ts +3 -9
  42. package/dist/types/store/next/target.d.ts +30 -46
  43. package/dist/types/store/store.d.ts +14 -9
  44. package/dist/types-cjs/core/attribution-hooks.d.cts +75 -0
  45. package/dist/types-cjs/core/attribution.d.cts +313 -9
  46. package/dist/types-cjs/core/constants.d.cts +8 -0
  47. package/dist/types-cjs/core/core.d.cts +12 -3
  48. package/dist/types-cjs/core/dev.d.cts +56 -9
  49. package/dist/types-cjs/core/heap.d.cts +5 -3
  50. package/dist/types-cjs/core/invariants.d.cts +1 -1
  51. package/dist/types-cjs/core/lanes.d.cts +10 -0
  52. package/dist/types-cjs/core/scheduler.d.cts +14 -4
  53. package/dist/types-cjs/signals.d.cts +10 -11
  54. package/dist/types-cjs/store/index.d.cts +3 -6
  55. package/dist/types-cjs/store/next/optimistic.d.cts +4 -2
  56. package/dist/types-cjs/store/next/reconcile.d.cts +5 -12
  57. package/dist/types-cjs/store/next/store.d.cts +3 -9
  58. package/dist/types-cjs/store/next/target.d.cts +30 -46
  59. package/dist/types-cjs/store/store.d.cts +14 -9
  60. package/package.json +3 -2
  61. package/dist/prod/store/next/patch-hooks.js +0 -13
  62. package/dist/prod/store/next/patch.js +0 -614
  63. package/dist/types/store/next/patch-hooks.d.ts +0 -41
  64. package/dist/types/store/next/patch.d.ts +0 -91
  65. package/dist/types-cjs/store/next/patch-hooks.d.cts +0 -41
  66. package/dist/types-cjs/store/next/patch.d.cts +0 -91
@@ -1,8 +1,10 @@
1
- import { CONFIG_AUTO_DISPOSE, STATUS_PENDING, NOT_PENDING, CONFIG_OPTIMISTIC, CONFIG_HELD_TRUTH, unwrapOverride } from "../../core/constants.js";
1
+ import { CONFIG_AUTO_DISPOSE, STATUS_PENDING, STATUS_UNINITIALIZED, NOT_PENDING, CONFIG_OPTIMISTIC, CONFIG_HELD_TRUTH, unwrapOverride } from "../../core/constants.js";
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
 
@@ -18,9 +20,7 @@ import { runProjectionComputedNext } from "./projection.js";
18
20
 
19
21
  import { wrapNext, runAuthoritative, storeSetterNext, hasActiveOverride, unwrapValue, targetsEqual, getNode, getHasNode, getKeySetNode, bumpDeep, stagedTruthPB, authoritativeRead } from "./store.js";
20
22
 
21
- import { patchHooks, rowHooks } from "./patch-hooks.js";
22
-
23
- import { sameKey, buildIdentityRowOps } from "./reconcile.js";
23
+ import { sameKey } from "./reconcile.js";
24
24
 
25
25
  import { setOptHooks } from "./target.js";
26
26
 
@@ -55,7 +55,7 @@ import { setOptHooks } from "./target.js";
55
55
  * staged values to converge (normal speculation). Resolves merges first:
56
56
  * merge unions optimistic nodes/stores into the target. */ function transitionHoldsOptimism(e) {
57
57
  const t = currentTransition(e);
58
- return t.sn !== true && (t.ze.length !== 0 || t.En.size !== 0);
58
+ return t.sn !== true && (t.Xe.length !== 0 || t.En.size !== 0);
59
59
  }
60
60
 
61
61
  let blockedInstalled = false;
@@ -76,29 +76,20 @@ function installNextBlockedHalf() {
76
76
  // Scheduler flush tails call _clearOptimisticStores whenever tracked
77
77
  // stores exist; next has no layer to clear — reverts are engine-native —
78
78
  // so the hook only empties the batch set.
79
- if (!GlobalQueue.qt) {
80
- GlobalQueue.qt = e => {
81
- // Patch channel (revert site): engine-native reverts flip node values
82
- // back to committed; patched records need a forced DOM re-apply from
83
- // the post-revert view. Emission only — next keeps no layer to clear.
79
+ if (!GlobalQueue.Wt) {
80
+ GlobalQueue.Wt = e => {
84
81
  for (const t of e) {
85
82
  const e = t?.[$TARGET];
86
83
  const n = e?.fam?.overlaid;
87
84
  if (n !== undefined) {
88
85
  for (const e of n) {
89
- if (e.pc !== null && e.pc.p !== null) patchHooks.emitPatchOptimistic(e, null, null);
90
- // Row-ops resync (family increment 2): reverts flip node values
91
- // back engine-natively; a driven list must rebuild retention by
92
- // row identity against the post-revert view (resolved from the
93
- // target at drain — overrides are gone by then).
94
- if (e.pc !== null && e.pc.ro !== null) rowHooks.emitRowOpsOptimistic(e, null, null);
95
86
  // Keyset resync (classic channel twin): the keyset node's own
96
87
  // revert can compare EQUAL (a landing's bump matched the
97
88
  // tentative bump) while the arrangement underneath changed —
98
89
  // mapArray/ownKeys subscribers must re-read the post-revert
99
90
  // view. Authoritative bump: never re-arm the node we are
100
91
  // clearing.
101
- if (e.k !== null) runAuthoritative(() => setSignal(e.k, e => e + 1));
92
+ if (e.k !== null) runAuthoritative(() => setSignal(e.k, e => e + 1));
102
93
  }
103
94
  }
104
95
  }
@@ -108,14 +99,22 @@ function installNextBlockedHalf() {
108
99
  const e = GlobalQueue.dn;
109
100
  GlobalQueue.dn = t => {
110
101
  for (const e of t.En) {
111
- const t = e?.[$TARGET];
112
- const n = t?.fam?.node;
102
+ const n = e?.[$TARGET];
103
+ const i = n?.fam;
104
+ const r = i?.node;
113
105
  // The hold exists to keep optimistic state alive until the store's own
114
106
  // truth lands (#2951). Once the family carries NO live overrides (a
115
107
  // landing consumed them, or they never existed), a pending firewall is
116
108
  // no reason to park the transaction — blocking then leaks it forever
117
109
  // when the in-flight question is never answered (undisposed fixtures).
118
- if (n != null && n.S & STATUS_PENDING && familyHasLiveOverrides(t.fam)) return true;
110
+ if (r == null || !(r.S & STATUS_PENDING)) continue;
111
+ // Ownership is declared (#3146): only the flight's OWN transaction
112
+ // parks on the flight (the #2951 anchor routed the bare write there).
113
+ // A transaction that merely brushed the store never waits for truth
114
+ // it does not carry.
115
+ const o = i.ft != null ? liveTransition(i.ft) : null;
116
+ if (o !== null && o !== currentTransition(t)) continue;
117
+ if (familyHasLiveOverrides(i)) return true;
119
118
  }
120
119
  return e(t);
121
120
  };
@@ -129,10 +128,10 @@ function familyHasLiveOverrides(e) {
129
128
  if (t === null) continue;
130
129
  for (const e of Reflect.ownKeys(t)) {
131
130
  const n = t[e];
132
- if (n.o?.Oe !== undefined && n.o?.Oe !== NOT_PENDING) return true;
131
+ if (n.o?.Pe !== undefined && n.o?.Pe !== NOT_PENDING) return true;
133
132
  }
134
133
  }
135
- if (e.k !== null && e.k.o?.Oe !== undefined && e.k.o?.Oe !== NOT_PENDING) return true;
134
+ if (e.k !== null && e.k.o?.Pe !== undefined && e.k.o?.Pe !== NOT_PENDING) return true;
136
135
  }
137
136
  t.clear();
138
137
  // nothing live — drop the bookkeeping
@@ -145,34 +144,55 @@ function createOptimisticStoreNext(e, t, n) {
145
144
  installOptimisticEngine();
146
145
  installNextBlockedHalf();
147
146
  const i = typeof e === "function";
148
- if (!i && n === undefined) n = t;
147
+ const r = i ? n : t;
149
148
  const o = i ? t : e;
150
- const r = {
149
+ const s = {
151
150
  map: new WeakMap,
152
151
  node: null,
153
- shallow: !!n?.shallow,
152
+ shallow: !!r?.shallow,
154
153
  opt: true
155
154
  };
156
- const s = wrapNext(o, null, null, r);
157
- r.px = s;
155
+ const l = wrapNext(o, null, null, s);
156
+ s.px = l;
158
157
  // Same key resolution the projection channels use ("id" default) — replay's
159
158
  // 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;
162
- if (r.shallow) {
163
- s[$TARGET].s = true;
159
+ const u = r?.key === undefined ? "id" : r.key;
160
+ s.key = typeof u === "function" ? u : u === null ? null : e => isWrappable(e) ? e[u] : undefined;
161
+ if (s.shallow) {
162
+ l[$TARGET].s = true;
164
163
  markRawIngest(o);
165
164
  }
166
165
  if (i) {
167
166
  const t = e;
167
+ // #3146: an async settle event belongs to the flight's OWN transaction.
168
+ // A live declared one re-enters (a merge if the generic settle path
169
+ // already entered a graph-stamped stranger — the landing supersedes any
170
+ // recompute deriving from that stranger's world); a dead one renews (per
171
+ // A18(1) each arrival reveals on its own schedule, so per-yield
172
+ // transactions die with their commit and the next settle event opens the
173
+ // flight's next one — still declared, never anonymous). An UNDECLARED
174
+ // flight (loading window) keeps the ambient reveal (#2933: the loading
175
+ // rail is transaction-invisible).
176
+ const enterFlightTransition = () => {
177
+ const e = s.ft;
178
+ if (e == null) return;
179
+ let t = liveTransition(e);
180
+ if (t === null) s.ft = t = createTransition();
181
+ s.node.Ae = t;
182
+ globalQueue.initTransition(t);
183
+ };
168
184
  // Landing router (#3164 fold ruling): while a transaction retains
169
185
  // optimistic edits on this family, truth landings stage INTO it and
170
186
  // reveal atomically at settle; with no retainer they commit immediately
171
187
  // under the authoritative posture (async commits land outside the
172
- // computed's sync body, so the posture is re-applied here).
188
+ // computed's sync body, so the posture is re-applied here) — inside the
189
+ // flight-owned transaction (#3146). Sync commits (the derive's body,
190
+ // owner is the firewall itself) reveal with their own recompute's flush.
173
191
  const wrapCommit = (e, t) => {
174
- const n = retainingTransition(r);
175
- if (n === null) runAuthoritative(e); else stageLanding(r, n, t);
192
+ const n = retainingTransition(s);
193
+ if (n !== null) return void stageLanding(s, n, t);
194
+ if (getOwner() !== s.node) enterFlightTransition();
195
+ runAuthoritative(e);
176
196
  };
177
197
  // Draft writes (the derive mutating its draft, sync body and post-await
178
198
  // continuations alike) are the same truth channel per-operation: bind
@@ -181,28 +201,74 @@ function createOptimisticStoreNext(e, t, n) {
181
201
  // batch; the write-override eager-commit branch in notifyWrites yields
182
202
  // to any active transaction).
183
203
  const aroundDraftWrite = e => {
184
- const t = retainingTransition(r);
204
+ const t = retainingTransition(s);
185
205
  if (t === null) e(); else runFolded(t, e);
186
206
  };
187
- let i;
188
- if (n?.seedLoadingValue) i = {
207
+ // Flight declaration (#3146): a recompute that registered a truth-flight
208
+ // OWNS its transaction. The ask's transaction is recorded on the family
209
+ // (created by the flight's own pending throw when none was ambient, the
210
+ // causing write's/refresh's when one was — graph-driven causality) and
211
+ // the firewall is stamped so every settle path resolves the flight's
212
+ // transaction by construction, not by whatever last brushed the node.
213
+ // When the ask took none (a dead stale stamp — the previous flight's,
214
+ // cleared nowhere — bare-returns the pre-throw entry), the flight opens
215
+ // its own here, same activation point as the pre-throw's creation: the
216
+ // ambient batch (the causing write, same-tick bare optimism) adopts into
217
+ // it exactly as it would have there, and the pending notification that
218
+ // follows this unwind registers observers against it. The flight
219
+ // also registers as its own async reporter: the transaction lives
220
+ // exactly as long as the question is unanswered, observed or not — the
221
+ // #2951 refetch-hold no longer depends on a tracked observer having
222
+ // happened to register one. Loading-window flights declare nothing
223
+ // (#2933: the loading rail is transaction-invisible); a sync run clears
224
+ // the declaration.
225
+ const declareFlight = e => {
226
+ if (e.o?.Ie == null) {
227
+ if (!e.Ne) s.ft = null;
228
+ return;
229
+ }
230
+ // First flight (#3146 carve-out): nothing has ever committed, so there
231
+ // is no truth to keep on screen and no optimistic state to protect. An
232
+ // uninitialized ask suspends its readers into their Loading boundary
233
+ // exactly like a plain derived store's first flight — declaring a
234
+ // transaction here instead held the ROOT MOUNT (render()'s scheduled
235
+ // insert rides transitions) until the fetch landed, so the boundary's
236
+ // fallback never showed and the whole page stayed blank. The loading
237
+ // window (#2933) already declares nothing for the same reason; once
238
+ // the first truth lands, every refetch flight declares as before.
239
+ if (e.Ne || e.S & STATUS_UNINITIALIZED) return;
240
+ let t = activeTransition;
241
+ if (t === null) globalQueue.initTransition(t = createTransition());
242
+ s.ft = t;
243
+ e.Ae = t;
244
+ let n = t._e.get(e);
245
+ if (n === undefined) t._e.set(e, n = new Set);
246
+ n.add(e);
247
+ };
248
+ let n;
249
+ if (r?.seedLoadingValue) n = {
189
250
  loadingValue: undefined
190
251
  };
191
- const o = computed(() => {
192
- runAuthoritative(() => runProjectionComputedNext(s, t, n?.key === undefined ? "id" : n.key, wrapCommit, aroundDraftWrite));
193
- }, i);
194
- o.T &= ~CONFIG_AUTO_DISPOSE;
195
- r.node = o;
252
+ const i = computed(() => {
253
+ const e = getOwner();
254
+ try {
255
+ runAuthoritative(() => runProjectionComputedNext(l, t, r?.key === undefined ? "id" : r.key, wrapCommit, aroundDraftWrite));
256
+ } finally {
257
+ declareFlight(e);
258
+ }
259
+ }, n);
260
+ i.T &= ~CONFIG_AUTO_DISPOSE;
261
+ s.node = i;
196
262
  }
197
- return [ s, e => {
263
+ return [ l, e => {
198
264
  // Retention ledger (#3164): record the owning transaction so landings
199
265
  // know to fold. Captured at entry — the action machinery has the
200
266
  // transaction ambient while user code runs; a bare write with no
201
267
  // transaction retains nothing (it rides the flight's own transition
202
268
  // per #2951 and dies with it).
203
269
  const t = activeTransition;
204
- storeSetterNext(s, e);
205
- if (t !== null) (r.rt ??= new Set).add(t);
270
+ storeSetterNext(l, e);
271
+ if (t !== null) (s.rt ??= new Set).add(t);
206
272
  } ];
207
273
  }
208
274
 
@@ -254,7 +320,7 @@ function createOptimisticStoreNext(e, t, n) {
254
320
  * stay unarmed — the override is their display and its revert their
255
321
  * notification, A17). */ function runFolded(e, t) {
256
322
  runAsTransitionBatch(e, t);
257
- const n = e.Lt;
323
+ const n = e.Mt;
258
324
  for (let t = 0; t < n.length; t++) {
259
325
  const i = n[t];
260
326
  i.Ae = e;
@@ -274,15 +340,15 @@ function createOptimisticStoreNext(e, t, n) {
274
340
  if (n !== null) {
275
341
  // Occurrence-aware key queues (parity with the adoption window):
276
342
  // duplicate keys match per occurrence, each current row consumed once.
277
- let o = null;
278
- const r = e.length;
279
- for (let t = 0; t < r; t++) {
343
+ let r = null;
344
+ const o = e.length;
345
+ for (let t = 0; t < o; t++) {
280
346
  const i = unwrapValue(e[t]);
281
347
  if (!isWrappable(i)) continue;
282
- const r = n(i);
283
- if (r === undefined) continue;
284
- const s = (o ??= new Map).get(r);
285
- if (s === undefined) o.set(r, [ i ]); else s.push(i);
348
+ const o = n(i);
349
+ if (o === undefined) continue;
350
+ const s = (r ??= new Map).get(o);
351
+ if (s === undefined) r.set(o, [ i ]); else s.push(i);
286
352
  }
287
353
  // Echo adoption: an optimistic structural add whose key the landing
288
354
  // confirms must keep its raw (and so its proxy — list drivers keep the
@@ -297,42 +363,42 @@ function createOptimisticStoreNext(e, t, n) {
297
363
  for (const e of Reflect.ownKeys(s)) {
298
364
  const t = s[e];
299
365
  if (!hasActiveOverride(t)) continue;
300
- const i = unwrapValue(unwrapOverride(t.o.Oe));
366
+ const i = unwrapValue(unwrapOverride(t.o.Pe));
301
367
  if (!isWrappable(i)) continue;
302
- const r = n(i);
303
- if (r === undefined) continue;
304
- const l = (o ??= new Map).get(r);
305
- if (l === undefined) o.set(r, [ i ]); else l.push(i);
368
+ const o = n(i);
369
+ if (o === undefined) continue;
370
+ const l = (r ??= new Map).get(o);
371
+ if (l === undefined) r.set(o, [ i ]); else l.push(i);
306
372
  }
307
373
  }
308
- for (let r = 0; r < i; r++) {
309
- const i = t[r];
374
+ for (let o = 0; o < i; o++) {
375
+ const i = t[o];
310
376
  let s;
311
- if (isWrappable(i) && o !== null) {
377
+ if (isWrappable(i) && r !== null) {
312
378
  const e = n(i);
313
379
  if (e !== undefined) {
314
- for (const [t, n] of o) {
380
+ for (const [t, n] of r) {
315
381
  if (!sameKey(t, e)) continue;
316
382
  s = n.shift();
317
- if (n.length === 0) o.delete(t);
383
+ if (n.length === 0) r.delete(t);
318
384
  break;
319
385
  }
320
386
  }
321
387
  }
322
388
  if (s !== undefined) {
323
- if (unwrapValue(e[r]) !== s) e[r] = s;
324
- stagedApply(e[r], i, n);
389
+ if (unwrapValue(e[o]) !== s) e[o] = s;
390
+ stagedApply(e[o], i, n);
325
391
  } else {
326
- const t = unwrapValue(e[r]);
327
- if (!isEqual(t, i) && !targetsEqual(t, i)) e[r] = i;
392
+ const t = unwrapValue(e[o]);
393
+ if (!isEqual(t, i) && !targetsEqual(t, i)) e[o] = i;
328
394
  }
329
395
  }
330
396
  } else {
331
- for (let o = 0; o < i; o++) {
332
- const i = t[o];
333
- const r = unwrapValue(e[o]);
334
- if (r === i) continue;
335
- if (isWrappable(i) && isWrappable(r) && Array.isArray(i) === Array.isArray(r)) stagedApply(e[o], i, n); else if (!isEqual(r, i) && !targetsEqual(r, i)) e[o] = i;
397
+ for (let r = 0; r < i; r++) {
398
+ const i = t[r];
399
+ const o = unwrapValue(e[r]);
400
+ if (o === i) continue;
401
+ if (isWrappable(i) && isWrappable(o) && Array.isArray(i) === Array.isArray(o)) stagedApply(e[r], i, n); else if (!isEqual(o, i) && !targetsEqual(o, i)) e[r] = i;
336
402
  }
337
403
  }
338
404
  if (e.length !== i) e.length = i;
@@ -340,25 +406,25 @@ function createOptimisticStoreNext(e, t, n) {
340
406
  }
341
407
  // Object merge; also the degenerate root-kind-change shape (arrays accept
342
408
  // keyed writes/deletes, so a wholesale restatement still lands staged).
343
- for (const o of Reflect.ownKeys(t)) {
344
- if (i && o === "length") continue;
345
- 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)) {
409
+ for (const r of Reflect.ownKeys(t)) {
410
+ if (i && r === "length") continue;
411
+ const o = t[r];
412
+ const s = unwrapValue(e[r]);
413
+ if (s === o) continue;
414
+ if (isWrappable(o) && isWrappable(s) && Array.isArray(o) === Array.isArray(s)) {
349
415
  // Different-keyed entities never merge (tentative-channel parity):
350
416
  // the incoming object replaces the slot wholesale.
351
417
  if (n !== null) {
352
418
  const t = n(s);
353
- const i = n(r);
419
+ const i = n(o);
354
420
  if (t !== undefined && i !== undefined && !sameKey(t, i)) {
355
- e[o] = r;
421
+ e[r] = o;
356
422
  continue;
357
423
  }
358
424
  }
359
- stagedApply(e[o], r, n);
360
- } else if (!isEqual(s, r) && !targetsEqual(s, r)) {
361
- e[o] = r;
425
+ stagedApply(e[r], o, n);
426
+ } else if (!isEqual(s, o) && !targetsEqual(s, o)) {
427
+ e[r] = o;
362
428
  }
363
429
  }
364
430
  for (const n of Reflect.ownKeys(e)) {
@@ -373,71 +439,61 @@ function createOptimisticStoreNext(e, t, n) {
373
439
  * overrides — the same view the draft was seeded from) and emit engine writes
374
440
  * for exactly the changed keys. Visible-view diffing keeps no-op writes from
375
441
  * 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
442
+ // A bare write while the store's own truth is in flight rides the FLIGHT'S
443
+ // OWN transaction (#2951 via the #3146 declaration): entangle it so the
444
+ // override survives until the refetch settles instead of flash-reverting
379
445
  // 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);
383
- const i = e.v;
384
- // Patch channel (override-application site): the draft IS the intended
385
- // visible state; prev is the view before these overrides apply. Bypasses
386
- // the transition stash — optimism is visible in flight.
387
- if (e.pc !== null && e.pc.p !== null) patchHooks.emitPatchOptimistic(e, t, optimisticView(e, i));
388
- // Row-ops channel (family increment 2): optimistic STRUCTURE on an array
389
- // rides node overrides — it never enters the reconcile walk — so a driven
390
- // list must get its structural ops here, lane-timed. Identity diff of the
391
- // pre-write optimistic view against the draft; aligned writes emit nothing.
392
- if (e.pc !== null && e.pc.ro !== null && Array.isArray(t)) {
393
- const n = optimisticView(e, i);
394
- if (Array.isArray(n)) {
395
- const i = buildIdentityRowOps(n, t);
396
- if (i !== null) rowHooks.emitRowOpsOptimistic(e, t, i);
397
- }
446
+ // from settling while the firewall is pending. Declared ownership replaces
447
+ // the old circumstantial route through the firewall's `_transition` stamp,
448
+ // which was whatever last brushed the node.
449
+ const n = e.fam?.ft;
450
+ if (n != null) {
451
+ const e = liveTransition(n);
452
+ if (e !== null) globalQueue.initTransition(e);
398
453
  }
454
+ const i = e.v;
399
455
  const visible = (t, n) => {
400
456
  const i = e.n?.[t];
401
- return i !== undefined && hasActiveOverride(i) ? unwrapOverride(i.o?.Oe) : n;
457
+ return i !== undefined && hasActiveOverride(i) ? unwrapOverride(i.o?.Pe) : n;
402
458
  };
403
459
  const visiblePresent = t => {
404
460
  const n = e.h?.[t];
405
- return n !== undefined && hasActiveOverride(n) ? !!unwrapOverride(n.o?.Oe) : t in i;
461
+ return n !== undefined && hasActiveOverride(n) ? !!unwrapOverride(n.o?.Pe) : t in i;
406
462
  };
407
- let o = false;
408
- const r = Array.isArray(t);
463
+ let r = false;
464
+ const o = Array.isArray(t);
409
465
  for (const n of Reflect.ownKeys(t)) {
410
- if (r && n === "length") continue;
466
+ if (o && n === "length") continue;
411
467
  const s = unwrapValue(t[n]);
412
468
  if (!visiblePresent(n)) {
413
469
  // Optimistic add: value node + presence node + membership bump.
414
470
  setSignal(getNode(e, n, i[n]), () => s);
415
471
  setSignal(getHasNode(e, n, n in i), true);
416
- o = true;
472
+ r = true;
417
473
  } else {
418
474
  const t = visible(n, i[n]);
419
475
  if (!isEqual(t, s) && !targetsEqual(t, s)) {
420
476
  setSignal(getNode(e, n, t), () => s);
421
- if (r) o = true;
477
+ if (o) r = true;
422
478
  }
423
479
  }
424
480
  }
425
481
  for (const n of Reflect.ownKeys(i)) {
426
- if (r && n === "length") continue;
482
+ if (o && n === "length") continue;
427
483
  if (n in t || !visiblePresent(n)) continue;
428
484
  // Optimistic delete: node reads undefined, presence flips, membership bumps.
429
485
  setSignal(getNode(e, n, i[n]), () => undefined);
430
486
  setSignal(getHasNode(e, n, true), false);
431
- o = true;
487
+ r = true;
432
488
  }
433
- if (r) {
489
+ if (o) {
434
490
  const n = visible("length", i.length);
435
491
  if (n !== t.length) {
436
492
  setSignal(getNode(e, "length", n), () => t.length);
437
- o = true;
493
+ r = true;
438
494
  }
439
495
  }
440
- if (o) setSignal(getKeySetNode(e), e => e + 1);
496
+ if (r) setSignal(getKeySetNode(e), e => e + 1);
441
497
  // Deep-witness: optimistic value writes notify deep() subscribers too
442
498
  // (structural ones already ride the key-set bump above).
443
499
  bumpDeep(e);
@@ -467,19 +523,19 @@ function createOptimisticStoreNext(e, t, n) {
467
523
  for (const e of Reflect.ownKeys(i)) {
468
524
  const n = i[e];
469
525
  if (!hasActiveOverride(n)) continue;
470
- const o = unwrapOverride(n.o?.Oe);
526
+ const r = unwrapOverride(n.o?.Pe);
471
527
  if (e === "length" && Array.isArray(t)) {
472
- if (t.length !== o) ensure().length = o;
473
- } else if (!isEqual(t[e], o)) ensure()[e] = o;
528
+ if (t.length !== r) ensure().length = r;
529
+ } else if (!isEqual(t[e], r)) ensure()[e] = r;
474
530
  }
475
531
  }
476
- const o = e.h;
477
- if (o !== null) {
478
- for (const e of Reflect.ownKeys(o)) {
479
- const i = o[e];
532
+ const r = e.h;
533
+ if (r !== null) {
534
+ for (const e of Reflect.ownKeys(r)) {
535
+ const i = r[e];
480
536
  if (!hasActiveOverride(i)) continue;
481
- const r = !!unwrapOverride(i.o?.Oe);
482
- if (!r && e in (n ?? t)) delete ensure()[e];
537
+ const o = !!unwrapOverride(i.o?.Pe);
538
+ if (!o && e in (n ?? t)) delete ensure()[e];
483
539
  }
484
540
  }
485
541
  return n ?? t;
@@ -487,35 +543,39 @@ function createOptimisticStoreNext(e, t, n) {
487
543
 
488
544
  function applyTentative(e, t, n) {
489
545
  const i = e.pb ?? e.v;
490
- const o = optimisticView(e, i);
491
- const r = e.fam.map;
546
+ const r = optimisticView(e, i);
547
+ const o = e.fam.map;
492
548
  const s = Array.isArray(t);
493
- if (Array.isArray(o) !== s) return;
549
+ if (Array.isArray(r) !== s) return;
494
550
  // kind change at root: flat overrides below
495
551
  const l = [];
496
- const u = s ? [ ...t ] : shallowWithSymbols(t);
552
+ let u;
553
+ if (s) u = [ ...t ]; else {
554
+ u = {};
555
+ for (const e of Reflect.ownKeys(t)) u[e] = t[e];
556
+ }
497
557
  const match = (e, t) => {
498
558
  if (!isWrappable(e) || !isWrappable(t)) return null;
499
559
  if (rawValuesUsed && (isRawValue(e) || isRawValue(t))) return null;
500
560
  if (Array.isArray(e) !== Array.isArray(t)) return null;
501
561
  if (n) {
502
562
  const i = n(e);
503
- const o = n(t);
563
+ const r = n(t);
504
564
  // SameValueZero (re-audit 3, P1-3): parity with the plain reconcile
505
565
  // channel — NaN keys are self-equal.
506
- if (i !== undefined && o !== undefined && !sameKey(i, o)) return null;
566
+ if (i !== undefined && r !== undefined && !sameKey(i, r)) return null;
507
567
  }
508
- return r.get(unwrapValue(e)) ?? null;
568
+ return o.get(unwrapValue(e)) ?? null;
509
569
  };
510
570
  if (s) {
511
- const e = o;
571
+ const e = r;
512
572
  let i = null;
513
- for (let o = 0; o < t.length; o++) {
514
- const r = t[o];
515
- if (!isWrappable(r)) continue;
573
+ for (let r = 0; r < t.length; r++) {
574
+ const o = t[r];
575
+ if (!isWrappable(o)) continue;
516
576
  let s;
517
577
  if (n) {
518
- const t = n(r);
578
+ const t = n(o);
519
579
  if (t !== undefined) {
520
580
  if (i === null) {
521
581
  // Occurrence-aware index queues (re-audit 3, P1-3): parity with
@@ -523,40 +583,40 @@ function applyTentative(e, t, n) {
523
583
  // occurrence, each view row consumed once.
524
584
  i = new Map;
525
585
  for (let t = 0; t < e.length; t++) {
526
- const o = unwrapValue(e[t]);
527
- if (isWrappable(o)) {
528
- const e = n(o);
586
+ const r = unwrapValue(e[t]);
587
+ if (isWrappable(r)) {
588
+ const e = n(r);
529
589
  if (e === undefined) continue;
530
- const r = i.get(e);
531
- if (r === undefined) i.set(e, t); else if (Array.isArray(r)) r.push(t); else i.set(e, [ r, t ]);
590
+ const o = i.get(e);
591
+ if (o === undefined) i.set(e, t); else if (Array.isArray(o)) o.push(t); else i.set(e, [ o, t ]);
532
592
  }
533
593
  }
534
594
  }
535
- const o = i.get(t);
536
- if (o === undefined) s = undefined; else if (Array.isArray(o)) {
537
- s = unwrapValue(e[o.shift()]);
538
- if (o.length === 1) i.set(t, o[0]);
595
+ const r = i.get(t);
596
+ if (r === undefined) s = undefined; else if (Array.isArray(r)) {
597
+ s = unwrapValue(e[r.shift()]);
598
+ if (r.length === 1) i.set(t, r[0]);
539
599
  } else {
540
- s = unwrapValue(e[o]);
600
+ s = unwrapValue(e[r]);
541
601
  i.delete(t);
542
602
  }
543
- } else s = unwrapValue(e[o]);
544
- } else s = unwrapValue(e[o]);
545
- const a = match(s, r);
603
+ } else s = unwrapValue(e[r]);
604
+ } else s = unwrapValue(e[r]);
605
+ const a = match(s, o);
546
606
  if (a !== null) {
547
607
  // Keep the existing row in the slot (identity preserved); recurse.
548
- u[o] = unwrapValue(s);
549
- l.push([ a, r ]);
608
+ u[r] = unwrapValue(s);
609
+ l.push([ a, o ]);
550
610
  }
551
611
  }
552
612
  } else {
553
613
  for (const e of Reflect.ownKeys(t)) {
554
- const n = unwrapValue(o[e]);
614
+ const n = unwrapValue(r[e]);
555
615
  const i = t[e];
556
- const r = match(n, i);
557
- if (r !== null) {
616
+ const o = match(n, i);
617
+ if (o !== null) {
558
618
  u[e] = n;
559
- l.push([ r, i ]);
619
+ l.push([ o, i ]);
560
620
  }
561
621
  }
562
622
  }
@@ -569,10 +629,4 @@ function applyTentative(e, t, n) {
569
629
  for (let e = 0; e < l.length; e++) applyTentative(l[e][0], unwrapValue(l[e][1]), n);
570
630
  }
571
631
 
572
- function shallowWithSymbols(e) {
573
- const t = {};
574
- for (const n of Reflect.ownKeys(e)) t[n] = e[n];
575
- return t;
576
- }
577
-
578
632
  export { createOptimisticStoreNext, notifyOptimisticWrites, optimisticView, transitionHoldsOptimism };
@@ -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.