@solidjs/signals 2.0.0-rc.13 → 2.0.0-rc.14

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 (118) hide show
  1. package/dist/dev-shared.js +2000 -3768
  2. package/dist/dev.attribution.js +132 -79
  3. package/dist/dev.js +7226 -8488
  4. package/dist/observe/affects.js +125 -104
  5. package/dist/observe/boundaries.js +458 -665
  6. package/dist/observe/core/action.js +63 -66
  7. package/dist/observe/core/async.js +240 -383
  8. package/dist/observe/core/attribution-queries.js +1 -1
  9. package/dist/observe/core/attribution.js +240 -183
  10. package/dist/observe/core/constants.js +115 -187
  11. package/dist/observe/core/context.js +3 -3
  12. package/dist/observe/core/core.js +840 -1438
  13. package/dist/observe/core/dev.js +3 -3
  14. package/dist/observe/core/effect.js +75 -86
  15. package/dist/observe/core/external.js +5 -5
  16. package/dist/observe/core/graph.js +61 -70
  17. package/dist/observe/core/heap.js +75 -98
  18. package/dist/observe/core/invariants.js +1 -34
  19. package/dist/observe/core/lanes.js +704 -181
  20. package/dist/observe/core/owner.js +80 -101
  21. package/dist/observe/core/scheduler.js +708 -1351
  22. package/dist/observe/core/verdict.js +302 -621
  23. package/dist/observe/flatten.js +79 -0
  24. package/dist/observe/index.js +15 -13
  25. package/dist/observe/map.js +232 -235
  26. package/dist/observe/reveal.js +212 -0
  27. package/dist/observe/signals.js +133 -215
  28. package/dist/observe/store/affects.js +135 -0
  29. package/dist/observe/store/index.js +29 -24
  30. package/dist/observe/store/optimistic.js +538 -0
  31. package/dist/observe/store/projection.js +302 -0
  32. package/dist/observe/store/reconcile.js +257 -0
  33. package/dist/observe/store/store.js +1945 -277
  34. package/dist/observe/store/storePath.js +1 -1
  35. package/dist/{prod/store/next → observe/store}/target.js +17 -18
  36. package/dist/observe/store/types.js +73 -0
  37. package/dist/observe/store/utils.js +1 -13
  38. package/dist/prod/affects.js +127 -106
  39. package/dist/prod/boundaries.js +442 -654
  40. package/dist/prod/core/action.js +71 -74
  41. package/dist/prod/core/async.js +307 -442
  42. package/dist/prod/core/constants.js +112 -186
  43. package/dist/prod/core/context.js +3 -3
  44. package/dist/prod/core/core.js +852 -1458
  45. package/dist/prod/core/effect.js +88 -99
  46. package/dist/prod/core/external.js +5 -5
  47. package/dist/prod/core/graph.js +61 -70
  48. package/dist/prod/core/heap.js +75 -98
  49. package/dist/prod/core/invariants.js +1 -32
  50. package/dist/prod/core/lanes.js +666 -181
  51. package/dist/prod/core/owner.js +75 -96
  52. package/dist/prod/core/scheduler.js +677 -1332
  53. package/dist/prod/core/verdict.js +297 -621
  54. package/dist/prod/flatten.js +77 -0
  55. package/dist/prod/index.js +15 -13
  56. package/dist/prod/map.js +143 -146
  57. package/dist/prod/reveal.js +212 -0
  58. package/dist/prod/signals.js +144 -226
  59. package/dist/prod/store/affects.js +135 -0
  60. package/dist/prod/store/index.js +28 -19
  61. package/dist/prod/store/optimistic.js +533 -0
  62. package/dist/prod/store/projection.js +295 -0
  63. package/dist/prod/store/reconcile.js +257 -0
  64. package/dist/prod/store/store.js +1840 -272
  65. package/dist/prod/store/storePath.js +1 -1
  66. package/dist/{observe/store/next → prod/store}/target.js +13 -17
  67. package/dist/prod/store/types.js +73 -0
  68. package/dist/prod/store/utils.js +1 -11
  69. package/dist/types/affects.d.ts +11 -30
  70. package/dist/types/boundaries.d.ts +62 -176
  71. package/dist/types/carved.d.ts +1 -0
  72. package/dist/types/core/action.d.ts +5 -6
  73. package/dist/types/core/async.d.ts +10 -5
  74. package/dist/types/core/attribution-hooks.d.ts +20 -1
  75. package/dist/types/core/attribution.d.ts +20 -9
  76. package/dist/types/core/constants.d.ts +125 -180
  77. package/dist/types/core/core.d.ts +104 -264
  78. package/dist/types/core/dev.d.ts +2 -2
  79. package/dist/types/core/effect.d.ts +0 -7
  80. package/dist/types/core/graph.d.ts +7 -1
  81. package/dist/types/core/heap.d.ts +5 -7
  82. package/dist/types/core/index.d.ts +5 -5
  83. package/dist/types/core/invariants.d.ts +0 -29
  84. package/dist/types/core/lanes.d.ts +65 -87
  85. package/dist/types/core/owner.d.ts +3 -3
  86. package/dist/types/core/scheduler.d.ts +249 -331
  87. package/dist/types/core/types.d.ts +51 -85
  88. package/dist/types/core/verdict.d.ts +3 -0
  89. package/dist/types/flatten.d.ts +8 -0
  90. package/dist/types/index.d.ts +6 -4
  91. package/dist/types/reveal.d.ts +19 -0
  92. package/dist/types/signals.d.ts +35 -91
  93. package/dist/types/store/affects.d.ts +1 -0
  94. package/dist/types/store/index.d.ts +35 -15
  95. package/dist/types/store/optimistic.d.ts +23 -0
  96. package/dist/types/store/projection.d.ts +18 -0
  97. package/dist/types/store/reconcile.d.ts +3 -0
  98. package/dist/types/store/store.d.ts +165 -143
  99. package/dist/types/store/storePath.d.ts +1 -1
  100. package/dist/types/store/target.d.ts +151 -0
  101. package/dist/types/store/types.d.ts +84 -0
  102. package/package.json +1 -1
  103. package/dist/observe/core/optimistic.js +0 -669
  104. package/dist/observe/store/next/optimistic.js +0 -717
  105. package/dist/observe/store/next/projection.js +0 -266
  106. package/dist/observe/store/next/reconcile.js +0 -375
  107. package/dist/observe/store/next/store.js +0 -2608
  108. package/dist/prod/core/optimistic.js +0 -658
  109. package/dist/prod/store/next/optimistic.js +0 -710
  110. package/dist/prod/store/next/projection.js +0 -257
  111. package/dist/prod/store/next/reconcile.js +0 -373
  112. package/dist/prod/store/next/store.js +0 -2484
  113. package/dist/types/core/optimistic.d.ts +0 -6
  114. package/dist/types/store/next/optimistic.d.ts +0 -25
  115. package/dist/types/store/next/projection.d.ts +0 -27
  116. package/dist/types/store/next/reconcile.d.ts +0 -10
  117. package/dist/types/store/next/store.d.ts +0 -145
  118. package/dist/types/store/next/target.d.ts +0 -196
@@ -1,710 +0,0 @@
1
- import { CONFIG_AUTO_DISPOSE, STATUS_PENDING, STATUS_UNINITIALIZED, NOT_PENDING, CONFIG_OPTIMISTIC, CONFIG_HELD_TRUTH, unwrapOverride } from "../../core/constants.js";
2
-
3
- import { computed, untrack, hasActiveOverride, setSignal, isEqual, visibleOverride } from "../../core/core.js";
4
-
5
- import { getOwner } from "../../core/owner.js";
6
-
7
- import { activeTransition, GlobalQueue, currentTransition, globalQueue, createTransition, runAsTransitionBatch } from "../../core/scheduler.js";
8
-
9
- import "../../core/invariants.js";
10
-
11
- import "../../core/verdict.js";
12
-
13
- import "../../core/effect.js";
14
-
15
- import { installOptimisticEngine } from "../../core/optimistic.js";
16
-
17
- import { $TARGET, markRawIngest, setNextOptimisticViewResolver, isWrappable, rawValuesUsed, isRawValue } from "../store.js";
18
-
19
- import { runProjectionComputedNext } from "./projection.js";
20
-
21
- import { wrapNext, runAuthoritative, storeSetterNext, unwrapValue, targetsEqual, heldMaskView, getNode, getHasNode, getKeySetNode, bumpDeep, stagedTruthPB, authoritativeRead, authoritativeServe, readerOverride } from "./store.js";
22
-
23
- import { sameKey } from "./reconcile.js";
24
-
25
- import { setOptHooks, $OWNER, lookupTarget } from "./target.js";
26
-
27
- /**
28
- * Store rewrite — optimistic stores (§3/§7, RUL-3): no store-side layer, no
29
- * backup snapshots. Nodes in an optimistic family are ARMED core signals
30
- * (`_overrideValue` slot), so every user write rides the engine's
31
- * optimisticWrite — per-transaction ownership, entanglement, reverts, and
32
- * flash-at-flush are inherited, not reimplemented. Membership edits live on
33
- * armed presence nodes (the §6 overlay), so structural optimism reverts with
34
- * the same per-transaction granularity (FINDING-2's fix by construction).
35
- *
36
- * Derived form = an optimistic projection. Landings follow the fold rule
37
- * (#3164, RUL-2 as re-ruled): while a transaction retains optimistic edits
38
- * on the family, truth that lands STAGES into that transaction — a keyed
39
- * identity-preserving walk written through the ordinary staged setter
40
- * channel — and reveals atomically at settle, exactly like a signal landing
41
- * under an active override (asyncWrite's held branch). Optimistic edits are
42
- * never consumed by landings; they live exactly as long as their transaction
43
- * and die by engine-native revert. With no retainer, landings commit
44
- * immediately under projectionWriteActive (authoritative, silently beneath
45
- * any bare-write overrides — those ride the flight's own transition, #2951).
46
- * Authoritative readers (until()'s predicate) tunnel into staged truth via
47
- * the node read path's pending-value arm. The transitionBlocked store-half
48
- * (#2951) is installed here for next-shaped targets, chaining the
49
- * legacy/engine checks.
50
- */
51
- /** #3164 fold: a stamped truth is HELD (masked from ordinary readers until
52
- * the reveal) only while its transition is live AND retaining optimism —
53
- * overrides are what make partial-coverage composition a tear. A plain
54
- * async transition carries no overrides, so downstream computes must see
55
- * staged values to converge (normal speculation). Resolves merges first:
56
- * merge unions optimistic nodes/stores into the target. */ function transitionHoldsOptimism(e) {
57
- const t = currentTransition(e);
58
- return t.Rn !== true && (t.cn.length !== 0 || t.ti.size !== 0);
59
- }
60
-
61
- let blockedInstalled = false;
62
-
63
- function installNextBlockedHalf() {
64
- if (blockedInstalled) return;
65
- blockedInstalled = true;
66
- // Late-bind the optimistic machinery into the plain store/reconcile paths
67
- // (all call sites are fam?.opt-gated, so this always runs first) and the
68
- // affects witness's view resolver.
69
- setOptHooks({
70
- notifyOptimisticWrites: notifyOptimisticWrites,
71
- optimisticView: optimisticView,
72
- applyTentative: applyTentative,
73
- retainsOptimism: transitionHoldsOptimism
74
- });
75
- // The affects() declaration walk is a WRITER channel (A28 (5)): tagging a
76
- // parent covers the record as the writer sees it, this tick's optimistic
77
- // writes included — the draft view, not the reader view.
78
- setNextOptimisticViewResolver((e, t) => optimisticView(e, t, true));
79
- // Scheduler flush tails call _clearOptimisticStores whenever tracked
80
- // stores exist; next has no layer to clear — reverts are engine-native —
81
- // so the hook only empties the batch set.
82
- if (!GlobalQueue._t) {
83
- GlobalQueue._t = e => {
84
- for (const t of e) {
85
- const e = t?.[$TARGET];
86
- const n = e?.fam?.overlaid;
87
- if (n !== undefined) {
88
- for (const e of n) {
89
- // Keyset resync (classic channel twin): the keyset node's own
90
- // revert can compare EQUAL (a landing's bump matched the
91
- // tentative bump) while the arrangement underneath changed —
92
- // mapArray/ownKeys subscribers must re-read the post-revert
93
- // view. Authoritative bump: never re-arm the node we are
94
- // clearing.
95
- if (e.k !== null) runAuthoritative(() => setSignal(e.k, e => e + 1));
96
- }
97
- }
98
- }
99
- e.clear();
100
- };
101
- }
102
- // Revert-side link refresh (#3672, §7b/O6): a chained node's `_value` is
103
- // never served — the base's live value is — so the engine cannot read
104
- // committed truth from it. The engine consults it at exactly two moments:
105
- // optimisticWrite's no-op check (the setter hands it the visible value,
106
- // see `emit` in notifyOptimisticWrites) and resolveOptimisticNodes'
107
- // notify compare (here). Left at the write-time value, a base commit
108
- // during the override — confirm 7, user writes back to the pre-write 5 —
109
- // made the revert compare 5 === 5 and notify nobody while the base read 7:
110
- // a memo over the view stayed at the guess forever (render effects were
111
- // rescued by readsHeldCommitted's replay). Refresh every armed node of
112
- // each chained host in the reverting batch to the base's live value right
113
- // before the compare, so it is exact: notify iff truth differs from the
114
- // guess. Untracked: a flush from inside a computation must not link it.
115
- const e = GlobalQueue.oi;
116
- GlobalQueue.oi = t => {
117
- let n = null;
118
- for (const e of t) {
119
- const t = e.Bn;
120
- if (t?.ch !== true || n?.has(t)) continue;
121
- (n ??= new Set).add(t);
122
- untrack(() => {
123
- const e = t.v;
124
- for (const n of Reflect.ownKeys(t.n)) {
125
- const i = t.n[n];
126
- if (hasActiveOverride(i)) i.ce = unwrapValue(e[n]);
127
- }
128
- if (t.h !== null) for (const n of Reflect.ownKeys(t.h)) {
129
- const i = t.h[n];
130
- if (hasActiveOverride(i)) i.ce = n in e;
131
- }
132
- });
133
- }
134
- e(t);
135
- };
136
- const t = GlobalQueue.ai;
137
- GlobalQueue.ai = e => {
138
- for (const t of e.ti) {
139
- const n = t?.[$TARGET];
140
- const i = n?.fam;
141
- const r = i?.node;
142
- // The hold exists to keep optimistic state alive until the store's own
143
- // truth lands (#2951). Once the family carries NO live overrides (a
144
- // landing consumed them, or they never existed), a pending firewall is
145
- // no reason to park the transaction — blocking then leaks it forever
146
- // when the in-flight question is never answered (undisposed fixtures).
147
- if (r == null || !(r.h & STATUS_PENDING)) continue;
148
- // Ownership is declared (#3146): only the flight's OWN transaction
149
- // parks on the flight (the #2951 anchor routed the bare write there).
150
- // A transaction that merely brushed the store never waits for truth
151
- // it does not carry. An undeclared flight is owned by the firewall's stamp.
152
- const o = liveTransition(i.ft ?? r.we);
153
- if (o !== null && o !== currentTransition(e)) continue;
154
- if (familyHasLiveOverrides(i)) return true;
155
- }
156
- return t(e);
157
- };
158
- }
159
-
160
- function familyHasLiveOverrides(e) {
161
- const t = e.overlaid;
162
- if (t === undefined || t.size === 0) return false;
163
- for (const e of t) {
164
- for (const t of [ e.n, e.h ]) {
165
- if (t === null) continue;
166
- for (const e of Reflect.ownKeys(t)) {
167
- const n = t[e];
168
- if (n.o?.Fe !== undefined && n.o?.Fe !== NOT_PENDING) return true;
169
- }
170
- }
171
- if (e.k !== null && e.k.o?.Fe !== undefined && e.k.o?.Fe !== NOT_PENDING) return true;
172
- }
173
- t.clear();
174
- // nothing live — drop the bookkeeping
175
- return false;
176
- }
177
-
178
- function createOptimisticStoreNext(e, t, n) {
179
- // Engine first (armed nodes need optimisticWrite installed before any
180
- // node exists), then the next-shape hooks.
181
- installOptimisticEngine();
182
- installNextBlockedHalf();
183
- const i = typeof e === "function";
184
- const r = i ? n : t;
185
- const o = i ? t : e;
186
- const s = {
187
- map: new WeakMap,
188
- node: null,
189
- shallow: !!r?.shallow,
190
- opt: true
191
- };
192
- const l = wrapNext(o, null, null, s);
193
- s.px = l;
194
- // Same key resolution the projection channels use ("id" default) — replay's
195
- // satisfaction rule reads it off the family.
196
- const u = r?.key === undefined ? "id" : r.key;
197
- s.key = typeof u === "function" ? u : u === null ? null : e => isWrappable(e) ? e[u] : undefined;
198
- if (s.shallow) {
199
- l[$TARGET].s = true;
200
- markRawIngest(o);
201
- }
202
- if (i) {
203
- const t = e;
204
- // #3146: an async settle event belongs to the flight's OWN transaction.
205
- // A live declared one re-enters (a merge if the generic settle path
206
- // already entered a graph-stamped stranger — the landing supersedes any
207
- // recompute deriving from that stranger's world); a dead one renews (per
208
- // A18(1) each arrival reveals on its own schedule, so per-yield
209
- // transactions die with their commit and the next settle event opens the
210
- // flight's next one — still declared, never anonymous). An UNDECLARED
211
- // flight (loading window) keeps the ambient reveal (#2933: the loading
212
- // rail is transaction-invisible).
213
- const enterFlightTransition = () => {
214
- const e = s.ft;
215
- if (e == null) return;
216
- let t = liveTransition(e);
217
- if (t === null) s.ft = t = createTransition();
218
- s.node.we = t;
219
- globalQueue.initTransition(t);
220
- };
221
- // Landing router (#3164 fold ruling): while a transaction retains
222
- // optimistic edits on this family, truth landings stage INTO it and
223
- // reveal atomically at settle; with no retainer they commit immediately
224
- // under the authoritative posture (async commits land outside the
225
- // computed's sync body, so the posture is re-applied here) — inside the
226
- // flight-owned transaction (#3146). Sync commits (the derive's body,
227
- // owner is the firewall itself) reveal with their own recompute's flush.
228
- const wrapCommit = (e, t) => {
229
- const n = retainingTransition(s);
230
- if (n !== null) return void stageLanding(s, n, t);
231
- if (getOwner() !== s.node) enterFlightTransition();
232
- runAuthoritative(e);
233
- };
234
- // Draft writes (the derive mutating its draft, sync body and post-await
235
- // continuations alike) are the same truth channel per-operation: bind
236
- // each op to the retaining transaction so its node writes stage and its
237
- // backing fold defers (ensurePB stamps foldBatches with the swapped-in
238
- // batch; the write-override eager-commit branch in notifyWrites yields
239
- // to any active transaction).
240
- const aroundDraftWrite = e => {
241
- const t = retainingTransition(s);
242
- if (t === null) e(); else runFolded(t, e);
243
- };
244
- // Flight declaration (#3146): a recompute that registered a truth-flight
245
- // OWNS its transaction. The ask's transaction is recorded on the family
246
- // (created by the flight's own pending throw when none was ambient, the
247
- // causing write's/refresh's when one was — graph-driven causality) and
248
- // the firewall is stamped so every settle path resolves the flight's
249
- // transaction by construction, not by whatever last brushed the node.
250
- // When the ask took none (a dead stale stamp — the previous flight's,
251
- // cleared nowhere — bare-returns the pre-throw entry), the flight opens
252
- // its own here, same activation point as the pre-throw's creation: the
253
- // ambient batch (the causing write, same-tick bare optimism) adopts into
254
- // it exactly as it would have there, and the pending notification that
255
- // follows this unwind registers observers against it. The flight
256
- // also registers as its own async reporter: the transaction lives
257
- // exactly as long as the question is unanswered, observed or not — the
258
- // #2951 refetch-hold no longer depends on a tracked observer having
259
- // happened to register one. Loading-window flights declare nothing
260
- // (#2933: the loading rail is transaction-invisible); a sync run clears
261
- // the declaration.
262
- const declareFlight = e => {
263
- if (e.o?.Ce == null) {
264
- if (!e.ve) s.ft = null;
265
- return;
266
- }
267
- // First flight (#3146 carve-out): nothing has ever committed, so there
268
- // is no truth to keep on screen and no optimistic state to protect. An
269
- // uninitialized ask suspends its readers into their Loading boundary
270
- // exactly like a plain derived store's first flight — declaring a
271
- // transaction here instead held the ROOT MOUNT (render()'s scheduled
272
- // insert rides transitions) until the fetch landed, so the boundary's
273
- // fallback never showed and the whole page stayed blank. The loading
274
- // window (#2933) already declares nothing for the same reason; once
275
- // the first truth lands, every refetch flight declares as before.
276
- if (e.ve || e.h & STATUS_UNINITIALIZED) return;
277
- let t = activeTransition;
278
- if (t === null) globalQueue.initTransition(t = createTransition());
279
- s.ft = t;
280
- e.we = t;
281
- let n = t.le.get(e);
282
- if (n === undefined) t.le.set(e, n = new Set);
283
- n.add(e);
284
- };
285
- let n;
286
- if (r?.seedLoadingValue) n = {
287
- loadingValue: undefined
288
- };
289
- const i = computed(() => {
290
- const e = getOwner();
291
- try {
292
- runAuthoritative(() => runProjectionComputedNext(l, t, r?.key === undefined ? "id" : r.key, wrapCommit, aroundDraftWrite));
293
- } finally {
294
- declareFlight(e);
295
- }
296
- }, n);
297
- i.C &= ~CONFIG_AUTO_DISPOSE;
298
- s.node = i;
299
- }
300
- return [ l, e => {
301
- // Retention ledger (#3164): record the owning transaction so landings
302
- // know to fold. Captured at entry — the action machinery has the
303
- // transaction ambient while user code runs; a bare write with no
304
- // transaction retains nothing (it rides the flight's own transition
305
- // per #2951 and dies with it).
306
- const t = activeTransition;
307
- storeSetterNext(l, e);
308
- if (t !== null) (s.rt ??= new Set).add(t);
309
- } ];
310
- }
311
-
312
- /** Resolve a retained transition through its merge chain (`_done` holds the
313
- * merge target while merged, `true` once settled). Null = dead. */ function liveTransition(e) {
314
- if (e === null) return null;
315
- while (typeof e.Rn === "object") e = e.Rn;
316
- return e.Rn === true ? null : e;
317
- }
318
-
319
- /** The transaction truth landings fold into: the first live member of the
320
- * family's retention ledger (dead members prune here). Multiple live
321
- * retainers entangle through their shared family writes and settle
322
- * together, so folding into the first reaches all of them. */ function retainingTransition(e) {
323
- const t = e.rt;
324
- if (t === undefined || t.size === 0) return null;
325
- let n = null;
326
- for (const e of t) {
327
- const i = liveTransition(e);
328
- if (i === null) t.delete(e); else n ??= i;
329
- }
330
- return n;
331
- }
332
-
333
- /** Fold a landing into the retaining transaction (#3164): the landed value
334
- * is written through the ORDINARY staged setter channel — node writes park
335
- * as `_pendingValue` registered with the transaction's batch (speculation
336
- * and until()'s authoritative tunnel see them; live view stays coherent),
337
- * and the backing fold defers via the foldBatches stamp — under the
338
- * authoritative posture, so armed nodes take the engine bypass and no
339
- * override is created. The engine's own commit machinery reveals everything
340
- * atomically when the transaction settles (transitions never abort: failed
341
- * actions still commit — only optimistic overrides revert). */ function stageLanding(e, t, n) {
342
- runFolded(t, () => runAuthoritative(() => storeSetterNext(e.px, t => {
343
- stagedApply(t, unwrapValue(n), e.key ?? null);
344
- }, false)));
345
- }
346
-
347
- /** Run a fold write inside the retaining transaction's batch, then
348
- * transition-stamp its staged nodes NOW (parity with the parked-transition
349
- * flush path's reassignPendingTransition): the stamp is what routes stale
350
- * (render) readers to the committed value — core read's cross-transaction
351
- * guard — and what makes foldHeld defer the backing for context-free
352
- * readers. A microtask staging never crosses that flush path, so without
353
- * the stamp a render effect's speculative recompute would compose staged
354
- * truth with live overrides — the #3164 tear, one window later. Armed
355
- * nodes additionally raise CONFIG_HELD_TRUTH: their staged value is
356
- * confirming truth masked from ordinary readers until the reveal (plain
357
- * staged nodes stay visible — normal speculation; override-covered nodes
358
- * stay unarmed — the override is their display and its revert their
359
- * notification, A17). */ function runFolded(e, t) {
360
- runAsTransitionBatch(e, t);
361
- const n = e.Gn;
362
- for (let t = 0; t < n.length; t++) {
363
- const i = n[t];
364
- i.we = e;
365
- if (i.C & CONFIG_OPTIMISTIC && !hasActiveOverride(i)) i.C |= CONFIG_HELD_TRUTH;
366
- }
367
- }
368
-
369
- /** Keyed identity-preserving deep merge through live draft proxies — the
370
- * staged twin of the adoption walk. Reads see the pending backing (staged
371
- * view), so consecutive landings during one hold compose; key-matched rows
372
- * keep their raw (and so their proxy) in the slot with only changed leaves
373
- * written; unmatched rows land wholesale. Runs inside stageLanding's
374
- * authoritative bracket: drafts seed from committed truth, never overlays. */ function stagedApply(e, t, n) {
375
- const i = Array.isArray(e);
376
- if (i && Array.isArray(t)) {
377
- const i = t.length;
378
- if (n !== null) {
379
- // Occurrence-aware key queues (parity with the adoption window):
380
- // duplicate keys match per occurrence, each current row consumed once.
381
- let r = null;
382
- const o = e.length;
383
- for (let t = 0; t < o; t++) {
384
- const i = unwrapValue(e[t]);
385
- if (!isWrappable(i)) continue;
386
- const o = n(i);
387
- if (o === undefined) continue;
388
- const s = (r ??= new Map).get(o);
389
- if (s === undefined) r.set(o, [ i ]); else s.push(i);
390
- }
391
- // Echo adoption: an optimistic structural add whose key the landing
392
- // confirms must keep its raw (and so its proxy — list drivers keep the
393
- // DOM row). Tentative rows never reach committed truth (they live in
394
- // node overrides), so key-match the draft target's active override
395
- // rows as a secondary pool. Committed rows queued first own their
396
- // keys; overlay rows only extend coverage. Adopted raws enter staged
397
- // truth; at settle the override reverts and the reveal re-seats the
398
- // same raw.
399
- const s = e[$TARGET]?.n;
400
- if (s != null) {
401
- for (const e of Reflect.ownKeys(s)) {
402
- const t = s[e];
403
- if (!hasActiveOverride(t)) continue;
404
- const i = unwrapValue(unwrapOverride(t.o.Fe));
405
- if (!isWrappable(i)) continue;
406
- const o = n(i);
407
- if (o === undefined) continue;
408
- const l = (r ??= new Map).get(o);
409
- if (l === undefined) r.set(o, [ i ]); else l.push(i);
410
- }
411
- }
412
- for (let o = 0; o < i; o++) {
413
- const i = t[o];
414
- let s;
415
- if (isWrappable(i) && r !== null) {
416
- const e = n(i);
417
- if (e !== undefined) {
418
- for (const [t, n] of r) {
419
- if (!sameKey(t, e)) continue;
420
- s = n.shift();
421
- if (n.length === 0) r.delete(t);
422
- break;
423
- }
424
- }
425
- }
426
- if (s !== undefined) {
427
- if (unwrapValue(e[o]) !== s) e[o] = s;
428
- stagedApply(e[o], i, n);
429
- } else {
430
- const t = unwrapValue(e[o]);
431
- if (!isEqual(t, i) && !targetsEqual(t, i)) e[o] = i;
432
- }
433
- }
434
- } else {
435
- for (let r = 0; r < i; r++) {
436
- const i = t[r];
437
- const o = unwrapValue(e[r]);
438
- if (o === i) continue;
439
- 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;
440
- }
441
- }
442
- if (e.length !== i) e.length = i;
443
- return;
444
- }
445
- // Object merge; also the degenerate root-kind-change shape (arrays accept
446
- // keyed writes/deletes, so a wholesale restatement still lands staged).
447
- for (const r of Reflect.ownKeys(t)) {
448
- if (i && r === "length" || r === $OWNER) continue;
449
- const o = t[r];
450
- const s = unwrapValue(e[r]);
451
- if (s === o) continue;
452
- if (isWrappable(o) && isWrappable(s) && Array.isArray(o) === Array.isArray(s)) {
453
- // Different-keyed entities never merge (tentative-channel parity):
454
- // the incoming object replaces the slot wholesale.
455
- if (n !== null) {
456
- const t = n(s);
457
- const i = n(o);
458
- if (t !== undefined && i !== undefined && !sameKey(t, i)) {
459
- e[r] = o;
460
- continue;
461
- }
462
- }
463
- stagedApply(e[r], o, n);
464
- } else if (!isEqual(s, o) && !targetsEqual(s, o)) {
465
- e[r] = o;
466
- }
467
- }
468
- for (const n of Reflect.ownKeys(e)) {
469
- if (i && n === "length" || n === $OWNER || n in t) continue;
470
- delete e[n];
471
- }
472
- }
473
-
474
- // ---- optimistic-only store machinery (moved from next/store.ts /
475
- // next/reconcile.ts so plain-store bundles tree-shake it) ----
476
- /** Diff the draft against the current OPTIMISTIC VIEW (committed + active
477
- * overrides — the same view the draft was seeded from) and emit engine writes
478
- * for exactly the changed keys. Visible-view diffing keeps no-op writes from
479
- * entangling lanes (RUL-10 / opt R38). */ function notifyOptimisticWrites(e, t) {
480
- // A bare write while the store's own truth is in flight rides the FLIGHT'S
481
- // OWN transaction (#2951 via the #3146 declaration): entangle it so the
482
- // override survives until the refetch settles instead of flash-reverting
483
- // at plain flush end. The blocked-check store-half keeps that transaction
484
- // from settling while the firewall is pending. Declared ownership replaces
485
- // the old circumstantial route through the firewall's `_transition` stamp,
486
- // which was whatever last brushed the node.
487
- const n = e.fam?.ft;
488
- if (n != null) {
489
- const e = liveTransition(n);
490
- if (e !== null) globalQueue.initTransition(e);
491
- }
492
- // The write is judged against what ordinary readers SEE, not against the
493
- // committed backing slot: under an adoption hold (#3074) `t.v` is already
494
- // the truth a live transaction is holding, and an optimistic write equal
495
- // to it compared as a no-op — no override, no lane, and the screen kept
496
- // the pre-hold value until the transaction committed (#3330's store
497
- // twin: `s.v = 1` after a `yield` while the derive had already staged
498
- // `v: 1`). Signal parity: the override reveals on its lane now, with its
499
- // derivations; the held truth reveals on the transaction's schedule.
500
- const i = heldMaskView(e) ?? e.v;
501
- // Compare RAWS on both sides (`nv` below is unwrapped already). A chained
502
- // target's `old` is the inner store's proxy, whose reads hand back inner
503
- // child PROXIES; the draft's clone holds the inner raws. Comparing the two
504
- // as-is marked every untouched row changed, and the overlay then served
505
- // each from an override as a fresh non-chained target — row identities
506
- // churned for the life of the action and snapped back at settle (#3323).
507
- const visible = (t, n) => {
508
- const i = e.n?.[t];
509
- return unwrapValue(i !== undefined && hasActiveOverride(i) ? unwrapOverride(i.o?.Fe) : n);
510
- };
511
- const visiblePresent = t => {
512
- const n = e.h?.[t];
513
- return n !== undefined && hasActiveOverride(n) ? !!unwrapOverride(n.o?.Fe) : t in i;
514
- };
515
- // Chained nodes are links (§7b, O6): their `_value` is never served and
516
- // never learns the base's commits, yet optimisticWrite's no-op check reads
517
- // it (#3672). Hand it the visible committed value at the write; the revert
518
- // compare gets the same treatment in installNextBlockedHalf.
519
- const emit = (t, n, i) => {
520
- if (e.ch && !hasActiveOverride(t)) t.ce = n;
521
- setSignal(t, () => i);
522
- };
523
- let r = false;
524
- const o = Array.isArray(t);
525
- for (const n of Reflect.ownKeys(t)) {
526
- if (o && n === "length" || n === $OWNER) continue;
527
- const s = unwrapValue(t[n]);
528
- if (!visiblePresent(n)) {
529
- // Optimistic add: value node + presence node + membership bump.
530
- const t = unwrapValue(i[n]);
531
- emit(getNode(e, n, t), t, s);
532
- emit(getHasNode(e, n, n in i), n in i, true);
533
- r = true;
534
- } else {
535
- const t = visible(n, i[n]);
536
- if (!isEqual(t, s) && !targetsEqual(t, s)) {
537
- emit(getNode(e, n, t), t, s);
538
- if (o) r = true;
539
- }
540
- }
541
- }
542
- for (const n of Reflect.ownKeys(i)) {
543
- if (o && n === "length" || n === $OWNER) continue;
544
- if (n in t || !visiblePresent(n)) continue;
545
- // Optimistic delete: node reads undefined, presence flips, membership bumps.
546
- const s = unwrapValue(i[n]);
547
- emit(getNode(e, n, s), s, undefined);
548
- emit(getHasNode(e, n, true), true, false);
549
- r = true;
550
- }
551
- if (o) {
552
- const n = visible("length", i.length);
553
- if (n !== t.length) {
554
- emit(getNode(e, "length", n), n, t.length);
555
- r = true;
556
- }
557
- }
558
- if (r) setSignal(getKeySetNode(e), e => e + 1);
559
- // Deep-witness: optimistic value writes notify deep() subscribers too
560
- // (structural ones already ride the key-set bump above).
561
- bumpDeep(e);
562
- // Discard the draft — committed raw is untouched (revert target by
563
- // construction) — restoring any truth-staged backing this draft displaced
564
- // (#3164 fold: ensurePB parked it so tentative writes could not pollute
565
- // staged truth). Register the root store for the scheduler's settle hooks.
566
- e.pb = stagedTruthPB.get(e) ?? null;
567
- if (e.pb !== null) stagedTruthPB.delete(e);
568
- (e.fam.overlaid ??= new Set).add(e);
569
- GlobalQueue.fi?.(e.fam.px ?? e.px);
570
- }
571
-
572
- /** Optimistic-view composition for snapshot/deep (O1: snapshot is the CURRENT
573
- * view, lane values included; a fresh copy per call during pending windows —
574
- * RUL-12). Returns `src` untouched when no override is active on `t`.
575
- * Authoritative-view reads (until()'s predicate) skip composition entirely:
576
- * the predicate observes authoritative truth, never the caller's tentative
577
- * overlay. (Write-side emission callers never run under such a compute.) */ function optimisticView(e, t, n = false) {
578
- if (e.fam?.opt !== true || authoritativeRead()) return t;
579
- let i = null;
580
- const ensure = () => i ??= Array.isArray(t) ? [ ...t ] : {
581
- ...t
582
- };
583
- // Reader composition (snapshot/deep, the length view with no armed length
584
- // node) takes a superseded override as the traps serve it (readerOverride,
585
- // #3331): the writer's draft and the write-side callers (applyTentative,
586
- // applyAdopt's key-matching view) keep the override itself.
587
- const r = !n && !authoritativeServe();
588
- const o = e.n;
589
- if (o !== null) {
590
- for (const e of Reflect.ownKeys(o)) {
591
- const i = o[e];
592
- // A28 (5): readers see an optimistic write once a flush carried it;
593
- // the draft (writer channel) composes on it now.
594
- if (!(n ? hasActiveOverride(i) : visibleOverride(i))) continue;
595
- if (e === "length" && Array.isArray(t)) {
596
- const e = t.length;
597
- const n = r ? readerOverride(i, e) : unwrapOverride(i.o?.Fe);
598
- if (e !== n) ensure().length = n;
599
- } else {
600
- const n = t[e];
601
- const o = r ? readerOverride(i, n) : unwrapOverride(i.o?.Fe);
602
- if (!isEqual(n, o)) ensure()[e] = o;
603
- }
604
- }
605
- }
606
- const s = e.h;
607
- if (s !== null) {
608
- for (const e of Reflect.ownKeys(s)) {
609
- const o = s[e];
610
- // A28 (5): readers see an optimistic write once a flush carried it;
611
- // the draft (writer channel) composes on it now.
612
- if (!(n ? hasActiveOverride(o) : visibleOverride(o))) continue;
613
- const l = e in (i ?? t);
614
- const u = !!(r ? readerOverride(o, l) : unwrapOverride(o.o?.Fe));
615
- if (!u && l) delete ensure()[e];
616
- }
617
- }
618
- return i ?? t;
619
- }
620
-
621
- function applyTentative(e, t, n) {
622
- const i = e.pb ?? e.v;
623
- const r = optimisticView(e, i);
624
- const o = e.fam;
625
- const s = Array.isArray(t);
626
- if (Array.isArray(r) !== s) return;
627
- // kind change at root: flat overrides below
628
- const l = [];
629
- let u;
630
- if (s) u = [ ...t ]; else {
631
- u = {};
632
- for (const e of Reflect.ownKeys(t)) if (e !== $OWNER) u[e] = t[e];
633
- }
634
- const match = (e, t) => {
635
- if (!isWrappable(e) || !isWrappable(t)) return null;
636
- if (rawValuesUsed && (isRawValue(e) || isRawValue(t))) return null;
637
- if (Array.isArray(e) !== Array.isArray(t)) return null;
638
- if (n) {
639
- const i = n(e);
640
- const r = n(t);
641
- // SameValueZero (re-audit 3, P1-3): parity with the plain reconcile
642
- // channel — NaN keys are self-equal.
643
- if (i !== undefined && r !== undefined && !sameKey(i, r)) return null;
644
- }
645
- return lookupTarget(unwrapValue(e), o) ?? null;
646
- };
647
- if (s) {
648
- const e = r;
649
- let i = null;
650
- for (let r = 0; r < t.length; r++) {
651
- const o = t[r];
652
- if (!isWrappable(o)) continue;
653
- let s;
654
- if (n) {
655
- const t = n(o);
656
- if (t !== undefined) {
657
- if (i === null) {
658
- // Occurrence-aware index queues (re-audit 3, P1-3): parity with
659
- // the plain adoption window — duplicate keys match per
660
- // occurrence, each view row consumed once.
661
- i = new Map;
662
- for (let t = 0; t < e.length; t++) {
663
- const r = unwrapValue(e[t]);
664
- if (isWrappable(r)) {
665
- const e = n(r);
666
- if (e === undefined) continue;
667
- const o = i.get(e);
668
- if (o === undefined) i.set(e, t); else if (Array.isArray(o)) o.push(t); else i.set(e, [ o, t ]);
669
- }
670
- }
671
- }
672
- const r = i.get(t);
673
- if (r === undefined) s = undefined; else if (Array.isArray(r)) {
674
- s = unwrapValue(e[r.shift()]);
675
- if (r.length === 1) i.set(t, r[0]);
676
- } else {
677
- s = unwrapValue(e[r]);
678
- i.delete(t);
679
- }
680
- } else s = unwrapValue(e[r]);
681
- } else s = unwrapValue(e[r]);
682
- const a = match(s, o);
683
- if (a !== null) {
684
- // Keep the existing row in the slot (identity preserved); recurse.
685
- u[r] = unwrapValue(s);
686
- l.push([ a, o ]);
687
- }
688
- }
689
- } else {
690
- for (const e of Reflect.ownKeys(t)) {
691
- if (e === $OWNER) continue;
692
- const n = unwrapValue(r[e]);
693
- const i = t[e];
694
- const o = match(n, i);
695
- if (o !== null) {
696
- u[e] = n;
697
- l.push([ o, i ]);
698
- }
699
- }
700
- }
701
- // Flat overrides for this level (adds, removals, moved slots, length, leaf
702
- // values) — preserve any live user draft backing across the call.
703
- const a = e.pb;
704
- e.pb = null;
705
- notifyOptimisticWrites(e, u);
706
- e.pb = a;
707
- for (let e = 0; e < l.length; e++) applyTentative(l[e][0], unwrapValue(l[e][1]), n);
708
- }
709
-
710
- export { createOptimisticStoreNext, notifyOptimisticWrites, optimisticView, transitionHoldsOptimism };