@solidjs/signals 2.0.0-rc.7 → 2.0.0-rc.9
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 +6357 -0
- package/dist/dev.attribution.js +2485 -0
- package/dist/dev.js +2851 -7027
- package/dist/observe/affects.js +129 -0
- package/dist/observe/attribution.js +7 -0
- package/dist/observe/boundaries.js +641 -0
- package/dist/observe/core/action.js +177 -0
- package/dist/observe/core/async.js +846 -0
- package/dist/observe/core/attribution-costs.js +66 -0
- package/dist/observe/core/attribution-feedback.js +282 -0
- package/dist/observe/core/attribution-hooks.js +82 -0
- package/dist/observe/core/attribution-queries.js +28 -0
- package/dist/observe/core/attribution.js +2095 -0
- package/dist/observe/core/constants.js +248 -0
- package/dist/{types-cjs/core/context.d.cts → observe/core/context.js} +37 -12
- package/dist/observe/core/core.js +1789 -0
- package/dist/observe/core/dev.js +293 -0
- package/dist/observe/core/effect.js +195 -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 +585 -0
- package/dist/observe/core/owner.js +322 -0
- package/dist/observe/core/scheduler.js +1428 -0
- package/dist/observe/core/verdict.js +669 -0
- package/dist/observe/index.js +59 -0
- package/dist/observe/map.js +484 -0
- package/dist/{types-cjs/signals.d.cts → observe/signals.js} +311 -317
- package/dist/observe/store/index.js +40 -0
- package/dist/observe/store/next/optimistic.js +660 -0
- package/dist/observe/store/next/projection.js +255 -0
- package/dist/observe/store/next/reconcile.js +375 -0
- package/dist/observe/store/next/store.js +2327 -0
- package/dist/observe/store/next/target.js +51 -0
- package/dist/observe/store/store.js +325 -0
- package/dist/observe/store/storePath.js +103 -0
- package/dist/observe/store/utils.js +1023 -0
- package/dist/prod/attribution.js +41 -0
- package/dist/prod/boundaries.js +128 -76
- package/dist/prod/core/action.js +65 -37
- package/dist/prod/core/async.js +324 -172
- package/dist/prod/core/constants.js +57 -1
- package/dist/prod/core/context.js +3 -3
- package/dist/prod/core/core.js +1003 -364
- package/dist/prod/core/dev.js +17 -1
- package/dist/prod/core/effect.js +76 -42
- package/dist/prod/core/error-hooks.js +71 -0
- package/dist/prod/core/external.js +4 -4
- package/dist/prod/core/graph.js +37 -37
- package/dist/prod/core/heap.js +61 -56
- package/dist/prod/core/lanes.js +107 -63
- package/dist/prod/core/optimistic.js +374 -93
- package/dist/prod/core/owner.js +62 -46
- package/dist/prod/core/scheduler.js +653 -286
- package/dist/prod/core/verdict.js +245 -127
- package/dist/prod/index.js +15 -5
- package/dist/prod/map.js +292 -141
- package/dist/prod/signals.js +29 -16
- package/dist/prod/store/next/optimistic.js +77 -55
- package/dist/prod/store/next/projection.js +31 -20
- package/dist/prod/store/next/reconcile.js +87 -84
- package/dist/prod/store/next/store.js +718 -401
- package/dist/prod/store/next/target.js +32 -10
- package/dist/prod/store/store.js +12 -14
- package/dist/prod/store/utils.js +950 -130
- package/dist/types/attribution.d.ts +20 -0
- package/dist/types/attribution.prod.d.ts +22 -0
- package/dist/types/boundaries.d.ts +10 -1
- package/dist/types/core/action.d.ts +12 -5
- package/dist/types/core/async.d.ts +1 -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 +147 -9
- package/dist/types/core/attribution-queries.d.ts +10 -0
- package/dist/types/core/attribution.d.ts +216 -182
- package/dist/types/core/constants.d.ts +56 -0
- package/dist/types/core/core.d.ts +190 -2
- package/dist/types/core/dev.d.ts +246 -28
- package/dist/types/core/effect.d.ts +8 -1
- package/dist/types/core/error-hooks.d.ts +71 -0
- package/dist/types/core/index.d.ts +4 -1
- package/dist/types/core/invariants.d.ts +4 -0
- package/dist/types/core/lanes.d.ts +39 -5
- package/dist/types/core/scheduler.d.ts +153 -0
- package/dist/types/core/types.d.ts +32 -9
- package/dist/types/index.d.ts +10 -3
- package/dist/types/map.d.ts +2 -0
- package/dist/types/signals.d.ts +8 -0
- package/dist/types/store/index.d.ts +2 -0
- package/dist/types/store/next/optimistic.d.ts +1 -1
- package/dist/types/store/next/store.d.ts +11 -5
- package/dist/types/store/next/target.d.ts +37 -10
- package/dist/types/store/utils.d.ts +178 -0
- package/package.json +21 -17
- package/dist/node.cjs +0 -10541
- package/dist/node.dev.cjs +0 -13724
- 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 -23
- package/dist/types-cjs/core/attribution-hooks.d.cts +0 -127
- package/dist/types-cjs/core/attribution.d.cts +0 -495
- package/dist/types-cjs/core/constants.d.cts +0 -159
- package/dist/types-cjs/core/core.d.cts +0 -185
- package/dist/types-cjs/core/dev.d.cts +0 -136
- package/dist/types-cjs/core/effect.d.cts +0 -30
- package/dist/types-cjs/core/error.d.cts +0 -56
- package/dist/types-cjs/core/external.d.cts +0 -15
- package/dist/types-cjs/core/graph.d.cts +0 -28
- package/dist/types-cjs/core/heap.d.cts +0 -24
- 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 -54
- 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 -236
- package/dist/types-cjs/core/types.d.cts +0 -204
- 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/store/index.d.cts +0 -18
- package/dist/types-cjs/store/next/optimistic.d.cts +0 -25
- package/dist/types-cjs/store/next/projection.d.cts +0 -8
- package/dist/types-cjs/store/next/reconcile.d.cts +0 -10
- package/dist/types-cjs/store/next/store.d.cts +0 -121
- package/dist/types-cjs/store/next/target.d.cts +0 -166
- package/dist/types-cjs/store/store.d.cts +0 -143
- package/dist/types-cjs/store/storePath.d.cts +0 -58
- package/dist/types-cjs/store/utils.d.cts +0 -74
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import { unwrapOverride, $REFRESH,
|
|
1
|
+
import { unwrapOverride, $REFRESH, CONFIG_CHILDREN_FORBIDDEN, CONFIG_OWNED_WRITE, NOT_PENDING, CONFIG_OPTIMISTIC, STATUS_UNINITIALIZED, STATUS_ERROR, CONFIG_AUTHORITATIVE_READ } from "../../core/constants.js";
|
|
2
2
|
|
|
3
|
-
import { setSlotUnobserved, setSignal, isEqual, read, pendingCheckActive, latestReadActive, readNodeFast, READ_SLOW, signal, ext, setLatestReadActive, prepareComputed, slotSignal, context } from "../../core/core.js";
|
|
3
|
+
import { setSlotUnobserved, setSignal, isEqual, read, hasActiveOverride, pendingCheckActive, visibleOverride, latestReadActive, readNodeFast, READ_SLOW, signal, unlinkFirewallChild, ext, ownsHold, setLatestReadActive, prepareComputed, slotSignal, serve, stale, recordStaleReplay, enterStagedRead, context } from "../../core/core.js";
|
|
4
4
|
|
|
5
|
-
import { projectionWriteActive, setProjectionWriteActive, activeTransition, schedule, currentTransition, setStoreCommitHook } from "../../core/scheduler.js";
|
|
5
|
+
import { projectionWriteActive, setProjectionWriteActive, activeTransition, schedule, currentTransition, deferSlotRelease, setStoreCommitHook } from "../../core/scheduler.js";
|
|
6
|
+
|
|
7
|
+
import "../../core/invariants.js";
|
|
6
8
|
|
|
7
9
|
import { getObserver, getOwner } from "../../core/owner.js";
|
|
8
10
|
|
|
9
11
|
import { $TARGET, isWrappable, markRawIngest, $PROXY, getWriteOverride, markRawOne, witnessAffectsMark, $TRACK, affectsScopesLive, inheritAffectsMarks, $AFFECTS, rawValuesUsed, isRawValue, setNextAffectsNodeResolver } from "../store.js";
|
|
10
12
|
|
|
11
|
-
import {
|
|
13
|
+
import { lookupTarget, optHooks, storeNextLookup, $OWNER, isOwned, markDescendants, devAssertNeverUserMutation } from "./target.js";
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* Store rewrite — increment 2: plain deep stores with pending-backing writes.
|
|
@@ -59,6 +61,7 @@ function TargetShape() {
|
|
|
59
61
|
this.d = undefined;
|
|
60
62
|
this.a = undefined;
|
|
61
63
|
this.sc = undefined;
|
|
64
|
+
this.kc = undefined;
|
|
62
65
|
this.nc = undefined;
|
|
63
66
|
this.ab = undefined;
|
|
64
67
|
this.fam = undefined;
|
|
@@ -94,7 +97,8 @@ function createTarget(e, t, n, r = t?.fam ?? null) {
|
|
|
94
97
|
i.px = null;
|
|
95
98
|
i.d = false;
|
|
96
99
|
i.a = false;
|
|
97
|
-
i.sc =
|
|
100
|
+
i.sc = 0;
|
|
101
|
+
i.kc = 0;
|
|
98
102
|
i.nc = 0;
|
|
99
103
|
i.ab = null;
|
|
100
104
|
i.fam = r;
|
|
@@ -115,13 +119,13 @@ function wrapNext(e, t = null, n = null, r = t?.fam ?? null) {
|
|
|
115
119
|
// markRaw'd values never wrap through ANY store (R42; sticky raw-marking
|
|
116
120
|
// is one half of the never-both-wrapped-and-raw invariant, RUL-12).
|
|
117
121
|
if (rawValuesUsed && isRawValue(e)) return e;
|
|
118
|
-
const i = (r
|
|
122
|
+
const i = lookupTarget(e, r);
|
|
119
123
|
if (i !== undefined) return i.px;
|
|
120
|
-
const
|
|
121
|
-
if (
|
|
124
|
+
const l = e[$TARGET];
|
|
125
|
+
if (l !== undefined && l.px === e) {
|
|
122
126
|
// Foreign-family proxies re-wrap into THIS family (writes stay isolated);
|
|
123
127
|
// same-family and plain-store proxies pass through.
|
|
124
|
-
if (r === null ||
|
|
128
|
+
if (r === null || l.fam === r) return e;
|
|
125
129
|
return createTarget(e, t, n, r).px;
|
|
126
130
|
}
|
|
127
131
|
return createTarget(e, t, n, r).px;
|
|
@@ -149,7 +153,7 @@ function wrapNext(e, t = null, n = null, r = t?.fam ?? null) {
|
|
|
149
153
|
// (privatization/adoption swap raw identity without changing the logical
|
|
150
154
|
// value — only changed leaves notify, R9).
|
|
151
155
|
const slotNodeEquals = function(e, t) {
|
|
152
|
-
return isEqual(e, t) || sameLogicalSlot(this.
|
|
156
|
+
return isEqual(e, t) || sameLogicalSlot(this.Wt, e, t);
|
|
153
157
|
};
|
|
154
158
|
|
|
155
159
|
// Shared slot-node unobserved handler (create-floor diet): registered once;
|
|
@@ -158,11 +162,20 @@ const slotNodeEquals = function(e, t) {
|
|
|
158
162
|
setSlotUnobserved(e => {
|
|
159
163
|
// A live affects() mark keeps the node addressable (sweep parity).
|
|
160
164
|
if (e.o?.t) return;
|
|
161
|
-
|
|
162
|
-
|
|
165
|
+
// An active override or a staged write is state only the node holds (an
|
|
166
|
+
// optimistic signal keeps its override whether or not anything reads it —
|
|
167
|
+
// store parity, posture-store-parity S7): defer the release to the flush
|
|
168
|
+
// that resolves it (the scheduler's transient-node sweep).
|
|
169
|
+
if (hasActiveOverride(e) || e.ve !== NOT_PENDING) return deferSlotRelease(e);
|
|
170
|
+
const t = e.Wt;
|
|
171
|
+
const n = e.qt;
|
|
163
172
|
if (t.n && t.n[n] === e) {
|
|
164
173
|
delete t.n[n];
|
|
165
174
|
t.nc--;
|
|
175
|
+
// Projection leaves also leave the firewall child chain (#3351): a
|
|
176
|
+
// dropped node is unreachable through the store — a fresh read makes a
|
|
177
|
+
// fresh node — so the chain would only retain it and its last value.
|
|
178
|
+
unlinkFirewallChild(e);
|
|
166
179
|
}
|
|
167
180
|
});
|
|
168
181
|
|
|
@@ -173,8 +186,25 @@ function getNode(e, t, n,
|
|
|
173
186
|
// callers), 0/1 = probed.
|
|
174
187
|
r = -1) {
|
|
175
188
|
const i = e.n ??= Object.create(null);
|
|
176
|
-
let
|
|
177
|
-
if (
|
|
189
|
+
let l = i[t];
|
|
190
|
+
if (l === undefined) {
|
|
191
|
+
// Born holding (#3330 store twin): a key first read while a live
|
|
192
|
+
// transaction holds an ADOPTION on this target (adoptPB's `ht`) is
|
|
193
|
+
// created as if it had existed when the hold was notified — the adopted
|
|
194
|
+
// value already swapped into `v`, so committed is the held view
|
|
195
|
+
// `hv[key]` and the adopted `v[key]` is staged under the transaction
|
|
196
|
+
// (stageHeldKey). Its held-adoption notification (stageHeldAdoptions)
|
|
197
|
+
// ran before the node existed and the drain has nothing left to say.
|
|
198
|
+
// Without this the node was born from whichever view its first reader
|
|
199
|
+
// saw and never learned the other. Two kinds of hold, one rule (#3336):
|
|
200
|
+
// - a held FOLD (pb): a setter's write to an unobserved key landed only
|
|
201
|
+
// in the pending backing; committed is `v[key]`, staged is `pb[key]`
|
|
202
|
+
// (undefined for a deleted key);
|
|
203
|
+
// - a held ADOPTION (ht): the adopted value already swapped into `v`;
|
|
204
|
+
// committed is the held view `hv[key]`, staged is `v[key]`.
|
|
205
|
+
const o = heldFoldTransition(e);
|
|
206
|
+
let f = heldAdoptionTransition(e);
|
|
207
|
+
if (f !== null) n = e.hv[t]; else if ((f = o) !== null) n = e.v[t];
|
|
178
208
|
// Create-floor diet: slotSignal bakes the whole node into one literal —
|
|
179
209
|
// no options object, no equals/unobserved closures, no NodeExtension,
|
|
180
210
|
// no post-construction expandos (acc + the wrap cache px/pxv are
|
|
@@ -185,7 +215,7 @@ r = -1) {
|
|
|
185
215
|
// notification machinery. Projection nodes carry the projection
|
|
186
216
|
// computed as their firewall: reads through them link the derive's
|
|
187
217
|
// status/lifecycle (§7b).
|
|
188
|
-
|
|
218
|
+
const s = l = slotSignal(n, slotNodeEquals, e, t,
|
|
189
219
|
// Accessor-ness resolved ONCE per node (no per-object descriptor
|
|
190
220
|
// scan on reads): accessor keys serve through Reflect.get with the
|
|
191
221
|
// proxy receiver.
|
|
@@ -193,43 +223,122 @@ r = -1) {
|
|
|
193
223
|
// Optimistic families: arm the override slot — setSignal routes armed
|
|
194
224
|
// nodes through the core engine (lanes, ownership, reverts all native).
|
|
195
225
|
if (e.fam?.opt) {
|
|
196
|
-
ext(
|
|
197
|
-
|
|
226
|
+
ext(s).Ce = NOT_PENDING;
|
|
227
|
+
s.T |= CONFIG_OPTIMISTIC;
|
|
198
228
|
}
|
|
199
229
|
// A node born inside a live mark's identity scope inherits the mark
|
|
200
230
|
// (the declaration walk could only cover nodes existing then).
|
|
201
|
-
if (t !== $AFFECTS && affectsScopesLive()) inheritAffectsMarks(
|
|
202
|
-
|
|
231
|
+
if (t !== $AFFECTS && affectsScopesLive()) inheritAffectsMarks(s, e.v, t);
|
|
232
|
+
if (f !== null) stageHeldKey(s, o !== null ? e.del !== null && e.del.has(t) ? undefined : e.pb[t] : e.v[t], f);
|
|
233
|
+
i[t] = l;
|
|
203
234
|
e.nc++;
|
|
204
235
|
markDescendants(e);
|
|
205
236
|
}
|
|
206
|
-
return
|
|
237
|
+
return l;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** The live transaction holding an adoption on `target` (adoptPB's `ht`;
|
|
241
|
+
* a latest()-pull PLAIN_HOLD is not a transaction), else null. */ function heldAdoptionTransition(e) {
|
|
242
|
+
if (e.ht === null || e.ht === PLAIN_HOLD || heldMaskView(e) === null) return null;
|
|
243
|
+
const t = currentTransition(e.ht);
|
|
244
|
+
return t.Tt === false ? t : null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Materialization under a hold. A node created by a first tracked read while
|
|
249
|
+
* a transaction holds this target must carry the same state a node that
|
|
250
|
+
* existed when the hold was notified carries — a transaction-stamped
|
|
251
|
+
* `_pendingValue` that core read() serves by ITS rules (a stale render reader
|
|
252
|
+
* of a foreign transaction's write sees committed, an owner-context reader
|
|
253
|
+
* inside the transaction sees the write) — or the reader's answer depends on
|
|
254
|
+
* whether some OTHER reader had materialized the key before the hold. Stage
|
|
255
|
+
* `nv` (the held value for the key — see getNode for which view it comes
|
|
256
|
+
* from) as the holding transaction's write — directly, not through
|
|
257
|
+
* setSignal: it is not a new write (it staged with the adoption's batch) and
|
|
258
|
+
* it walks no subscriber (the node has none yet). Transition-stamped now, as
|
|
259
|
+
* `runFolded` does — no parked-flush pass will stamp it.
|
|
260
|
+
*/
|
|
261
|
+
/** The live transaction holding `target`'s pending backing (the #3089
|
|
262
|
+
* write-time stamp, resolved through merges), else null. */ function liveFoldTransition(e) {
|
|
263
|
+
if (e.pb === null) return null;
|
|
264
|
+
const t = foldBatches.get(e);
|
|
265
|
+
if (t === undefined) return null;
|
|
266
|
+
const n = currentTransition(t);
|
|
267
|
+
return n.Tt === false ? n : null;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** liveFoldTransition for node materialization (stageHeldKey). Inside the
|
|
271
|
+
* draft the backing is the setter's working copy, not a flushed hold — its
|
|
272
|
+
* nodes take their writes at setter exit (notifyWrites). Optimistic families
|
|
273
|
+
* hold at the backing: tentative writes are node overrides over a discarded
|
|
274
|
+
* clone, and a truth-staged landing is masked from ordinary readers by
|
|
275
|
+
* heldTruthMasked — neither is a plain staged write to mirror. Chained
|
|
276
|
+
* backings serve the inner store's live value, never a node value. */ function heldFoldTransition(e) {
|
|
277
|
+
if (e.ch || e.fam?.opt === true || inDraft(e)) return null;
|
|
278
|
+
return liveFoldTransition(e);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Rule 1 at the backing, the hold half (core serve()'s stale-of-foreign and
|
|
283
|
+
* A29 arms, for a container instead of a node): given the transaction
|
|
284
|
+
* holding what a reader `c` (a pass; callers serve context-free readers the
|
|
285
|
+
* committed container themselves) is about to be served, is the STAGED
|
|
286
|
+
* container its to see? Its own hold, or none — yes. A foreign hold — a
|
|
287
|
+
* stale pass (render effect) keeps the committed frame and is recorded for
|
|
288
|
+
* replay at the hold's commit (recordStaleReplay, A15 / A26); a deriving
|
|
289
|
+
* pass takes the staged world and enters the transaction (enterStagedRead,
|
|
290
|
+
* A29). Shared by both hold kinds: a setter's fold (committed `v`, staged
|
|
291
|
+
* `pb`, the write-time stamp) and an adoption under a transaction
|
|
292
|
+
* (committed = the held view `hv`, staged = the adopted `v`, #3074).
|
|
293
|
+
*/ function holdVisible(e, t) {
|
|
294
|
+
if (e === null || ownsHold(e)) return true;
|
|
295
|
+
if (stale) {
|
|
296
|
+
recordStaleReplay(e, t);
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
enterStagedRead(null, e);
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function stageHeldKey(e, t, n) {
|
|
304
|
+
if (slotNodeEquals.call(e, e.Qe, t)) return;
|
|
305
|
+
e.ve = t;
|
|
306
|
+
e.Ge = n;
|
|
307
|
+
n.Ot.push(e);
|
|
207
308
|
}
|
|
208
309
|
|
|
209
310
|
function sameLogicalSlot(e, t, n) {
|
|
210
311
|
if (t === null || typeof t !== "object" || n === null || typeof n !== "object") return false;
|
|
211
|
-
const r = e.fam
|
|
212
|
-
|
|
213
|
-
return i !== undefined && i === r.get(n);
|
|
312
|
+
const r = lookupTarget(t, e.fam);
|
|
313
|
+
return r !== undefined && r === lookupTarget(n, e.fam);
|
|
214
314
|
}
|
|
215
315
|
|
|
216
316
|
function getHasNode(e, t, n) {
|
|
217
317
|
const r = e.h ??= Object.create(null);
|
|
218
318
|
let i = r[t];
|
|
219
319
|
if (i === undefined) {
|
|
220
|
-
const
|
|
320
|
+
const l = i = signal(n, {
|
|
221
321
|
equals: isEqual,
|
|
222
322
|
unobserved() {
|
|
223
|
-
if (
|
|
224
|
-
|
|
323
|
+
if (l.o?.t) return;
|
|
324
|
+
// The structural twin of the value slot's rule (setSlotUnobserved,
|
|
325
|
+
// S7): an optimistic add/delete lives on this presence node as its
|
|
326
|
+
// override — releasing it with the override on would let `in`,
|
|
327
|
+
// `Object.keys` and descriptors fall back to committed structure
|
|
328
|
+
// while the action is live. Defer to the flush that resolves it.
|
|
329
|
+
if (hasActiveOverride(l) || l.ve !== NOT_PENDING) return deferSlotRelease(l);
|
|
330
|
+
if (e.h && e.h[t] === l) {
|
|
331
|
+
delete e.h[t];
|
|
332
|
+
unlinkFirewallChild(l);
|
|
333
|
+
}
|
|
225
334
|
}
|
|
226
335
|
}, e.fam?.node ?? undefined);
|
|
227
|
-
|
|
336
|
+
l.T |= CONFIG_OWNED_WRITE;
|
|
228
337
|
if (e.fam?.opt) {
|
|
229
|
-
ext(
|
|
230
|
-
|
|
338
|
+
ext(l).Ce = NOT_PENDING;
|
|
339
|
+
l.T |= CONFIG_OPTIMISTIC;
|
|
231
340
|
}
|
|
232
|
-
if (affectsScopesLive()) inheritAffectsMarks(
|
|
341
|
+
if (affectsScopesLive()) inheritAffectsMarks(l, e.v, t);
|
|
233
342
|
r[t] = i;
|
|
234
343
|
markDescendants(e);
|
|
235
344
|
}
|
|
@@ -242,12 +351,15 @@ function getKeySetNode(e) {
|
|
|
242
351
|
const n = t = signal(0, {
|
|
243
352
|
equals: false,
|
|
244
353
|
unobserved() {
|
|
245
|
-
if (e.k === n)
|
|
354
|
+
if (e.k === n) {
|
|
355
|
+
e.k = null;
|
|
356
|
+
unlinkFirewallChild(n);
|
|
357
|
+
}
|
|
246
358
|
}
|
|
247
359
|
}, e.fam?.node ?? undefined);
|
|
248
360
|
n.T |= CONFIG_OWNED_WRITE;
|
|
249
361
|
if (e.fam?.opt) {
|
|
250
|
-
ext(n).
|
|
362
|
+
ext(n).Ce = NOT_PENDING;
|
|
251
363
|
n.T |= CONFIG_OPTIMISTIC;
|
|
252
364
|
}
|
|
253
365
|
e.k = t;
|
|
@@ -262,12 +374,15 @@ function getDeepNode(e) {
|
|
|
262
374
|
const n = t = signal(0, {
|
|
263
375
|
equals: false,
|
|
264
376
|
unobserved() {
|
|
265
|
-
if (e.dk === n)
|
|
377
|
+
if (e.dk === n) {
|
|
378
|
+
e.dk = null;
|
|
379
|
+
unlinkFirewallChild(n);
|
|
380
|
+
}
|
|
266
381
|
}
|
|
267
382
|
}, e.fam?.node ?? undefined);
|
|
268
383
|
n.T |= CONFIG_OWNED_WRITE;
|
|
269
384
|
if (e.fam?.opt) {
|
|
270
|
-
ext(n).
|
|
385
|
+
ext(n).Ce = NOT_PENDING;
|
|
271
386
|
n.T |= CONFIG_OPTIMISTIC;
|
|
272
387
|
}
|
|
273
388
|
if (affectsScopesLive()) inheritAffectsMarks(n, e.v, $TRACK);
|
|
@@ -288,21 +403,36 @@ function getDeepNode(e) {
|
|
|
288
403
|
|
|
289
404
|
let hookInstalled = false;
|
|
290
405
|
|
|
291
|
-
|
|
406
|
+
/** Shallow-clone `source` as a backing OWNED by `t` (stamped `$OWNER`, see
|
|
407
|
+
* target.ts). Callers always pass their own committed backing as `source`. */ function cloneRaw(e, t) {
|
|
408
|
+
// Plain-data fast path (#3360): a scanned `Object.prototype` container
|
|
409
|
+
// whose own keys are all enumerable data clones by spread — the same
|
|
410
|
+
// result as the descriptor walk below (own enumerable string+symbol keys,
|
|
411
|
+
// normalized writable+configurable), at ~1/50th the cost.
|
|
412
|
+
t.sc || scanAccessorsOnce(t);
|
|
413
|
+
if (t.sc === 2) {
|
|
414
|
+
const n = {
|
|
415
|
+
...e
|
|
416
|
+
};
|
|
417
|
+
n[$OWNER] = t;
|
|
418
|
+
return n;
|
|
419
|
+
}
|
|
292
420
|
// Descriptor-preserving shallow clone (R29: installed getters stay live;
|
|
293
421
|
// ruled 2026-08-17: frozen sources clone unfrozen — theirs stays frozen).
|
|
294
422
|
// Data descriptors normalize to writable+configurable (the clone is OURS to
|
|
295
423
|
// mutate — R51's "source-non-configurable is writable through the store");
|
|
296
424
|
// enumerability and accessors are preserved. The scan doubles as the
|
|
297
425
|
// accessor-flag detector (free — we're enumerating descriptors anyway).
|
|
298
|
-
|
|
426
|
+
const n = Object.getOwnPropertyDescriptors(e);
|
|
299
427
|
for (const r of Reflect.ownKeys(n)) {
|
|
300
428
|
const i = n[r];
|
|
301
429
|
if (r === "length" && Array.isArray(e)) continue;
|
|
302
430
|
i.configurable = true;
|
|
303
|
-
if (!i.get && !i.set) i.writable = true; else
|
|
431
|
+
if (!i.get && !i.set) i.writable = true; else t.a = true;
|
|
304
432
|
}
|
|
305
|
-
|
|
433
|
+
const r = Array.isArray(e) ? Object.defineProperties([], n) : Object.create(Object.getPrototypeOf(e), n);
|
|
434
|
+
r[$OWNER] = t;
|
|
435
|
+
return r;
|
|
306
436
|
}
|
|
307
437
|
|
|
308
438
|
/** Copy own `key` from `from` onto `to`. A plain data slot (enumerable,
|
|
@@ -314,19 +444,33 @@ function cloneRaw(e, t) {
|
|
|
314
444
|
}
|
|
315
445
|
|
|
316
446
|
/** One-time own-accessor scan (Annex-B probes, no descriptor allocation);
|
|
317
|
-
* returns true when the container is plain data (overlay-safe).
|
|
447
|
+
* returns true when the container is plain data (overlay-safe). Also grades
|
|
448
|
+
* the container for the plain-data fast paths (`sc` = 2): `Object.prototype`
|
|
449
|
+
* and every own key an enumerable data property — what a spread copies
|
|
450
|
+
* exactly and what a bare assignment lands exactly. */ function scanAccessorsOnce(e) {
|
|
318
451
|
const t = e.v;
|
|
319
|
-
|
|
452
|
+
const n = Reflect.ownKeys(t);
|
|
453
|
+
let r = Object.getPrototypeOf(t) === Object.prototype;
|
|
454
|
+
for (const i of n) {
|
|
320
455
|
// Own keys shadow prototype accessors, so the lookups are exact here.
|
|
321
|
-
if (lookupGetter.call(t,
|
|
456
|
+
if (lookupGetter.call(t, i) !== undefined || lookupSetter.call(t, i) !== undefined) {
|
|
322
457
|
e.a = true;
|
|
458
|
+
r = false;
|
|
323
459
|
break;
|
|
324
460
|
}
|
|
461
|
+
if (r && !propertyIsEnumerable.call(t, i)) r = false;
|
|
325
462
|
}
|
|
326
|
-
e.sc =
|
|
463
|
+
e.sc = r ? 2 : 1;
|
|
464
|
+
e.kc = n.length;
|
|
327
465
|
return !e.a;
|
|
328
466
|
}
|
|
329
467
|
|
|
468
|
+
/** Own-key count above which a draft opens as a prototype overlay rather
|
|
469
|
+
* than a clone (#3360). Below it a spread clone of a plain container is
|
|
470
|
+
* cheaper than the overlay's `Object.create` (V8 converts the committed
|
|
471
|
+
* backing into a prototype, and every later flatten writes into that
|
|
472
|
+
* prototype); above it the clone's O(keys) copy dominates (#3044). */ const OVERLAY_MIN_KEYS = 32;
|
|
473
|
+
|
|
330
474
|
/** Downgrade a prototype-overlay pending backing to the clone path: builds
|
|
331
475
|
* the real container (committed + overlay writes − deletes) that fold will
|
|
332
476
|
* SWAP in as the committed backing, exactly as if the draft had started on
|
|
@@ -340,10 +484,6 @@ function cloneRaw(e, t) {
|
|
|
340
484
|
for (const t of e.del) delete n[t];
|
|
341
485
|
e.del = null;
|
|
342
486
|
}
|
|
343
|
-
const r = e.fam?.map ?? storeNextLookup;
|
|
344
|
-
r.delete(t);
|
|
345
|
-
ownedRaw.add(n);
|
|
346
|
-
r.set(n, e);
|
|
347
487
|
e.pb = n;
|
|
348
488
|
e.ovl = false;
|
|
349
489
|
}
|
|
@@ -366,15 +506,32 @@ function ensurePB(e) {
|
|
|
366
506
|
}
|
|
367
507
|
if (activeTransition !== null) foldBatches.set(e, activeTransition);
|
|
368
508
|
if (t === null) {
|
|
369
|
-
// Prototype-chain overlay (#3044): plain-data non-array containers
|
|
370
|
-
//
|
|
371
|
-
//
|
|
372
|
-
//
|
|
373
|
-
//
|
|
374
|
-
|
|
375
|
-
|
|
509
|
+
// Prototype-chain overlay (#3044): plain-data non-array containers open
|
|
510
|
+
// drafts in O(1) — own keys are the writes, reads fall through to
|
|
511
|
+
// committed. Projection and derived-store families take it too (#3352:
|
|
512
|
+
// a derive touching one root key of a wide keyed record paid an
|
|
513
|
+
// O(keys) clone per recompute). Everything else keeps the descriptor
|
|
514
|
+
// clone: arrays (splice/length semantics), OPTIMISTIC families (the
|
|
515
|
+
// view-seeding below writes and deletes on the draft container, and the
|
|
516
|
+
// tentative discard/truth-park hand whole backings around), chained
|
|
517
|
+
// backings (the committed layer is another store's proxy — an overlay
|
|
518
|
+
// would route every read-through into its traps), accessor containers
|
|
519
|
+
// (live getters).
|
|
520
|
+
// The overlay pays off only for a WIDE container over an OWNED committed
|
|
521
|
+
// backing (#3360). Narrow containers clone cheaper than they overlay (a
|
|
522
|
+
// spread is a fast-path copy; `Object.create` turns the backing into a
|
|
523
|
+
// V8 prototype and every later flatten writes into that prototype). An
|
|
524
|
+
// unowned backing has to be privatized (cloned) at commit anyway, so an
|
|
525
|
+
// overlay there would cost the create PLUS the clone PLUS a per-key
|
|
526
|
+
// copy — the clone path does the one clone and swaps it in. The first
|
|
527
|
+
// write on a fresh store is exactly that case.
|
|
528
|
+
const n = e.v;
|
|
529
|
+
if (!e.fam?.opt && !e.ch && !Array.isArray(n) && (e.sc !== 0 ? !e.a : scanAccessorsOnce(e)) && e.kc > OVERLAY_MIN_KEYS && isOwned(n)) {
|
|
530
|
+
// Inherits `v`'s $OWNER stamp — the overlay resolves and reads as
|
|
531
|
+
// owned without a registration of its own.
|
|
532
|
+
t = e.pb = Object.create(n);
|
|
376
533
|
e.ovl = true;
|
|
377
|
-
} else t = e.pb = cloneRaw(
|
|
534
|
+
} else t = e.pb = cloneRaw(n, e);
|
|
378
535
|
// Optimistic families: seed USER drafts from the OPTIMISTIC VIEW
|
|
379
536
|
// (committed + active node overrides), so follow-up writes compose on
|
|
380
537
|
// optimism instead of clobbering from base (#2951's compose half).
|
|
@@ -387,19 +544,17 @@ function ensurePB(e) {
|
|
|
387
544
|
if (n !== null) {
|
|
388
545
|
for (const e of Reflect.ownKeys(n)) {
|
|
389
546
|
const r = n[e];
|
|
390
|
-
if (hasActiveOverride(r)) t[e] = unwrapOverride(r.o?.
|
|
547
|
+
if (hasActiveOverride(r)) t[e] = unwrapOverride(r.o?.Ce);
|
|
391
548
|
}
|
|
392
549
|
}
|
|
393
550
|
const r = e.h;
|
|
394
551
|
if (r !== null) {
|
|
395
552
|
for (const e of Reflect.ownKeys(r)) {
|
|
396
553
|
const n = r[e];
|
|
397
|
-
if (hasActiveOverride(n) && !unwrapOverride(n.o?.
|
|
554
|
+
if (hasActiveOverride(n) && !unwrapOverride(n.o?.Ce)) delete t[e];
|
|
398
555
|
}
|
|
399
556
|
}
|
|
400
557
|
}
|
|
401
|
-
ownedRaw.add(t);
|
|
402
|
-
(e.fam?.map ?? storeNextLookup).set(t, e);
|
|
403
558
|
queueFold(e);
|
|
404
559
|
}
|
|
405
560
|
return t;
|
|
@@ -420,7 +575,7 @@ function ensurePB(e) {
|
|
|
420
575
|
* foldHeld's node stamps). */ function heldMaskView(e) {
|
|
421
576
|
const t = e.ht;
|
|
422
577
|
if (t === null) return null;
|
|
423
|
-
if (t !== PLAIN_HOLD && currentTransition(t)?.
|
|
578
|
+
if (t !== PLAIN_HOLD && currentTransition(t)?.Tt === true) return e.ht = e.hv = null;
|
|
424
579
|
return e.hv;
|
|
425
580
|
}
|
|
426
581
|
|
|
@@ -450,20 +605,26 @@ function ensurePB(e) {
|
|
|
450
605
|
if (e.ovl) materializePB(e);
|
|
451
606
|
e.ab = e.pb;
|
|
452
607
|
} else e.ab ??= foldOlds.get(e);
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
608
|
+
}
|
|
609
|
+
// #3074/#3075: a projection recompute deriving from uncommitted inputs
|
|
610
|
+
// swaps the backing SPECULATIVELY — committed-visibility readers must
|
|
611
|
+
// keep the pre-hold view until the hold resolves (a source held by a
|
|
612
|
+
// live transition, or a latest()-pull ahead of the flush). Post-await
|
|
613
|
+
// landings (write-override) stay immediately visible — landed truth —
|
|
614
|
+
// and clear any hold. Optimistic families hold too (#3330 store twin):
|
|
615
|
+
// their tentative edits ride the lane machinery, but a sync derive
|
|
616
|
+
// adopting under a transaction is held TRUTH like any projection's —
|
|
617
|
+
// unheld, handlers read it early and an optimistic write equal to it
|
|
618
|
+
// compared as a no-op against the swapped-in backing. A plain store's
|
|
619
|
+
// reconcile inside an action holds the same way: its nodes stage under the
|
|
620
|
+
// transaction (the inline notify), and the backing must not show handlers
|
|
621
|
+
// and stale readers what the tracked read masks (signal parity, #3336).
|
|
622
|
+
if (getWriteOverride()) {
|
|
623
|
+
e.ht = e.hv = null;
|
|
624
|
+
} else if (activeTransition !== null || !n && latestPullActive) {
|
|
625
|
+
if (heldMaskView(e) === null) e.hv = e.v;
|
|
626
|
+
e.ht = activeTransition ?? PLAIN_HOLD;
|
|
627
|
+
if (!n && activeTransition !== null) heldAdoptions.add(e);
|
|
467
628
|
}
|
|
468
629
|
e.pb = null;
|
|
469
630
|
// Overlay and accessor-scan state describe the OUTGOING backing — a
|
|
@@ -475,13 +636,17 @@ function ensurePB(e) {
|
|
|
475
636
|
// draft rescans once (#3044 audit follow-up).
|
|
476
637
|
e.ovl = false;
|
|
477
638
|
e.del = null;
|
|
478
|
-
e.sc =
|
|
639
|
+
e.sc = 0;
|
|
479
640
|
e.a = false;
|
|
480
641
|
e.wk = null;
|
|
481
642
|
// adoption supersedes staged trap writes
|
|
482
643
|
e.v = t;
|
|
483
644
|
e.ch = t[$TARGET] !== undefined;
|
|
484
|
-
|
|
645
|
+
// An adoptee we own within this family (a draft aliasing one of the
|
|
646
|
+
// family's own backings) re-stamps to its new owner — the stamp is the
|
|
647
|
+
// family's registration for it; everything else registers in the map.
|
|
648
|
+
const r = t[$OWNER];
|
|
649
|
+
if (r !== undefined && r.fam === e.fam) t[$OWNER] = e; else (e.fam?.map ?? storeNextLookup).set(t, e);
|
|
485
650
|
}
|
|
486
651
|
|
|
487
652
|
/** Sentinel for `t.wk`: the written-keys bound is unusable this batch (an
|
|
@@ -543,22 +708,22 @@ function queueFold(e) {
|
|
|
543
708
|
}
|
|
544
709
|
|
|
545
710
|
/** Committed-time privatization for parent-chain slot updates (path copying). */ function privatizeCommitted(e) {
|
|
546
|
-
if (
|
|
711
|
+
if (isOwned(e.v)) return;
|
|
547
712
|
const t = e.v;
|
|
548
713
|
const n = cloneRaw(t, e);
|
|
549
|
-
ownedRaw.add(n);
|
|
550
|
-
// Register in the target's OWN registration map (#3284): family targets
|
|
551
|
-
// (derived stores, projections, optimistic) resolve children through
|
|
552
|
-
// fam.map — a clone parked only in the global lookup makes the next parent
|
|
553
|
-
// read miss, wrap a fresh target, and orphan every node (subscribers) on
|
|
554
|
-
// this one.
|
|
555
|
-
(e.fam?.map ?? storeNextLookup).set(n, e);
|
|
556
714
|
e.v = n;
|
|
557
715
|
e.ch = false;
|
|
558
716
|
if (e.u) {
|
|
559
717
|
privatizeCommitted(e.u);
|
|
560
718
|
devAssertNeverUserMutation(e.u.v);
|
|
561
|
-
|
|
719
|
+
// CAS, same as drainFolds' path copy: re-point the parent slot only
|
|
720
|
+
// while it still holds the raw we cloned. A parent that folded EARLIER
|
|
721
|
+
// in this drain may have replaced or deleted this slot (the same batch
|
|
722
|
+
// wrote the child and then `parent.row = fresh` / `parent.length = 0`)
|
|
723
|
+
// — an unconditional write resurrected the dropped child over it.
|
|
724
|
+
const n = e.u.v;
|
|
725
|
+
const r = parentSlotKey(e, t);
|
|
726
|
+
if (n[r] === t) n[r] = e.v;
|
|
562
727
|
}
|
|
563
728
|
}
|
|
564
729
|
|
|
@@ -577,6 +742,28 @@ function queueFold(e) {
|
|
|
577
742
|
return i;
|
|
578
743
|
}
|
|
579
744
|
|
|
745
|
+
/** Overlay commit (#3044): apply the batch's writes onto an OWNED committed
|
|
746
|
+
* backing in place — O(written), not O(container). Unowned backings
|
|
747
|
+
* privatize first (clone once, parents re-slotted) — the never-mutate-user-
|
|
748
|
+
* data contract holds. Shared by the deferred fold (drainFolds) and the
|
|
749
|
+
* projection landing's immediate commit (notifyWrites). */ function flattenOverlay(e, t) {
|
|
750
|
+
privatizeCommitted(e);
|
|
751
|
+
const n = e.v;
|
|
752
|
+
// Plain-data grade (sc 2): every own key on the overlay is an enumerable
|
|
753
|
+
// writable data slot (set-trap writes; a non-plain defineProperty
|
|
754
|
+
// downgrades the grade) — a bare assignment lands it without the
|
|
755
|
+
// descriptor round trip.
|
|
756
|
+
for (const r of Reflect.ownKeys(t)) e.sc === 2 ? n[r] = t[r] : copyOwn(n, t, r);
|
|
757
|
+
if (e.del !== null) {
|
|
758
|
+
for (const t of e.del) delete n[t];
|
|
759
|
+
e.del = null;
|
|
760
|
+
}
|
|
761
|
+
e.pb = null;
|
|
762
|
+
e.ovl = false;
|
|
763
|
+
e.wk = null;
|
|
764
|
+
// written-keys window closes with the commit
|
|
765
|
+
}
|
|
766
|
+
|
|
580
767
|
function drainFolds() {
|
|
581
768
|
if (foldOlds.size === 0) return;
|
|
582
769
|
const e = [ ...foldOlds ];
|
|
@@ -592,7 +779,7 @@ function drainFolds() {
|
|
|
592
779
|
// keys; observed keys also hit the pending-node held check below).
|
|
593
780
|
const e = foldBatches.get(t);
|
|
594
781
|
if (e !== undefined) {
|
|
595
|
-
if (currentTransition(e).
|
|
782
|
+
if (currentTransition(e).Tt === false) {
|
|
596
783
|
foldOlds.set(t, n);
|
|
597
784
|
continue;
|
|
598
785
|
}
|
|
@@ -604,8 +791,8 @@ function drainFolds() {
|
|
|
604
791
|
// node (transition-held) re-queues the target for the settling flush.
|
|
605
792
|
let r = false;
|
|
606
793
|
const i = t.pb;
|
|
607
|
-
const
|
|
608
|
-
if (
|
|
794
|
+
const l = t.n;
|
|
795
|
+
if (l !== null) {
|
|
609
796
|
// Only written keys can hold (their nodes took the setSignal); the
|
|
610
797
|
// wk bound keeps this O(written) — see notifyWrites. Same fallback
|
|
611
798
|
// rules as the notify (WK_ALL / accessors / non-plain prototypes).
|
|
@@ -613,10 +800,10 @@ function drainFolds() {
|
|
|
613
800
|
const n = e === null || e === WK_ALL || t.a === true ||
|
|
614
801
|
// Overlay pbs chain to the COMMITTED object (#3044) — plainness is
|
|
615
802
|
// the committed container's prototype, not the overlay's.
|
|
616
|
-
!plainProto(t.ovl ? t.v : i) ? Reflect.ownKeys(
|
|
803
|
+
!plainProto(t.ovl ? t.v : i) ? Reflect.ownKeys(l) : e;
|
|
617
804
|
for (const e of n) {
|
|
618
|
-
const t =
|
|
619
|
-
if (t !== undefined && t.
|
|
805
|
+
const t = l[e];
|
|
806
|
+
if (t !== undefined && t.ve !== NOT_PENDING) {
|
|
620
807
|
r = true;
|
|
621
808
|
break;
|
|
622
809
|
}
|
|
@@ -628,26 +815,12 @@ function drainFolds() {
|
|
|
628
815
|
continue;
|
|
629
816
|
}
|
|
630
817
|
if (t.ovl) {
|
|
631
|
-
// Overlay flatten (#3044):
|
|
632
|
-
//
|
|
633
|
-
//
|
|
634
|
-
//
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
// — the never-mutate-user-data contract holds.
|
|
638
|
-
privatizeCommitted(t);
|
|
639
|
-
const e = t.v;
|
|
640
|
-
for (const t of Reflect.ownKeys(i)) copyOwn(e, i, t);
|
|
641
|
-
if (t.del !== null) {
|
|
642
|
-
for (const n of t.del) delete e[n];
|
|
643
|
-
t.del = null;
|
|
644
|
-
}
|
|
645
|
-
(t.fam?.map ?? storeNextLookup).delete(i);
|
|
646
|
-
t.pb = null;
|
|
647
|
-
t.ovl = false;
|
|
648
|
-
t.wk = null;
|
|
649
|
-
// written-keys window closes with the fold commit
|
|
650
|
-
} else if (t.v !== n) {
|
|
818
|
+
// Overlay flatten (#3044): the backing keeps its identity, so the
|
|
819
|
+
// `t.v === old` gate below skips path copying (the parent slot
|
|
820
|
+
// already points here) and the adopted-notify (setter notifications
|
|
821
|
+
// happened at write time).
|
|
822
|
+
flattenOverlay(t, i);
|
|
823
|
+
} else if (t.v !== n) {
|
|
651
824
|
// Privatized mid-batch (#3271): an earlier fold in this drain
|
|
652
825
|
// path-copied THROUGH this target — privatizeCommitted cloned the
|
|
653
826
|
// committed backing, re-pointed the parent slot at the clone, and
|
|
@@ -682,7 +855,6 @@ function drainFolds() {
|
|
|
682
855
|
if (!hasOwn.call(i, t)) delete e[t];
|
|
683
856
|
}
|
|
684
857
|
}
|
|
685
|
-
(t.fam?.map ?? storeNextLookup).delete(i);
|
|
686
858
|
t.pb = null;
|
|
687
859
|
t.wk = null;
|
|
688
860
|
// written-keys window closes with the fold commit
|
|
@@ -773,20 +945,20 @@ function drainFolds() {
|
|
|
773
945
|
// bound never engages for overlay writes (every plain-object setter batch
|
|
774
946
|
// would full-scan: the exact selection-map workload wk exists for; jf
|
|
775
947
|
// `select` regressed 2x on this).
|
|
776
|
-
const
|
|
948
|
+
const l = i === WK_ALL || e.a === true || !plainProto(e.ovl ? e.v : t) ? null : i;
|
|
777
949
|
if (r !== null) {
|
|
778
|
-
const i =
|
|
779
|
-
for (const
|
|
780
|
-
const i = r[
|
|
950
|
+
const i = l ?? Reflect.ownKeys(r);
|
|
951
|
+
for (const l of i) {
|
|
952
|
+
const i = r[l];
|
|
781
953
|
if (i === undefined) continue;
|
|
782
954
|
// Per-key accessor handling: the node's cached flag plus ONE getter
|
|
783
955
|
// probe on the incoming side (getters arriving via merge/adoption).
|
|
784
956
|
// Setter-only props read as data (value undefined) so lookupSetter is
|
|
785
957
|
// not consulted on this hot path; prototype getters never own nodes.
|
|
786
|
-
if (i.acc === true || hasOwn.call(t,
|
|
787
|
-
i.acc = isOwnAccessor(t,
|
|
788
|
-
const e = Object.getOwnPropertyDescriptor(n,
|
|
789
|
-
const r = Object.getOwnPropertyDescriptor(t,
|
|
958
|
+
if (i.acc === true || hasOwn.call(t, l) && lookupGetter.call(t, l) !== undefined) {
|
|
959
|
+
i.acc = isOwnAccessor(t, l);
|
|
960
|
+
const e = Object.getOwnPropertyDescriptor(n, l);
|
|
961
|
+
const r = Object.getOwnPropertyDescriptor(t, l);
|
|
790
962
|
if (e && (e.get || e.set) || r && (r.get || r.set)) {
|
|
791
963
|
if (e?.get !== r?.get || e?.set !== r?.set || e?.value !== r?.value) setSignal(i, () => FORCE);
|
|
792
964
|
continue;
|
|
@@ -798,25 +970,26 @@ function drainFolds() {
|
|
|
798
970
|
// projection recompute can run before the prior fold commits) — the
|
|
799
971
|
// node's OWN current value is the true old side, and setSignal's
|
|
800
972
|
// internal equality already checks exactly that.
|
|
801
|
-
const
|
|
802
|
-
setSignal(i, () =>
|
|
973
|
+
const o = e.del !== null && e.del.has(l) ? undefined : t[l];
|
|
974
|
+
setSignal(i, () => o);
|
|
803
975
|
}
|
|
804
976
|
}
|
|
805
|
-
const
|
|
806
|
-
if (
|
|
807
|
-
const n =
|
|
977
|
+
const o = e.h;
|
|
978
|
+
if (o !== null) {
|
|
979
|
+
const n = l ?? Reflect.ownKeys(o);
|
|
808
980
|
for (const r of n) {
|
|
809
|
-
const n =
|
|
981
|
+
const n = o[r];
|
|
810
982
|
if (n !== undefined) setSignal(n, r in t && !(e.del !== null && e.del.has(r)));
|
|
811
983
|
}
|
|
812
984
|
}
|
|
813
985
|
// Deep-witness (dk): setter writes must notify a deep() subscriber even on
|
|
814
986
|
// keys with no node. O(written/pb keys) equality only when a witness exists.
|
|
815
987
|
if (e.dk !== null) {
|
|
816
|
-
if (e.del !== null && e.del.size !== 0) bumpDeep(e); else for (const r of
|
|
988
|
+
if (e.del !== null && e.del.size !== 0) bumpDeep(e); else for (const r of l ?? Reflect.ownKeys(t)) {
|
|
989
|
+
if (r === $OWNER) continue;
|
|
817
990
|
const i = t[r];
|
|
818
|
-
const
|
|
819
|
-
if (i !== null && typeof i === "object" ? !targetsEqual(
|
|
991
|
+
const l = n[r];
|
|
992
|
+
if (i !== null && typeof i === "object" ? !targetsEqual(l, i) : !isEqual(l, i)) {
|
|
820
993
|
bumpDeep(e);
|
|
821
994
|
break;
|
|
822
995
|
}
|
|
@@ -860,6 +1033,10 @@ function drainFolds() {
|
|
|
860
1033
|
// Landed truth (post-await write-override): immediately visible to every
|
|
861
1034
|
// reader — any staged held view is superseded.
|
|
862
1035
|
if (e.ht !== null) e.ht = e.hv = null;
|
|
1036
|
+
// Overlay landing (#3352): flatten in place — identity-stable, and
|
|
1037
|
+
// privatizeCommitted re-slots the parent itself when it has to clone an
|
|
1038
|
+
// unowned seed, so no path copy is needed.
|
|
1039
|
+
if (e.ovl) return flattenOverlay(e, t);
|
|
863
1040
|
const n = e.v;
|
|
864
1041
|
e.pb = null;
|
|
865
1042
|
e.v = t;
|
|
@@ -880,9 +1057,9 @@ const FORCE = Symbol();
|
|
|
880
1057
|
|
|
881
1058
|
/** Same logical slot: both values resolve to one (re-pointed) child target —
|
|
882
1059
|
* adoption preserved identity, so the slot did not change (R9). */ function targetsEqual(e, t) {
|
|
883
|
-
if (e === null || typeof e !== "object") return false;
|
|
884
|
-
const n =
|
|
885
|
-
return n !== undefined && n ===
|
|
1060
|
+
if (e === null || typeof e !== "object" || t === null || typeof t !== "object") return false;
|
|
1061
|
+
const n = lookupTarget(e, null);
|
|
1062
|
+
return n !== undefined && n === lookupTarget(t, null);
|
|
886
1063
|
}
|
|
887
1064
|
|
|
888
1065
|
function arrayStructureChanged(e, t) {
|
|
@@ -897,8 +1074,9 @@ function arrayStructureChanged(e, t) {
|
|
|
897
1074
|
|
|
898
1075
|
function membershipChanged(e, t) {
|
|
899
1076
|
const n = Reflect.ownKeys(t);
|
|
900
|
-
|
|
901
|
-
|
|
1077
|
+
// The $OWNER stamp is not membership: an owned side counts one key more.
|
|
1078
|
+
if (Reflect.ownKeys(e).length - +isOwned(e) !== n.length - +isOwned(t)) return true;
|
|
1079
|
+
for (const t of n) if (t !== $OWNER && !(t in e)) return true;
|
|
902
1080
|
return false;
|
|
903
1081
|
}
|
|
904
1082
|
|
|
@@ -921,23 +1099,23 @@ i = true) {
|
|
|
921
1099
|
if (e.acc === true || i && hasOwn.call(r, t) && lookupGetter.call(r, t) !== undefined) {
|
|
922
1100
|
e.acc = isOwnAccessor(r, t);
|
|
923
1101
|
const i = Object.getOwnPropertyDescriptor(n, t);
|
|
924
|
-
const
|
|
925
|
-
if (i && (i.get || i.set) ||
|
|
1102
|
+
const l = Object.getOwnPropertyDescriptor(r, t);
|
|
1103
|
+
if (i && (i.get || i.set) || l && (l.get || l.set)) {
|
|
926
1104
|
// Accessor involved: never invoke; force-notify on shape change so
|
|
927
1105
|
// subscribers re-read (and re-track) through the trap.
|
|
928
|
-
if (i?.get !==
|
|
1106
|
+
if (i?.get !== l?.get || i?.set !== l?.set || i?.value !== l?.value) setSignal(e, () => FORCE);
|
|
929
1107
|
return;
|
|
930
1108
|
}
|
|
931
|
-
const
|
|
932
|
-
const
|
|
933
|
-
if (!isEqual(
|
|
1109
|
+
const o = i?.value;
|
|
1110
|
+
const f = l?.value;
|
|
1111
|
+
if (!isEqual(o, f) && !targetsEqual(o, f)) setSignal(e, typeof f === "function" ? () => f : f);
|
|
934
1112
|
} else {
|
|
935
1113
|
const i = n[t];
|
|
936
|
-
const
|
|
1114
|
+
const l = r[t];
|
|
937
1115
|
// Direct value write when not a function (setSignal treats functions as
|
|
938
1116
|
// updaters) — saves a closure allocation per changed key on the fold
|
|
939
1117
|
// hot path.
|
|
940
|
-
if (!isEqual(i,
|
|
1118
|
+
if (!isEqual(i, l) && !targetsEqual(i, l)) setSignal(e, typeof l === "function" ? () => l : l);
|
|
941
1119
|
}
|
|
942
1120
|
}
|
|
943
1121
|
|
|
@@ -949,9 +1127,9 @@ i = true) {
|
|
|
949
1127
|
/** Fused-walk per-key notification with values already in hand: the caller
|
|
950
1128
|
* fetched both sides and handled the identity skip; this applies the
|
|
951
1129
|
* accessor branch (cached flag only — reconcile channel) or the plain
|
|
952
|
-
* equality/identity-gated write. */ function notifyKeyValue(e, t, n, r, i,
|
|
1130
|
+
* equality/identity-gated write. */ function notifyKeyValue(e, t, n, r, i, l) {
|
|
953
1131
|
if (e.acc === true) {
|
|
954
|
-
notifyKeyDiff(e, t, i,
|
|
1132
|
+
notifyKeyDiff(e, t, i, l, false);
|
|
955
1133
|
return;
|
|
956
1134
|
}
|
|
957
1135
|
// The pre-compare is NOT redundant with the node's equals: setSignal parks
|
|
@@ -1046,28 +1224,46 @@ function inDraft(e) {
|
|
|
1046
1224
|
|
|
1047
1225
|
/** Targets written during the current (outermost) setter — notified at exit. */ const pendingNotify = new Set;
|
|
1048
1226
|
|
|
1227
|
+
/** Targets adopted under a live transaction this setter (adoptPB set a
|
|
1228
|
+
* transaction hold) — their nodes are notified at the outermost exit. */ const heldAdoptions = new Set;
|
|
1229
|
+
|
|
1230
|
+
/**
|
|
1231
|
+
* Write-time notification for a transaction-held adoption (#3330 store twin;
|
|
1232
|
+
* the setter path's notifyWrites twin for adoptions). A projection's fold
|
|
1233
|
+
* normally notifies its nodes at the drain — and the drain of a batch parked
|
|
1234
|
+
* in a live transaction is the transaction's COMMIT, so the nodes took the
|
|
1235
|
+
* adopted values as fresh writes at commit time: every subscriber was
|
|
1236
|
+
* re-marked and re-ran against a frame the lane had already published (a
|
|
1237
|
+
* third `v=1 d=2`), where a signal's write had staged at write time and
|
|
1238
|
+
* promoted silently. Staging here, inside the transaction's batch, makes the
|
|
1239
|
+
* two paths one: the nodes carry transition-stamped `_pendingValue`s, their
|
|
1240
|
+
* subscribers recompute in this flush and park with the transaction, and the
|
|
1241
|
+
* commit promotes without re-notifying. `ab` moves to the adopted backing —
|
|
1242
|
+
* the view the nodes were last told (#3296) — so the drain has nothing left
|
|
1243
|
+
* to say and only path-copies.
|
|
1244
|
+
*/ function stageHeldAdoptions() {
|
|
1245
|
+
const e = [ ...heldAdoptions ];
|
|
1246
|
+
heldAdoptions.clear();
|
|
1247
|
+
for (const t of e) {
|
|
1248
|
+
const e = t.ab;
|
|
1249
|
+
if (e === null || e === t.v) continue;
|
|
1250
|
+
notifyFold(t, e, t.v);
|
|
1251
|
+
t.ab = t.v;
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1049
1255
|
const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
|
|
1050
1256
|
|
|
1051
1257
|
/** Mirror of core read()'s context rule: the OWNER context (not the tracking
|
|
1052
1258
|
* observer) decides pending visibility, with Roots resolving to their parent
|
|
1053
1259
|
* computed (#2687 — untracked reads inside mapArray Roots see in-flight
|
|
1054
1260
|
* values mid-flush). CHILDREN_FORBIDDEN execution scopes (createTrackedEffect
|
|
1055
|
-
* / onSettled callbacks) get COMMITTED visibility (#3006), same as core. */
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
return t != null && !(t.T & CONFIG_CHILDREN_FORBIDDEN);
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
/** CHILDREN_FORBIDDEN execution scope (createTrackedEffect / onSettled
|
|
1063
|
-
* callbacks). Distinct from context-free: these scopes get committed
|
|
1064
|
-
* visibility even against a projection's authoritative-elect pending
|
|
1065
|
-
* backing (#3082) — parity with signals, where core read() serves
|
|
1066
|
-
* committed to them regardless of staged writes. */ function inForbiddenScope() {
|
|
1261
|
+
* / onSettled callbacks) get COMMITTED visibility (#3006), same as core. */
|
|
1262
|
+
/** Core read()'s reader: the current computation, a root reading as its
|
|
1263
|
+
* parent computed (`context` persists under untrack — an untracked read
|
|
1264
|
+
* inside an effect is still that effect's read). */ function readerContext() {
|
|
1067
1265
|
const e = getOwner();
|
|
1068
|
-
|
|
1069
|
-
const t = e.Dt ? e.Pt : e;
|
|
1070
|
-
return t != null && !!(t.T & CONFIG_CHILDREN_FORBIDDEN);
|
|
1266
|
+
return e === null ? null : e.xt ? e.Qt ?? null : e;
|
|
1071
1267
|
}
|
|
1072
1268
|
|
|
1073
1269
|
/** A pending fold is transition-held when any written node's parked value is
|
|
@@ -1077,19 +1273,24 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
|
|
|
1077
1273
|
if (t === null) return false;
|
|
1078
1274
|
for (const e of Reflect.ownKeys(t)) {
|
|
1079
1275
|
const n = t[e];
|
|
1080
|
-
if (n.
|
|
1276
|
+
if (n.ve !== NOT_PENDING && n.Ge != null && n.Ge.Tt !== true) return true;
|
|
1081
1277
|
}
|
|
1082
1278
|
return false;
|
|
1083
1279
|
}
|
|
1084
1280
|
|
|
1085
1281
|
function readSource(e) {
|
|
1086
|
-
//
|
|
1087
|
-
// pre-hold committed
|
|
1088
|
-
// readers —
|
|
1089
|
-
//
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1282
|
+
// Adoption hold first (#3074): an adoption staged under a live transaction
|
|
1283
|
+
// (or a latest()-pull, PLAIN_HOLD) serves the pre-hold committed view to
|
|
1284
|
+
// committed-visibility readers — context-free and children-forbidden ones,
|
|
1285
|
+
// and stale passes off a foreign hold. Drafts, write-override and latest()
|
|
1286
|
+
// see the adopted backing.
|
|
1287
|
+
const t = e.ht;
|
|
1288
|
+
if (t !== null && !latestReadActive && !inDraft(e) && !getWriteOverride()) {
|
|
1289
|
+
const n = heldMaskView(e);
|
|
1290
|
+
if (n !== null) {
|
|
1291
|
+
const e = readerContext();
|
|
1292
|
+
if (e === null || e.T & CONFIG_CHILDREN_FORBIDDEN || !holdVisible(t === PLAIN_HOLD ? null : currentTransition(t), e)) return n;
|
|
1293
|
+
}
|
|
1093
1294
|
}
|
|
1094
1295
|
return pendingBackingVisible(e, false) ? e.pb : e.v;
|
|
1095
1296
|
}
|
|
@@ -1109,21 +1310,28 @@ function readSource(e) {
|
|
|
1109
1310
|
* ordinary pending staging regardless of owner context (the documented
|
|
1110
1311
|
* divergence from context-free per-key reads) — but never through a hold:
|
|
1111
1312
|
* held truth stays masked exactly as it is for per-key readers. */ function pendingBackingVisible(e, t) {
|
|
1112
|
-
|
|
1113
|
-
//
|
|
1114
|
-
|
|
1115
|
-
//
|
|
1116
|
-
//
|
|
1117
|
-
//
|
|
1118
|
-
//
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1313
|
+
if (e.pb === null) return false;
|
|
1314
|
+
// The writer's own channels compose on the pending backing regardless.
|
|
1315
|
+
if (inDraft(e) || getWriteOverride()) return true;
|
|
1316
|
+
// HELD truth on an optimistic family (#3164 fold) is masked from ordinary
|
|
1317
|
+
// readers until the transaction's reveal (the backing-level twin of core
|
|
1318
|
+
// serve()'s CONFIG_HELD_TRUTH arm; authoritative postures and latest()
|
|
1319
|
+
// tunnel through inside heldTruthMasked).
|
|
1320
|
+
if (heldTruthMasked(e)) return false;
|
|
1321
|
+
const n = readerContext();
|
|
1322
|
+
if (n === null || n.T & CONFIG_CHILDREN_FORBIDDEN) {
|
|
1323
|
+
// No pass, or a children-forbidden one: the committed frame (A32) —
|
|
1324
|
+
// except the speculative peek (deep()/snapshot()), which sees ordinary
|
|
1325
|
+
// pending staging but never through a live foreign hold, and a
|
|
1326
|
+
// projection's pending backing, authoritative-elect for context-free
|
|
1327
|
+
// readers UNLESS a transition holds the node commits (downstream async
|
|
1328
|
+
// hold — stale committed is the contract; the write-time stamp covers
|
|
1329
|
+
// keys with no node, #3336) or the scope is children-forbidden (#3082).
|
|
1330
|
+
const r = liveFoldTransition(e);
|
|
1331
|
+
if (t) return r === null || ownsHold(r);
|
|
1332
|
+
return e.fam !== null && n === null && !foldHeld(e) && r === null;
|
|
1333
|
+
}
|
|
1334
|
+
return holdVisible(liveFoldTransition(e), n);
|
|
1127
1335
|
}
|
|
1128
1336
|
|
|
1129
1337
|
/** #3164 fold: HELD truth on an optimistic family — a pending backing
|
|
@@ -1149,6 +1357,8 @@ const lookupGetter = Object.prototype.__lookupGetter__;
|
|
|
1149
1357
|
|
|
1150
1358
|
const lookupSetter = Object.prototype.__lookupSetter__;
|
|
1151
1359
|
|
|
1360
|
+
const propertyIsEnumerable = Object.prototype.propertyIsEnumerable;
|
|
1361
|
+
|
|
1152
1362
|
function isOwnAccessor(e, t) {
|
|
1153
1363
|
return hasOwn.call(e, t) && (lookupGetter.call(e, t) !== undefined || lookupSetter.call(e, t) !== undefined);
|
|
1154
1364
|
}
|
|
@@ -1166,11 +1376,6 @@ function isOwnAccessor(e, t) {
|
|
|
1166
1376
|
}
|
|
1167
1377
|
}
|
|
1168
1378
|
|
|
1169
|
-
/** Active optimistic override on an armed node (armed slot idles at
|
|
1170
|
-
* NOT_PENDING; undefined = unarmed plain node). */ function hasActiveOverride(e) {
|
|
1171
|
-
return e.o?.Pe !== undefined && e.o?.Pe !== NOT_PENDING;
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1174
1379
|
/** The reading computation is until()'s authoritative-view predicate — same
|
|
1175
1380
|
* source of truth as core read()'s A17 carve-out (`context`, which persists
|
|
1176
1381
|
* under untrack). optimisticView()'s composition gate consults exactly this:
|
|
@@ -1198,35 +1403,56 @@ function isOwnAccessor(e, t) {
|
|
|
1198
1403
|
* reader that forced it (backing commits eagerly; node values fold at flush).
|
|
1199
1404
|
* FORCE sentinels never surface (they only bump subscribers of accessor
|
|
1200
1405
|
* keys, which are served by the trap, not the node). */ function nodeValue(e, t) {
|
|
1201
|
-
//
|
|
1202
|
-
//
|
|
1203
|
-
//
|
|
1204
|
-
//
|
|
1205
|
-
//
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
//
|
|
1209
|
-
//
|
|
1210
|
-
//
|
|
1211
|
-
|
|
1212
|
-
// exempted by the leading arm above).
|
|
1213
|
-
(inOwnerContext() || authoritativeServe()) && !(e.T & CONFIG_HELD_TRUTH && !authoritativeServe())) ? e.Re : t;
|
|
1406
|
+
// Store-only tunnels, ahead of Rule 1: truth authors (authoritativeServe —
|
|
1407
|
+
// the projection derive's draft, the write-override continuation) see
|
|
1408
|
+
// staged truth and never an override; latest() reaching this untracked
|
|
1409
|
+
// path for a store key sees the in-flight parked value like an
|
|
1410
|
+
// owner-context reader does (#3075), the visible override first.
|
|
1411
|
+
let n;
|
|
1412
|
+
if (authoritativeServe()) n = e.ve !== NOT_PENDING ? e.ve : t; else if (latestReadActive) n = visibleOverride(e) ? unwrapOverride(e.o?.Ce) : e.ve !== NOT_PENDING ? e.ve : t;
|
|
1413
|
+
// Otherwise the one slow selection core read() uses (serve): override,
|
|
1414
|
+
// lane gate, A28, readerSeesCommitted / A29 — with the BACKING as the
|
|
1415
|
+
// committed value (single-home rule, O6).
|
|
1416
|
+
else n = serve(e, readerContext(), e.Te || e, t);
|
|
1214
1417
|
return n === FORCE ? t : n;
|
|
1215
1418
|
}
|
|
1216
1419
|
|
|
1420
|
+
/** §7b: a chained target's child found as a RAW — from its pending backing
|
|
1421
|
+
* (a cloneRaw of the inner proxy, whose descriptors yield the inner store's
|
|
1422
|
+
* raws) or from the deep() walk's descriptor read through the chain — must
|
|
1423
|
+
* resolve to the inner family's proxy for that object before this family
|
|
1424
|
+
* wraps it, so the overlay serves the same chained targets as the settled
|
|
1425
|
+
* state. Otherwise every row re-wrapped as a fresh non-chained target keyed
|
|
1426
|
+
* by the raw: identities churned for the life of an optimistic action and
|
|
1427
|
+
* snapped back at settle, and writes landed on those orphans while deep()'s
|
|
1428
|
+
* witnesses sat on the chained targets (#3323). The inner family owns the
|
|
1429
|
+
* raw iff it has served it or its backing holds that exact object at `key`;
|
|
1430
|
+
* anything else is a draft's own replacement object — view-owned, correctly
|
|
1431
|
+
* non-chained — and is returned as is. Recurses down further chains. */ function resolveChainedRaw(e, t, n) {
|
|
1432
|
+
const r = e.v[$TARGET];
|
|
1433
|
+
if (r.ch) {
|
|
1434
|
+
const e = resolveChainedRaw(r, t, n);
|
|
1435
|
+
return e === n ? n : wrapNext(e, r, t);
|
|
1436
|
+
}
|
|
1437
|
+
const i = lookupTarget(n, r.fam);
|
|
1438
|
+
if (i !== undefined) return i.px;
|
|
1439
|
+
if ((r.v[t] === n || r.pb?.[t] === n) && isWrappable(n)) return wrapNext(n, r, t);
|
|
1440
|
+
return n;
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1217
1443
|
/** Serve an own data key: node-first when a node exists (pending visibility,
|
|
1218
1444
|
* holds, lanes ride the node); backing otherwise. Chained backings (§7b: the
|
|
1219
1445
|
* backing IS another store's proxy) serve the read-through value — the outer
|
|
1220
1446
|
* node is linked only for adoption-swap notification, its value never
|
|
1221
|
-
* shadows the live chain. */ function serveDataKey(e, t, n, r, i,
|
|
1222
|
-
const
|
|
1223
|
-
let
|
|
1447
|
+
* shadows the live chain. */ function serveDataKey(e, t, n, r, i, l = -1) {
|
|
1448
|
+
const o = e.ch && r === e.v;
|
|
1449
|
+
let f = n;
|
|
1224
1450
|
// §6: on optimistic arrays LENGTH IS A VIEW, not a node value — one home
|
|
1225
1451
|
// (backing ± presence overrides) for both length and indices makes torn
|
|
1226
1452
|
// iteration impossible (a length node's value rides different visibility
|
|
1227
1453
|
// rails than index overrides mid-settle). The node still carries
|
|
1228
1454
|
// subscriptions; its value is never served here.
|
|
1229
|
-
if (t === "length" && e.fam?.opt === true && !
|
|
1455
|
+
if (t === "length" && e.fam?.opt === true && !o && Array.isArray(r)) {
|
|
1230
1456
|
if (!inDraft(e)) {
|
|
1231
1457
|
const r = e.n?.length;
|
|
1232
1458
|
if (r !== undefined) {
|
|
@@ -1238,7 +1464,7 @@ function isOwnAccessor(e, t) {
|
|
|
1238
1464
|
// Truth authors read the backing's own length — an optimistic row from
|
|
1239
1465
|
// the caller's transaction must not shift where the author's next write
|
|
1240
1466
|
// lands (#3108).
|
|
1241
|
-
return (authoritativeServe() ? r : optHooks.optimisticView(e, r)).length;
|
|
1467
|
+
return (authoritativeServe() ? r : optHooks.optimisticView(e, r, inDraft(e))).length;
|
|
1242
1468
|
}
|
|
1243
1469
|
if (inDraft(e)) {
|
|
1244
1470
|
// Optimistic drafts before their first write have no pending backing yet;
|
|
@@ -1248,42 +1474,43 @@ function isOwnAccessor(e, t) {
|
|
|
1248
1474
|
// seeding rule, applied to the read side (#3108).
|
|
1249
1475
|
if (e.fam?.opt && draftSeesOverrides(e) && !authoritativeServe()) {
|
|
1250
1476
|
const n = e.n?.[t];
|
|
1251
|
-
if (n !== undefined && hasActiveOverride(n))
|
|
1477
|
+
if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.o?.Ce);
|
|
1252
1478
|
}
|
|
1253
1479
|
} else {
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1480
|
+
// §7b: a lane value on the outer node SHADOWS read-through — an active
|
|
1481
|
+
// override pierces the chained gate; otherwise chained backings always
|
|
1482
|
+
// serve the live inner value.
|
|
1483
|
+
if (getObserver() !== null) {
|
|
1484
|
+
// First tracked read: create + link (the wrap-cache branch below
|
|
1485
|
+
// populates px/pxv so read #2 skips wrapNext, slice 2). The value is
|
|
1486
|
+
// served THROUGH the node from this read on — a node born under a held
|
|
1487
|
+
// fold carries the hold (getNode, #3336), and the backing it was read
|
|
1488
|
+
// from does not.
|
|
1489
|
+
if (i === undefined) i = getNode(e, t, n, l);
|
|
1490
|
+
// read()'s plain-signal fast path hoisted over the call (legacy trap
|
|
1491
|
+
// parity): READ_SLOW = a global read window or non-plain node.
|
|
1492
|
+
let r = readNodeFast(i);
|
|
1493
|
+
if (r === READ_SLOW) r = read(i);
|
|
1494
|
+
if (!o || hasActiveOverride(i)) f = r === FORCE ? n : r;
|
|
1495
|
+
} else if (i !== undefined && (!o || hasActiveOverride(i))) {
|
|
1496
|
+
f = nodeValue(i, n);
|
|
1271
1497
|
}
|
|
1272
1498
|
}
|
|
1273
1499
|
// Shallow stores serve data raw; store-proxy slots get boundary wrappers.
|
|
1274
|
-
if (e.s) return serveShallow(e, t,
|
|
1500
|
+
if (e.s) return serveShallow(e, t, f);
|
|
1501
|
+
if (e.ch && !o && f !== null && typeof f === "object" && f[$TARGET] === undefined) f = resolveChainedRaw(e, t, f);
|
|
1275
1502
|
if (i !== undefined) {
|
|
1276
1503
|
// Wrap cache (see getNode): only wrappables are ever cached, so a hit
|
|
1277
1504
|
// skips isWrappable too — pointer-compare replaces both checks.
|
|
1278
|
-
if (i.pxv ===
|
|
1279
|
-
if (!isWrappable(
|
|
1280
|
-
const n = wrapNext(
|
|
1505
|
+
if (i.pxv === f && f !== undefined) return draftServe(e, i.px);
|
|
1506
|
+
if (!isWrappable(f)) return f;
|
|
1507
|
+
const n = wrapNext(f, e, t);
|
|
1281
1508
|
i.px = n;
|
|
1282
|
-
i.pxv =
|
|
1509
|
+
i.pxv = f;
|
|
1283
1510
|
return draftServe(e, n);
|
|
1284
1511
|
}
|
|
1285
|
-
if (!isWrappable(
|
|
1286
|
-
return draftServe(e, wrapNext(
|
|
1512
|
+
if (!isWrappable(f)) return f;
|
|
1513
|
+
return draftServe(e, wrapNext(f, e, t));
|
|
1287
1514
|
}
|
|
1288
1515
|
|
|
1289
1516
|
/** §6c store-wide status gate for reads that DON'T flow through a node:
|
|
@@ -1334,6 +1561,8 @@ const traps = {
|
|
|
1334
1561
|
if (typeof t !== "string") {
|
|
1335
1562
|
if (t === $TARGET) return e;
|
|
1336
1563
|
if (t === $PROXY) return n;
|
|
1564
|
+
if (t === $OWNER) return undefined;
|
|
1565
|
+
// ownership stamp: never a user key
|
|
1337
1566
|
// refresh()/isPending resolve the projection computed through $REFRESH.
|
|
1338
1567
|
if (t === $REFRESH) return e.fam?.node ?? undefined;
|
|
1339
1568
|
if (t === $TRACK) {
|
|
@@ -1405,18 +1634,18 @@ const traps = {
|
|
|
1405
1634
|
// node creation downstream reuses it instead of re-scanning the
|
|
1406
1635
|
// descriptor, but only when the probed object IS the one getNode would
|
|
1407
1636
|
// scan (pb ?? v).
|
|
1408
|
-
let
|
|
1637
|
+
let l = -1;
|
|
1409
1638
|
{
|
|
1410
|
-
let
|
|
1411
|
-
if (i !== undefined)
|
|
1412
|
-
|
|
1413
|
-
if (r === (e.pb ?? e.v))
|
|
1414
|
-
} else
|
|
1415
|
-
if (
|
|
1416
|
-
if (!inDraft(e) && getObserver() !== null) read(i ?? getNode(e, t, undefined,
|
|
1417
|
-
const
|
|
1418
|
-
if (e.s) return serveShallow(e, t,
|
|
1419
|
-
return isWrappable(
|
|
1639
|
+
let o;
|
|
1640
|
+
if (i !== undefined) o = i.acc === true; else if (!inDraft(e) && getObserver() !== null) {
|
|
1641
|
+
o = isOwnAccessor(r, t);
|
|
1642
|
+
if (r === (e.pb ?? e.v)) l = o ? 1 : 0;
|
|
1643
|
+
} else o = false;
|
|
1644
|
+
if (o) {
|
|
1645
|
+
if (!inDraft(e) && getObserver() !== null) read(i ?? getNode(e, t, undefined, l));
|
|
1646
|
+
const o = Reflect.get(r, t, n);
|
|
1647
|
+
if (e.s) return serveShallow(e, t, o);
|
|
1648
|
+
return isWrappable(o) ? draftServe(e, wrapNext(o, e, t)) : o;
|
|
1420
1649
|
}
|
|
1421
1650
|
}
|
|
1422
1651
|
// Plain-data fast path: no descriptor allocation per read.
|
|
@@ -1426,40 +1655,41 @@ const traps = {
|
|
|
1426
1655
|
// backings chain to the committed backing, so "own in the view" means
|
|
1427
1656
|
// own on either layer (ownInView) — a genuine prototype method is one
|
|
1428
1657
|
// that is own on NEITHER.
|
|
1429
|
-
const
|
|
1430
|
-
if ((t === "constructor" || t === "__proto__" || t === "prototype") && !hasOwn.call(r, t) && !(
|
|
1431
|
-
let
|
|
1432
|
-
if (
|
|
1658
|
+
const o = e.ovl && r === e.pb;
|
|
1659
|
+
if ((t === "constructor" || t === "__proto__" || t === "prototype") && !hasOwn.call(r, t) && !(o && hasOwn.call(e.v, t))) return undefined;
|
|
1660
|
+
let f = r[t];
|
|
1661
|
+
if (f === undefined ? !hasOwn.call(r, t) && !(o && hasOwn.call(e.v, t)) : false) {
|
|
1433
1662
|
// Inherited: prototype getters/methods run with the proxy receiver.
|
|
1434
|
-
|
|
1435
|
-
if (typeof
|
|
1663
|
+
f = Reflect.get(r, t, n);
|
|
1664
|
+
if (typeof f === "function") return f;
|
|
1436
1665
|
// proto methods untracked
|
|
1437
1666
|
// Reading a currently-absent own key subscribes to it (R12) — for any
|
|
1438
1667
|
// target OUTSIDE its own draft scope, even mid-setter (#3037, above).
|
|
1439
|
-
if (
|
|
1440
|
-
if (getObserver() !== null) read(getNode(e, t, undefined,
|
|
1668
|
+
if (f === undefined && !inDraft(e)) {
|
|
1669
|
+
if (getObserver() !== null) read(getNode(e, t, undefined, l));
|
|
1441
1670
|
const n = e.n?.[t];
|
|
1442
1671
|
if (n) {
|
|
1443
1672
|
const r = nodeValue(n, undefined);
|
|
1444
1673
|
if (e.s) return serveShallow(e, t, r);
|
|
1445
1674
|
return isWrappable(r) ? draftServe(e, wrapNext(r, e, t)) : r;
|
|
1446
1675
|
}
|
|
1447
|
-
} else if (
|
|
1676
|
+
} else if (f === undefined && inDraft(e) && e.fam?.opt && draftSeesOverrides(e) &&
|
|
1448
1677
|
// AUTHORITATIVE drafts (landing folds) never seed from overrides —
|
|
1449
1678
|
// the caller's optimism is not truth (has-trap twin below).
|
|
1450
1679
|
!authoritativeServe()) {
|
|
1451
1680
|
const n = e.n?.[t];
|
|
1452
|
-
if (n !== undefined &&
|
|
1681
|
+
if (n !== undefined && visibleOverride(n)) f = unwrapOverride(n.o?.Ce);
|
|
1453
1682
|
}
|
|
1454
|
-
if (e.s) return serveShallow(e, t,
|
|
1455
|
-
return isWrappable(
|
|
1683
|
+
if (e.s) return serveShallow(e, t, f);
|
|
1684
|
+
return isWrappable(f) ? draftServe(e, wrapNext(f, e, t)) : f;
|
|
1456
1685
|
}
|
|
1457
|
-
if (typeof
|
|
1686
|
+
if (typeof f === "function" && !hasOwn.call(r, t) && !(o && hasOwn.call(e.v, t))) return f;
|
|
1458
1687
|
// proto method
|
|
1459
|
-
return serveDataKey(e, t,
|
|
1688
|
+
return serveDataKey(e, t, f, r, i, l);
|
|
1460
1689
|
},
|
|
1461
1690
|
has(e, t) {
|
|
1462
1691
|
if (t === $TARGET || t === $PROXY || t === $TRACK) return true;
|
|
1692
|
+
if (t === $OWNER) return false;
|
|
1463
1693
|
if (pendingCheckActive) witnessAffectsMark(e, t);
|
|
1464
1694
|
if (e.fam !== null && getObserver() === null && !inDraft(e)) firewallGate(e);
|
|
1465
1695
|
const n = readSource(e);
|
|
@@ -1475,11 +1705,11 @@ const traps = {
|
|
|
1475
1705
|
if (hasActiveOverride(n)) r = !!i;
|
|
1476
1706
|
} else if (!authoritativeServe()) {
|
|
1477
1707
|
const n = e.h?.[t];
|
|
1478
|
-
if (n !== undefined &&
|
|
1708
|
+
if (n !== undefined && visibleOverride(n)) r = !!unwrapOverride(n.o?.Ce);
|
|
1479
1709
|
}
|
|
1480
1710
|
} else if (e.fam?.opt && draftSeesOverrides(e) && !authoritativeServe()) {
|
|
1481
1711
|
const n = e.h?.[t];
|
|
1482
|
-
if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.
|
|
1712
|
+
if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.Ce);
|
|
1483
1713
|
}
|
|
1484
1714
|
return r;
|
|
1485
1715
|
},
|
|
@@ -1487,59 +1717,27 @@ const traps = {
|
|
|
1487
1717
|
if (pendingCheckActive) witnessAffectsMark(e);
|
|
1488
1718
|
if (e.fam !== null && getObserver() === null && !inDraft(e)) firewallGate(e);
|
|
1489
1719
|
if (!inDraft(e) && getObserver() !== null) read(getKeySetNode(e));
|
|
1490
|
-
|
|
1491
|
-
let n;
|
|
1492
|
-
if (e.ovl && t === e.pb) {
|
|
1493
|
-
// Overlay merge (#3044): committed keys in their order, then this
|
|
1494
|
-
// batch's NEW keys, minus deletes.
|
|
1495
|
-
n = Reflect.ownKeys(e.v);
|
|
1496
|
-
const r = e.del;
|
|
1497
|
-
if (r !== null && r.size !== 0) n = n.filter(e => !r.has(e));
|
|
1498
|
-
for (const r of Reflect.ownKeys(t)) {
|
|
1499
|
-
if (!hasOwn.call(e.v, r)) n.push(r);
|
|
1500
|
-
}
|
|
1501
|
-
} else n = Reflect.ownKeys(t);
|
|
1502
|
-
// Optimistic membership overlay: presence-node overrides add/remove keys
|
|
1503
|
-
// (per-transaction lifecycle rides the nodes — §6, FINDING-2's fix).
|
|
1504
|
-
// Draft reads before the first write overlay too (pb, once created, is
|
|
1505
|
-
// seeded with the view). Authoritative-view reads (until()'s predicate,
|
|
1506
|
-
// truth-author drafts) skip the overlay.
|
|
1507
|
-
if (!authoritativeServe() && e.fam?.opt && e.h !== null && (!inDraft(e) || draftSeesOverrides(e))) {
|
|
1508
|
-
let t = null;
|
|
1509
|
-
for (const r of Reflect.ownKeys(e.h)) {
|
|
1510
|
-
const i = e.h[r];
|
|
1511
|
-
if (!hasActiveOverride(i)) continue;
|
|
1512
|
-
t ??= new Set(n);
|
|
1513
|
-
if (unwrapOverride(i.o?.Pe)) t.add(r); else t.delete(r);
|
|
1514
|
-
}
|
|
1515
|
-
if (t !== null) return [ ...t ];
|
|
1516
|
-
}
|
|
1517
|
-
return n;
|
|
1720
|
+
return visibleKeys(e, readSource(e));
|
|
1518
1721
|
},
|
|
1519
1722
|
getOwnPropertyDescriptor(e, t) {
|
|
1723
|
+
if (t === $OWNER) return undefined;
|
|
1724
|
+
// A descriptor read is a PRESENCE read: it subscribes to the key's
|
|
1725
|
+
// presence node and witnesses affects()/isPending() exactly as `in` does
|
|
1726
|
+
// (structural oracle, 2026-09-17 — the trap read no node before, so a
|
|
1727
|
+
// render effect inspecting a key through getOwnPropertyDescriptor never
|
|
1728
|
+
// re-ran for an optimistic add or delete, and an isPending() probe over
|
|
1729
|
+
// it witnessed nothing). The value it reports rides the value node's
|
|
1730
|
+
// view through visibleDescriptor.
|
|
1731
|
+
if (pendingCheckActive) witnessAffectsMark(e, t);
|
|
1732
|
+
if (e.fam !== null && getObserver() === null && !inDraft(e)) firewallGate(e);
|
|
1520
1733
|
const n = readSource(e);
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
if (r
|
|
1527
|
-
|
|
1528
|
-
if (!authoritativeServe() && e.fam?.opt && !inDraft(e)) {
|
|
1529
|
-
const n = e.h?.[t];
|
|
1530
|
-
if (n !== undefined && hasActiveOverride(n)) {
|
|
1531
|
-
if (!unwrapOverride(n.o?.Pe)) return undefined;
|
|
1532
|
-
// opt delete
|
|
1533
|
-
if (r === undefined) {
|
|
1534
|
-
const n = e.n?.[t];
|
|
1535
|
-
return {
|
|
1536
|
-
value: n !== undefined ? nodeValue(n, undefined) : undefined,
|
|
1537
|
-
writable: true,
|
|
1538
|
-
enumerable: true,
|
|
1539
|
-
configurable: true
|
|
1540
|
-
};
|
|
1541
|
-
}
|
|
1542
|
-
}
|
|
1734
|
+
const r = visibleDescriptor(e, n, t);
|
|
1735
|
+
if (!inDraft(e) && getObserver() !== null) {
|
|
1736
|
+
// The node is born from the source's presence (as `has` births it),
|
|
1737
|
+
// not the override-adjusted answer.
|
|
1738
|
+
let r = t in n;
|
|
1739
|
+
if (r && e.del !== null && n === e.pb && e.del.has(t)) r = false;
|
|
1740
|
+
read(getHasNode(e, t, r));
|
|
1543
1741
|
}
|
|
1544
1742
|
if (r === undefined) return undefined;
|
|
1545
1743
|
// Array targets carry a real non-configurable `length` the proxy
|
|
@@ -1563,24 +1761,31 @@ const traps = {
|
|
|
1563
1761
|
// Shallow slots store what was written VERBATIM — another store's proxy
|
|
1564
1762
|
// passes through by reference (#2932; markRawOne skips proxies), while
|
|
1565
1763
|
// deep stores unwrap to raw backings.
|
|
1566
|
-
const
|
|
1567
|
-
const
|
|
1764
|
+
const l = e.s ? n : unwrapValue(n);
|
|
1765
|
+
const o = ensurePB(e);
|
|
1568
1766
|
pendingNotify.add(e);
|
|
1569
1767
|
// Array length writes implicitly delete indices — the written-keys bound
|
|
1570
1768
|
// can't see them, so poison to the full scan for this batch. Index
|
|
1571
1769
|
// writes implicitly GROW length, so arrays always record it alongside.
|
|
1572
|
-
if (Array.isArray(
|
|
1770
|
+
if (Array.isArray(o)) {
|
|
1573
1771
|
if (t === "length") e.wk = WK_ALL; else if (e.wk !== WK_ALL) {
|
|
1574
1772
|
const n = e.wk ??= new Set;
|
|
1575
1773
|
n.add(t);
|
|
1576
1774
|
n.add("length");
|
|
1577
1775
|
}
|
|
1578
|
-
} else
|
|
1776
|
+
} else {
|
|
1777
|
+
if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
|
|
1778
|
+
// Live own-key estimate for the overlay/clone choice (#3360): `in`
|
|
1779
|
+
// sees through an overlay to the committed keys, so this counts keys
|
|
1780
|
+
// NEW to the container. Deletes are not un-counted (a stale high
|
|
1781
|
+
// count only picks the overlay a little early).
|
|
1782
|
+
if (!(t in o)) e.kc++;
|
|
1783
|
+
}
|
|
1579
1784
|
// Own data keys literally named "prototype"/"constructor" land as data —
|
|
1580
1785
|
// defineProperty sidesteps a proto-chain setter named the same.
|
|
1581
1786
|
if (UNSAFE_KEYS.has(t)) {
|
|
1582
|
-
Object.defineProperty(
|
|
1583
|
-
value:
|
|
1787
|
+
Object.defineProperty(o, t, {
|
|
1788
|
+
value: l,
|
|
1584
1789
|
writable: true,
|
|
1585
1790
|
enumerable: true,
|
|
1586
1791
|
configurable: true
|
|
@@ -1590,19 +1795,21 @@ const traps = {
|
|
|
1590
1795
|
}
|
|
1591
1796
|
// Overlay first-write DEFINES the own key: assignment through the proto
|
|
1592
1797
|
// chain would reject on a non-writable committed property (the clone
|
|
1593
|
-
// path normalized descriptors for exactly this — R51 parity).
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1798
|
+
// path normalized descriptors for exactly this — R51 parity). A
|
|
1799
|
+
// plain-data-graded backing (sc 2, owned: every slot writable) takes the
|
|
1800
|
+
// bare assignment — it lands as an own key on the overlay all the same.
|
|
1801
|
+
if (e.ovl && e.sc !== 2 && !hasOwn.call(o, t)) {
|
|
1802
|
+
Object.defineProperty(o, t, {
|
|
1803
|
+
value: l,
|
|
1597
1804
|
writable: true,
|
|
1598
1805
|
enumerable: true,
|
|
1599
1806
|
configurable: true
|
|
1600
1807
|
});
|
|
1601
|
-
} else
|
|
1808
|
+
} else o[t] = l;
|
|
1602
1809
|
if (e.del !== null) e.del.delete(t);
|
|
1603
1810
|
// Shallow ingest: written records are sticky raw-marked (one entity is
|
|
1604
1811
|
// never both deep-wrapped and raw — R41/#2932, shared invariant).
|
|
1605
|
-
if (e.s &&
|
|
1812
|
+
if (e.s && l !== null && typeof l === "object") markRawOne(l);
|
|
1606
1813
|
// Override-mode (post-await draft) writes have no setter exit — notify
|
|
1607
1814
|
// per-op (setSignal equality-gates repeats).
|
|
1608
1815
|
if (i) notifyWrites(e);
|
|
@@ -1619,10 +1826,14 @@ const traps = {
|
|
|
1619
1826
|
...n,
|
|
1620
1827
|
value: unwrapValue(n.value)
|
|
1621
1828
|
};
|
|
1622
|
-
const
|
|
1829
|
+
const l = ensurePB(e);
|
|
1830
|
+
// A non-default data descriptor (or an accessor) leaves the plain-data
|
|
1831
|
+
// grade: the key reaches the committed backing as defined, so the spread
|
|
1832
|
+
// clone and bare-assignment paths no longer describe it.
|
|
1833
|
+
if (e.a || !(n.enumerable && n.writable && n.configurable)) e.sc = 1;
|
|
1623
1834
|
pendingNotify.add(e);
|
|
1624
1835
|
if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
|
|
1625
|
-
Object.defineProperty(
|
|
1836
|
+
Object.defineProperty(l, t, n);
|
|
1626
1837
|
if (e.del !== null) e.del.delete(t);
|
|
1627
1838
|
if (i) notifyWrites(e);
|
|
1628
1839
|
return true;
|
|
@@ -1645,20 +1856,22 @@ const traps = {
|
|
|
1645
1856
|
|
|
1646
1857
|
/** Low-level setter primitive: opens write mode on a next proxy, runs `fn`,
|
|
1647
1858
|
* emits write-time notifications at outermost exit, applies returned
|
|
1648
|
-
* replacements as adoptions. `guard=false` skips the owned-scope
|
|
1649
|
-
* projection recomputes legitimately write from
|
|
1859
|
+
* replacements as adoptions. `guard=false` skips the dev guards (owned-scope
|
|
1860
|
+
* write, thenable result) — projection recomputes legitimately write from
|
|
1861
|
+
* inside their computed, and their async derive is handled by the recompute,
|
|
1862
|
+
* not returned through here. */ function storeSetterNext(e, t, n = true) {
|
|
1650
1863
|
const r = e[$TARGET];
|
|
1651
1864
|
const i = writeScopes;
|
|
1652
1865
|
writeScopes = new Set;
|
|
1653
1866
|
writeScopes.add(scopeKey(r));
|
|
1654
1867
|
writing++;
|
|
1655
|
-
let
|
|
1868
|
+
let l;
|
|
1656
1869
|
try {
|
|
1657
1870
|
// No untrack: the writing flag already disables store-node linking
|
|
1658
1871
|
// (draft reads never self-track, proj R2), while EXTERNAL reads (signals
|
|
1659
1872
|
// inside a projection derive) must keep tracking — they are the derive's
|
|
1660
1873
|
// dependencies.
|
|
1661
|
-
|
|
1874
|
+
l = t(e);
|
|
1662
1875
|
} finally {
|
|
1663
1876
|
writing--;
|
|
1664
1877
|
writeScopes = i;
|
|
@@ -1670,17 +1883,18 @@ const traps = {
|
|
|
1670
1883
|
for (const t of e) notifyWrites(t);
|
|
1671
1884
|
}
|
|
1672
1885
|
}
|
|
1673
|
-
if (
|
|
1886
|
+
if (l !== undefined && l !== e && isWrappable(l)) {
|
|
1674
1887
|
// Returned replacement: on an optimistic family (outside authoritative
|
|
1675
1888
|
// writes) the replacement is itself an optimistic edit — diff it against
|
|
1676
1889
|
// the visible view as engine writes (reverts at settle). Otherwise it is
|
|
1677
1890
|
// an adoption of the incoming object (unowned).
|
|
1678
1891
|
if (r.fam?.opt && !projectionWriteActive && !getWriteOverride()) {
|
|
1679
|
-
optHooks.notifyOptimisticWrites(r, unwrapValue(
|
|
1892
|
+
optHooks.notifyOptimisticWrites(r, unwrapValue(l));
|
|
1680
1893
|
} else {
|
|
1681
|
-
adoptPB(r, unwrapValue(
|
|
1894
|
+
adoptPB(r, unwrapValue(l));
|
|
1682
1895
|
}
|
|
1683
1896
|
}
|
|
1897
|
+
if (writing === 0 && heldAdoptions.size) stageHeldAdoptions();
|
|
1684
1898
|
}
|
|
1685
1899
|
|
|
1686
1900
|
// Affects integration: the legacy affects machinery reads next targets
|
|
@@ -1732,6 +1946,79 @@ function createStoreNext(e, t = false) {
|
|
|
1732
1946
|
return t !== undefined && t.fam?.opt === true;
|
|
1733
1947
|
}
|
|
1734
1948
|
|
|
1949
|
+
// ---------------------------------------------------------------------------
|
|
1950
|
+
// visibility: what a reader sees on a record — ONE rule for the ownKeys /
|
|
1951
|
+
// getOwnPropertyDescriptor traps and the deep() walk (#3323). The walk used
|
|
1952
|
+
// to re-derive the trap rules over raw backings and missed each new one in
|
|
1953
|
+
// turn (the #3044 overlay merge, then #3323's chain, then optimistic presence
|
|
1954
|
+
// and value overrides); sharing the body makes parity structural.
|
|
1955
|
+
/** Keys visible on `target` served from `src` (= readSource(target)): the
|
|
1956
|
+
* pending-overlay merge (#3044) and, on optimistic families, presence-node
|
|
1957
|
+
* overrides (§6, FINDING-2's fix). Draft reads before the first write overlay
|
|
1958
|
+
* too (pb, once created, is seeded with the view). Authoritative-view reads
|
|
1959
|
+
* (until()'s predicate, truth-author drafts) skip the overlay. */ function visibleKeys(e, t) {
|
|
1960
|
+
let n;
|
|
1961
|
+
if (e.ovl && t === e.pb) {
|
|
1962
|
+
// Overlay merge (#3044): committed keys in their order, then this
|
|
1963
|
+
// batch's NEW keys, minus deletes.
|
|
1964
|
+
n = Reflect.ownKeys(e.v);
|
|
1965
|
+
const r = e.del;
|
|
1966
|
+
if (r !== null && r.size !== 0) n = n.filter(e => !r.has(e));
|
|
1967
|
+
for (const r of Reflect.ownKeys(t)) {
|
|
1968
|
+
if (!hasOwn.call(e.v, r)) n.push(r);
|
|
1969
|
+
}
|
|
1970
|
+
} else n = Reflect.ownKeys(t);
|
|
1971
|
+
// Drop the $OWNER stamp (owned backings carry it as an own enumerable
|
|
1972
|
+
// symbol). Symbols enumerate last, so the scan stops at the first string.
|
|
1973
|
+
for (let e = n.length - 1; e >= 0 && typeof n[e] === "symbol"; e--) {
|
|
1974
|
+
if (n[e] === $OWNER) {
|
|
1975
|
+
n.splice(e, 1);
|
|
1976
|
+
break;
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
if (!authoritativeServe() && e.fam?.opt && e.h !== null && (!inDraft(e) || draftSeesOverrides(e))) {
|
|
1980
|
+
let t = null;
|
|
1981
|
+
const r = inDraft(e);
|
|
1982
|
+
for (const i of Reflect.ownKeys(e.h)) {
|
|
1983
|
+
const l = e.h[i];
|
|
1984
|
+
if (!(r ? hasActiveOverride(l) : visibleOverride(l))) continue;
|
|
1985
|
+
t ??= new Set(n);
|
|
1986
|
+
if (unwrapOverride(l.o?.Ce)) t.add(i); else t.delete(i);
|
|
1987
|
+
}
|
|
1988
|
+
if (t !== null) return [ ...t ];
|
|
1989
|
+
}
|
|
1990
|
+
return n;
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1993
|
+
/** The data/accessor descriptor visible for `key` on `target` served from
|
|
1994
|
+
* `src`: overlay (#3044 — unwritten keys live on the committed backing,
|
|
1995
|
+
* deleted keys are absent) and optimistic presence overrides (an opt delete
|
|
1996
|
+
* hides the key; an opt add synthesizes a data descriptor from the value
|
|
1997
|
+
* node). `configurable` is the trap's concern (proxy invariant). */ function visibleDescriptor(e, t, n) {
|
|
1998
|
+
let r = Object.getOwnPropertyDescriptor(t, n);
|
|
1999
|
+
if (e.ovl && t === e.pb) {
|
|
2000
|
+
if (e.del !== null && e.del.has(n)) return undefined;
|
|
2001
|
+
if (r === undefined) r = Object.getOwnPropertyDescriptor(e.v, n);
|
|
2002
|
+
}
|
|
2003
|
+
if (!authoritativeServe() && e.fam?.opt && !inDraft(e)) {
|
|
2004
|
+
const t = e.h?.[n];
|
|
2005
|
+
if (t !== undefined && visibleOverride(t)) {
|
|
2006
|
+
if (!unwrapOverride(t.o?.Ce)) return undefined;
|
|
2007
|
+
// opt delete
|
|
2008
|
+
if (r === undefined) {
|
|
2009
|
+
const t = e.n?.[n];
|
|
2010
|
+
return {
|
|
2011
|
+
value: t !== undefined ? nodeValue(t, undefined) : undefined,
|
|
2012
|
+
writable: true,
|
|
2013
|
+
enumerable: true,
|
|
2014
|
+
configurable: true
|
|
2015
|
+
};
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
return r;
|
|
2020
|
+
}
|
|
2021
|
+
|
|
1735
2022
|
/** Tracking deep snapshot (`deep()` for next targets): subscribes to the
|
|
1736
2023
|
* key-set and deep-witness node at every reachable level, then returns the
|
|
1737
2024
|
* plain view. Shared references and cycles handled via the visited set. */ function deepNext(e) {
|
|
@@ -1743,50 +2030,69 @@ function createStoreNext(e, t = false) {
|
|
|
1743
2030
|
// in per-key nodes, and it walks TARGETS directly — no per-child proxy
|
|
1744
2031
|
// round-trip (wrapNext → proxy → $TARGET trap) on the re-walk every
|
|
1745
2032
|
// effect run performs.
|
|
1746
|
-
|
|
2033
|
+
// Resolve `child` (a raw or a stored proxy found under `t`) to the target
|
|
2034
|
+
// that `t`'s family serves for it — created on first visit, as the get trap
|
|
2035
|
+
// would. Undefined = leaf (unwrappable or raw-marked).
|
|
2036
|
+
const childTarget = (e, t, n) => {
|
|
2037
|
+
let r = lookupTarget(t, e.fam);
|
|
2038
|
+
if (r === undefined) {
|
|
2039
|
+
if (!isWrappable(t)) return undefined;
|
|
2040
|
+
wrapNext(t, e, n);
|
|
2041
|
+
// Stored proxies (chained slots) that this family passes through
|
|
2042
|
+
// resolve to their own target.
|
|
2043
|
+
r = lookupTarget(t, e.fam) ?? t[$TARGET];
|
|
2044
|
+
}
|
|
2045
|
+
return r;
|
|
2046
|
+
};
|
|
2047
|
+
const walkT = e => {
|
|
1747
2048
|
const t = readSource(e);
|
|
1748
2049
|
if (n.has(t)) return;
|
|
1749
2050
|
n.add(t);
|
|
1750
2051
|
read(getKeySetNode(e));
|
|
1751
2052
|
read(getDeepNode(e));
|
|
1752
|
-
|
|
1753
|
-
//
|
|
1754
|
-
//
|
|
1755
|
-
//
|
|
1756
|
-
//
|
|
1757
|
-
//
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
const n = e.del;
|
|
1763
|
-
const r = n !== null && n.size !== 0 ? t.filter(e => !n.has(e)) : t;
|
|
1764
|
-
for (const t of i) {
|
|
1765
|
-
if (!hasOwn.call(e.v, t)) r.push(t);
|
|
1766
|
-
}
|
|
1767
|
-
i = r;
|
|
2053
|
+
// Chained backing (§7b): the committed backing IS another store's proxy.
|
|
2054
|
+
// Writes to the inner store bump the INNER record's witnesses; this
|
|
2055
|
+
// target's k/dk hear only its own family's writes (optimistic overrides).
|
|
2056
|
+
// Read through the whole chain — the $TRACK trap's rule (#2864 / R21)
|
|
2057
|
+
// applied to deep() (#3323). From `t.v`, not `src`: a pending backing is a
|
|
2058
|
+
// raw clone and would hide the chain. Non-chained targets pay one flag.
|
|
2059
|
+
for (let t = e; t.ch; ) {
|
|
2060
|
+
t = t.v[$TARGET];
|
|
2061
|
+
read(getKeySetNode(t));
|
|
2062
|
+
read(getDeepNode(t));
|
|
1768
2063
|
}
|
|
1769
|
-
|
|
1770
|
-
|
|
2064
|
+
// Keys and children exactly as the traps serve them — the overlay merge
|
|
2065
|
+
// (#3044/#3283: a bare ownKeys over a pending backing mid-flush dropped
|
|
2066
|
+
// every untouched child from the re-subscribing effect's dependencies)
|
|
2067
|
+
// and optimistic presence overrides (a row added under a held action
|
|
2068
|
+
// lives in `h`/`n`, not the committed backing; the walk never reached its
|
|
2069
|
+
// record, so deep() was deaf to every write on it until settle).
|
|
2070
|
+
const r = e.fam?.opt === true;
|
|
2071
|
+
for (const n of visibleKeys(e, t)) {
|
|
2072
|
+
const i = visibleDescriptor(e, t, n);
|
|
1771
2073
|
if (i === undefined) continue;
|
|
1772
2074
|
if (i.get || i.set) {
|
|
1773
2075
|
e.a = true;
|
|
1774
2076
|
continue;
|
|
1775
2077
|
// accessors track through their own reads when invoked
|
|
1776
2078
|
}
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
//
|
|
1780
|
-
//
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
if (!
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
2079
|
+
let l = i.value;
|
|
2080
|
+
// An active value override on an optimistic node shadows the backing's
|
|
2081
|
+
// child (an optimistic replacement `d[i] = {...}`) — follow what reads
|
|
2082
|
+
// serve, as nodeValue's leading arm does.
|
|
2083
|
+
if (r) {
|
|
2084
|
+
const t = e.n?.[n];
|
|
2085
|
+
if (t !== undefined && hasActiveOverride(t) && !authoritativeServe()) l = unwrapOverride(t.o.Ce);
|
|
2086
|
+
}
|
|
2087
|
+
if (l === null || typeof l !== "object") continue;
|
|
2088
|
+
// Through a chain the descriptor yields the INNERMOST raw: resolve it to
|
|
2089
|
+
// the inner family's proxy so this level wraps the chained `view[i]` the
|
|
2090
|
+
// get trap serves, never a fresh non-chained wrapper of the base raw.
|
|
2091
|
+
if (e.ch && l[$TARGET] === undefined) l = resolveChainedRaw(e, n, l);
|
|
2092
|
+
const o = childTarget(e, l, n);
|
|
2093
|
+
if (o === undefined) continue;
|
|
1787
2094
|
// raw-marked: leaf by contract
|
|
1788
|
-
|
|
1789
|
-
walkT(f);
|
|
2095
|
+
walkT(o);
|
|
1790
2096
|
}
|
|
1791
2097
|
};
|
|
1792
2098
|
walkT(t);
|
|
@@ -1816,24 +2122,35 @@ function snapshotWalk(e, t, n) {
|
|
|
1816
2122
|
// encountered and compose their views over the resolved base, innermost
|
|
1817
2123
|
// outward.
|
|
1818
2124
|
let i = null;
|
|
1819
|
-
for (
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
if (
|
|
1823
|
-
if (
|
|
1824
|
-
if (
|
|
1825
|
-
|
|
2125
|
+
for (let e = true; ;e = false) {
|
|
2126
|
+
const t = r?.[$TARGET]?.v !== undefined;
|
|
2127
|
+
let l = t ? r[$TARGET] : undefined;
|
|
2128
|
+
if (l === undefined && n !== null) l = lookupTarget(r, n);
|
|
2129
|
+
if (l === undefined) l = lookupTarget(r, null);
|
|
2130
|
+
if (l === undefined) break;
|
|
2131
|
+
// Entering a level from a RAW below a chained family: the raw resolves to
|
|
2132
|
+
// the INNER store's target, but this family's wrapper for it — keyed by
|
|
2133
|
+
// the inner proxy, §7b — is where the outer overrides live. Start from
|
|
2134
|
+
// the wrapper so they compose (#3323: a nested optimistic write on a view
|
|
2135
|
+
// row was served by reads but missing from deep()/snapshot()). Entry only:
|
|
2136
|
+
// the descent then runs wrapper → inner → raw and terminates.
|
|
2137
|
+
if (e && !t && n !== null && l.fam !== n) {
|
|
2138
|
+
const e = n.map.get(l.px);
|
|
2139
|
+
if (e !== undefined) l = e;
|
|
2140
|
+
}
|
|
2141
|
+
if (l.fam !== null) n = l.fam;
|
|
2142
|
+
if (l.fam?.opt === true) (i ??= []).push(l);
|
|
1826
2143
|
// The shared visibility decision (#3147): the speculative peek serves
|
|
1827
2144
|
// pending staging, but a HELD landing is masked to committed exactly as
|
|
1828
2145
|
// it is for per-key readers — the two families must answer alike while
|
|
1829
2146
|
// a transaction holds store landings.
|
|
1830
|
-
const
|
|
2147
|
+
const o = pendingBackingVisible(l, true);
|
|
1831
2148
|
// Snapshot runs mid-flush (tracked memos execute before commit), so a
|
|
1832
2149
|
// pending prototype overlay must present as a REAL merged container.
|
|
1833
|
-
if (
|
|
1834
|
-
const
|
|
1835
|
-
if (
|
|
1836
|
-
r =
|
|
2150
|
+
if (o && l.ovl) materializePB(l);
|
|
2151
|
+
const f = o ? l.pb : l.v;
|
|
2152
|
+
if (f === r) break;
|
|
2153
|
+
r = f;
|
|
1837
2154
|
}
|
|
1838
2155
|
if (!isWrappable(r)) return r;
|
|
1839
2156
|
// Optimistic families: compose the visible view; a composed view is a fresh
|
|
@@ -1844,40 +2161,40 @@ function snapshotWalk(e, t, n) {
|
|
|
1844
2161
|
if (e !== r) {
|
|
1845
2162
|
const i = t.get(r);
|
|
1846
2163
|
if (i !== undefined) return i;
|
|
1847
|
-
const
|
|
1848
|
-
const
|
|
1849
|
-
t.set(r,
|
|
2164
|
+
const l = Array.isArray(e);
|
|
2165
|
+
const o = l ? [] : Object.create(Object.getPrototypeOf(e));
|
|
2166
|
+
t.set(r, o);
|
|
1850
2167
|
for (const r of Reflect.ownKeys(e)) {
|
|
1851
|
-
if (
|
|
2168
|
+
if (l && r === "length" || r === $OWNER) continue;
|
|
1852
2169
|
const i = e[r];
|
|
1853
|
-
|
|
2170
|
+
o[r] = i !== null && typeof i === "object" ? snapshotWalk(i, t, n) : i;
|
|
1854
2171
|
}
|
|
1855
|
-
if (
|
|
1856
|
-
return
|
|
2172
|
+
if (l) o.length = e.length;
|
|
2173
|
+
return o;
|
|
1857
2174
|
}
|
|
1858
2175
|
}
|
|
1859
|
-
const
|
|
1860
|
-
if (
|
|
2176
|
+
const l = t.get(r);
|
|
2177
|
+
if (l !== undefined) return l;
|
|
1861
2178
|
// OWNED (written) subtrees snapshot as copies (§7b: identity is only for
|
|
1862
2179
|
// subtrees "unmodified relative to source"): non-enumerable symbols are
|
|
1863
2180
|
// excluded (recon-snap R29), and the copy registers BEFORE descent so
|
|
1864
2181
|
// cycles keep identity (FINDING-3).
|
|
1865
|
-
if (
|
|
2182
|
+
if (isOwned(r)) {
|
|
1866
2183
|
const e = Array.isArray(r);
|
|
1867
2184
|
const i = e ? [] : Object.create(Object.getPrototypeOf(r));
|
|
1868
2185
|
t.set(r, i);
|
|
1869
|
-
for (const
|
|
1870
|
-
if (e &&
|
|
1871
|
-
const
|
|
1872
|
-
if (typeof
|
|
1873
|
-
if (
|
|
1874
|
-
Object.defineProperty(i,
|
|
2186
|
+
for (const l of Reflect.ownKeys(r)) {
|
|
2187
|
+
if (e && l === "length" || l === $OWNER) continue;
|
|
2188
|
+
const o = Object.getOwnPropertyDescriptor(r, l);
|
|
2189
|
+
if (typeof l === "symbol" && !o.enumerable) continue;
|
|
2190
|
+
if (o.get || o.set) {
|
|
2191
|
+
Object.defineProperty(i, l, o);
|
|
1875
2192
|
continue;
|
|
1876
2193
|
}
|
|
1877
|
-
const
|
|
1878
|
-
const s =
|
|
1879
|
-
if (
|
|
1880
|
-
...
|
|
2194
|
+
const f = o.value;
|
|
2195
|
+
const s = f !== null && typeof f === "object" ? snapshotWalk(f, t, n) : f;
|
|
2196
|
+
if (o.enumerable && o.writable && o.configurable) i[l] = s; else Object.defineProperty(i, l, {
|
|
2197
|
+
...o,
|
|
1881
2198
|
value: s
|
|
1882
2199
|
});
|
|
1883
2200
|
}
|
|
@@ -1887,22 +2204,22 @@ function snapshotWalk(e, t, n) {
|
|
|
1887
2204
|
// UNOWNED (shared/user) subtrees keep identity unless a descendant
|
|
1888
2205
|
// substituted; copy-on-substitution preserves the documented CoW contract.
|
|
1889
2206
|
t.set(r, r);
|
|
1890
|
-
let
|
|
2207
|
+
let o = null;
|
|
1891
2208
|
for (const e of Reflect.ownKeys(r)) {
|
|
1892
2209
|
const i = Object.getOwnPropertyDescriptor(r, e);
|
|
1893
2210
|
if (!i || i.get || i.set) continue;
|
|
1894
|
-
const
|
|
1895
|
-
if (
|
|
1896
|
-
const
|
|
1897
|
-
if (
|
|
1898
|
-
if (
|
|
1899
|
-
|
|
1900
|
-
t.set(r,
|
|
2211
|
+
const l = i.value;
|
|
2212
|
+
if (l === null || typeof l !== "object") continue;
|
|
2213
|
+
const f = snapshotWalk(l, t, n);
|
|
2214
|
+
if (f !== l) {
|
|
2215
|
+
if (o === null) {
|
|
2216
|
+
o = Array.isArray(r) ? [ ...r ] : Object.create(Object.getPrototypeOf(r), Object.getOwnPropertyDescriptors(r));
|
|
2217
|
+
t.set(r, o);
|
|
1901
2218
|
}
|
|
1902
|
-
|
|
2219
|
+
o[e] = f;
|
|
1903
2220
|
}
|
|
1904
2221
|
}
|
|
1905
|
-
return
|
|
2222
|
+
return o ?? r;
|
|
1906
2223
|
}
|
|
1907
2224
|
|
|
1908
|
-
export { adoptPB, arrayStructureChanged, authoritativeRead, authoritativeServe, bumpDeep, createStoreNext, deepNext, getHasNode, getKeySetNode, getNode, hasAccessorFlag, hasActiveOverride, materializePB, membershipChanged, notifyFold, notifyFoldTail, notifyKeyDiff, notifyKeyValue, runAuthoritative, snapshotNext, stagedTruthPB, storeHasFamily, storeHasOptimisticFamily, storeIsShallow, storeSetterNext, targetsEqual, unwrapValue, wrapNext };
|
|
2225
|
+
export { adoptPB, arrayStructureChanged, authoritativeRead, authoritativeServe, bumpDeep, createStoreNext, deepNext, getHasNode, getKeySetNode, getNode, hasAccessorFlag, hasActiveOverride, heldMaskView, materializePB, membershipChanged, notifyFold, notifyFoldTail, notifyKeyDiff, notifyKeyValue, runAuthoritative, snapshotNext, stagedTruthPB, storeHasFamily, storeHasOptimisticFamily, storeIsShallow, storeSetterNext, targetsEqual, unwrapValue, visibleOverride, wrapNext };
|