@solidjs/signals 2.0.0-rc.7 → 2.0.0-rc.8
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 +5243 -0
- package/dist/dev.attribution.js +2333 -0
- package/dist/dev.js +1229 -6834
- package/dist/observe/affects.js +127 -0
- package/dist/observe/attribution.js +1 -0
- package/dist/observe/boundaries.js +586 -0
- package/dist/observe/core/action.js +167 -0
- package/dist/observe/core/async.js +736 -0
- package/dist/observe/core/attribution-hooks.js +60 -0
- package/dist/observe/core/attribution.js +2318 -0
- package/dist/{types-cjs/core/constants.d.cts → observe/core/constants.js} +129 -73
- package/dist/{types-cjs/core/context.d.cts → observe/core/context.js} +37 -12
- package/dist/observe/core/core.js +1289 -0
- package/dist/observe/core/dev.js +232 -0
- package/dist/observe/core/effect.js +179 -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 +43 -0
- package/dist/observe/core/lanes.js +168 -0
- package/dist/observe/core/optimistic.js +438 -0
- package/dist/observe/core/owner.js +308 -0
- package/dist/observe/core/scheduler.js +1190 -0
- package/dist/observe/core/verdict.js +551 -0
- package/dist/observe/index.js +55 -0
- package/dist/observe/map.js +482 -0
- package/dist/{types-cjs/signals.d.cts → observe/signals.js} +295 -317
- package/dist/observe/store/index.js +38 -0
- package/dist/observe/store/next/optimistic.js +651 -0
- package/dist/observe/store/next/projection.js +242 -0
- package/dist/observe/store/next/reconcile.js +373 -0
- package/dist/observe/store/next/store.js +2213 -0
- package/dist/observe/store/next/target.js +51 -0
- package/dist/observe/store/store.js +323 -0
- package/dist/observe/store/storePath.js +103 -0
- package/dist/observe/store/utils.js +210 -0
- package/dist/prod/attribution.js +32 -0
- package/dist/prod/core/action.js +52 -32
- package/dist/prod/core/async.js +171 -127
- package/dist/prod/core/constants.js +24 -1
- package/dist/prod/core/context.js +3 -3
- package/dist/prod/core/core.js +434 -291
- package/dist/prod/core/effect.js +54 -34
- package/dist/prod/core/external.js +2 -2
- package/dist/prod/core/graph.js +32 -32
- package/dist/prod/core/heap.js +49 -44
- package/dist/prod/core/lanes.js +45 -38
- package/dist/prod/core/optimistic.js +207 -73
- package/dist/prod/core/owner.js +43 -39
- package/dist/prod/core/scheduler.js +319 -190
- package/dist/prod/core/verdict.js +67 -67
- package/dist/prod/index.js +9 -3
- package/dist/prod/map.js +292 -141
- package/dist/prod/signals.js +7 -10
- package/dist/prod/store/next/optimistic.js +140 -125
- package/dist/prod/store/next/projection.js +3 -3
- package/dist/prod/store/next/reconcile.js +87 -84
- package/dist/prod/store/next/store.js +524 -280
- package/dist/prod/store/next/target.js +32 -10
- package/dist/prod/store/store.js +12 -14
- package/dist/prod/store/utils.js +36 -27
- package/dist/types/attribution.d.ts +15 -0
- package/dist/types/attribution.prod.d.ts +13 -0
- package/dist/types/core/async.d.ts +1 -1
- package/dist/types/core/attribution-hooks.d.ts +119 -6
- package/dist/types/core/attribution.d.ts +208 -53
- package/dist/types/core/constants.d.ts +23 -0
- package/dist/types/core/core.d.ts +7 -0
- package/dist/types/core/dev.d.ts +124 -26
- package/dist/types/core/effect.d.ts +8 -1
- package/dist/types/core/index.d.ts +2 -1
- package/dist/types/core/lanes.d.ts +8 -1
- package/dist/types/core/scheduler.d.ts +60 -0
- package/dist/types/core/types.d.ts +29 -9
- package/dist/types/index.d.ts +9 -2
- package/dist/types/map.d.ts +2 -0
- package/dist/types/store/index.d.ts +1 -0
- package/dist/types/store/next/store.d.ts +5 -0
- package/dist/types/store/next/target.d.ts +36 -9
- package/dist/types/store/utils.d.ts +7 -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/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, NOT_PENDING, CONFIG_OWNED_WRITE, CONFIG_OPTIMISTIC,
|
|
1
|
+
import { unwrapOverride, $REFRESH, NOT_PENDING, CONFIG_OWNED_WRITE, CONFIG_OPTIMISTIC, STATUS_UNINITIALIZED, STATUS_ERROR, CONFIG_HELD_TRUTH, CONFIG_CHILDREN_FORBIDDEN, 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, pendingCheckActive, latestReadActive, readNodeFast, READ_SLOW, signal, unlinkFirewallChild, ext, setLatestReadActive, prepareComputed, slotSignal, context } from "../../core/core.js";
|
|
4
4
|
|
|
5
5
|
import { projectionWriteActive, setProjectionWriteActive, activeTransition, schedule, currentTransition, setStoreCommitHook } from "../../core/scheduler.js";
|
|
6
6
|
|
|
7
|
+
import "../../core/invariants.js";
|
|
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,7 +119,7 @@ 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
124
|
const o = e[$TARGET];
|
|
121
125
|
if (o !== undefined && o.px === e) {
|
|
@@ -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.Ut, e, t);
|
|
153
157
|
};
|
|
154
158
|
|
|
155
159
|
// Shared slot-node unobserved handler (create-floor diet): registered once;
|
|
@@ -158,11 +162,15 @@ 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
|
-
const t = e.
|
|
162
|
-
const n = e.
|
|
165
|
+
const t = e.Ut;
|
|
166
|
+
const n = e.xt;
|
|
163
167
|
if (t.n && t.n[n] === e) {
|
|
164
168
|
delete t.n[n];
|
|
165
169
|
t.nc--;
|
|
170
|
+
// Projection leaves also leave the firewall child chain (#3351): a
|
|
171
|
+
// dropped node is unreachable through the store — a fresh read makes a
|
|
172
|
+
// fresh node — so the chain would only retain it and its last value.
|
|
173
|
+
unlinkFirewallChild(e);
|
|
166
174
|
}
|
|
167
175
|
});
|
|
168
176
|
|
|
@@ -175,6 +183,19 @@ r = -1) {
|
|
|
175
183
|
const i = e.n ??= Object.create(null);
|
|
176
184
|
let o = i[t];
|
|
177
185
|
if (o === undefined) {
|
|
186
|
+
// Born holding (#3330 store twin): a key first read while a live
|
|
187
|
+
// transaction holds an ADOPTION on this target (adoptPB's `ht`) is
|
|
188
|
+
// created as if it had existed when the hold was notified — the adopted
|
|
189
|
+
// value already swapped into `v`, so committed is the held view
|
|
190
|
+
// `hv[key]` and the adopted `v[key]` is staged under the transaction
|
|
191
|
+
// (stageHeldKey). Its held-adoption notification (stageHeldAdoptions)
|
|
192
|
+
// ran before the node existed and the drain has nothing left to say.
|
|
193
|
+
// Without this the node was born from whichever view its first reader
|
|
194
|
+
// saw and never learned the other. (The held-FOLD twin — a setter's
|
|
195
|
+
// write to an unobserved key landing only in the pending backing — is
|
|
196
|
+
// #3336's, and lands with #3337.)
|
|
197
|
+
const l = heldAdoptionTransition(e);
|
|
198
|
+
if (l !== null) n = e.hv[t];
|
|
178
199
|
// Create-floor diet: slotSignal bakes the whole node into one literal —
|
|
179
200
|
// no options object, no equals/unobserved closures, no NodeExtension,
|
|
180
201
|
// no post-construction expandos (acc + the wrap cache px/pxv are
|
|
@@ -185,7 +206,7 @@ r = -1) {
|
|
|
185
206
|
// notification machinery. Projection nodes carry the projection
|
|
186
207
|
// computed as their firewall: reads through them link the derive's
|
|
187
208
|
// status/lifecycle (§7b).
|
|
188
|
-
|
|
209
|
+
const f = o = slotSignal(n, slotNodeEquals, e, t,
|
|
189
210
|
// Accessor-ness resolved ONCE per node (no per-object descriptor
|
|
190
211
|
// scan on reads): accessor keys serve through Reflect.get with the
|
|
191
212
|
// proxy receiver.
|
|
@@ -193,12 +214,13 @@ r = -1) {
|
|
|
193
214
|
// Optimistic families: arm the override slot — setSignal routes armed
|
|
194
215
|
// nodes through the core engine (lanes, ownership, reverts all native).
|
|
195
216
|
if (e.fam?.opt) {
|
|
196
|
-
ext(f).
|
|
217
|
+
ext(f).be = NOT_PENDING;
|
|
197
218
|
f.T |= CONFIG_OPTIMISTIC;
|
|
198
219
|
}
|
|
199
220
|
// A node born inside a live mark's identity scope inherits the mark
|
|
200
221
|
// (the declaration walk could only cover nodes existing then).
|
|
201
222
|
if (t !== $AFFECTS && affectsScopesLive()) inheritAffectsMarks(f, e.v, t);
|
|
223
|
+
if (l !== null) stageHeldKey(f, e.v[t], l);
|
|
202
224
|
i[t] = o;
|
|
203
225
|
e.nc++;
|
|
204
226
|
markDescendants(e);
|
|
@@ -206,11 +228,37 @@ r = -1) {
|
|
|
206
228
|
return o;
|
|
207
229
|
}
|
|
208
230
|
|
|
231
|
+
/** The live transaction holding an adoption on `target` (adoptPB's `ht`;
|
|
232
|
+
* a latest()-pull PLAIN_HOLD is not a transaction), else null. */ function heldAdoptionTransition(e) {
|
|
233
|
+
if (e.ht === null || e.ht === PLAIN_HOLD || heldMaskView(e) === null) return null;
|
|
234
|
+
const t = currentTransition(e.ht);
|
|
235
|
+
return t.ft === false ? t : null;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Materialization under a hold. A node created by a first tracked read while
|
|
240
|
+
* a transaction holds this target must carry the same state a node that
|
|
241
|
+
* existed when the hold was notified carries — a transaction-stamped
|
|
242
|
+
* `_pendingValue` that core read() serves by ITS rules (a stale render reader
|
|
243
|
+
* of a foreign transaction's write sees committed, an owner-context reader
|
|
244
|
+
* inside the transaction sees the write) — or the reader's answer depends on
|
|
245
|
+
* whether some OTHER reader had materialized the key before the hold. Stage
|
|
246
|
+
* `nv` (the held value for the key — see getNode for which view it comes
|
|
247
|
+
* from) as the holding transaction's write — directly, not through
|
|
248
|
+
* setSignal: it is not a new write (it staged with the adoption's batch) and
|
|
249
|
+
* it walks no subscriber (the node has none yet). Transition-stamped now, as
|
|
250
|
+
* `runFolded` does — no parked-flush pass will stamp it.
|
|
251
|
+
*/ function stageHeldKey(e, t, n) {
|
|
252
|
+
if (slotNodeEquals.call(e, e.me, t)) return;
|
|
253
|
+
e.Ge = t;
|
|
254
|
+
e.ge = n;
|
|
255
|
+
n.$t.push(e);
|
|
256
|
+
}
|
|
257
|
+
|
|
209
258
|
function sameLogicalSlot(e, t, n) {
|
|
210
259
|
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);
|
|
260
|
+
const r = lookupTarget(t, e.fam);
|
|
261
|
+
return r !== undefined && r === lookupTarget(n, e.fam);
|
|
214
262
|
}
|
|
215
263
|
|
|
216
264
|
function getHasNode(e, t, n) {
|
|
@@ -221,12 +269,15 @@ function getHasNode(e, t, n) {
|
|
|
221
269
|
equals: isEqual,
|
|
222
270
|
unobserved() {
|
|
223
271
|
if (o.o?.t) return;
|
|
224
|
-
if (e.h && e.h[t] === o)
|
|
272
|
+
if (e.h && e.h[t] === o) {
|
|
273
|
+
delete e.h[t];
|
|
274
|
+
unlinkFirewallChild(o);
|
|
275
|
+
}
|
|
225
276
|
}
|
|
226
277
|
}, e.fam?.node ?? undefined);
|
|
227
278
|
o.T |= CONFIG_OWNED_WRITE;
|
|
228
279
|
if (e.fam?.opt) {
|
|
229
|
-
ext(o).
|
|
280
|
+
ext(o).be = NOT_PENDING;
|
|
230
281
|
o.T |= CONFIG_OPTIMISTIC;
|
|
231
282
|
}
|
|
232
283
|
if (affectsScopesLive()) inheritAffectsMarks(o, e.v, t);
|
|
@@ -242,12 +293,15 @@ function getKeySetNode(e) {
|
|
|
242
293
|
const n = t = signal(0, {
|
|
243
294
|
equals: false,
|
|
244
295
|
unobserved() {
|
|
245
|
-
if (e.k === n)
|
|
296
|
+
if (e.k === n) {
|
|
297
|
+
e.k = null;
|
|
298
|
+
unlinkFirewallChild(n);
|
|
299
|
+
}
|
|
246
300
|
}
|
|
247
301
|
}, e.fam?.node ?? undefined);
|
|
248
302
|
n.T |= CONFIG_OWNED_WRITE;
|
|
249
303
|
if (e.fam?.opt) {
|
|
250
|
-
ext(n).
|
|
304
|
+
ext(n).be = NOT_PENDING;
|
|
251
305
|
n.T |= CONFIG_OPTIMISTIC;
|
|
252
306
|
}
|
|
253
307
|
e.k = t;
|
|
@@ -262,12 +316,15 @@ function getDeepNode(e) {
|
|
|
262
316
|
const n = t = signal(0, {
|
|
263
317
|
equals: false,
|
|
264
318
|
unobserved() {
|
|
265
|
-
if (e.dk === n)
|
|
319
|
+
if (e.dk === n) {
|
|
320
|
+
e.dk = null;
|
|
321
|
+
unlinkFirewallChild(n);
|
|
322
|
+
}
|
|
266
323
|
}
|
|
267
324
|
}, e.fam?.node ?? undefined);
|
|
268
325
|
n.T |= CONFIG_OWNED_WRITE;
|
|
269
326
|
if (e.fam?.opt) {
|
|
270
|
-
ext(n).
|
|
327
|
+
ext(n).be = NOT_PENDING;
|
|
271
328
|
n.T |= CONFIG_OPTIMISTIC;
|
|
272
329
|
}
|
|
273
330
|
if (affectsScopesLive()) inheritAffectsMarks(n, e.v, $TRACK);
|
|
@@ -288,21 +345,36 @@ function getDeepNode(e) {
|
|
|
288
345
|
|
|
289
346
|
let hookInstalled = false;
|
|
290
347
|
|
|
291
|
-
|
|
348
|
+
/** Shallow-clone `source` as a backing OWNED by `t` (stamped `$OWNER`, see
|
|
349
|
+
* target.ts). Callers always pass their own committed backing as `source`. */ function cloneRaw(e, t) {
|
|
350
|
+
// Plain-data fast path (#3360): a scanned `Object.prototype` container
|
|
351
|
+
// whose own keys are all enumerable data clones by spread — the same
|
|
352
|
+
// result as the descriptor walk below (own enumerable string+symbol keys,
|
|
353
|
+
// normalized writable+configurable), at ~1/50th the cost.
|
|
354
|
+
t.sc || scanAccessorsOnce(t);
|
|
355
|
+
if (t.sc === 2) {
|
|
356
|
+
const n = {
|
|
357
|
+
...e
|
|
358
|
+
};
|
|
359
|
+
n[$OWNER] = t;
|
|
360
|
+
return n;
|
|
361
|
+
}
|
|
292
362
|
// Descriptor-preserving shallow clone (R29: installed getters stay live;
|
|
293
363
|
// ruled 2026-08-17: frozen sources clone unfrozen — theirs stays frozen).
|
|
294
364
|
// Data descriptors normalize to writable+configurable (the clone is OURS to
|
|
295
365
|
// mutate — R51's "source-non-configurable is writable through the store");
|
|
296
366
|
// enumerability and accessors are preserved. The scan doubles as the
|
|
297
367
|
// accessor-flag detector (free — we're enumerating descriptors anyway).
|
|
298
|
-
|
|
368
|
+
const n = Object.getOwnPropertyDescriptors(e);
|
|
299
369
|
for (const r of Reflect.ownKeys(n)) {
|
|
300
370
|
const i = n[r];
|
|
301
371
|
if (r === "length" && Array.isArray(e)) continue;
|
|
302
372
|
i.configurable = true;
|
|
303
|
-
if (!i.get && !i.set) i.writable = true; else
|
|
373
|
+
if (!i.get && !i.set) i.writable = true; else t.a = true;
|
|
304
374
|
}
|
|
305
|
-
|
|
375
|
+
const r = Array.isArray(e) ? Object.defineProperties([], n) : Object.create(Object.getPrototypeOf(e), n);
|
|
376
|
+
r[$OWNER] = t;
|
|
377
|
+
return r;
|
|
306
378
|
}
|
|
307
379
|
|
|
308
380
|
/** Copy own `key` from `from` onto `to`. A plain data slot (enumerable,
|
|
@@ -314,19 +386,33 @@ function cloneRaw(e, t) {
|
|
|
314
386
|
}
|
|
315
387
|
|
|
316
388
|
/** One-time own-accessor scan (Annex-B probes, no descriptor allocation);
|
|
317
|
-
* returns true when the container is plain data (overlay-safe).
|
|
389
|
+
* returns true when the container is plain data (overlay-safe). Also grades
|
|
390
|
+
* the container for the plain-data fast paths (`sc` = 2): `Object.prototype`
|
|
391
|
+
* and every own key an enumerable data property — what a spread copies
|
|
392
|
+
* exactly and what a bare assignment lands exactly. */ function scanAccessorsOnce(e) {
|
|
318
393
|
const t = e.v;
|
|
319
|
-
|
|
394
|
+
const n = Reflect.ownKeys(t);
|
|
395
|
+
let r = Object.getPrototypeOf(t) === Object.prototype;
|
|
396
|
+
for (const i of n) {
|
|
320
397
|
// Own keys shadow prototype accessors, so the lookups are exact here.
|
|
321
|
-
if (lookupGetter.call(t,
|
|
398
|
+
if (lookupGetter.call(t, i) !== undefined || lookupSetter.call(t, i) !== undefined) {
|
|
322
399
|
e.a = true;
|
|
400
|
+
r = false;
|
|
323
401
|
break;
|
|
324
402
|
}
|
|
403
|
+
if (r && !propertyIsEnumerable.call(t, i)) r = false;
|
|
325
404
|
}
|
|
326
|
-
e.sc =
|
|
405
|
+
e.sc = r ? 2 : 1;
|
|
406
|
+
e.kc = n.length;
|
|
327
407
|
return !e.a;
|
|
328
408
|
}
|
|
329
409
|
|
|
410
|
+
/** Own-key count above which a draft opens as a prototype overlay rather
|
|
411
|
+
* than a clone (#3360). Below it a spread clone of a plain container is
|
|
412
|
+
* cheaper than the overlay's `Object.create` (V8 converts the committed
|
|
413
|
+
* backing into a prototype, and every later flatten writes into that
|
|
414
|
+
* prototype); above it the clone's O(keys) copy dominates (#3044). */ const OVERLAY_MIN_KEYS = 32;
|
|
415
|
+
|
|
330
416
|
/** Downgrade a prototype-overlay pending backing to the clone path: builds
|
|
331
417
|
* the real container (committed + overlay writes − deletes) that fold will
|
|
332
418
|
* SWAP in as the committed backing, exactly as if the draft had started on
|
|
@@ -340,10 +426,6 @@ function cloneRaw(e, t) {
|
|
|
340
426
|
for (const t of e.del) delete n[t];
|
|
341
427
|
e.del = null;
|
|
342
428
|
}
|
|
343
|
-
const r = e.fam?.map ?? storeNextLookup;
|
|
344
|
-
r.delete(t);
|
|
345
|
-
ownedRaw.add(n);
|
|
346
|
-
r.set(n, e);
|
|
347
429
|
e.pb = n;
|
|
348
430
|
e.ovl = false;
|
|
349
431
|
}
|
|
@@ -366,15 +448,32 @@ function ensurePB(e) {
|
|
|
366
448
|
}
|
|
367
449
|
if (activeTransition !== null) foldBatches.set(e, activeTransition);
|
|
368
450
|
if (t === null) {
|
|
369
|
-
// Prototype-chain overlay (#3044): plain-data non-array containers
|
|
370
|
-
//
|
|
371
|
-
//
|
|
372
|
-
//
|
|
373
|
-
//
|
|
374
|
-
|
|
375
|
-
|
|
451
|
+
// Prototype-chain overlay (#3044): plain-data non-array containers open
|
|
452
|
+
// drafts in O(1) — own keys are the writes, reads fall through to
|
|
453
|
+
// committed. Projection and derived-store families take it too (#3352:
|
|
454
|
+
// a derive touching one root key of a wide keyed record paid an
|
|
455
|
+
// O(keys) clone per recompute). Everything else keeps the descriptor
|
|
456
|
+
// clone: arrays (splice/length semantics), OPTIMISTIC families (the
|
|
457
|
+
// view-seeding below writes and deletes on the draft container, and the
|
|
458
|
+
// tentative discard/truth-park hand whole backings around), chained
|
|
459
|
+
// backings (the committed layer is another store's proxy — an overlay
|
|
460
|
+
// would route every read-through into its traps), accessor containers
|
|
461
|
+
// (live getters).
|
|
462
|
+
// The overlay pays off only for a WIDE container over an OWNED committed
|
|
463
|
+
// backing (#3360). Narrow containers clone cheaper than they overlay (a
|
|
464
|
+
// spread is a fast-path copy; `Object.create` turns the backing into a
|
|
465
|
+
// V8 prototype and every later flatten writes into that prototype). An
|
|
466
|
+
// unowned backing has to be privatized (cloned) at commit anyway, so an
|
|
467
|
+
// overlay there would cost the create PLUS the clone PLUS a per-key
|
|
468
|
+
// copy — the clone path does the one clone and swaps it in. The first
|
|
469
|
+
// write on a fresh store is exactly that case.
|
|
470
|
+
const n = e.v;
|
|
471
|
+
if (!e.fam?.opt && !e.ch && !Array.isArray(n) && (e.sc !== 0 ? !e.a : scanAccessorsOnce(e)) && e.kc > OVERLAY_MIN_KEYS && isOwned(n)) {
|
|
472
|
+
// Inherits `v`'s $OWNER stamp — the overlay resolves and reads as
|
|
473
|
+
// owned without a registration of its own.
|
|
474
|
+
t = e.pb = Object.create(n);
|
|
376
475
|
e.ovl = true;
|
|
377
|
-
} else t = e.pb = cloneRaw(
|
|
476
|
+
} else t = e.pb = cloneRaw(n, e);
|
|
378
477
|
// Optimistic families: seed USER drafts from the OPTIMISTIC VIEW
|
|
379
478
|
// (committed + active node overrides), so follow-up writes compose on
|
|
380
479
|
// optimism instead of clobbering from base (#2951's compose half).
|
|
@@ -387,19 +486,17 @@ function ensurePB(e) {
|
|
|
387
486
|
if (n !== null) {
|
|
388
487
|
for (const e of Reflect.ownKeys(n)) {
|
|
389
488
|
const r = n[e];
|
|
390
|
-
if (hasActiveOverride(r)) t[e] = unwrapOverride(r.o?.
|
|
489
|
+
if (hasActiveOverride(r)) t[e] = unwrapOverride(r.o?.be);
|
|
391
490
|
}
|
|
392
491
|
}
|
|
393
492
|
const r = e.h;
|
|
394
493
|
if (r !== null) {
|
|
395
494
|
for (const e of Reflect.ownKeys(r)) {
|
|
396
495
|
const n = r[e];
|
|
397
|
-
if (hasActiveOverride(n) && !unwrapOverride(n.o?.
|
|
496
|
+
if (hasActiveOverride(n) && !unwrapOverride(n.o?.be)) delete t[e];
|
|
398
497
|
}
|
|
399
498
|
}
|
|
400
499
|
}
|
|
401
|
-
ownedRaw.add(t);
|
|
402
|
-
(e.fam?.map ?? storeNextLookup).set(t, e);
|
|
403
500
|
queueFold(e);
|
|
404
501
|
}
|
|
405
502
|
return t;
|
|
@@ -420,7 +517,7 @@ function ensurePB(e) {
|
|
|
420
517
|
* foldHeld's node stamps). */ function heldMaskView(e) {
|
|
421
518
|
const t = e.ht;
|
|
422
519
|
if (t === null) return null;
|
|
423
|
-
if (t !== PLAIN_HOLD && currentTransition(t)?.
|
|
520
|
+
if (t !== PLAIN_HOLD && currentTransition(t)?.ft === true) return e.ht = e.hv = null;
|
|
424
521
|
return e.hv;
|
|
425
522
|
}
|
|
426
523
|
|
|
@@ -450,20 +547,26 @@ function ensurePB(e) {
|
|
|
450
547
|
if (e.ovl) materializePB(e);
|
|
451
548
|
e.ab = e.pb;
|
|
452
549
|
} else e.ab ??= foldOlds.get(e);
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
550
|
+
}
|
|
551
|
+
// #3074/#3075: a projection recompute deriving from uncommitted inputs
|
|
552
|
+
// swaps the backing SPECULATIVELY — committed-visibility readers must
|
|
553
|
+
// keep the pre-hold view until the hold resolves (a source held by a
|
|
554
|
+
// live transition, or a latest()-pull ahead of the flush). Post-await
|
|
555
|
+
// landings (write-override) stay immediately visible — landed truth —
|
|
556
|
+
// and clear any hold. Optimistic families hold too (#3330 store twin):
|
|
557
|
+
// their tentative edits ride the lane machinery, but a sync derive
|
|
558
|
+
// adopting under a transaction is held TRUTH like any projection's —
|
|
559
|
+
// unheld, handlers read it early and an optimistic write equal to it
|
|
560
|
+
// compared as a no-op against the swapped-in backing. A plain store's
|
|
561
|
+
// reconcile inside an action holds the same way: its nodes stage under the
|
|
562
|
+
// transaction (the inline notify), and the backing must not show handlers
|
|
563
|
+
// and stale readers what the tracked read masks (signal parity, #3336).
|
|
564
|
+
if (getWriteOverride()) {
|
|
565
|
+
e.ht = e.hv = null;
|
|
566
|
+
} else if (activeTransition !== null || !n && latestPullActive) {
|
|
567
|
+
if (heldMaskView(e) === null) e.hv = e.v;
|
|
568
|
+
e.ht = activeTransition ?? PLAIN_HOLD;
|
|
569
|
+
if (!n && activeTransition !== null) heldAdoptions.add(e);
|
|
467
570
|
}
|
|
468
571
|
e.pb = null;
|
|
469
572
|
// Overlay and accessor-scan state describe the OUTGOING backing — a
|
|
@@ -475,13 +578,17 @@ function ensurePB(e) {
|
|
|
475
578
|
// draft rescans once (#3044 audit follow-up).
|
|
476
579
|
e.ovl = false;
|
|
477
580
|
e.del = null;
|
|
478
|
-
e.sc =
|
|
581
|
+
e.sc = 0;
|
|
479
582
|
e.a = false;
|
|
480
583
|
e.wk = null;
|
|
481
584
|
// adoption supersedes staged trap writes
|
|
482
585
|
e.v = t;
|
|
483
586
|
e.ch = t[$TARGET] !== undefined;
|
|
484
|
-
|
|
587
|
+
// An adoptee we own within this family (a draft aliasing one of the
|
|
588
|
+
// family's own backings) re-stamps to its new owner — the stamp is the
|
|
589
|
+
// family's registration for it; everything else registers in the map.
|
|
590
|
+
const r = t[$OWNER];
|
|
591
|
+
if (r !== undefined && r.fam === e.fam) t[$OWNER] = e; else (e.fam?.map ?? storeNextLookup).set(t, e);
|
|
485
592
|
}
|
|
486
593
|
|
|
487
594
|
/** Sentinel for `t.wk`: the written-keys bound is unusable this batch (an
|
|
@@ -543,22 +650,22 @@ function queueFold(e) {
|
|
|
543
650
|
}
|
|
544
651
|
|
|
545
652
|
/** Committed-time privatization for parent-chain slot updates (path copying). */ function privatizeCommitted(e) {
|
|
546
|
-
if (
|
|
653
|
+
if (isOwned(e.v)) return;
|
|
547
654
|
const t = e.v;
|
|
548
655
|
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
656
|
e.v = n;
|
|
557
657
|
e.ch = false;
|
|
558
658
|
if (e.u) {
|
|
559
659
|
privatizeCommitted(e.u);
|
|
560
660
|
devAssertNeverUserMutation(e.u.v);
|
|
561
|
-
|
|
661
|
+
// CAS, same as drainFolds' path copy: re-point the parent slot only
|
|
662
|
+
// while it still holds the raw we cloned. A parent that folded EARLIER
|
|
663
|
+
// in this drain may have replaced or deleted this slot (the same batch
|
|
664
|
+
// wrote the child and then `parent.row = fresh` / `parent.length = 0`)
|
|
665
|
+
// — an unconditional write resurrected the dropped child over it.
|
|
666
|
+
const n = e.u.v;
|
|
667
|
+
const r = parentSlotKey(e, t);
|
|
668
|
+
if (n[r] === t) n[r] = e.v;
|
|
562
669
|
}
|
|
563
670
|
}
|
|
564
671
|
|
|
@@ -577,6 +684,28 @@ function queueFold(e) {
|
|
|
577
684
|
return i;
|
|
578
685
|
}
|
|
579
686
|
|
|
687
|
+
/** Overlay commit (#3044): apply the batch's writes onto an OWNED committed
|
|
688
|
+
* backing in place — O(written), not O(container). Unowned backings
|
|
689
|
+
* privatize first (clone once, parents re-slotted) — the never-mutate-user-
|
|
690
|
+
* data contract holds. Shared by the deferred fold (drainFolds) and the
|
|
691
|
+
* projection landing's immediate commit (notifyWrites). */ function flattenOverlay(e, t) {
|
|
692
|
+
privatizeCommitted(e);
|
|
693
|
+
const n = e.v;
|
|
694
|
+
// Plain-data grade (sc 2): every own key on the overlay is an enumerable
|
|
695
|
+
// writable data slot (set-trap writes; a non-plain defineProperty
|
|
696
|
+
// downgrades the grade) — a bare assignment lands it without the
|
|
697
|
+
// descriptor round trip.
|
|
698
|
+
for (const r of Reflect.ownKeys(t)) e.sc === 2 ? n[r] = t[r] : copyOwn(n, t, r);
|
|
699
|
+
if (e.del !== null) {
|
|
700
|
+
for (const t of e.del) delete n[t];
|
|
701
|
+
e.del = null;
|
|
702
|
+
}
|
|
703
|
+
e.pb = null;
|
|
704
|
+
e.ovl = false;
|
|
705
|
+
e.wk = null;
|
|
706
|
+
// written-keys window closes with the commit
|
|
707
|
+
}
|
|
708
|
+
|
|
580
709
|
function drainFolds() {
|
|
581
710
|
if (foldOlds.size === 0) return;
|
|
582
711
|
const e = [ ...foldOlds ];
|
|
@@ -592,7 +721,7 @@ function drainFolds() {
|
|
|
592
721
|
// keys; observed keys also hit the pending-node held check below).
|
|
593
722
|
const e = foldBatches.get(t);
|
|
594
723
|
if (e !== undefined) {
|
|
595
|
-
if (currentTransition(e).
|
|
724
|
+
if (currentTransition(e).ft === false) {
|
|
596
725
|
foldOlds.set(t, n);
|
|
597
726
|
continue;
|
|
598
727
|
}
|
|
@@ -616,7 +745,7 @@ function drainFolds() {
|
|
|
616
745
|
!plainProto(t.ovl ? t.v : i) ? Reflect.ownKeys(o) : e;
|
|
617
746
|
for (const e of n) {
|
|
618
747
|
const t = o[e];
|
|
619
|
-
if (t !== undefined && t.
|
|
748
|
+
if (t !== undefined && t.Ge !== NOT_PENDING) {
|
|
620
749
|
r = true;
|
|
621
750
|
break;
|
|
622
751
|
}
|
|
@@ -628,26 +757,12 @@ function drainFolds() {
|
|
|
628
757
|
continue;
|
|
629
758
|
}
|
|
630
759
|
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) {
|
|
760
|
+
// Overlay flatten (#3044): the backing keeps its identity, so the
|
|
761
|
+
// `t.v === old` gate below skips path copying (the parent slot
|
|
762
|
+
// already points here) and the adopted-notify (setter notifications
|
|
763
|
+
// happened at write time).
|
|
764
|
+
flattenOverlay(t, i);
|
|
765
|
+
} else if (t.v !== n) {
|
|
651
766
|
// Privatized mid-batch (#3271): an earlier fold in this drain
|
|
652
767
|
// path-copied THROUGH this target — privatizeCommitted cloned the
|
|
653
768
|
// committed backing, re-pointed the parent slot at the clone, and
|
|
@@ -682,7 +797,6 @@ function drainFolds() {
|
|
|
682
797
|
if (!hasOwn.call(i, t)) delete e[t];
|
|
683
798
|
}
|
|
684
799
|
}
|
|
685
|
-
(t.fam?.map ?? storeNextLookup).delete(i);
|
|
686
800
|
t.pb = null;
|
|
687
801
|
t.wk = null;
|
|
688
802
|
// written-keys window closes with the fold commit
|
|
@@ -798,15 +912,15 @@ function drainFolds() {
|
|
|
798
912
|
// projection recompute can run before the prior fold commits) — the
|
|
799
913
|
// node's OWN current value is the true old side, and setSignal's
|
|
800
914
|
// internal equality already checks exactly that.
|
|
801
|
-
const
|
|
802
|
-
setSignal(i, () =>
|
|
915
|
+
const l = e.del !== null && e.del.has(o) ? undefined : t[o];
|
|
916
|
+
setSignal(i, () => l);
|
|
803
917
|
}
|
|
804
918
|
}
|
|
805
|
-
const
|
|
806
|
-
if (
|
|
807
|
-
const n = o ?? Reflect.ownKeys(
|
|
919
|
+
const l = e.h;
|
|
920
|
+
if (l !== null) {
|
|
921
|
+
const n = o ?? Reflect.ownKeys(l);
|
|
808
922
|
for (const r of n) {
|
|
809
|
-
const n =
|
|
923
|
+
const n = l[r];
|
|
810
924
|
if (n !== undefined) setSignal(n, r in t && !(e.del !== null && e.del.has(r)));
|
|
811
925
|
}
|
|
812
926
|
}
|
|
@@ -814,6 +928,7 @@ function drainFolds() {
|
|
|
814
928
|
// keys with no node. O(written/pb keys) equality only when a witness exists.
|
|
815
929
|
if (e.dk !== null) {
|
|
816
930
|
if (e.del !== null && e.del.size !== 0) bumpDeep(e); else for (const r of o ?? Reflect.ownKeys(t)) {
|
|
931
|
+
if (r === $OWNER) continue;
|
|
817
932
|
const i = t[r];
|
|
818
933
|
const o = n[r];
|
|
819
934
|
if (i !== null && typeof i === "object" ? !targetsEqual(o, i) : !isEqual(o, i)) {
|
|
@@ -860,6 +975,10 @@ function drainFolds() {
|
|
|
860
975
|
// Landed truth (post-await write-override): immediately visible to every
|
|
861
976
|
// reader — any staged held view is superseded.
|
|
862
977
|
if (e.ht !== null) e.ht = e.hv = null;
|
|
978
|
+
// Overlay landing (#3352): flatten in place — identity-stable, and
|
|
979
|
+
// privatizeCommitted re-slots the parent itself when it has to clone an
|
|
980
|
+
// unowned seed, so no path copy is needed.
|
|
981
|
+
if (e.ovl) return flattenOverlay(e, t);
|
|
863
982
|
const n = e.v;
|
|
864
983
|
e.pb = null;
|
|
865
984
|
e.v = t;
|
|
@@ -880,9 +999,9 @@ const FORCE = Symbol();
|
|
|
880
999
|
|
|
881
1000
|
/** Same logical slot: both values resolve to one (re-pointed) child target —
|
|
882
1001
|
* 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 ===
|
|
1002
|
+
if (e === null || typeof e !== "object" || t === null || typeof t !== "object") return false;
|
|
1003
|
+
const n = lookupTarget(e, null);
|
|
1004
|
+
return n !== undefined && n === lookupTarget(t, null);
|
|
886
1005
|
}
|
|
887
1006
|
|
|
888
1007
|
function arrayStructureChanged(e, t) {
|
|
@@ -897,8 +1016,9 @@ function arrayStructureChanged(e, t) {
|
|
|
897
1016
|
|
|
898
1017
|
function membershipChanged(e, t) {
|
|
899
1018
|
const n = Reflect.ownKeys(t);
|
|
900
|
-
|
|
901
|
-
|
|
1019
|
+
// The $OWNER stamp is not membership: an owned side counts one key more.
|
|
1020
|
+
if (Reflect.ownKeys(e).length - +isOwned(e) !== n.length - +isOwned(t)) return true;
|
|
1021
|
+
for (const t of n) if (t !== $OWNER && !(t in e)) return true;
|
|
902
1022
|
return false;
|
|
903
1023
|
}
|
|
904
1024
|
|
|
@@ -928,9 +1048,9 @@ i = true) {
|
|
|
928
1048
|
if (i?.get !== o?.get || i?.set !== o?.set || i?.value !== o?.value) setSignal(e, () => FORCE);
|
|
929
1049
|
return;
|
|
930
1050
|
}
|
|
931
|
-
const
|
|
932
|
-
const
|
|
933
|
-
if (!isEqual(
|
|
1051
|
+
const l = i?.value;
|
|
1052
|
+
const f = o?.value;
|
|
1053
|
+
if (!isEqual(l, f) && !targetsEqual(l, f)) setSignal(e, typeof f === "function" ? () => f : f);
|
|
934
1054
|
} else {
|
|
935
1055
|
const i = n[t];
|
|
936
1056
|
const o = r[t];
|
|
@@ -1046,6 +1166,34 @@ function inDraft(e) {
|
|
|
1046
1166
|
|
|
1047
1167
|
/** Targets written during the current (outermost) setter — notified at exit. */ const pendingNotify = new Set;
|
|
1048
1168
|
|
|
1169
|
+
/** Targets adopted under a live transaction this setter (adoptPB set a
|
|
1170
|
+
* transaction hold) — their nodes are notified at the outermost exit. */ const heldAdoptions = new Set;
|
|
1171
|
+
|
|
1172
|
+
/**
|
|
1173
|
+
* Write-time notification for a transaction-held adoption (#3330 store twin;
|
|
1174
|
+
* the setter path's notifyWrites twin for adoptions). A projection's fold
|
|
1175
|
+
* normally notifies its nodes at the drain — and the drain of a batch parked
|
|
1176
|
+
* in a live transaction is the transaction's COMMIT, so the nodes took the
|
|
1177
|
+
* adopted values as fresh writes at commit time: every subscriber was
|
|
1178
|
+
* re-marked and re-ran against a frame the lane had already published (a
|
|
1179
|
+
* third `v=1 d=2`), where a signal's write had staged at write time and
|
|
1180
|
+
* promoted silently. Staging here, inside the transaction's batch, makes the
|
|
1181
|
+
* two paths one: the nodes carry transition-stamped `_pendingValue`s, their
|
|
1182
|
+
* subscribers recompute in this flush and park with the transaction, and the
|
|
1183
|
+
* commit promotes without re-notifying. `ab` moves to the adopted backing —
|
|
1184
|
+
* the view the nodes were last told (#3296) — so the drain has nothing left
|
|
1185
|
+
* to say and only path-copies.
|
|
1186
|
+
*/ function stageHeldAdoptions() {
|
|
1187
|
+
const e = [ ...heldAdoptions ];
|
|
1188
|
+
heldAdoptions.clear();
|
|
1189
|
+
for (const t of e) {
|
|
1190
|
+
const e = t.ab;
|
|
1191
|
+
if (e === null || e === t.v) continue;
|
|
1192
|
+
notifyFold(t, e, t.v);
|
|
1193
|
+
t.ab = t.v;
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1049
1197
|
const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
|
|
1050
1198
|
|
|
1051
1199
|
/** Mirror of core read()'s context rule: the OWNER context (not the tracking
|
|
@@ -1055,7 +1203,7 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
|
|
|
1055
1203
|
* / onSettled callbacks) get COMMITTED visibility (#3006), same as core. */ function inOwnerContext() {
|
|
1056
1204
|
const e = getOwner();
|
|
1057
1205
|
if (e === null) return false;
|
|
1058
|
-
const t = e.
|
|
1206
|
+
const t = e.gt ? e.bt : e;
|
|
1059
1207
|
return t != null && !(t.T & CONFIG_CHILDREN_FORBIDDEN);
|
|
1060
1208
|
}
|
|
1061
1209
|
|
|
@@ -1066,7 +1214,7 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
|
|
|
1066
1214
|
* committed to them regardless of staged writes. */ function inForbiddenScope() {
|
|
1067
1215
|
const e = getOwner();
|
|
1068
1216
|
if (e === null) return false;
|
|
1069
|
-
const t = e.
|
|
1217
|
+
const t = e.gt ? e.bt : e;
|
|
1070
1218
|
return t != null && !!(t.T & CONFIG_CHILDREN_FORBIDDEN);
|
|
1071
1219
|
}
|
|
1072
1220
|
|
|
@@ -1077,7 +1225,7 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
|
|
|
1077
1225
|
if (t === null) return false;
|
|
1078
1226
|
for (const e of Reflect.ownKeys(t)) {
|
|
1079
1227
|
const n = t[e];
|
|
1080
|
-
if (n.
|
|
1228
|
+
if (n.Ge !== NOT_PENDING && n.ge != null && n.ge.ft !== true) return true;
|
|
1081
1229
|
}
|
|
1082
1230
|
return false;
|
|
1083
1231
|
}
|
|
@@ -1149,6 +1297,8 @@ const lookupGetter = Object.prototype.__lookupGetter__;
|
|
|
1149
1297
|
|
|
1150
1298
|
const lookupSetter = Object.prototype.__lookupSetter__;
|
|
1151
1299
|
|
|
1300
|
+
const propertyIsEnumerable = Object.prototype.propertyIsEnumerable;
|
|
1301
|
+
|
|
1152
1302
|
function isOwnAccessor(e, t) {
|
|
1153
1303
|
return hasOwn.call(e, t) && (lookupGetter.call(e, t) !== undefined || lookupSetter.call(e, t) !== undefined);
|
|
1154
1304
|
}
|
|
@@ -1168,7 +1318,7 @@ function isOwnAccessor(e, t) {
|
|
|
1168
1318
|
|
|
1169
1319
|
/** Active optimistic override on an armed node (armed slot idles at
|
|
1170
1320
|
* NOT_PENDING; undefined = unarmed plain node). */ function hasActiveOverride(e) {
|
|
1171
|
-
return e.o?.
|
|
1321
|
+
return e.o?.be !== undefined && e.o?.be !== NOT_PENDING;
|
|
1172
1322
|
}
|
|
1173
1323
|
|
|
1174
1324
|
/** The reading computation is until()'s authoritative-view predicate — same
|
|
@@ -1203,30 +1353,53 @@ function isOwnAccessor(e, t) {
|
|
|
1203
1353
|
// Authoritative-view reads (until()'s predicate) skip the override arm
|
|
1204
1354
|
// only: staged pending values are authoritative, overrides are the
|
|
1205
1355
|
// caller's optimism.
|
|
1206
|
-
const n = !authoritativeServe() && hasActiveOverride(e) ? unwrapOverride(e.o?.
|
|
1356
|
+
const n = !authoritativeServe() && hasActiveOverride(e) ? unwrapOverride(e.o?.be) : e.Ge !== NOT_PENDING && (latestReadActive ||
|
|
1207
1357
|
// Owner-context pending visibility — except HELD truth (#3164,
|
|
1208
1358
|
// see CONFIG_HELD_TRUTH: fold-staged or entangle-stolen
|
|
1209
1359
|
// confirming truth), which only authoritative/latest readers
|
|
1210
1360
|
// see (core read()'s A17-for-held-truth twin; ordinary readers
|
|
1211
1361
|
// keep committed until the transaction's reveal — latest() is
|
|
1212
1362
|
// exempted by the leading arm above).
|
|
1213
|
-
(inOwnerContext() || authoritativeServe()) && !(e.T & CONFIG_HELD_TRUTH && !authoritativeServe())) ? e.
|
|
1363
|
+
(inOwnerContext() || authoritativeServe()) && !(e.T & CONFIG_HELD_TRUTH && !authoritativeServe())) ? e.Ge : t;
|
|
1214
1364
|
return n === FORCE ? t : n;
|
|
1215
1365
|
}
|
|
1216
1366
|
|
|
1367
|
+
/** §7b: a chained target's child found as a RAW — from its pending backing
|
|
1368
|
+
* (a cloneRaw of the inner proxy, whose descriptors yield the inner store's
|
|
1369
|
+
* raws) or from the deep() walk's descriptor read through the chain — must
|
|
1370
|
+
* resolve to the inner family's proxy for that object before this family
|
|
1371
|
+
* wraps it, so the overlay serves the same chained targets as the settled
|
|
1372
|
+
* state. Otherwise every row re-wrapped as a fresh non-chained target keyed
|
|
1373
|
+
* by the raw: identities churned for the life of an optimistic action and
|
|
1374
|
+
* snapped back at settle, and writes landed on those orphans while deep()'s
|
|
1375
|
+
* witnesses sat on the chained targets (#3323). The inner family owns the
|
|
1376
|
+
* raw iff it has served it or its backing holds that exact object at `key`;
|
|
1377
|
+
* anything else is a draft's own replacement object — view-owned, correctly
|
|
1378
|
+
* non-chained — and is returned as is. Recurses down further chains. */ function resolveChainedRaw(e, t, n) {
|
|
1379
|
+
const r = e.v[$TARGET];
|
|
1380
|
+
if (r.ch) {
|
|
1381
|
+
const e = resolveChainedRaw(r, t, n);
|
|
1382
|
+
return e === n ? n : wrapNext(e, r, t);
|
|
1383
|
+
}
|
|
1384
|
+
const i = lookupTarget(n, r.fam);
|
|
1385
|
+
if (i !== undefined) return i.px;
|
|
1386
|
+
if ((r.v[t] === n || r.pb?.[t] === n) && isWrappable(n)) return wrapNext(n, r, t);
|
|
1387
|
+
return n;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1217
1390
|
/** Serve an own data key: node-first when a node exists (pending visibility,
|
|
1218
1391
|
* holds, lanes ride the node); backing otherwise. Chained backings (§7b: the
|
|
1219
1392
|
* backing IS another store's proxy) serve the read-through value — the outer
|
|
1220
1393
|
* node is linked only for adoption-swap notification, its value never
|
|
1221
1394
|
* shadows the live chain. */ function serveDataKey(e, t, n, r, i, o = -1) {
|
|
1222
|
-
const
|
|
1223
|
-
let
|
|
1395
|
+
const l = e.ch && r === e.v;
|
|
1396
|
+
let f = n;
|
|
1224
1397
|
// §6: on optimistic arrays LENGTH IS A VIEW, not a node value — one home
|
|
1225
1398
|
// (backing ± presence overrides) for both length and indices makes torn
|
|
1226
1399
|
// iteration impossible (a length node's value rides different visibility
|
|
1227
1400
|
// rails than index overrides mid-settle). The node still carries
|
|
1228
1401
|
// subscriptions; its value is never served here.
|
|
1229
|
-
if (t === "length" && e.fam?.opt === true && !
|
|
1402
|
+
if (t === "length" && e.fam?.opt === true && !l && Array.isArray(r)) {
|
|
1230
1403
|
if (!inDraft(e)) {
|
|
1231
1404
|
const r = e.n?.length;
|
|
1232
1405
|
if (r !== undefined) {
|
|
@@ -1248,7 +1421,7 @@ function isOwnAccessor(e, t) {
|
|
|
1248
1421
|
// seeding rule, applied to the read side (#3108).
|
|
1249
1422
|
if (e.fam?.opt && draftSeesOverrides(e) && !authoritativeServe()) {
|
|
1250
1423
|
const n = e.n?.[t];
|
|
1251
|
-
if (n !== undefined && hasActiveOverride(n))
|
|
1424
|
+
if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.o?.be);
|
|
1252
1425
|
}
|
|
1253
1426
|
} else {
|
|
1254
1427
|
if (i !== undefined) {
|
|
@@ -1260,9 +1433,9 @@ function isOwnAccessor(e, t) {
|
|
|
1260
1433
|
// parity): READ_SLOW = a global read window or non-plain node.
|
|
1261
1434
|
let e = readNodeFast(i);
|
|
1262
1435
|
if (e === READ_SLOW) e = read(i);
|
|
1263
|
-
if (!
|
|
1264
|
-
} else if (!
|
|
1265
|
-
|
|
1436
|
+
if (!l || hasActiveOverride(i)) f = e === FORCE ? n : e;
|
|
1437
|
+
} else if (!l || hasActiveOverride(i)) {
|
|
1438
|
+
f = nodeValue(i, n);
|
|
1266
1439
|
}
|
|
1267
1440
|
} else if (getObserver() !== null) {
|
|
1268
1441
|
// First tracked read: create + link, and let the wrap-cache branch
|
|
@@ -1271,19 +1444,20 @@ function isOwnAccessor(e, t) {
|
|
|
1271
1444
|
}
|
|
1272
1445
|
}
|
|
1273
1446
|
// Shallow stores serve data raw; store-proxy slots get boundary wrappers.
|
|
1274
|
-
if (e.s) return serveShallow(e, t,
|
|
1447
|
+
if (e.s) return serveShallow(e, t, f);
|
|
1448
|
+
if (e.ch && !l && f !== null && typeof f === "object" && f[$TARGET] === undefined) f = resolveChainedRaw(e, t, f);
|
|
1275
1449
|
if (i !== undefined) {
|
|
1276
1450
|
// Wrap cache (see getNode): only wrappables are ever cached, so a hit
|
|
1277
1451
|
// skips isWrappable too — pointer-compare replaces both checks.
|
|
1278
|
-
if (i.pxv ===
|
|
1279
|
-
if (!isWrappable(
|
|
1280
|
-
const n = wrapNext(
|
|
1452
|
+
if (i.pxv === f && f !== undefined) return draftServe(e, i.px);
|
|
1453
|
+
if (!isWrappable(f)) return f;
|
|
1454
|
+
const n = wrapNext(f, e, t);
|
|
1281
1455
|
i.px = n;
|
|
1282
|
-
i.pxv =
|
|
1456
|
+
i.pxv = f;
|
|
1283
1457
|
return draftServe(e, n);
|
|
1284
1458
|
}
|
|
1285
|
-
if (!isWrappable(
|
|
1286
|
-
return draftServe(e, wrapNext(
|
|
1459
|
+
if (!isWrappable(f)) return f;
|
|
1460
|
+
return draftServe(e, wrapNext(f, e, t));
|
|
1287
1461
|
}
|
|
1288
1462
|
|
|
1289
1463
|
/** §6c store-wide status gate for reads that DON'T flow through a node:
|
|
@@ -1334,6 +1508,8 @@ const traps = {
|
|
|
1334
1508
|
if (typeof t !== "string") {
|
|
1335
1509
|
if (t === $TARGET) return e;
|
|
1336
1510
|
if (t === $PROXY) return n;
|
|
1511
|
+
if (t === $OWNER) return undefined;
|
|
1512
|
+
// ownership stamp: never a user key
|
|
1337
1513
|
// refresh()/isPending resolve the projection computed through $REFRESH.
|
|
1338
1514
|
if (t === $REFRESH) return e.fam?.node ?? undefined;
|
|
1339
1515
|
if (t === $TRACK) {
|
|
@@ -1407,16 +1583,16 @@ const traps = {
|
|
|
1407
1583
|
// scan (pb ?? v).
|
|
1408
1584
|
let o = -1;
|
|
1409
1585
|
{
|
|
1410
|
-
let
|
|
1411
|
-
if (i !== undefined)
|
|
1412
|
-
|
|
1413
|
-
if (r === (e.pb ?? e.v)) o =
|
|
1414
|
-
} else
|
|
1415
|
-
if (
|
|
1586
|
+
let l;
|
|
1587
|
+
if (i !== undefined) l = i.acc === true; else if (!inDraft(e) && getObserver() !== null) {
|
|
1588
|
+
l = isOwnAccessor(r, t);
|
|
1589
|
+
if (r === (e.pb ?? e.v)) o = l ? 1 : 0;
|
|
1590
|
+
} else l = false;
|
|
1591
|
+
if (l) {
|
|
1416
1592
|
if (!inDraft(e) && getObserver() !== null) read(i ?? getNode(e, t, undefined, o));
|
|
1417
|
-
const
|
|
1418
|
-
if (e.s) return serveShallow(e, t,
|
|
1419
|
-
return isWrappable(
|
|
1593
|
+
const l = Reflect.get(r, t, n);
|
|
1594
|
+
if (e.s) return serveShallow(e, t, l);
|
|
1595
|
+
return isWrappable(l) ? draftServe(e, wrapNext(l, e, t)) : l;
|
|
1420
1596
|
}
|
|
1421
1597
|
}
|
|
1422
1598
|
// Plain-data fast path: no descriptor allocation per read.
|
|
@@ -1426,17 +1602,17 @@ const traps = {
|
|
|
1426
1602
|
// backings chain to the committed backing, so "own in the view" means
|
|
1427
1603
|
// own on either layer (ownInView) — a genuine prototype method is one
|
|
1428
1604
|
// that is own on NEITHER.
|
|
1429
|
-
const
|
|
1430
|
-
if ((t === "constructor" || t === "__proto__" || t === "prototype") && !hasOwn.call(r, t) && !(
|
|
1431
|
-
let
|
|
1432
|
-
if (
|
|
1605
|
+
const l = e.ovl && r === e.pb;
|
|
1606
|
+
if ((t === "constructor" || t === "__proto__" || t === "prototype") && !hasOwn.call(r, t) && !(l && hasOwn.call(e.v, t))) return undefined;
|
|
1607
|
+
let f = r[t];
|
|
1608
|
+
if (f === undefined ? !hasOwn.call(r, t) && !(l && hasOwn.call(e.v, t)) : false) {
|
|
1433
1609
|
// Inherited: prototype getters/methods run with the proxy receiver.
|
|
1434
|
-
|
|
1435
|
-
if (typeof
|
|
1610
|
+
f = Reflect.get(r, t, n);
|
|
1611
|
+
if (typeof f === "function") return f;
|
|
1436
1612
|
// proto methods untracked
|
|
1437
1613
|
// Reading a currently-absent own key subscribes to it (R12) — for any
|
|
1438
1614
|
// target OUTSIDE its own draft scope, even mid-setter (#3037, above).
|
|
1439
|
-
if (
|
|
1615
|
+
if (f === undefined && !inDraft(e)) {
|
|
1440
1616
|
if (getObserver() !== null) read(getNode(e, t, undefined, o));
|
|
1441
1617
|
const n = e.n?.[t];
|
|
1442
1618
|
if (n) {
|
|
@@ -1444,22 +1620,23 @@ const traps = {
|
|
|
1444
1620
|
if (e.s) return serveShallow(e, t, r);
|
|
1445
1621
|
return isWrappable(r) ? draftServe(e, wrapNext(r, e, t)) : r;
|
|
1446
1622
|
}
|
|
1447
|
-
} else if (
|
|
1623
|
+
} else if (f === undefined && inDraft(e) && e.fam?.opt && draftSeesOverrides(e) &&
|
|
1448
1624
|
// AUTHORITATIVE drafts (landing folds) never seed from overrides —
|
|
1449
1625
|
// the caller's optimism is not truth (has-trap twin below).
|
|
1450
1626
|
!authoritativeServe()) {
|
|
1451
1627
|
const n = e.n?.[t];
|
|
1452
|
-
if (n !== undefined && hasActiveOverride(n))
|
|
1628
|
+
if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.o?.be);
|
|
1453
1629
|
}
|
|
1454
|
-
if (e.s) return serveShallow(e, t,
|
|
1455
|
-
return isWrappable(
|
|
1630
|
+
if (e.s) return serveShallow(e, t, f);
|
|
1631
|
+
return isWrappable(f) ? draftServe(e, wrapNext(f, e, t)) : f;
|
|
1456
1632
|
}
|
|
1457
|
-
if (typeof
|
|
1633
|
+
if (typeof f === "function" && !hasOwn.call(r, t) && !(l && hasOwn.call(e.v, t))) return f;
|
|
1458
1634
|
// proto method
|
|
1459
|
-
return serveDataKey(e, t,
|
|
1635
|
+
return serveDataKey(e, t, f, r, i, o);
|
|
1460
1636
|
},
|
|
1461
1637
|
has(e, t) {
|
|
1462
1638
|
if (t === $TARGET || t === $PROXY || t === $TRACK) return true;
|
|
1639
|
+
if (t === $OWNER) return false;
|
|
1463
1640
|
if (pendingCheckActive) witnessAffectsMark(e, t);
|
|
1464
1641
|
if (e.fam !== null && getObserver() === null && !inDraft(e)) firewallGate(e);
|
|
1465
1642
|
const n = readSource(e);
|
|
@@ -1475,11 +1652,11 @@ const traps = {
|
|
|
1475
1652
|
if (hasActiveOverride(n)) r = !!i;
|
|
1476
1653
|
} else if (!authoritativeServe()) {
|
|
1477
1654
|
const n = e.h?.[t];
|
|
1478
|
-
if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.
|
|
1655
|
+
if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.be);
|
|
1479
1656
|
}
|
|
1480
1657
|
} else if (e.fam?.opt && draftSeesOverrides(e) && !authoritativeServe()) {
|
|
1481
1658
|
const n = e.h?.[t];
|
|
1482
|
-
if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.
|
|
1659
|
+
if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.be);
|
|
1483
1660
|
}
|
|
1484
1661
|
return r;
|
|
1485
1662
|
},
|
|
@@ -1487,66 +1664,17 @@ const traps = {
|
|
|
1487
1664
|
if (pendingCheckActive) witnessAffectsMark(e);
|
|
1488
1665
|
if (e.fam !== null && getObserver() === null && !inDraft(e)) firewallGate(e);
|
|
1489
1666
|
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;
|
|
1667
|
+
return visibleKeys(e, readSource(e));
|
|
1518
1668
|
},
|
|
1519
1669
|
getOwnPropertyDescriptor(e, t) {
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
// deleted keys are absent from the pending view.
|
|
1524
|
-
if (e.ovl && n === e.pb) {
|
|
1525
|
-
if (e.del !== null && e.del.has(t)) return undefined;
|
|
1526
|
-
if (r === undefined) r = Object.getOwnPropertyDescriptor(e.v, t);
|
|
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
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
if (r === undefined) return undefined;
|
|
1670
|
+
if (t === $OWNER) return undefined;
|
|
1671
|
+
const n = visibleDescriptor(e, readSource(e), t);
|
|
1672
|
+
if (n === undefined) return undefined;
|
|
1545
1673
|
// Array targets carry a real non-configurable `length` the proxy
|
|
1546
1674
|
// invariant forces us to report faithfully; everything else reports
|
|
1547
1675
|
// configurable via target indirection (core R51).
|
|
1548
|
-
if (!(t === "length" && Array.isArray(e)))
|
|
1549
|
-
return
|
|
1676
|
+
if (!(t === "length" && Array.isArray(e))) n.configurable = true;
|
|
1677
|
+
return n;
|
|
1550
1678
|
},
|
|
1551
1679
|
set(e, t, n) {
|
|
1552
1680
|
// Writes require the target's draft scope OR the projection write
|
|
@@ -1564,22 +1692,29 @@ const traps = {
|
|
|
1564
1692
|
// passes through by reference (#2932; markRawOne skips proxies), while
|
|
1565
1693
|
// deep stores unwrap to raw backings.
|
|
1566
1694
|
const o = e.s ? n : unwrapValue(n);
|
|
1567
|
-
const
|
|
1695
|
+
const l = ensurePB(e);
|
|
1568
1696
|
pendingNotify.add(e);
|
|
1569
1697
|
// Array length writes implicitly delete indices — the written-keys bound
|
|
1570
1698
|
// can't see them, so poison to the full scan for this batch. Index
|
|
1571
1699
|
// writes implicitly GROW length, so arrays always record it alongside.
|
|
1572
|
-
if (Array.isArray(
|
|
1700
|
+
if (Array.isArray(l)) {
|
|
1573
1701
|
if (t === "length") e.wk = WK_ALL; else if (e.wk !== WK_ALL) {
|
|
1574
1702
|
const n = e.wk ??= new Set;
|
|
1575
1703
|
n.add(t);
|
|
1576
1704
|
n.add("length");
|
|
1577
1705
|
}
|
|
1578
|
-
} else
|
|
1706
|
+
} else {
|
|
1707
|
+
if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
|
|
1708
|
+
// Live own-key estimate for the overlay/clone choice (#3360): `in`
|
|
1709
|
+
// sees through an overlay to the committed keys, so this counts keys
|
|
1710
|
+
// NEW to the container. Deletes are not un-counted (a stale high
|
|
1711
|
+
// count only picks the overlay a little early).
|
|
1712
|
+
if (!(t in l)) e.kc++;
|
|
1713
|
+
}
|
|
1579
1714
|
// Own data keys literally named "prototype"/"constructor" land as data —
|
|
1580
1715
|
// defineProperty sidesteps a proto-chain setter named the same.
|
|
1581
1716
|
if (UNSAFE_KEYS.has(t)) {
|
|
1582
|
-
Object.defineProperty(
|
|
1717
|
+
Object.defineProperty(l, t, {
|
|
1583
1718
|
value: o,
|
|
1584
1719
|
writable: true,
|
|
1585
1720
|
enumerable: true,
|
|
@@ -1590,15 +1725,17 @@ const traps = {
|
|
|
1590
1725
|
}
|
|
1591
1726
|
// Overlay first-write DEFINES the own key: assignment through the proto
|
|
1592
1727
|
// chain would reject on a non-writable committed property (the clone
|
|
1593
|
-
// path normalized descriptors for exactly this — R51 parity).
|
|
1594
|
-
|
|
1595
|
-
|
|
1728
|
+
// path normalized descriptors for exactly this — R51 parity). A
|
|
1729
|
+
// plain-data-graded backing (sc 2, owned: every slot writable) takes the
|
|
1730
|
+
// bare assignment — it lands as an own key on the overlay all the same.
|
|
1731
|
+
if (e.ovl && e.sc !== 2 && !hasOwn.call(l, t)) {
|
|
1732
|
+
Object.defineProperty(l, t, {
|
|
1596
1733
|
value: o,
|
|
1597
1734
|
writable: true,
|
|
1598
1735
|
enumerable: true,
|
|
1599
1736
|
configurable: true
|
|
1600
1737
|
});
|
|
1601
|
-
} else
|
|
1738
|
+
} else l[t] = o;
|
|
1602
1739
|
if (e.del !== null) e.del.delete(t);
|
|
1603
1740
|
// Shallow ingest: written records are sticky raw-marked (one entity is
|
|
1604
1741
|
// never both deep-wrapped and raw — R41/#2932, shared invariant).
|
|
@@ -1620,6 +1757,10 @@ const traps = {
|
|
|
1620
1757
|
value: unwrapValue(n.value)
|
|
1621
1758
|
};
|
|
1622
1759
|
const o = ensurePB(e);
|
|
1760
|
+
// A non-default data descriptor (or an accessor) leaves the plain-data
|
|
1761
|
+
// grade: the key reaches the committed backing as defined, so the spread
|
|
1762
|
+
// clone and bare-assignment paths no longer describe it.
|
|
1763
|
+
if (e.a || !(n.enumerable && n.writable && n.configurable)) e.sc = 1;
|
|
1623
1764
|
pendingNotify.add(e);
|
|
1624
1765
|
if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
|
|
1625
1766
|
Object.defineProperty(o, t, n);
|
|
@@ -1681,6 +1822,7 @@ const traps = {
|
|
|
1681
1822
|
adoptPB(r, unwrapValue(o));
|
|
1682
1823
|
}
|
|
1683
1824
|
}
|
|
1825
|
+
if (writing === 0 && heldAdoptions.size) stageHeldAdoptions();
|
|
1684
1826
|
}
|
|
1685
1827
|
|
|
1686
1828
|
// Affects integration: the legacy affects machinery reads next targets
|
|
@@ -1732,6 +1874,78 @@ function createStoreNext(e, t = false) {
|
|
|
1732
1874
|
return t !== undefined && t.fam?.opt === true;
|
|
1733
1875
|
}
|
|
1734
1876
|
|
|
1877
|
+
// ---------------------------------------------------------------------------
|
|
1878
|
+
// visibility: what a reader sees on a record — ONE rule for the ownKeys /
|
|
1879
|
+
// getOwnPropertyDescriptor traps and the deep() walk (#3323). The walk used
|
|
1880
|
+
// to re-derive the trap rules over raw backings and missed each new one in
|
|
1881
|
+
// turn (the #3044 overlay merge, then #3323's chain, then optimistic presence
|
|
1882
|
+
// and value overrides); sharing the body makes parity structural.
|
|
1883
|
+
/** Keys visible on `target` served from `src` (= readSource(target)): the
|
|
1884
|
+
* pending-overlay merge (#3044) and, on optimistic families, presence-node
|
|
1885
|
+
* overrides (§6, FINDING-2's fix). Draft reads before the first write overlay
|
|
1886
|
+
* too (pb, once created, is seeded with the view). Authoritative-view reads
|
|
1887
|
+
* (until()'s predicate, truth-author drafts) skip the overlay. */ function visibleKeys(e, t) {
|
|
1888
|
+
let n;
|
|
1889
|
+
if (e.ovl && t === e.pb) {
|
|
1890
|
+
// Overlay merge (#3044): committed keys in their order, then this
|
|
1891
|
+
// batch's NEW keys, minus deletes.
|
|
1892
|
+
n = Reflect.ownKeys(e.v);
|
|
1893
|
+
const r = e.del;
|
|
1894
|
+
if (r !== null && r.size !== 0) n = n.filter(e => !r.has(e));
|
|
1895
|
+
for (const r of Reflect.ownKeys(t)) {
|
|
1896
|
+
if (!hasOwn.call(e.v, r)) n.push(r);
|
|
1897
|
+
}
|
|
1898
|
+
} else n = Reflect.ownKeys(t);
|
|
1899
|
+
// Drop the $OWNER stamp (owned backings carry it as an own enumerable
|
|
1900
|
+
// symbol). Symbols enumerate last, so the scan stops at the first string.
|
|
1901
|
+
for (let e = n.length - 1; e >= 0 && typeof n[e] === "symbol"; e--) {
|
|
1902
|
+
if (n[e] === $OWNER) {
|
|
1903
|
+
n.splice(e, 1);
|
|
1904
|
+
break;
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
if (!authoritativeServe() && e.fam?.opt && e.h !== null && (!inDraft(e) || draftSeesOverrides(e))) {
|
|
1908
|
+
let t = null;
|
|
1909
|
+
for (const r of Reflect.ownKeys(e.h)) {
|
|
1910
|
+
const i = e.h[r];
|
|
1911
|
+
if (!hasActiveOverride(i)) continue;
|
|
1912
|
+
t ??= new Set(n);
|
|
1913
|
+
if (unwrapOverride(i.o?.be)) t.add(r); else t.delete(r);
|
|
1914
|
+
}
|
|
1915
|
+
if (t !== null) return [ ...t ];
|
|
1916
|
+
}
|
|
1917
|
+
return n;
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
/** The data/accessor descriptor visible for `key` on `target` served from
|
|
1921
|
+
* `src`: overlay (#3044 — unwritten keys live on the committed backing,
|
|
1922
|
+
* deleted keys are absent) and optimistic presence overrides (an opt delete
|
|
1923
|
+
* hides the key; an opt add synthesizes a data descriptor from the value
|
|
1924
|
+
* node). `configurable` is the trap's concern (proxy invariant). */ function visibleDescriptor(e, t, n) {
|
|
1925
|
+
let r = Object.getOwnPropertyDescriptor(t, n);
|
|
1926
|
+
if (e.ovl && t === e.pb) {
|
|
1927
|
+
if (e.del !== null && e.del.has(n)) return undefined;
|
|
1928
|
+
if (r === undefined) r = Object.getOwnPropertyDescriptor(e.v, n);
|
|
1929
|
+
}
|
|
1930
|
+
if (!authoritativeServe() && e.fam?.opt && !inDraft(e)) {
|
|
1931
|
+
const t = e.h?.[n];
|
|
1932
|
+
if (t !== undefined && hasActiveOverride(t)) {
|
|
1933
|
+
if (!unwrapOverride(t.o?.be)) return undefined;
|
|
1934
|
+
// opt delete
|
|
1935
|
+
if (r === undefined) {
|
|
1936
|
+
const t = e.n?.[n];
|
|
1937
|
+
return {
|
|
1938
|
+
value: t !== undefined ? nodeValue(t, undefined) : undefined,
|
|
1939
|
+
writable: true,
|
|
1940
|
+
enumerable: true,
|
|
1941
|
+
configurable: true
|
|
1942
|
+
};
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
return r;
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1735
1949
|
/** Tracking deep snapshot (`deep()` for next targets): subscribes to the
|
|
1736
1950
|
* key-set and deep-witness node at every reachable level, then returns the
|
|
1737
1951
|
* plain view. Shared references and cycles handled via the visited set. */ function deepNext(e) {
|
|
@@ -1743,50 +1957,69 @@ function createStoreNext(e, t = false) {
|
|
|
1743
1957
|
// in per-key nodes, and it walks TARGETS directly — no per-child proxy
|
|
1744
1958
|
// round-trip (wrapNext → proxy → $TARGET trap) on the re-walk every
|
|
1745
1959
|
// effect run performs.
|
|
1746
|
-
|
|
1960
|
+
// Resolve `child` (a raw or a stored proxy found under `t`) to the target
|
|
1961
|
+
// that `t`'s family serves for it — created on first visit, as the get trap
|
|
1962
|
+
// would. Undefined = leaf (unwrappable or raw-marked).
|
|
1963
|
+
const childTarget = (e, t, n) => {
|
|
1964
|
+
let r = lookupTarget(t, e.fam);
|
|
1965
|
+
if (r === undefined) {
|
|
1966
|
+
if (!isWrappable(t)) return undefined;
|
|
1967
|
+
wrapNext(t, e, n);
|
|
1968
|
+
// Stored proxies (chained slots) that this family passes through
|
|
1969
|
+
// resolve to their own target.
|
|
1970
|
+
r = lookupTarget(t, e.fam) ?? t[$TARGET];
|
|
1971
|
+
}
|
|
1972
|
+
return r;
|
|
1973
|
+
};
|
|
1974
|
+
const walkT = e => {
|
|
1747
1975
|
const t = readSource(e);
|
|
1748
1976
|
if (n.has(t)) return;
|
|
1749
1977
|
n.add(t);
|
|
1750
1978
|
read(getKeySetNode(e));
|
|
1751
1979
|
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;
|
|
1980
|
+
// Chained backing (§7b): the committed backing IS another store's proxy.
|
|
1981
|
+
// Writes to the inner store bump the INNER record's witnesses; this
|
|
1982
|
+
// target's k/dk hear only its own family's writes (optimistic overrides).
|
|
1983
|
+
// Read through the whole chain — the $TRACK trap's rule (#2864 / R21)
|
|
1984
|
+
// applied to deep() (#3323). From `t.v`, not `src`: a pending backing is a
|
|
1985
|
+
// raw clone and would hide the chain. Non-chained targets pay one flag.
|
|
1986
|
+
for (let t = e; t.ch; ) {
|
|
1987
|
+
t = t.v[$TARGET];
|
|
1988
|
+
read(getKeySetNode(t));
|
|
1989
|
+
read(getDeepNode(t));
|
|
1768
1990
|
}
|
|
1769
|
-
|
|
1770
|
-
|
|
1991
|
+
// Keys and children exactly as the traps serve them — the overlay merge
|
|
1992
|
+
// (#3044/#3283: a bare ownKeys over a pending backing mid-flush dropped
|
|
1993
|
+
// every untouched child from the re-subscribing effect's dependencies)
|
|
1994
|
+
// and optimistic presence overrides (a row added under a held action
|
|
1995
|
+
// lives in `h`/`n`, not the committed backing; the walk never reached its
|
|
1996
|
+
// record, so deep() was deaf to every write on it until settle).
|
|
1997
|
+
const r = e.fam?.opt === true;
|
|
1998
|
+
for (const n of visibleKeys(e, t)) {
|
|
1999
|
+
const i = visibleDescriptor(e, t, n);
|
|
1771
2000
|
if (i === undefined) continue;
|
|
1772
2001
|
if (i.get || i.set) {
|
|
1773
2002
|
e.a = true;
|
|
1774
2003
|
continue;
|
|
1775
2004
|
// accessors track through their own reads when invoked
|
|
1776
2005
|
}
|
|
1777
|
-
|
|
2006
|
+
let o = i.value;
|
|
2007
|
+
// An active value override on an optimistic node shadows the backing's
|
|
2008
|
+
// child (an optimistic replacement `d[i] = {...}`) — follow what reads
|
|
2009
|
+
// serve, as nodeValue's leading arm does.
|
|
2010
|
+
if (r) {
|
|
2011
|
+
const t = e.n?.[n];
|
|
2012
|
+
if (t !== undefined && hasActiveOverride(t) && !authoritativeServe()) o = unwrapOverride(t.o.be);
|
|
2013
|
+
}
|
|
1778
2014
|
if (o === null || typeof o !== "object") continue;
|
|
1779
|
-
//
|
|
1780
|
-
//
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
f = r.get(o);
|
|
1786
|
-
if (f === undefined) continue;
|
|
2015
|
+
// Through a chain the descriptor yields the INNERMOST raw: resolve it to
|
|
2016
|
+
// the inner family's proxy so this level wraps the chained `view[i]` the
|
|
2017
|
+
// get trap serves, never a fresh non-chained wrapper of the base raw.
|
|
2018
|
+
if (e.ch && o[$TARGET] === undefined) o = resolveChainedRaw(e, n, o);
|
|
2019
|
+
const l = childTarget(e, o, n);
|
|
2020
|
+
if (l === undefined) continue;
|
|
1787
2021
|
// raw-marked: leaf by contract
|
|
1788
|
-
|
|
1789
|
-
walkT(f);
|
|
2022
|
+
walkT(l);
|
|
1790
2023
|
}
|
|
1791
2024
|
};
|
|
1792
2025
|
walkT(t);
|
|
@@ -1816,24 +2049,35 @@ function snapshotWalk(e, t, n) {
|
|
|
1816
2049
|
// encountered and compose their views over the resolved base, innermost
|
|
1817
2050
|
// outward.
|
|
1818
2051
|
let i = null;
|
|
1819
|
-
for (
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
if (
|
|
1823
|
-
if (
|
|
1824
|
-
if (
|
|
1825
|
-
|
|
2052
|
+
for (let e = true; ;e = false) {
|
|
2053
|
+
const t = r?.[$TARGET]?.v !== undefined;
|
|
2054
|
+
let o = t ? r[$TARGET] : undefined;
|
|
2055
|
+
if (o === undefined && n !== null) o = lookupTarget(r, n);
|
|
2056
|
+
if (o === undefined) o = lookupTarget(r, null);
|
|
2057
|
+
if (o === undefined) break;
|
|
2058
|
+
// Entering a level from a RAW below a chained family: the raw resolves to
|
|
2059
|
+
// the INNER store's target, but this family's wrapper for it — keyed by
|
|
2060
|
+
// the inner proxy, §7b — is where the outer overrides live. Start from
|
|
2061
|
+
// the wrapper so they compose (#3323: a nested optimistic write on a view
|
|
2062
|
+
// row was served by reads but missing from deep()/snapshot()). Entry only:
|
|
2063
|
+
// the descent then runs wrapper → inner → raw and terminates.
|
|
2064
|
+
if (e && !t && n !== null && o.fam !== n) {
|
|
2065
|
+
const e = n.map.get(o.px);
|
|
2066
|
+
if (e !== undefined) o = e;
|
|
2067
|
+
}
|
|
2068
|
+
if (o.fam !== null) n = o.fam;
|
|
2069
|
+
if (o.fam?.opt === true) (i ??= []).push(o);
|
|
1826
2070
|
// The shared visibility decision (#3147): the speculative peek serves
|
|
1827
2071
|
// pending staging, but a HELD landing is masked to committed exactly as
|
|
1828
2072
|
// it is for per-key readers — the two families must answer alike while
|
|
1829
2073
|
// a transaction holds store landings.
|
|
1830
|
-
const
|
|
2074
|
+
const l = pendingBackingVisible(o, true);
|
|
1831
2075
|
// Snapshot runs mid-flush (tracked memos execute before commit), so a
|
|
1832
2076
|
// pending prototype overlay must present as a REAL merged container.
|
|
1833
|
-
if (
|
|
1834
|
-
const
|
|
1835
|
-
if (
|
|
1836
|
-
r =
|
|
2077
|
+
if (l && o.ovl) materializePB(o);
|
|
2078
|
+
const f = l ? o.pb : o.v;
|
|
2079
|
+
if (f === r) break;
|
|
2080
|
+
r = f;
|
|
1837
2081
|
}
|
|
1838
2082
|
if (!isWrappable(r)) return r;
|
|
1839
2083
|
// Optimistic families: compose the visible view; a composed view is a fresh
|
|
@@ -1845,15 +2089,15 @@ function snapshotWalk(e, t, n) {
|
|
|
1845
2089
|
const i = t.get(r);
|
|
1846
2090
|
if (i !== undefined) return i;
|
|
1847
2091
|
const o = Array.isArray(e);
|
|
1848
|
-
const
|
|
1849
|
-
t.set(r,
|
|
2092
|
+
const l = o ? [] : Object.create(Object.getPrototypeOf(e));
|
|
2093
|
+
t.set(r, l);
|
|
1850
2094
|
for (const r of Reflect.ownKeys(e)) {
|
|
1851
|
-
if (o && r === "length") continue;
|
|
2095
|
+
if (o && r === "length" || r === $OWNER) continue;
|
|
1852
2096
|
const i = e[r];
|
|
1853
|
-
|
|
2097
|
+
l[r] = i !== null && typeof i === "object" ? snapshotWalk(i, t, n) : i;
|
|
1854
2098
|
}
|
|
1855
|
-
if (o)
|
|
1856
|
-
return
|
|
2099
|
+
if (o) l.length = e.length;
|
|
2100
|
+
return l;
|
|
1857
2101
|
}
|
|
1858
2102
|
}
|
|
1859
2103
|
const o = t.get(r);
|
|
@@ -1862,22 +2106,22 @@ function snapshotWalk(e, t, n) {
|
|
|
1862
2106
|
// subtrees "unmodified relative to source"): non-enumerable symbols are
|
|
1863
2107
|
// excluded (recon-snap R29), and the copy registers BEFORE descent so
|
|
1864
2108
|
// cycles keep identity (FINDING-3).
|
|
1865
|
-
if (
|
|
2109
|
+
if (isOwned(r)) {
|
|
1866
2110
|
const e = Array.isArray(r);
|
|
1867
2111
|
const i = e ? [] : Object.create(Object.getPrototypeOf(r));
|
|
1868
2112
|
t.set(r, i);
|
|
1869
2113
|
for (const o of Reflect.ownKeys(r)) {
|
|
1870
|
-
if (e && o === "length") continue;
|
|
1871
|
-
const
|
|
1872
|
-
if (typeof o === "symbol" && !
|
|
1873
|
-
if (
|
|
1874
|
-
Object.defineProperty(i, o,
|
|
2114
|
+
if (e && o === "length" || o === $OWNER) continue;
|
|
2115
|
+
const l = Object.getOwnPropertyDescriptor(r, o);
|
|
2116
|
+
if (typeof o === "symbol" && !l.enumerable) continue;
|
|
2117
|
+
if (l.get || l.set) {
|
|
2118
|
+
Object.defineProperty(i, o, l);
|
|
1875
2119
|
continue;
|
|
1876
2120
|
}
|
|
1877
|
-
const
|
|
1878
|
-
const s =
|
|
1879
|
-
if (
|
|
1880
|
-
...
|
|
2121
|
+
const f = l.value;
|
|
2122
|
+
const s = f !== null && typeof f === "object" ? snapshotWalk(f, t, n) : f;
|
|
2123
|
+
if (l.enumerable && l.writable && l.configurable) i[o] = s; else Object.defineProperty(i, o, {
|
|
2124
|
+
...l,
|
|
1881
2125
|
value: s
|
|
1882
2126
|
});
|
|
1883
2127
|
}
|
|
@@ -1887,22 +2131,22 @@ function snapshotWalk(e, t, n) {
|
|
|
1887
2131
|
// UNOWNED (shared/user) subtrees keep identity unless a descendant
|
|
1888
2132
|
// substituted; copy-on-substitution preserves the documented CoW contract.
|
|
1889
2133
|
t.set(r, r);
|
|
1890
|
-
let
|
|
2134
|
+
let l = null;
|
|
1891
2135
|
for (const e of Reflect.ownKeys(r)) {
|
|
1892
2136
|
const i = Object.getOwnPropertyDescriptor(r, e);
|
|
1893
2137
|
if (!i || i.get || i.set) continue;
|
|
1894
2138
|
const o = i.value;
|
|
1895
2139
|
if (o === null || typeof o !== "object") continue;
|
|
1896
|
-
const
|
|
1897
|
-
if (
|
|
1898
|
-
if (
|
|
1899
|
-
|
|
1900
|
-
t.set(r,
|
|
2140
|
+
const f = snapshotWalk(o, t, n);
|
|
2141
|
+
if (f !== o) {
|
|
2142
|
+
if (l === null) {
|
|
2143
|
+
l = Array.isArray(r) ? [ ...r ] : Object.create(Object.getPrototypeOf(r), Object.getOwnPropertyDescriptors(r));
|
|
2144
|
+
t.set(r, l);
|
|
1901
2145
|
}
|
|
1902
|
-
|
|
2146
|
+
l[e] = f;
|
|
1903
2147
|
}
|
|
1904
2148
|
}
|
|
1905
|
-
return
|
|
2149
|
+
return l ?? r;
|
|
1906
2150
|
}
|
|
1907
2151
|
|
|
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 };
|
|
2152
|
+
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, wrapNext };
|