@solidjs/signals 2.0.0-rc.1 → 2.0.0-rc.10
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.
- package/dist/dev-shared.js +7138 -0
- package/dist/dev.attribution.js +3307 -0
- package/dist/dev.js +5691 -4491
- package/dist/observe/affects.js +129 -0
- package/dist/observe/attribution.js +7 -0
- package/dist/observe/boundaries.js +793 -0
- package/dist/observe/core/action.js +177 -0
- package/dist/observe/core/async.js +849 -0
- package/dist/observe/core/attribution-costs.js +66 -0
- package/dist/observe/core/attribution-feedback.js +280 -0
- package/dist/observe/core/attribution-hooks.js +83 -0
- package/dist/observe/core/attribution-queries.js +43 -0
- package/dist/observe/core/attribution.js +2904 -0
- package/dist/observe/core/constants.js +283 -0
- package/dist/{types-cjs/core/context.d.cts → observe/core/context.js} +37 -12
- package/dist/observe/core/core.js +1999 -0
- package/dist/observe/core/dev.js +343 -0
- package/dist/observe/core/effect.js +200 -0
- package/dist/observe/core/error-hooks.js +71 -0
- package/dist/observe/core/error.js +80 -0
- package/dist/observe/core/external.js +98 -0
- package/dist/observe/core/graph.js +155 -0
- package/dist/observe/core/heap.js +147 -0
- package/dist/observe/core/invariants.js +45 -0
- package/dist/observe/core/lanes.js +205 -0
- package/dist/observe/core/optimistic.js +627 -0
- package/dist/observe/core/owner.js +353 -0
- package/dist/observe/core/scheduler.js +1547 -0
- package/dist/observe/core/verdict.js +674 -0
- package/dist/observe/index.js +59 -0
- package/dist/observe/map.js +484 -0
- package/dist/observe/signals.js +731 -0
- package/dist/observe/store/index.js +44 -0
- package/dist/observe/store/next/optimistic.js +717 -0
- package/dist/observe/store/next/projection.js +266 -0
- package/dist/observe/store/next/reconcile.js +375 -0
- package/dist/observe/store/next/store.js +2480 -0
- package/dist/observe/store/next/target.js +51 -0
- package/dist/observe/store/store.js +331 -0
- package/dist/observe/store/storePath.js +103 -0
- package/dist/observe/store/utils.js +1070 -0
- package/dist/prod/affects.js +10 -9
- package/dist/prod/attribution.js +45 -0
- package/dist/prod/boundaries.js +388 -178
- package/dist/prod/core/action.js +65 -37
- package/dist/prod/core/async.js +378 -163
- package/dist/prod/core/attribution-hooks.js +3 -0
- package/dist/prod/core/constants.js +186 -1
- package/dist/prod/core/context.js +10 -16
- package/dist/prod/core/core.js +1546 -416
- package/dist/prod/core/dev.js +18 -1
- package/dist/prod/core/effect.js +119 -79
- package/dist/prod/core/error-hooks.js +71 -0
- package/dist/prod/core/error.js +13 -1
- package/dist/prod/core/external.js +4 -4
- package/dist/prod/core/graph.js +105 -54
- package/dist/prod/core/heap.js +72 -65
- package/dist/prod/core/invariants.js +3 -2
- package/dist/prod/core/lanes.js +117 -52
- package/dist/prod/core/optimistic.js +458 -104
- package/dist/prod/core/owner.js +131 -84
- package/dist/prod/core/scheduler.js +998 -241
- package/dist/prod/core/verdict.js +417 -112
- package/dist/prod/index.js +20 -8
- package/dist/prod/map.js +312 -151
- package/dist/prod/signals.js +363 -32
- package/dist/prod/store/index.js +2 -0
- package/dist/prod/store/next/optimistic.js +530 -197
- package/dist/prod/store/next/projection.js +142 -57
- package/dist/prod/store/next/reconcile.js +149 -103
- package/dist/prod/store/next/store.js +1470 -346
- package/dist/prod/store/next/target.js +40 -9
- package/dist/prod/store/store.js +23 -19
- package/dist/prod/store/utils.js +997 -130
- package/dist/types/attribution.d.ts +19 -0
- package/dist/types/attribution.prod.d.ts +22 -0
- package/dist/types/boundaries.d.ts +78 -5
- package/dist/types/core/action.d.ts +12 -5
- package/dist/types/core/async.d.ts +3 -1
- package/dist/types/core/attribution-costs.d.ts +35 -0
- package/dist/types/core/attribution-feedback.d.ts +133 -0
- package/dist/types/core/attribution-hooks.d.ts +304 -0
- package/dist/types/core/attribution-queries.d.ts +24 -0
- package/dist/types/core/attribution.d.ts +892 -0
- package/dist/types/core/constants.d.ts +179 -0
- package/dist/types/core/core.d.ts +299 -25
- package/dist/types/core/dev.d.ts +421 -8
- package/dist/types/core/effect.d.ts +8 -1
- package/dist/types/core/error-hooks.d.ts +71 -0
- package/dist/types/core/error.d.ts +9 -0
- package/dist/types/core/graph.d.ts +23 -0
- package/dist/types/core/heap.d.ts +5 -3
- package/dist/types/core/index.d.ts +6 -3
- package/dist/types/core/invariants.d.ts +5 -1
- package/dist/types/core/lanes.d.ts +51 -19
- package/dist/types/core/owner.d.ts +15 -3
- package/dist/types/core/scheduler.d.ts +248 -1
- package/dist/types/core/types.d.ts +124 -41
- package/dist/types/index.d.ts +18 -5
- package/dist/types/map.d.ts +2 -0
- package/dist/types/signals.d.ts +145 -13
- package/dist/types/store/index.d.ts +7 -6
- package/dist/types/store/next/optimistic.d.ts +18 -13
- package/dist/types/store/next/projection.d.ts +5 -2
- package/dist/types/store/next/reconcile.d.ts +7 -0
- package/dist/types/store/next/store.d.ts +83 -11
- package/dist/types/store/next/target.d.ts +108 -11
- package/dist/types/store/store.d.ts +20 -11
- package/dist/types/store/utils.d.ts +188 -0
- package/package.json +31 -26
- package/dist/node.cjs +0 -8137
- package/dist/types/store/optimistic.d.ts +0 -45
- package/dist/types/store/projection.d.ts +0 -70
- package/dist/types/store/reconcile.d.ts +0 -46
- package/dist/types-cjs/affects.d.cts +0 -47
- package/dist/types-cjs/boundaries.d.cts +0 -175
- package/dist/types-cjs/core/action.d.cts +0 -64
- package/dist/types-cjs/core/async.d.cts +0 -21
- package/dist/types-cjs/core/constants.d.cts +0 -71
- package/dist/types-cjs/core/core.d.cts +0 -156
- package/dist/types-cjs/core/dev.d.cts +0 -64
- package/dist/types-cjs/core/effect.d.cts +0 -30
- package/dist/types-cjs/core/error.d.cts +0 -47
- package/dist/types-cjs/core/external.d.cts +0 -15
- package/dist/types-cjs/core/graph.d.cts +0 -5
- package/dist/types-cjs/core/heap.d.cts +0 -22
- package/dist/types-cjs/core/index.d.cts +0 -13
- package/dist/types-cjs/core/invariants.d.cts +0 -59
- package/dist/types-cjs/core/lanes.d.cts +0 -56
- package/dist/types-cjs/core/optimistic.d.cts +0 -6
- package/dist/types-cjs/core/owner.d.cts +0 -124
- package/dist/types-cjs/core/scheduler.d.cts +0 -172
- package/dist/types-cjs/core/types.d.cts +0 -148
- package/dist/types-cjs/core/verdict.d.cts +0 -2
- package/dist/types-cjs/index.d.cts +0 -10
- package/dist/types-cjs/map.d.cts +0 -69
- package/dist/types-cjs/package.json +0 -3
- package/dist/types-cjs/signals.d.cts +0 -624
- package/dist/types-cjs/store/index.d.cts +0 -19
- package/dist/types-cjs/store/next/optimistic.d.cts +0 -20
- package/dist/types-cjs/store/next/projection.d.cts +0 -24
- package/dist/types-cjs/store/next/reconcile.d.cts +0 -3
- package/dist/types-cjs/store/next/store.d.cts +0 -71
- package/dist/types-cjs/store/next/target.d.cts +0 -99
- package/dist/types-cjs/store/optimistic.d.cts +0 -45
- package/dist/types-cjs/store/projection.d.cts +0 -70
- package/dist/types-cjs/store/reconcile.d.cts +0 -46
- package/dist/types-cjs/store/store.d.cts +0 -138
- package/dist/types-cjs/store/storePath.d.cts +0 -58
- package/dist/types-cjs/store/utils.d.cts +0 -74
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { CONFIG_AUTO_DISPOSE, STATUS_PENDING, NOT_PENDING, 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
|
-
import { computed, setSignal, isEqual } from "../../core/core.js";
|
|
3
|
+
import { computed, untrack, hasActiveOverride, setSignal, isEqual, visibleOverride } from "../../core/core.js";
|
|
4
4
|
|
|
5
|
-
import {
|
|
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
|
|
|
@@ -16,9 +18,11 @@ import { $TARGET, markRawIngest, setNextOptimisticViewResolver, isWrappable, raw
|
|
|
16
18
|
|
|
17
19
|
import { runProjectionComputedNext } from "./projection.js";
|
|
18
20
|
|
|
19
|
-
import { wrapNext, runAuthoritative, storeSetterNext,
|
|
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";
|
|
20
24
|
|
|
21
|
-
import { setOptHooks } from "./target.js";
|
|
25
|
+
import { setOptHooks, $OWNER, lookupTarget } from "./target.js";
|
|
22
26
|
|
|
23
27
|
/**
|
|
24
28
|
* Store rewrite — optimistic stores (§3/§7, RUL-3): no store-side layer, no
|
|
@@ -29,12 +33,32 @@ import { setOptHooks } from "./target.js";
|
|
|
29
33
|
* armed presence nodes (the §6 overlay), so structural optimism reverts with
|
|
30
34
|
* the same per-transaction granularity (FINDING-2's fix by construction).
|
|
31
35
|
*
|
|
32
|
-
* Derived form = an optimistic projection
|
|
33
|
-
*
|
|
34
|
-
*
|
|
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
|
|
36
49
|
* legacy/engine checks.
|
|
37
|
-
*/
|
|
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.ri.size !== 0);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let blockedInstalled = false;
|
|
38
62
|
|
|
39
63
|
function installNextBlockedHalf() {
|
|
40
64
|
if (blockedInstalled) return;
|
|
@@ -45,30 +69,91 @@ function installNextBlockedHalf() {
|
|
|
45
69
|
setOptHooks({
|
|
46
70
|
notifyOptimisticWrites: notifyOptimisticWrites,
|
|
47
71
|
optimisticView: optimisticView,
|
|
48
|
-
applyTentative: applyTentative
|
|
72
|
+
applyTentative: applyTentative,
|
|
73
|
+
retainsOptimism: transitionHoldsOptimism
|
|
49
74
|
});
|
|
50
|
-
|
|
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));
|
|
51
79
|
// Scheduler flush tails call _clearOptimisticStores whenever tracked
|
|
52
80
|
// stores exist; next has no layer to clear — reverts are engine-native —
|
|
53
81
|
// so the hook only empties the batch set.
|
|
54
|
-
if (!GlobalQueue.
|
|
55
|
-
GlobalQueue.
|
|
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
|
+
}
|
|
56
99
|
e.clear();
|
|
57
100
|
};
|
|
58
101
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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.ri) {
|
|
139
|
+
const n = t?.[$TARGET];
|
|
140
|
+
const i = n?.fam;
|
|
141
|
+
const r = i?.node;
|
|
64
142
|
// The hold exists to keep optimistic state alive until the store's own
|
|
65
143
|
// truth lands (#2951). Once the family carries NO live overrides (a
|
|
66
144
|
// landing consumed them, or they never existed), a pending firewall is
|
|
67
145
|
// no reason to park the transaction — blocking then leaks it forever
|
|
68
146
|
// when the in-flight question is never answered (undisposed fixtures).
|
|
69
|
-
if (
|
|
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;
|
|
70
155
|
}
|
|
71
|
-
return e
|
|
156
|
+
return t(e);
|
|
72
157
|
};
|
|
73
158
|
}
|
|
74
159
|
|
|
@@ -80,10 +165,10 @@ function familyHasLiveOverrides(e) {
|
|
|
80
165
|
if (t === null) continue;
|
|
81
166
|
for (const e of Reflect.ownKeys(t)) {
|
|
82
167
|
const n = t[e];
|
|
83
|
-
if (n.
|
|
168
|
+
if (n.o?.Fe !== undefined && n.o?.Fe !== NOT_PENDING) return true;
|
|
84
169
|
}
|
|
85
170
|
}
|
|
86
|
-
if (e.k !== null && e.k.
|
|
171
|
+
if (e.k !== null && e.k.o?.Fe !== undefined && e.k.o?.Fe !== NOT_PENDING) return true;
|
|
87
172
|
}
|
|
88
173
|
t.clear();
|
|
89
174
|
// nothing live — drop the bookkeeping
|
|
@@ -96,42 +181,294 @@ function createOptimisticStoreNext(e, t, n) {
|
|
|
96
181
|
installOptimisticEngine();
|
|
97
182
|
installNextBlockedHalf();
|
|
98
183
|
const i = typeof e === "function";
|
|
99
|
-
|
|
184
|
+
const r = i ? n : t;
|
|
100
185
|
const o = i ? t : e;
|
|
101
|
-
const
|
|
186
|
+
const s = {
|
|
102
187
|
map: new WeakMap,
|
|
103
188
|
node: null,
|
|
104
|
-
shallow: !!
|
|
189
|
+
shallow: !!r?.shallow,
|
|
105
190
|
opt: true
|
|
106
191
|
};
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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;
|
|
111
200
|
markRawIngest(o);
|
|
112
201
|
}
|
|
113
202
|
if (i) {
|
|
114
203
|
const t = e;
|
|
115
|
-
//
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
|
|
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();
|
|
121
232
|
runAuthoritative(e);
|
|
122
|
-
consume();
|
|
123
233
|
};
|
|
124
|
-
|
|
125
|
-
|
|
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 = {
|
|
126
287
|
loadingValue: undefined
|
|
127
288
|
};
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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];
|
|
133
471
|
}
|
|
134
|
-
return [ s, e => storeSetterNext(s, e) ];
|
|
135
472
|
}
|
|
136
473
|
|
|
137
474
|
// ---- optimistic-only store machinery (moved from next/store.ts /
|
|
@@ -140,238 +477,234 @@ function createOptimisticStoreNext(e, t, n) {
|
|
|
140
477
|
* overrides — the same view the draft was seeded from) and emit engine writes
|
|
141
478
|
* for exactly the changed keys. Visible-view diffing keeps no-op writes from
|
|
142
479
|
* entangling lanes (RUL-10 / opt R38). */ function notifyOptimisticWrites(e, t) {
|
|
143
|
-
// A bare write while the store's own truth is in flight rides
|
|
144
|
-
// transaction (#2951
|
|
145
|
-
//
|
|
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
|
|
146
483
|
// at plain flush end. The blocked-check store-half keeps that transaction
|
|
147
|
-
// from settling while the firewall is pending.
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
-
|
|
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) => {
|
|
152
508
|
const i = e.n?.[t];
|
|
153
|
-
return i !== undefined && hasActiveOverride(i) ? unwrapOverride(i.
|
|
509
|
+
return unwrapValue(i !== undefined && hasActiveOverride(i) ? unwrapOverride(i.o?.Fe) : n);
|
|
154
510
|
};
|
|
155
511
|
const visiblePresent = t => {
|
|
156
512
|
const n = e.h?.[t];
|
|
157
|
-
return n !== undefined && hasActiveOverride(n) ? !!unwrapOverride(n.
|
|
513
|
+
return n !== undefined && hasActiveOverride(n) ? !!unwrapOverride(n.o?.Fe) : t in i;
|
|
158
514
|
};
|
|
159
|
-
|
|
160
|
-
|
|
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);
|
|
161
525
|
for (const n of Reflect.ownKeys(t)) {
|
|
162
|
-
if (
|
|
526
|
+
if (o && n === "length" || n === $OWNER) continue;
|
|
163
527
|
const s = unwrapValue(t[n]);
|
|
164
528
|
if (!visiblePresent(n)) {
|
|
165
529
|
// Optimistic add: value node + presence node + membership bump.
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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;
|
|
169
534
|
} else {
|
|
170
535
|
const t = visible(n, i[n]);
|
|
171
536
|
if (!isEqual(t, s) && !targetsEqual(t, s)) {
|
|
172
|
-
|
|
173
|
-
if (
|
|
537
|
+
emit(getNode(e, n, t), t, s);
|
|
538
|
+
if (o) r = true;
|
|
174
539
|
}
|
|
175
540
|
}
|
|
176
541
|
}
|
|
177
542
|
for (const n of Reflect.ownKeys(i)) {
|
|
178
|
-
if (
|
|
543
|
+
if (o && n === "length" || n === $OWNER) continue;
|
|
179
544
|
if (n in t || !visiblePresent(n)) continue;
|
|
180
545
|
// Optimistic delete: node reads undefined, presence flips, membership bumps.
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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;
|
|
184
550
|
}
|
|
185
|
-
if (
|
|
551
|
+
if (o) {
|
|
186
552
|
const n = visible("length", i.length);
|
|
187
553
|
if (n !== t.length) {
|
|
188
|
-
|
|
189
|
-
|
|
554
|
+
emit(getNode(e, "length", n), n, t.length);
|
|
555
|
+
r = true;
|
|
190
556
|
}
|
|
191
557
|
}
|
|
192
|
-
if (
|
|
558
|
+
if (r) setSignal(getKeySetNode(e), e => e + 1);
|
|
193
559
|
// Deep-witness: optimistic value writes notify deep() subscribers too
|
|
194
560
|
// (structural ones already ride the key-set bump above).
|
|
195
561
|
bumpDeep(e);
|
|
196
562
|
// Discard the draft — committed raw is untouched (revert target by
|
|
197
|
-
// construction)
|
|
198
|
-
//
|
|
199
|
-
|
|
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);
|
|
200
568
|
(e.fam.overlaid ??= new Set).add(e);
|
|
201
|
-
GlobalQueue.
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Landing consumption (RUL-2): fresh authoritative data supersedes every
|
|
206
|
-
* tentative override in the family. Mirrors legacy clearProjectionOverride —
|
|
207
|
-
* drop the override, clear lane/ownership, notify subscribers whose visible
|
|
208
|
-
* value changes (reversion effects go to regular queues via the projection
|
|
209
|
-
* write posture the caller holds).
|
|
210
|
-
*/ function consumeOverridesNext(e) {
|
|
211
|
-
const t = e.overlaid;
|
|
212
|
-
if (t === undefined || t.size === 0) return;
|
|
213
|
-
runAuthoritative(() => {
|
|
214
|
-
for (const e of t) {
|
|
215
|
-
const drop = (e, t) => {
|
|
216
|
-
if (!hasActiveOverride(e)) return;
|
|
217
|
-
const n = unwrapOverride(e.De);
|
|
218
|
-
// Full legacy reset (clearOptimisticOverride parity): the landing is
|
|
219
|
-
// authoritative NOW — fold committed into the node directly instead
|
|
220
|
-
// of riding a transaction's commit (whose queues may be stashed with
|
|
221
|
-
// the transaction parked; the wake would strand until it settles).
|
|
222
|
-
e.De = NOT_PENDING;
|
|
223
|
-
e.sn = null;
|
|
224
|
-
e.Ke = undefined;
|
|
225
|
-
e._e = NOT_PENDING;
|
|
226
|
-
e.be = t;
|
|
227
|
-
if (!e.Ue || !e.Ue(n, t)) {
|
|
228
|
-
insertSubs(e, true);
|
|
229
|
-
schedule();
|
|
230
|
-
}
|
|
231
|
-
};
|
|
232
|
-
// Landing consumes STRUCTURAL optimism only (legacy layer parity):
|
|
233
|
-
// membership edits, array length, and the value overrides written WITH
|
|
234
|
-
// them (a key carrying an active presence override is an add/delete —
|
|
235
|
-
// classified BEFORE the adoption may have made the key exist in landed
|
|
236
|
-
// data). A pure value override on a key the landing carries stays with
|
|
237
|
-
// its owning transaction (rapid-toggle contract: a live action's edit
|
|
238
|
-
// of an existing entity rides on top of landed truth).
|
|
239
|
-
const t = Array.isArray(e.v);
|
|
240
|
-
const n = e.h;
|
|
241
|
-
let i = null;
|
|
242
|
-
if (n !== null) {
|
|
243
|
-
for (const e of Reflect.ownKeys(n)) {
|
|
244
|
-
if (hasActiveOverride(n[e])) (i ??= new Set).add(e);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
const o = e.n;
|
|
248
|
-
if (o !== null) {
|
|
249
|
-
for (const n of Reflect.ownKeys(o)) {
|
|
250
|
-
const r = i?.has(n) || !(n in e.v) || t && n === "length";
|
|
251
|
-
if (!r) continue;
|
|
252
|
-
drop(o[n], t && n === "length" ? e.v.length : e.v[n]);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
if (n !== null) {
|
|
256
|
-
for (const t of Reflect.ownKeys(n)) drop(n[t], t in e.v);
|
|
257
|
-
}
|
|
258
|
-
if (e.k !== null && hasActiveOverride(e.k)) {
|
|
259
|
-
e.k.De = NOT_PENDING;
|
|
260
|
-
e.k.sn = null;
|
|
261
|
-
e.k.Ke = undefined;
|
|
262
|
-
insertSubs(e.k, true);
|
|
263
|
-
schedule();
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
t.clear();
|
|
267
|
-
});
|
|
569
|
+
GlobalQueue.fi?.(e.fam.px ?? e.px);
|
|
268
570
|
}
|
|
269
571
|
|
|
270
572
|
/** Optimistic-view composition for snapshot/deep (O1: snapshot is the CURRENT
|
|
271
573
|
* view, lane values included; a fresh copy per call during pending windows —
|
|
272
|
-
* RUL-12). Returns `src` untouched when no override is active on `t`.
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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 ] : {
|
|
276
581
|
...t
|
|
277
582
|
};
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
if (e === "length" && Array.isArray(t)) {
|
|
285
|
-
if (t.length !== o) ensure().length = o;
|
|
286
|
-
} else if (!isEqual(t[e], o)) ensure()[e] = o;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
const o = e.h;
|
|
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;
|
|
290
589
|
if (o !== null) {
|
|
291
590
|
for (const e of Reflect.ownKeys(o)) {
|
|
292
591
|
const i = o[e];
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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];
|
|
296
616
|
}
|
|
297
617
|
}
|
|
298
|
-
return
|
|
618
|
+
return i ?? t;
|
|
299
619
|
}
|
|
300
620
|
|
|
301
621
|
function applyTentative(e, t, n) {
|
|
302
622
|
const i = e.pb ?? e.v;
|
|
303
|
-
const
|
|
304
|
-
const
|
|
623
|
+
const r = optimisticView(e, i);
|
|
624
|
+
const o = e.fam;
|
|
305
625
|
const s = Array.isArray(t);
|
|
306
|
-
if (Array.isArray(
|
|
626
|
+
if (Array.isArray(r) !== s) return;
|
|
307
627
|
// kind change at root: flat overrides below
|
|
308
628
|
const l = [];
|
|
309
|
-
|
|
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
|
+
}
|
|
310
634
|
const match = (e, t) => {
|
|
311
635
|
if (!isWrappable(e) || !isWrappable(t)) return null;
|
|
312
636
|
if (rawValuesUsed && (isRawValue(e) || isRawValue(t))) return null;
|
|
313
637
|
if (Array.isArray(e) !== Array.isArray(t)) return null;
|
|
314
638
|
if (n) {
|
|
315
639
|
const i = n(e);
|
|
316
|
-
const
|
|
317
|
-
|
|
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;
|
|
318
644
|
}
|
|
319
|
-
return
|
|
645
|
+
return lookupTarget(unwrapValue(e), o) ?? null;
|
|
320
646
|
};
|
|
321
647
|
if (s) {
|
|
322
|
-
const e =
|
|
648
|
+
const e = r;
|
|
323
649
|
let i = null;
|
|
324
|
-
for (let
|
|
325
|
-
const
|
|
326
|
-
if (!isWrappable(
|
|
650
|
+
for (let r = 0; r < t.length; r++) {
|
|
651
|
+
const o = t[r];
|
|
652
|
+
if (!isWrappable(o)) continue;
|
|
327
653
|
let s;
|
|
328
654
|
if (n) {
|
|
329
|
-
const t = n(
|
|
655
|
+
const t = n(o);
|
|
330
656
|
if (t !== undefined) {
|
|
331
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.
|
|
332
661
|
i = new Map;
|
|
333
662
|
for (let t = 0; t < e.length; t++) {
|
|
334
|
-
const
|
|
335
|
-
if (isWrappable(
|
|
336
|
-
const e = n(
|
|
337
|
-
if (e
|
|
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 ]);
|
|
338
669
|
}
|
|
339
670
|
}
|
|
340
671
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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) {
|
|
346
684
|
// Keep the existing row in the slot (identity preserved); recurse.
|
|
347
|
-
u[
|
|
348
|
-
l.push([
|
|
685
|
+
u[r] = unwrapValue(s);
|
|
686
|
+
l.push([ a, o ]);
|
|
349
687
|
}
|
|
350
688
|
}
|
|
351
689
|
} else {
|
|
352
690
|
for (const e of Reflect.ownKeys(t)) {
|
|
353
|
-
|
|
691
|
+
if (e === $OWNER) continue;
|
|
692
|
+
const n = unwrapValue(r[e]);
|
|
354
693
|
const i = t[e];
|
|
355
|
-
const
|
|
356
|
-
if (
|
|
694
|
+
const o = match(n, i);
|
|
695
|
+
if (o !== null) {
|
|
357
696
|
u[e] = n;
|
|
358
|
-
l.push([
|
|
697
|
+
l.push([ o, i ]);
|
|
359
698
|
}
|
|
360
699
|
}
|
|
361
700
|
}
|
|
362
701
|
// Flat overrides for this level (adds, removals, moved slots, length, leaf
|
|
363
702
|
// values) — preserve any live user draft backing across the call.
|
|
364
|
-
const
|
|
703
|
+
const a = e.pb;
|
|
365
704
|
e.pb = null;
|
|
366
705
|
notifyOptimisticWrites(e, u);
|
|
367
|
-
e.pb =
|
|
706
|
+
e.pb = a;
|
|
368
707
|
for (let e = 0; e < l.length; e++) applyTentative(l[e][0], unwrapValue(l[e][1]), n);
|
|
369
708
|
}
|
|
370
709
|
|
|
371
|
-
|
|
372
|
-
const t = {};
|
|
373
|
-
for (const n of Reflect.ownKeys(e)) t[n] = e[n];
|
|
374
|
-
return t;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
export { consumeOverridesNext, createOptimisticStoreNext, notifyOptimisticWrites, optimisticView };
|
|
710
|
+
export { createOptimisticStoreNext, notifyOptimisticWrites, optimisticView, transitionHoldsOptimism };
|