@solidjs/signals 2.0.0-beta.9 → 2.0.0-rc.1
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/README.md +14 -10
- package/dist/dev.js +6740 -2011
- package/dist/node.cjs +7927 -3340
- package/dist/prod/affects.js +126 -0
- package/dist/prod/boundaries.js +572 -0
- package/dist/prod/core/action.js +139 -0
- package/dist/prod/core/async.js +607 -0
- package/dist/prod/core/constants.js +92 -0
- package/dist/prod/core/context.js +67 -0
- package/dist/prod/core/core.js +828 -0
- package/dist/prod/core/dev.js +3 -0
- package/dist/prod/core/effect.js +145 -0
- package/dist/prod/core/error.js +68 -0
- package/dist/prod/core/external.js +98 -0
- package/dist/prod/core/graph.js +104 -0
- package/dist/prod/core/heap.js +140 -0
- package/dist/prod/core/invariants.js +42 -0
- package/dist/prod/core/lanes.js +140 -0
- package/dist/prod/core/optimistic.js +262 -0
- package/dist/prod/core/owner.js +302 -0
- package/dist/prod/core/scheduler.js +757 -0
- package/dist/prod/core/verdict.js +369 -0
- package/dist/prod/index.js +45 -0
- package/dist/prod/map.js +319 -0
- package/dist/prod/signals.js +394 -0
- package/dist/prod/store/index.js +36 -0
- package/dist/prod/store/next/optimistic.js +377 -0
- package/dist/prod/store/next/projection.js +172 -0
- package/dist/prod/store/next/reconcile.js +327 -0
- package/dist/prod/store/next/store.js +1232 -0
- package/dist/prod/store/next/target.js +20 -0
- package/dist/prod/store/store.js +325 -0
- package/dist/prod/store/storePath.js +103 -0
- package/dist/prod/store/utils.js +201 -0
- package/dist/types/affects.d.ts +47 -0
- package/dist/types/boundaries.d.ts +60 -13
- package/dist/types/core/action.d.ts +35 -6
- package/dist/types/core/async.d.ts +16 -1
- package/dist/types/core/constants.d.ts +35 -6
- package/dist/types/core/context.d.ts +10 -2
- package/dist/types/core/core.d.ts +66 -63
- package/dist/types/core/dev.d.ts +17 -2
- package/dist/types/core/effect.d.ts +1 -2
- package/dist/types/core/error.d.ts +33 -0
- package/dist/types/core/external.d.ts +0 -11
- package/dist/types/core/graph.d.ts +2 -1
- package/dist/types/core/heap.d.ts +8 -0
- package/dist/types/core/index.d.ts +3 -2
- package/dist/types/core/invariants.d.ts +59 -0
- package/dist/types/core/lanes.d.ts +2 -0
- package/dist/types/core/optimistic.d.ts +6 -0
- package/dist/types/core/owner.d.ts +50 -3
- package/dist/types/core/scheduler.d.ts +85 -11
- package/dist/types/core/types.d.ts +66 -1
- package/dist/types/core/verdict.d.ts +2 -0
- package/dist/types/index.d.ts +3 -2
- package/dist/types/map.d.ts +21 -5
- package/dist/types/signals.d.ts +183 -12
- package/dist/types/store/index.d.ts +16 -6
- package/dist/types/store/next/optimistic.d.ts +20 -0
- package/dist/types/store/next/projection.d.ts +24 -0
- package/dist/types/store/next/reconcile.d.ts +3 -0
- package/dist/types/store/next/store.d.ts +71 -0
- package/dist/types/store/next/target.d.ts +99 -0
- package/dist/types/store/optimistic.d.ts +3 -3
- package/dist/types/store/projection.d.ts +10 -6
- package/dist/types/store/reconcile.d.ts +31 -8
- package/dist/types/store/store.d.ts +91 -71
- package/dist/types/store/utils.d.ts +0 -40
- package/dist/types-cjs/affects.d.cts +47 -0
- package/dist/types-cjs/boundaries.d.cts +60 -13
- package/dist/types-cjs/core/action.d.cts +35 -6
- package/dist/types-cjs/core/async.d.cts +16 -1
- package/dist/types-cjs/core/constants.d.cts +35 -6
- package/dist/types-cjs/core/context.d.cts +10 -2
- package/dist/types-cjs/core/core.d.cts +66 -63
- package/dist/types-cjs/core/dev.d.cts +17 -2
- package/dist/types-cjs/core/effect.d.cts +1 -2
- package/dist/types-cjs/core/error.d.cts +33 -0
- package/dist/types-cjs/core/external.d.cts +0 -11
- package/dist/types-cjs/core/graph.d.cts +2 -1
- package/dist/types-cjs/core/heap.d.cts +8 -0
- package/dist/types-cjs/core/index.d.cts +3 -2
- package/dist/types-cjs/core/invariants.d.cts +59 -0
- package/dist/types-cjs/core/lanes.d.cts +2 -0
- package/dist/types-cjs/core/optimistic.d.cts +6 -0
- package/dist/types-cjs/core/owner.d.cts +50 -3
- package/dist/types-cjs/core/scheduler.d.cts +85 -11
- package/dist/types-cjs/core/types.d.cts +66 -1
- package/dist/types-cjs/core/verdict.d.cts +2 -0
- package/dist/types-cjs/index.d.cts +3 -2
- package/dist/types-cjs/map.d.cts +21 -5
- package/dist/types-cjs/signals.d.cts +183 -12
- package/dist/types-cjs/store/index.d.cts +16 -6
- package/dist/types-cjs/store/next/optimistic.d.cts +20 -0
- package/dist/types-cjs/store/next/projection.d.cts +24 -0
- package/dist/types-cjs/store/next/reconcile.d.cts +3 -0
- package/dist/types-cjs/store/next/store.d.cts +71 -0
- package/dist/types-cjs/store/next/target.d.cts +99 -0
- package/dist/types-cjs/store/optimistic.d.cts +3 -3
- package/dist/types-cjs/store/projection.d.cts +10 -6
- package/dist/types-cjs/store/reconcile.d.cts +31 -8
- package/dist/types-cjs/store/store.d.cts +91 -71
- package/dist/types-cjs/store/utils.d.cts +0 -40
- package/package.json +12 -9
- package/dist/prod.js +0 -3627
|
@@ -0,0 +1,1232 @@
|
|
|
1
|
+
import { unwrapOverride, $REFRESH, CONFIG_OWNED_WRITE, NOT_PENDING, STATUS_UNINITIALIZED, STATUS_ERROR, CONFIG_CHILDREN_FORBIDDEN } from "../../core/constants.js";
|
|
2
|
+
|
|
3
|
+
import { setSignal, isEqual, read, pendingCheckActive, readNodeFast, READ_SLOW, signal } from "../../core/core.js";
|
|
4
|
+
|
|
5
|
+
import { projectionWriteActive, setProjectionWriteActive, schedule, setStoreCommitHook } from "../../core/scheduler.js";
|
|
6
|
+
|
|
7
|
+
import { getObserver, getOwner } from "../../core/owner.js";
|
|
8
|
+
|
|
9
|
+
import { $TARGET, isWrappable, markRawIngest, $PROXY, getWriteOverride, markRawOne, witnessAffectsMark, $TRACK, affectsScopesLive, inheritAffectsMarks, $AFFECTS, rawValuesUsed, isRawValue, setNextAffectsNodeResolver } from "../store.js";
|
|
10
|
+
|
|
11
|
+
import { storeNextLookup, optHooks, ownedRaw, devAssertNeverUserMutation } from "./target.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Store rewrite — increment 2: plain deep stores with pending-backing writes.
|
|
15
|
+
* Contract: INTERNALS-STORE-STATE.md.
|
|
16
|
+
*
|
|
17
|
+
* Write model (RUL-1, unified): the first draft write to a target creates its
|
|
18
|
+
* pending backing `pb` — a descriptor-preserving CoW clone. The draft mutates
|
|
19
|
+
* `pb` natively (array methods, defineProperty, deletes all just work).
|
|
20
|
+
* Reads: drafts and owner-context reads see `pb`; context-free reads see the
|
|
21
|
+
* committed `b` until flush. At flush commit (core's storeCommitHook), each
|
|
22
|
+
* written target folds: diff old `b` vs new backing notifies exactly the
|
|
23
|
+
* changed keys through equality-gated nodes, then `b` becomes the new
|
|
24
|
+
* backing. Setter return-value replacement parks the UNOWNED incoming object
|
|
25
|
+
* in `pb` — adoption: fold swaps it in, ownership resets (2026-08-16c).
|
|
26
|
+
*
|
|
27
|
+
* Nodes carry no pending state — they are pure subscription points; `pb` is
|
|
28
|
+
* the pending home. Laziness: a written target with no subscriptions folds as
|
|
29
|
+
* a pointer swap with zero node work.
|
|
30
|
+
*/
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// wrap / dedupe
|
|
33
|
+
function createTarget(e, t, n, r = t?.fam ?? null) {
|
|
34
|
+
// The proxy target carries the array exotic class when the value is an
|
|
35
|
+
// array, so Array.isArray(proxy) is true; the fields live on it directly.
|
|
36
|
+
// Direct field assignment in one fixed order (no Object.assign literal
|
|
37
|
+
// copy): every target shares a hidden-class transition chain — createTarget
|
|
38
|
+
// was the #2 store cost in the uibench creation profile.
|
|
39
|
+
const i = Array.isArray(e) ? [] : {};
|
|
40
|
+
i.v = e;
|
|
41
|
+
// Chained-backing flag (backing IS another store's proxy, §7b) — cached so
|
|
42
|
+
// the hot read path never does a per-read symbol lookup on the backing.
|
|
43
|
+
i.ch = e[$TARGET] !== undefined;
|
|
44
|
+
i.pb = null;
|
|
45
|
+
i.n = null;
|
|
46
|
+
i.h = null;
|
|
47
|
+
i.k = null;
|
|
48
|
+
i.dk = null;
|
|
49
|
+
i.u = t;
|
|
50
|
+
i.pk = n;
|
|
51
|
+
i.px = null;
|
|
52
|
+
i.d = false;
|
|
53
|
+
i.a = false;
|
|
54
|
+
i.sc = false;
|
|
55
|
+
i.nc = 0;
|
|
56
|
+
i.adopted = false;
|
|
57
|
+
i.fam = r;
|
|
58
|
+
i.s = false;
|
|
59
|
+
i.px = new Proxy(i, traps);
|
|
60
|
+
// Legacy interop: shared machinery (affects walks, wrap dedupe) reads the
|
|
61
|
+
// proxy off looked-up targets as a field.
|
|
62
|
+
i[$PROXY] = i.px;
|
|
63
|
+
(r?.map ?? storeNextLookup).set(e, i);
|
|
64
|
+
return i;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function wrapNext(e, t = null, n = null, r = t?.fam ?? null) {
|
|
68
|
+
// markRaw'd values never wrap through ANY store (R42; sticky raw-marking
|
|
69
|
+
// is one half of the never-both-wrapped-and-raw invariant, RUL-12).
|
|
70
|
+
if (rawValuesUsed && isRawValue(e)) return e;
|
|
71
|
+
const i = (r?.map ?? storeNextLookup).get(e);
|
|
72
|
+
if (i !== undefined) return i.px;
|
|
73
|
+
const o = e[$TARGET];
|
|
74
|
+
if (o !== undefined && o.px === e) {
|
|
75
|
+
// Foreign-family proxies re-wrap into THIS family (writes stay isolated);
|
|
76
|
+
// same-family and plain-store proxies pass through.
|
|
77
|
+
if (r === null || o.fam === r) return e;
|
|
78
|
+
return createTarget(e, t, n, r).px;
|
|
79
|
+
}
|
|
80
|
+
return createTarget(e, t, n, r).px;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Unwrap our own proxies to their current backing; leave everything else. */ function unwrapValue(e) {
|
|
84
|
+
if (e == null || typeof e !== "object") return e;
|
|
85
|
+
const t = e[$TARGET];
|
|
86
|
+
if (t !== undefined && t.px === e && t.v !== undefined) return t.pb ?? t.v;
|
|
87
|
+
return e;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
// nodes: pure subscription points (values used only for equality gating)
|
|
92
|
+
function getNode(e, t, n) {
|
|
93
|
+
const r = e.n ??= Object.create(null);
|
|
94
|
+
let i = r[t];
|
|
95
|
+
if (i === undefined) {
|
|
96
|
+
const o = i = signal(n, {
|
|
97
|
+
// Logical-slot equality: values resolving to the same child target
|
|
98
|
+
// are the same slot (privatization/adoption swap raw identity without
|
|
99
|
+
// changing the logical value — only changed leaves notify, R9).
|
|
100
|
+
equals: (t, n) => isEqual(t, n) || sameLogicalSlot(e, t, n),
|
|
101
|
+
unobserved() {
|
|
102
|
+
// A live affects() mark keeps the node addressable (sweep parity).
|
|
103
|
+
if (o.t) return;
|
|
104
|
+
if (e.n && e.n[t] === o) {
|
|
105
|
+
delete e.n[t];
|
|
106
|
+
e.nc--;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
// Projection nodes carry the projection computed as their firewall:
|
|
111
|
+
// reads through them link the derive's status/lifecycle (§7b).
|
|
112
|
+
e.fam?.node ?? undefined);
|
|
113
|
+
// Store nodes are ownedWrite: the setter carries the owned-scope write
|
|
114
|
+
// guard; node-level setSignals are internal notification machinery.
|
|
115
|
+
o.T |= CONFIG_OWNED_WRITE;
|
|
116
|
+
// Accessor-ness resolved ONCE per node (no per-object descriptor scan):
|
|
117
|
+
// accessor keys serve through Reflect.get with the proxy receiver.
|
|
118
|
+
o.acc = isOwnAccessor(e.pb ?? e.v, t);
|
|
119
|
+
// Wrap cache: the proxy last served for this key and the raw it wrapped.
|
|
120
|
+
// Raw-as-truth stores raw in nodes, so every object read needs a wrapper;
|
|
121
|
+
// one pointer compare (pxv === value) replaces the per-read WeakMap
|
|
122
|
+
// lookup in wrapNext — the dominant read-path cost vs legacy, whose
|
|
123
|
+
// nodes stored pre-wrapped values. A replaced child fails the compare
|
|
124
|
+
// and re-wraps; at most one stale proxy is pinned until the next read.
|
|
125
|
+
o.px = undefined;
|
|
126
|
+
o.pxv = undefined;
|
|
127
|
+
// Optimistic families: arm the override slot — setSignal routes armed
|
|
128
|
+
// nodes through the core engine (lanes, ownership, reverts all native).
|
|
129
|
+
if (e.fam?.opt) o.De = NOT_PENDING;
|
|
130
|
+
// A node born inside a live mark's identity scope inherits the mark
|
|
131
|
+
// (the declaration walk could only cover nodes existing then).
|
|
132
|
+
if (t !== $AFFECTS && affectsScopesLive()) inheritAffectsMarks(o, e.v, t);
|
|
133
|
+
r[t] = i;
|
|
134
|
+
e.nc++;
|
|
135
|
+
markDescendants(e);
|
|
136
|
+
}
|
|
137
|
+
return i;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function sameLogicalSlot(e, t, n) {
|
|
141
|
+
if (t === null || typeof t !== "object" || n === null || typeof n !== "object") return false;
|
|
142
|
+
const r = e.fam?.map ?? storeNextLookup;
|
|
143
|
+
const i = r.get(t);
|
|
144
|
+
return i !== undefined && i === r.get(n);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function getHasNode(e, t, n) {
|
|
148
|
+
const r = e.h ??= Object.create(null);
|
|
149
|
+
let i = r[t];
|
|
150
|
+
if (i === undefined) {
|
|
151
|
+
const o = i = signal(n, {
|
|
152
|
+
equals: isEqual,
|
|
153
|
+
unobserved() {
|
|
154
|
+
if (o.t) return;
|
|
155
|
+
if (e.h && e.h[t] === o) delete e.h[t];
|
|
156
|
+
}
|
|
157
|
+
}, e.fam?.node ?? undefined);
|
|
158
|
+
o.T |= CONFIG_OWNED_WRITE;
|
|
159
|
+
if (e.fam?.opt) o.De = NOT_PENDING;
|
|
160
|
+
if (affectsScopesLive()) inheritAffectsMarks(o, e.v, t);
|
|
161
|
+
r[t] = i;
|
|
162
|
+
markDescendants(e);
|
|
163
|
+
}
|
|
164
|
+
return i;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function getKeySetNode(e) {
|
|
168
|
+
let t = e.k;
|
|
169
|
+
if (t === null) {
|
|
170
|
+
const n = t = signal(0, {
|
|
171
|
+
equals: false,
|
|
172
|
+
unobserved() {
|
|
173
|
+
if (e.k === n) e.k = null;
|
|
174
|
+
}
|
|
175
|
+
}, e.fam?.node ?? undefined);
|
|
176
|
+
n.T |= CONFIG_OWNED_WRITE;
|
|
177
|
+
if (e.fam?.opt) n.De = NOT_PENDING;
|
|
178
|
+
e.k = t;
|
|
179
|
+
markDescendants(e);
|
|
180
|
+
}
|
|
181
|
+
return t;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function getDeepNode(e) {
|
|
185
|
+
let t = e.dk;
|
|
186
|
+
if (t === null) {
|
|
187
|
+
const n = t = signal(0, {
|
|
188
|
+
equals: false,
|
|
189
|
+
unobserved() {
|
|
190
|
+
if (e.dk === n) e.dk = null;
|
|
191
|
+
}
|
|
192
|
+
}, e.fam?.node ?? undefined);
|
|
193
|
+
n.T |= CONFIG_OWNED_WRITE;
|
|
194
|
+
if (e.fam?.opt) n.De = NOT_PENDING;
|
|
195
|
+
if (affectsScopesLive()) inheritAffectsMarks(n, e.v, $TRACK);
|
|
196
|
+
e.dk = t;
|
|
197
|
+
markDescendants(e);
|
|
198
|
+
}
|
|
199
|
+
return t;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Deep-witness bump: any value/shape change on a record with a live deep()
|
|
203
|
+
* subscriber notifies it. One null check when unused. */ function bumpDeep(e) {
|
|
204
|
+
if (e.dk !== null) setSignal(e.dk, 1);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function markDescendants(e) {
|
|
208
|
+
let t = e;
|
|
209
|
+
while (t && !t.d) {
|
|
210
|
+
t.d = true;
|
|
211
|
+
t = t.u;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
// pending backing + fold (the single mutation point)
|
|
217
|
+
/** target → committed backing at batch start (the fold diff's old side). */ const foldOlds = new Map;
|
|
218
|
+
|
|
219
|
+
let hookInstalled = false;
|
|
220
|
+
|
|
221
|
+
function cloneRaw(e, t) {
|
|
222
|
+
// Descriptor-preserving shallow clone (R29: installed getters stay live;
|
|
223
|
+
// ruled 2026-08-17: frozen sources clone unfrozen — theirs stays frozen).
|
|
224
|
+
// Data descriptors normalize to writable+configurable (the clone is OURS to
|
|
225
|
+
// mutate — R51's "source-non-configurable is writable through the store");
|
|
226
|
+
// enumerability and accessors are preserved. The scan doubles as the
|
|
227
|
+
// accessor-flag detector (free — we're enumerating descriptors anyway).
|
|
228
|
+
const n = Object.getOwnPropertyDescriptors(e);
|
|
229
|
+
for (const r of Reflect.ownKeys(n)) {
|
|
230
|
+
const i = n[r];
|
|
231
|
+
if (r === "length" && Array.isArray(e)) continue;
|
|
232
|
+
i.configurable = true;
|
|
233
|
+
if (!i.get && !i.set) i.writable = true; else if (t) t.a = true;
|
|
234
|
+
}
|
|
235
|
+
return Array.isArray(e) ? Object.defineProperties([], n) : Object.create(Object.getPrototypeOf(e), n);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function ensurePB(e) {
|
|
239
|
+
let t = e.pb;
|
|
240
|
+
if (t === null) {
|
|
241
|
+
t = e.pb = cloneRaw(e.v, e);
|
|
242
|
+
// Optimistic families: seed USER drafts from the OPTIMISTIC VIEW
|
|
243
|
+
// (committed + active node overrides), so follow-up writes compose on
|
|
244
|
+
// optimism instead of clobbering from base (#2951's compose half).
|
|
245
|
+
// AUTHORITATIVE drafts (projection recompute / write-override landings)
|
|
246
|
+
// seed from committed truth — seeding overrides there would fold a lane
|
|
247
|
+
// value into the committed home ("authority wins at reveal" would break).
|
|
248
|
+
if (e.fam?.opt && !projectionWriteActive && !getWriteOverride()) {
|
|
249
|
+
const n = e.n;
|
|
250
|
+
if (n !== null) {
|
|
251
|
+
for (const e of Reflect.ownKeys(n)) {
|
|
252
|
+
const r = n[e];
|
|
253
|
+
if (hasActiveOverride(r)) t[e] = unwrapOverride(r.De);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
const r = e.h;
|
|
257
|
+
if (r !== null) {
|
|
258
|
+
for (const e of Reflect.ownKeys(r)) {
|
|
259
|
+
const n = r[e];
|
|
260
|
+
if (hasActiveOverride(n) && !unwrapOverride(n.De)) delete t[e];
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
ownedRaw.add(t);
|
|
265
|
+
(e.fam?.map ?? storeNextLookup).set(t, e);
|
|
266
|
+
queueFold(e);
|
|
267
|
+
}
|
|
268
|
+
return t;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Adoption (2026-08-16c): the incoming object becomes the committed backing
|
|
273
|
+
* IMMEDIATELY — reconcile is eagerly visible to every reader (shipped
|
|
274
|
+
* contract; only its notifications batch), unlike setter writes which stay
|
|
275
|
+
* pending until flush. Ownership resets (incoming is unowned/user data). Any
|
|
276
|
+
* staged draft clone folds into the diff and is discarded — next is the
|
|
277
|
+
* authoritative base (R21/R32).
|
|
278
|
+
*/ function adoptPB(e, t, n = false) {
|
|
279
|
+
// Eager mode (plain-store adoption): the caller notifies inline after its
|
|
280
|
+
// descent — no foldOlds queue/drain round trip (the reconcile diff IS the
|
|
281
|
+
// fold diff; ~half of dbmon tick time was this duplication).
|
|
282
|
+
if (!n) {
|
|
283
|
+
queueFold(e);
|
|
284
|
+
// records the pre-batch old before we swap
|
|
285
|
+
e.adopted = true;
|
|
286
|
+
}
|
|
287
|
+
e.pb = null;
|
|
288
|
+
e.v = t;
|
|
289
|
+
e.ch = t[$TARGET] !== undefined;
|
|
290
|
+
(e.fam?.map ?? storeNextLookup).set(t, e);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function queueFold(e) {
|
|
294
|
+
if (foldOlds.has(e)) return;
|
|
295
|
+
if (foldOlds.size === 0) {
|
|
296
|
+
if (!hookInstalled) {
|
|
297
|
+
hookInstalled = true;
|
|
298
|
+
setStoreCommitHook(drainFolds);
|
|
299
|
+
}
|
|
300
|
+
schedule();
|
|
301
|
+
// once per batch — drain clears the map
|
|
302
|
+
}
|
|
303
|
+
foldOlds.set(e, e.v);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** Committed-time privatization for parent-chain slot updates (path copying). */ function privatizeCommitted(e) {
|
|
307
|
+
if (ownedRaw.has(e.v)) return;
|
|
308
|
+
const t = cloneRaw(e.v, e);
|
|
309
|
+
ownedRaw.add(t);
|
|
310
|
+
storeNextLookup.set(t, e);
|
|
311
|
+
e.v = t;
|
|
312
|
+
e.ch = false;
|
|
313
|
+
if (e.u) {
|
|
314
|
+
privatizeCommitted(e.u);
|
|
315
|
+
devAssertNeverUserMutation(e.u.v);
|
|
316
|
+
e.u.v[e.pk] = e.v;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function drainFolds() {
|
|
321
|
+
if (foldOlds.size === 0) return;
|
|
322
|
+
const e = [ ...foldOlds ];
|
|
323
|
+
foldOlds.clear();
|
|
324
|
+
for (const [t, n] of e) {
|
|
325
|
+
if (t.pb !== null) {
|
|
326
|
+
// Setter path: nodes were setSignal'd at setter exit (write-time
|
|
327
|
+
// notification — transitions/holds ride core machinery). Commit the
|
|
328
|
+
// backing only for keys whose nodes have committed; a still-pending
|
|
329
|
+
// node (transition-held) re-queues the target for the settling flush.
|
|
330
|
+
let e = false;
|
|
331
|
+
const r = t.pb;
|
|
332
|
+
const i = t.n;
|
|
333
|
+
if (i !== null) {
|
|
334
|
+
for (const t of Reflect.ownKeys(i)) {
|
|
335
|
+
const n = i[t];
|
|
336
|
+
if (n._e !== NOT_PENDING) {
|
|
337
|
+
e = true;
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (e) {
|
|
343
|
+
foldOlds.set(t, n);
|
|
344
|
+
// re-queue: commit happens when the hold settles
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
t.v = r;
|
|
348
|
+
t.ch = false;
|
|
349
|
+
// pb is always a plain clone
|
|
350
|
+
t.pb = null;
|
|
351
|
+
}
|
|
352
|
+
if (t.v === n) continue;
|
|
353
|
+
// adopted then re-adopted back, or no-op
|
|
354
|
+
// Path copying (CAS: see the eager-fold twin above).
|
|
355
|
+
if (t.u && t.u.v[t.pk] === n) {
|
|
356
|
+
privatizeCommitted(t.u);
|
|
357
|
+
devAssertNeverUserMutation(t.u.v);
|
|
358
|
+
t.u.v[t.pk] = t.v;
|
|
359
|
+
}
|
|
360
|
+
if (t.adopted) {
|
|
361
|
+
t.adopted = false;
|
|
362
|
+
notifyFold(t, n, t.v);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Setter-exit notification (write channel): diff the draft's pending backing
|
|
369
|
+
* against committed and setSignal every changed OBSERVED key — write-time
|
|
370
|
+
* notification with commit deferred to node commit, so transition holds,
|
|
371
|
+
* isPending, affects, and lane machinery ride the core natively (§3's
|
|
372
|
+
* "pending home = the node when a node exists"). Unobserved keys stay in the
|
|
373
|
+
* pending backing and fold directly at commit.
|
|
374
|
+
*/ function notifyWrites(e) {
|
|
375
|
+
const t = e.pb;
|
|
376
|
+
if (t === null) return;
|
|
377
|
+
// Optimistic channel: user writes on an optimistic family become node-level
|
|
378
|
+
// engine writes (armed nodes route setSignal through optimisticWrite) — the
|
|
379
|
+
// committed backing is NEVER touched; the draft clone is discarded. Reverts,
|
|
380
|
+
// per-transaction ownership, and flash-at-flush are all core-native.
|
|
381
|
+
// Projection recompute writes (projectionWriteActive) and projection draft
|
|
382
|
+
// writes (write-override, incl. post-await async landings) are
|
|
383
|
+
// authoritative and take the plain channel below (they commit silently
|
|
384
|
+
// under overrides per the engine's no-revert-stash contract).
|
|
385
|
+
if (e.fam?.opt) {
|
|
386
|
+
if (!projectionWriteActive && !getWriteOverride()) {
|
|
387
|
+
optHooks.notifyOptimisticWrites(e, t);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
// Authoritative path on an optimistic family: armed nodes must commit
|
|
391
|
+
// silently (engine bypass) — without this, a landing's setSignals would
|
|
392
|
+
// create lanes and block their own transition's settle.
|
|
393
|
+
if (!projectionWriteActive) {
|
|
394
|
+
setProjectionWriteActive(true);
|
|
395
|
+
try {
|
|
396
|
+
notifyWrites(e);
|
|
397
|
+
} finally {
|
|
398
|
+
setProjectionWriteActive(false);
|
|
399
|
+
}
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
const n = e.v;
|
|
404
|
+
const r = e.n;
|
|
405
|
+
if (r !== null) {
|
|
406
|
+
for (const e of Reflect.ownKeys(r)) {
|
|
407
|
+
const i = r[e];
|
|
408
|
+
// Per-key accessor handling: the node's cached flag plus ONE getter
|
|
409
|
+
// probe on the incoming side (getters arriving via merge/adoption).
|
|
410
|
+
// Setter-only props read as data (value undefined) so lookupSetter is
|
|
411
|
+
// not consulted on this hot path; prototype getters never own nodes.
|
|
412
|
+
if (i.acc === true || hasOwn.call(t, e) && lookupGetter.call(t, e) !== undefined) {
|
|
413
|
+
i.acc = isOwnAccessor(t, e);
|
|
414
|
+
const r = Object.getOwnPropertyDescriptor(n, e);
|
|
415
|
+
const o = Object.getOwnPropertyDescriptor(t, e);
|
|
416
|
+
if (r && (r.get || r.set) || o && (o.get || o.set)) {
|
|
417
|
+
if (r?.get !== o?.get || r?.set !== o?.set || r?.value !== o?.value) setSignal(i, () => FORCE);
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
if (!isEqual(r?.value, o?.value)) setSignal(i, () => o?.value);
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
// No old-side pre-compare: t.v lags across multi-batch windows (a
|
|
424
|
+
// projection recompute can run before the prior fold commits) — the
|
|
425
|
+
// node's OWN current value is the true old side, and setSignal's
|
|
426
|
+
// internal equality already checks exactly that.
|
|
427
|
+
const o = t[e];
|
|
428
|
+
setSignal(i, () => o);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
const i = e.h;
|
|
432
|
+
if (i !== null) {
|
|
433
|
+
for (const e of Reflect.ownKeys(i)) setSignal(i[e], e in t);
|
|
434
|
+
}
|
|
435
|
+
// Deep-witness (dk): setter writes must notify a deep() subscriber even on
|
|
436
|
+
// keys with no node. O(pb keys) equality only when a witness exists.
|
|
437
|
+
if (e.dk !== null) {
|
|
438
|
+
for (const r of Reflect.ownKeys(t)) {
|
|
439
|
+
const i = t[r];
|
|
440
|
+
const o = n[r];
|
|
441
|
+
if (i !== null && typeof i === "object" ? !targetsEqual(o, i) : !isEqual(o, i)) {
|
|
442
|
+
bumpDeep(e);
|
|
443
|
+
break;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (e.k !== null) {
|
|
448
|
+
const r = Array.isArray(t) && Array.isArray(n) ? arrayStructureChanged(n, t) : membershipChanged(n, t);
|
|
449
|
+
if (r) setSignal(e.k, e => e + 1);
|
|
450
|
+
}
|
|
451
|
+
// Projection backing folds split by channel (two pinned contracts):
|
|
452
|
+
// - sync-derive drafts (recompute body): NEVER eager — a downstream async
|
|
453
|
+
// hold can form LATER in the same flush and the leaf must stay at stale
|
|
454
|
+
// committed for context-free readers (spec-async "pends only the written
|
|
455
|
+
// leaf"). drainFolds commits when held-ness is knowable.
|
|
456
|
+
// - post-await async LANDINGS (write-override per-op, microtask context —
|
|
457
|
+
// no enclosing flush can capture them): the data-level commit is
|
|
458
|
+
// IMMEDIATE — landed truth shows to untracked readers even while a
|
|
459
|
+
// downstream consumer's own async still holds the effect-level reveal
|
|
460
|
+
// (spec-async "verdicts never inherit consumers' in-flight state").
|
|
461
|
+
if (e.fam !== null && e.pb !== null && getWriteOverride()) {
|
|
462
|
+
const n = e.v;
|
|
463
|
+
e.pb = null;
|
|
464
|
+
e.v = t;
|
|
465
|
+
e.ch = false;
|
|
466
|
+
if (e.u && e.u.v[e.pk] === n) {
|
|
467
|
+
privatizeCommitted(e.u);
|
|
468
|
+
devAssertNeverUserMutation(e.u.v);
|
|
469
|
+
e.u.v[e.pk] = t;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const FORCE = Symbol();
|
|
475
|
+
|
|
476
|
+
/** Same logical slot: both values resolve to one (re-pointed) child target —
|
|
477
|
+
* adoption preserved identity, so the slot did not change (R9). */ function targetsEqual(e, t) {
|
|
478
|
+
if (e === null || typeof e !== "object") return false;
|
|
479
|
+
const n = storeNextLookup.get(e);
|
|
480
|
+
return n !== undefined && n === storeNextLookup.get(t);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function arrayStructureChanged(e, t) {
|
|
484
|
+
if (e.length !== t.length) return true;
|
|
485
|
+
for (let n = 0; n < t.length; n++) {
|
|
486
|
+
const r = e[n];
|
|
487
|
+
const i = t[n];
|
|
488
|
+
if (!isEqual(r, i) && !targetsEqual(r, i)) return true;
|
|
489
|
+
}
|
|
490
|
+
return false;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function membershipChanged(e, t) {
|
|
494
|
+
const n = Reflect.ownKeys(t);
|
|
495
|
+
if (Reflect.ownKeys(e).length !== n.length) return true;
|
|
496
|
+
for (const t of n) if (!(t in e)) return true;
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* The fold diff walks SUBSCRIPTION KEYS ONLY (legacy parity: `for key in
|
|
502
|
+
* nodes`): nodes exist exactly where something tracked, so unobserved data
|
|
503
|
+
* costs nothing here regardless of object size. Accessor safety rides the
|
|
504
|
+
* sticky `t.a` flag — a node's key was necessarily read, so the get trap has
|
|
505
|
+
* already seen whether it is an accessor.
|
|
506
|
+
*/
|
|
507
|
+
/** One node's fold notification (shared by notifyFold's walk and the fused
|
|
508
|
+
* adoption walk): accessor-aware compare + equality/identity-gated setSignal. */ function notifyKeyDiff(e, t, n, r,
|
|
509
|
+
// The incoming-side getter probe covers SETTER-channel arrivals (return-
|
|
510
|
+
// form merges, defineProperty) — those flow through notifyWrites/
|
|
511
|
+
// notifyFold, which probe. The RECONCILE channel (fused walk) passes
|
|
512
|
+
// false: reconcile adopts immutable data by contract (R2a) and the pinned
|
|
513
|
+
// getter-preservation tests are all setter-channel; skipping ~2 Annex-B
|
|
514
|
+
// calls per key per tick is a measured dbmon win.
|
|
515
|
+
i = true) {
|
|
516
|
+
if (e.acc === true || i && hasOwn.call(r, t) && lookupGetter.call(r, t) !== undefined) {
|
|
517
|
+
e.acc = isOwnAccessor(r, t);
|
|
518
|
+
const i = Object.getOwnPropertyDescriptor(n, t);
|
|
519
|
+
const o = Object.getOwnPropertyDescriptor(r, t);
|
|
520
|
+
if (i && (i.get || i.set) || o && (o.get || o.set)) {
|
|
521
|
+
// Accessor involved: never invoke; force-notify on shape change so
|
|
522
|
+
// subscribers re-read (and re-track) through the trap.
|
|
523
|
+
if (i?.get !== o?.get || i?.set !== o?.set || i?.value !== o?.value) setSignal(e, () => FORCE);
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
const f = i?.value;
|
|
527
|
+
const u = o?.value;
|
|
528
|
+
if (!isEqual(f, u) && !targetsEqual(f, u)) setSignal(e, typeof u === "function" ? () => u : u);
|
|
529
|
+
} else {
|
|
530
|
+
const i = n[t];
|
|
531
|
+
const o = r[t];
|
|
532
|
+
// Direct value write when not a function (setSignal treats functions as
|
|
533
|
+
// updaters) — saves a closure allocation per changed key on the fold
|
|
534
|
+
// hot path.
|
|
535
|
+
if (!isEqual(i, o) && !targetsEqual(i, o)) setSignal(e, typeof o === "function" ? () => o : o);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/** Accessor-flag probe for the fused walk's early-continue (accessor keys
|
|
540
|
+
* can never identity-skip: their VALUE is the descriptor's product). */ function hasAccessorFlag(e) {
|
|
541
|
+
return e.acc === true;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/** Fused-walk per-key notification with values already in hand: the caller
|
|
545
|
+
* fetched both sides and handled the identity skip; this applies the
|
|
546
|
+
* accessor branch (cached flag only — reconcile channel) or the plain
|
|
547
|
+
* equality/identity-gated write. */ function notifyKeyValue(e, t, n, r, i, o) {
|
|
548
|
+
if (e.acc === true) {
|
|
549
|
+
notifyKeyDiff(e, t, i, o, false);
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
// The pre-compare is NOT redundant with the node's equals: setSignal parks
|
|
553
|
+
// a pending value and registers with the batch before equality applies at
|
|
554
|
+
// commit (RUL-1), so identity-preserved slots (adopted child containers —
|
|
555
|
+
// every row's fresh `queries` array) must be gated out HERE or each one
|
|
556
|
+
// pays the full write machinery every tick (measured: +0.5ms/tick dbmon).
|
|
557
|
+
if (!isEqual(n, r) && !targetsEqual(n, r)) setSignal(e, typeof r === "function" ? () => r : r);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/** Presence + membership halves of a fold notification (shared tail). */ function notifyFoldTail(e, t, n) {
|
|
561
|
+
const r = e.h;
|
|
562
|
+
if (r !== null) {
|
|
563
|
+
for (const e of Reflect.ownKeys(r)) setSignal(r[e], e in n);
|
|
564
|
+
}
|
|
565
|
+
if (e.k !== null) {
|
|
566
|
+
const r = Array.isArray(n) && Array.isArray(t) ? arrayStructureChanged(t, n) : membershipChanged(t, n);
|
|
567
|
+
if (r) setSignal(e.k, e => e + 1);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function notifyFold(e, t, n) {
|
|
572
|
+
if (e.dk !== null && t !== n) bumpDeep(e);
|
|
573
|
+
// Optimistic targets: adoption notifications are authoritative landings —
|
|
574
|
+
// bypass the engine (commit into _value; active overrides keep shadowing
|
|
575
|
+
// until their transaction settles, per the no-revert-stash contract).
|
|
576
|
+
if (e.fam?.opt && !projectionWriteActive) {
|
|
577
|
+
setProjectionWriteActive(true);
|
|
578
|
+
try {
|
|
579
|
+
notifyFold(e, t, n);
|
|
580
|
+
} finally {
|
|
581
|
+
setProjectionWriteActive(false);
|
|
582
|
+
}
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
const r = e.n;
|
|
586
|
+
if (r !== null) {
|
|
587
|
+
for (const e of Reflect.ownKeys(r)) {
|
|
588
|
+
notifyKeyDiff(r[e], e, t, n);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
const i = e.h;
|
|
592
|
+
if (i !== null) {
|
|
593
|
+
for (const e of Reflect.ownKeys(i)) setSignal(i[e], e in n);
|
|
594
|
+
}
|
|
595
|
+
if (e.k !== null) {
|
|
596
|
+
// Key-set/$TRACK: objects notify on membership; arrays on any index or
|
|
597
|
+
// length change (mapArray and iteration re-read values — R15).
|
|
598
|
+
const r = Array.isArray(n) && Array.isArray(t) ? arrayStructureChanged(t, n) : membershipChanged(t, n);
|
|
599
|
+
if (r) setSignal(e.k, e => e + 1);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// ---------------------------------------------------------------------------
|
|
604
|
+
// traps
|
|
605
|
+
/** >0 while inside a setter: writes allowed, reads are read-your-writes. */ let writing = 0;
|
|
606
|
+
|
|
607
|
+
/** Write scope keys (a family object or a plain store's root target): draft
|
|
608
|
+
* semantics — write permission, read-your-writes, tracking suppression —
|
|
609
|
+
* apply ONLY to targets under a scope being written. Reads of OTHER stores
|
|
610
|
+
* inside a setter track normally (they are dependencies: a projection derive
|
|
611
|
+
* reading another store must link it). */ let writeScopes = null;
|
|
612
|
+
|
|
613
|
+
function scopeKey(e) {
|
|
614
|
+
if (e.fam !== null) return e.fam;
|
|
615
|
+
let t = e;
|
|
616
|
+
while (t.u !== null) t = t.u;
|
|
617
|
+
return t;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function inDraft(e) {
|
|
621
|
+
return writeScopes !== null && writeScopes.has(scopeKey(e));
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/** Shallow serve rule (#2932): raw-marked data serves VERBATIM, but a
|
|
625
|
+
* store-proxy slot value gets a boundary wrapper in THIS store's own family —
|
|
626
|
+
* write isolation through derived chains (downstream writes must never land
|
|
627
|
+
* upstream). markRawOne skips proxies for exactly this reason. */ function serveShallow(e, t, n) {
|
|
628
|
+
if (n !== null && typeof n === "object" && n[$TARGET] !== undefined) return draftServe(e, wrapNext(n, e, t));
|
|
629
|
+
return n;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/** Draft reads extend write permission to reachable stores (legacy Writing
|
|
633
|
+
* semantics: wrapping a child through a draft get admits it — cross-store
|
|
634
|
+
* writes like `s.inner.a = 10` work when `inner` is another store's proxy). */ function draftServe(e, t) {
|
|
635
|
+
if (writeScopes !== null && inDraft(e)) {
|
|
636
|
+
const e = t?.[$TARGET];
|
|
637
|
+
if (e !== undefined && e.v !== undefined) writeScopes.add(scopeKey(e));
|
|
638
|
+
}
|
|
639
|
+
return t;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/** Targets written during the current (outermost) setter — notified at exit. */ const pendingNotify = new Set;
|
|
643
|
+
|
|
644
|
+
const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
|
|
645
|
+
|
|
646
|
+
/** Mirror of core read()'s context rule: the OWNER context (not the tracking
|
|
647
|
+
* observer) decides pending visibility, with Roots resolving to their parent
|
|
648
|
+
* computed (#2687 — untracked reads inside mapArray Roots see in-flight
|
|
649
|
+
* values mid-flush). CHILDREN_FORBIDDEN execution scopes (createTrackedEffect
|
|
650
|
+
* / onSettled callbacks) get COMMITTED visibility (#3006), same as core. */ function inOwnerContext() {
|
|
651
|
+
const e = getOwner();
|
|
652
|
+
if (e === null) return false;
|
|
653
|
+
const t = e.Tt ? e.It : e;
|
|
654
|
+
return t != null && !(t.T & CONFIG_CHILDREN_FORBIDDEN);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/** A pending fold is transition-held when any written node's parked value is
|
|
658
|
+
* stamped by a live transition (a plain batch parking — the lazy-recompute
|
|
659
|
+
* read case — has no transition stamp and serves fresh). */ function foldHeld(e) {
|
|
660
|
+
const t = e.n;
|
|
661
|
+
if (t === null) return false;
|
|
662
|
+
for (const e of Reflect.ownKeys(t)) {
|
|
663
|
+
const n = t[e];
|
|
664
|
+
if (n._e !== NOT_PENDING && n.Ne != null && n.Ne.fn !== true) return true;
|
|
665
|
+
}
|
|
666
|
+
return false;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function readSource(e) {
|
|
670
|
+
// Signal-parity visibility (core read(): owner-context reads serve
|
|
671
|
+
// _pendingValue, context-free reads serve committed — effects recompute
|
|
672
|
+
// BEFORE commitPendingNodes in the flush, so the pending view must be
|
|
673
|
+
// servable). Drafts (setter window OR projection write-override) and
|
|
674
|
+
// owner-context reads see the pending backing; context-free reads see
|
|
675
|
+
// committed. Node reads apply the same rule, so both homes agree.
|
|
676
|
+
if (e.pb !== null && (inDraft(e) || getWriteOverride() || inOwnerContext() ||
|
|
677
|
+
// A projection's pending backing is authoritative-elect: serve it to
|
|
678
|
+
// context-free readers too UNLESS a transition is holding the node
|
|
679
|
+
// commits (downstream async hold — stale committed is the contract).
|
|
680
|
+
e.fam !== null && !foldHeld(e))) return e.pb;
|
|
681
|
+
return e.v;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
const hasOwn = Object.prototype.hasOwnProperty;
|
|
685
|
+
|
|
686
|
+
// Allocation-free own-accessor probe (replaces eager descriptor scans — the
|
|
687
|
+
// single biggest creation cost in the uibench profile): Annex-B lookups
|
|
688
|
+
// return the fn or undefined with no descriptor object. Own data properties
|
|
689
|
+
// shadow prototype accessors, so hasOwn + lookup is an exact own-check.
|
|
690
|
+
const lookupGetter = Object.prototype.__lookupGetter__;
|
|
691
|
+
|
|
692
|
+
const lookupSetter = Object.prototype.__lookupSetter__;
|
|
693
|
+
|
|
694
|
+
function isOwnAccessor(e, t) {
|
|
695
|
+
return hasOwn.call(e, t) && (lookupGetter.call(e, t) !== undefined || lookupSetter.call(e, t) !== undefined);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/** Authoritative-write wrapper exported for the optimistic module: sets the
|
|
699
|
+
* scheduler's projectionWriteActive through THIS module's binding (proven to
|
|
700
|
+
* share the instance core reads — cross-module live-binding writes from other
|
|
701
|
+
* store modules were observed not to propagate under the test transform). */ function runAuthoritative(e) {
|
|
702
|
+
const t = projectionWriteActive;
|
|
703
|
+
setProjectionWriteActive(true);
|
|
704
|
+
try {
|
|
705
|
+
return e();
|
|
706
|
+
} finally {
|
|
707
|
+
setProjectionWriteActive(t);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/** Active optimistic override on an armed node (armed slot idles at
|
|
712
|
+
* NOT_PENDING; undefined = unarmed plain node). */ function hasActiveOverride(e) {
|
|
713
|
+
return e.De !== undefined && e.De !== NOT_PENDING;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/** Context-aware node view for reads outside tracking: active override >
|
|
717
|
+
* held pending (owner context) > the BACKING value. Committed truth lives in
|
|
718
|
+
* the backing (single-home rule, O6) — node `_value` is never served here,
|
|
719
|
+
* so a lazy recompute's landing is immediately visible to the untracked
|
|
720
|
+
* reader that forced it (backing commits eagerly; node values fold at flush).
|
|
721
|
+
* FORCE sentinels never surface (they only bump subscribers of accessor
|
|
722
|
+
* keys, which are served by the trap, not the node). */ function nodeValue(e, t) {
|
|
723
|
+
const n = hasActiveOverride(e) ? unwrapOverride(e.De) : e._e !== NOT_PENDING && inOwnerContext() ? e._e : t;
|
|
724
|
+
return n === FORCE ? t : n;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/** Serve an own data key: node-first when a node exists (pending visibility,
|
|
728
|
+
* holds, lanes ride the node); backing otherwise. Chained backings (§7b: the
|
|
729
|
+
* backing IS another store's proxy) serve the read-through value — the outer
|
|
730
|
+
* node is linked only for adoption-swap notification, its value never
|
|
731
|
+
* shadows the live chain. */ function serveDataKey(e, t, n, r, i) {
|
|
732
|
+
const o = e.ch && r === e.v;
|
|
733
|
+
let f = n;
|
|
734
|
+
// §6: on optimistic arrays LENGTH IS A VIEW, not a node value — one home
|
|
735
|
+
// (backing ± presence overrides) for both length and indices makes torn
|
|
736
|
+
// iteration impossible (a length node's value rides different visibility
|
|
737
|
+
// rails than index overrides mid-settle). The node still carries
|
|
738
|
+
// subscriptions; its value is never served here.
|
|
739
|
+
if (t === "length" && e.fam?.opt === true && !o && Array.isArray(r)) {
|
|
740
|
+
if (!inDraft(e)) {
|
|
741
|
+
const r = e.n?.length;
|
|
742
|
+
if (r !== undefined) {
|
|
743
|
+
if (getObserver() !== null) read(r);
|
|
744
|
+
} else if (getObserver() !== null) {
|
|
745
|
+
read(getNode(e, t, n));
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
return optHooks.optimisticView(e, r).length;
|
|
749
|
+
}
|
|
750
|
+
if (inDraft(e)) {
|
|
751
|
+
// Optimistic drafts before their first write have no pending backing yet;
|
|
752
|
+
// reads must still see the live optimistic view (compose, not clobber —
|
|
753
|
+
// #2951). Once ensurePB runs, the seeded clone carries the view.
|
|
754
|
+
if (e.fam?.opt && e.pb === null) {
|
|
755
|
+
const n = e.n?.[t];
|
|
756
|
+
if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.De);
|
|
757
|
+
}
|
|
758
|
+
} else {
|
|
759
|
+
if (i !== undefined) {
|
|
760
|
+
// §7b: a lane value on the outer node SHADOWS read-through — an active
|
|
761
|
+
// override pierces the chained gate; otherwise chained backings always
|
|
762
|
+
// serve the live inner value.
|
|
763
|
+
if (getObserver() !== null) {
|
|
764
|
+
// read()'s plain-signal fast path hoisted over the call (legacy trap
|
|
765
|
+
// parity): READ_SLOW = a global read window or non-plain node.
|
|
766
|
+
let e = readNodeFast(i);
|
|
767
|
+
if (e === READ_SLOW) e = read(i);
|
|
768
|
+
if (!o || hasActiveOverride(i)) f = e === FORCE ? n : e;
|
|
769
|
+
} else if (!o || hasActiveOverride(i)) {
|
|
770
|
+
f = nodeValue(i, n);
|
|
771
|
+
}
|
|
772
|
+
} else if (getObserver() !== null) {
|
|
773
|
+
read(getNode(e, t, n));
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
// Shallow stores serve data raw; store-proxy slots get boundary wrappers.
|
|
777
|
+
if (e.s) return serveShallow(e, t, f);
|
|
778
|
+
if (i !== undefined) {
|
|
779
|
+
// Wrap cache (see getNode): only wrappables are ever cached, so a hit
|
|
780
|
+
// skips isWrappable too — pointer-compare replaces both checks.
|
|
781
|
+
if (i.pxv === f && f !== undefined) return draftServe(e, i.px);
|
|
782
|
+
if (!isWrappable(f)) return f;
|
|
783
|
+
const n = wrapNext(f, e, t);
|
|
784
|
+
i.px = n;
|
|
785
|
+
i.pxv = f;
|
|
786
|
+
return draftServe(e, n);
|
|
787
|
+
}
|
|
788
|
+
if (!isWrappable(f)) return f;
|
|
789
|
+
return draftServe(e, wrapNext(f, e, t));
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/** §6c store-wide status gate for reads that DON'T flow through a node:
|
|
793
|
+
* untracked/raw fallthrough must still throw while the derive is
|
|
794
|
+
* uninitialized (seed invisibility, proj R23) or errored (memo parity).
|
|
795
|
+
* TRACKED reads never call this — store nodes carry `_firewall`, so core
|
|
796
|
+
* read() links the node and throws the firewall's error itself (the node
|
|
797
|
+
* link is what wakes async-memo readers when the landing writes values;
|
|
798
|
+
* the firewall link rides the same read). */ function firewallGate(e) {
|
|
799
|
+
const t = e.fam?.node;
|
|
800
|
+
if (t != null && t.S & (STATUS_UNINITIALIZED | STATUS_ERROR)) read(t);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
const traps = {
|
|
804
|
+
get(e, t, n) {
|
|
805
|
+
// One typeof gates every brand-symbol compare off the hot string path
|
|
806
|
+
// (four symbol comparisons per property read otherwise).
|
|
807
|
+
if (typeof t !== "string") {
|
|
808
|
+
if (t === $TARGET) return e;
|
|
809
|
+
if (t === $PROXY) return n;
|
|
810
|
+
// refresh()/isPending resolve the projection computed through $REFRESH.
|
|
811
|
+
if (t === $REFRESH) return e.fam?.node ?? undefined;
|
|
812
|
+
if (t === $TRACK) {
|
|
813
|
+
if (pendingCheckActive) witnessAffectsMark(e, t);
|
|
814
|
+
if (e.fam !== null && getObserver() === null && !inDraft(e)) firewallGate(e);
|
|
815
|
+
if (!inDraft(e) && getObserver() !== null) {
|
|
816
|
+
read(getKeySetNode(e));
|
|
817
|
+
// Structural chaining (§7b, #2864 / core R21): a chained backing's
|
|
818
|
+
// $TRACK reads through to the INNER store's key-set — structural
|
|
819
|
+
// notifications land on the source's own node, never on this
|
|
820
|
+
// wrapper view's.
|
|
821
|
+
const t = readSource(e);
|
|
822
|
+
if (t[$TARGET] !== undefined) t[$TRACK];
|
|
823
|
+
}
|
|
824
|
+
return undefined;
|
|
825
|
+
}
|
|
826
|
+
// user symbols fall through to the generic path
|
|
827
|
+
}
|
|
828
|
+
if (pendingCheckActive) witnessAffectsMark(e, t);
|
|
829
|
+
if (e.fam !== null && getObserver() === null && !inDraft(e)) firewallGate(e);
|
|
830
|
+
const r = readSource(e);
|
|
831
|
+
// Hot inline case: existing PLAIN node (non-accessor), unchained backing,
|
|
832
|
+
// tracked read of a present data key — the dbmon/uibench effect re-read
|
|
833
|
+
// shape. Skips serveDataKey's frame, the FORCE compare (only accessor
|
|
834
|
+
// keys ever hold the sentinel), and isWrappable for primitives.
|
|
835
|
+
if (e.ch === false && writeScopes === null) {
|
|
836
|
+
const n = e.n?.[t];
|
|
837
|
+
if (n !== undefined && n.acc !== true && getObserver() !== null) {
|
|
838
|
+
let r = readNodeFast(n);
|
|
839
|
+
if (r === READ_SLOW) r = read(n);
|
|
840
|
+
if (r === null || typeof r !== "object") return r;
|
|
841
|
+
if (e.s) return serveShallow(e, t, r);
|
|
842
|
+
if (n.pxv === r) return n.px;
|
|
843
|
+
if (isWrappable(r)) {
|
|
844
|
+
const i = wrapNext(r, e, t);
|
|
845
|
+
n.px = i;
|
|
846
|
+
n.pxv = r;
|
|
847
|
+
return i;
|
|
848
|
+
}
|
|
849
|
+
return r;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
// Accessor keys serve through Reflect.get with the PROXY receiver
|
|
853
|
+
// (R20/R29: internal reads track; the node is linked for shape-change
|
|
854
|
+
// notification but its value is never served). Accessor-ness comes from
|
|
855
|
+
// the node's cached flag; the first TRACKED read (which creates the
|
|
856
|
+
// node) probes once — untracked node-less reads take the plain path,
|
|
857
|
+
// where a raw-receiver getter still returns correct committed values.
|
|
858
|
+
const i = e.n?.[t];
|
|
859
|
+
{
|
|
860
|
+
const o = i !== undefined ? i.acc === true : !writing && getObserver() !== null && isOwnAccessor(r, t);
|
|
861
|
+
if (o) {
|
|
862
|
+
if (!writing && getObserver() !== null) read(i ?? getNode(e, t, undefined));
|
|
863
|
+
const o = Reflect.get(r, t, n);
|
|
864
|
+
if (e.s) return serveShallow(e, t, o);
|
|
865
|
+
return isWrappable(o) ? draftServe(e, wrapNext(o, e, t)) : o;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
// Plain-data fast path: no descriptor allocation per read.
|
|
869
|
+
// Inherited pollution keys are never served (core R30) — checked before
|
|
870
|
+
// the proto-function branch can leak `constructor`. Interned-string
|
|
871
|
+
// compares beat a Set hash on this per-read path.
|
|
872
|
+
if ((t === "constructor" || t === "__proto__" || t === "prototype") && !hasOwn.call(r, t)) return undefined;
|
|
873
|
+
let o = r[t];
|
|
874
|
+
if (o === undefined ? !hasOwn.call(r, t) : false) {
|
|
875
|
+
// Inherited: prototype getters/methods run with the proxy receiver.
|
|
876
|
+
o = Reflect.get(r, t, n);
|
|
877
|
+
if (typeof o === "function") return o;
|
|
878
|
+
// proto methods untracked
|
|
879
|
+
// Reading a currently-absent own key subscribes to it (R12).
|
|
880
|
+
if (o === undefined && !writing) {
|
|
881
|
+
if (getObserver() !== null) read(getNode(e, t, undefined));
|
|
882
|
+
const n = e.n?.[t];
|
|
883
|
+
if (n) {
|
|
884
|
+
const r = nodeValue(n, undefined);
|
|
885
|
+
if (e.s) return serveShallow(e, t, r);
|
|
886
|
+
return isWrappable(r) ? draftServe(e, wrapNext(r, e, t)) : r;
|
|
887
|
+
}
|
|
888
|
+
} else if (o === undefined && inDraft(e) && e.fam?.opt && e.pb === null) {
|
|
889
|
+
const n = e.n?.[t];
|
|
890
|
+
if (n !== undefined && hasActiveOverride(n)) o = unwrapOverride(n.De);
|
|
891
|
+
}
|
|
892
|
+
if (e.s) return serveShallow(e, t, o);
|
|
893
|
+
return isWrappable(o) ? draftServe(e, wrapNext(o, e, t)) : o;
|
|
894
|
+
}
|
|
895
|
+
if (typeof o === "function" && !hasOwn.call(r, t)) return o;
|
|
896
|
+
// proto method
|
|
897
|
+
return serveDataKey(e, t, o, r, i);
|
|
898
|
+
},
|
|
899
|
+
has(e, t) {
|
|
900
|
+
if (t === $TARGET || t === $PROXY || t === $TRACK) return true;
|
|
901
|
+
if (pendingCheckActive) witnessAffectsMark(e, t);
|
|
902
|
+
if (e.fam !== null && getObserver() === null && !inDraft(e)) firewallGate(e);
|
|
903
|
+
const n = readSource(e);
|
|
904
|
+
let r = t in n;
|
|
905
|
+
if (!inDraft(e)) {
|
|
906
|
+
if (getObserver() !== null) {
|
|
907
|
+
const n = getHasNode(e, t, r);
|
|
908
|
+
const i = read(n);
|
|
909
|
+
if (hasActiveOverride(n)) r = !!i;
|
|
910
|
+
} else {
|
|
911
|
+
const n = e.h?.[t];
|
|
912
|
+
if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.De);
|
|
913
|
+
}
|
|
914
|
+
} else if (e.fam?.opt && e.pb === null) {
|
|
915
|
+
const n = e.h?.[t];
|
|
916
|
+
if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.De);
|
|
917
|
+
}
|
|
918
|
+
return r;
|
|
919
|
+
},
|
|
920
|
+
ownKeys(e) {
|
|
921
|
+
if (pendingCheckActive) witnessAffectsMark(e);
|
|
922
|
+
if (e.fam !== null && getObserver() === null && !inDraft(e)) firewallGate(e);
|
|
923
|
+
if (!inDraft(e) && getObserver() !== null) read(getKeySetNode(e));
|
|
924
|
+
const t = Reflect.ownKeys(readSource(e));
|
|
925
|
+
// Optimistic membership overlay: presence-node overrides add/remove keys
|
|
926
|
+
// (per-transaction lifecycle rides the nodes — §6, FINDING-2's fix).
|
|
927
|
+
// Draft reads before the first write overlay too (pb, once created, is
|
|
928
|
+
// seeded with the view).
|
|
929
|
+
if (e.fam?.opt && e.h !== null && (!inDraft(e) || e.pb === null)) {
|
|
930
|
+
let n = null;
|
|
931
|
+
for (const r of Reflect.ownKeys(e.h)) {
|
|
932
|
+
const i = e.h[r];
|
|
933
|
+
if (!hasActiveOverride(i)) continue;
|
|
934
|
+
n ??= new Set(t);
|
|
935
|
+
if (unwrapOverride(i.De)) n.add(r); else n.delete(r);
|
|
936
|
+
}
|
|
937
|
+
if (n !== null) return [ ...n ];
|
|
938
|
+
}
|
|
939
|
+
return t;
|
|
940
|
+
},
|
|
941
|
+
getOwnPropertyDescriptor(e, t) {
|
|
942
|
+
const n = Object.getOwnPropertyDescriptor(readSource(e), t);
|
|
943
|
+
if (e.fam?.opt && !inDraft(e)) {
|
|
944
|
+
const r = e.h?.[t];
|
|
945
|
+
if (r !== undefined && hasActiveOverride(r)) {
|
|
946
|
+
if (!unwrapOverride(r.De)) return undefined;
|
|
947
|
+
// opt delete
|
|
948
|
+
if (n === undefined) {
|
|
949
|
+
const n = e.n?.[t];
|
|
950
|
+
return {
|
|
951
|
+
value: n !== undefined ? nodeValue(n, undefined) : undefined,
|
|
952
|
+
writable: true,
|
|
953
|
+
enumerable: true,
|
|
954
|
+
configurable: true
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
if (n === undefined) return undefined;
|
|
960
|
+
// Array targets carry a real non-configurable `length` the proxy
|
|
961
|
+
// invariant forces us to report faithfully; everything else reports
|
|
962
|
+
// configurable via target indirection (core R51).
|
|
963
|
+
if (!(t === "length" && Array.isArray(e))) n.configurable = true;
|
|
964
|
+
return n;
|
|
965
|
+
},
|
|
966
|
+
set(e, t, n) {
|
|
967
|
+
// Writes require the target's draft scope OR the projection write
|
|
968
|
+
// override (post-await async draft writes arrive outside any window);
|
|
969
|
+
// everything else is silently ignored (R23).
|
|
970
|
+
const r = inDraft(e);
|
|
971
|
+
const i = !r && getWriteOverride();
|
|
972
|
+
if (!r && !i) return true;
|
|
973
|
+
if (t === "__proto__") return true;
|
|
974
|
+
// pollution guard (core R30)
|
|
975
|
+
const o = ensurePB(e);
|
|
976
|
+
pendingNotify.add(e);
|
|
977
|
+
// Own data keys literally named "prototype"/"constructor" land as data —
|
|
978
|
+
// defineProperty sidesteps a proto-chain setter named the same.
|
|
979
|
+
if (UNSAFE_KEYS.has(t)) {
|
|
980
|
+
Object.defineProperty(o, t, {
|
|
981
|
+
value: unwrapValue(n),
|
|
982
|
+
writable: true,
|
|
983
|
+
enumerable: true,
|
|
984
|
+
configurable: true
|
|
985
|
+
});
|
|
986
|
+
return true;
|
|
987
|
+
}
|
|
988
|
+
// Shallow slots store what was written VERBATIM — another store's proxy
|
|
989
|
+
// passes through by reference (#2932; markRawOne skips proxies), while
|
|
990
|
+
// deep stores unwrap to raw backings.
|
|
991
|
+
const f = e.s ? n : unwrapValue(n);
|
|
992
|
+
o[t] = f;
|
|
993
|
+
// Shallow ingest: written records are sticky raw-marked (one entity is
|
|
994
|
+
// never both deep-wrapped and raw — R41/#2932, shared invariant).
|
|
995
|
+
if (e.s && f !== null && typeof f === "object") markRawOne(f);
|
|
996
|
+
// Override-mode (post-await draft) writes have no setter exit — notify
|
|
997
|
+
// per-op (setSignal equality-gates repeats).
|
|
998
|
+
if (i) notifyWrites(e);
|
|
999
|
+
return true;
|
|
1000
|
+
},
|
|
1001
|
+
defineProperty(e, t, n) {
|
|
1002
|
+
const r = inDraft(e);
|
|
1003
|
+
const i = !r && getWriteOverride();
|
|
1004
|
+
if (!r && !i) return true;
|
|
1005
|
+
if (t === "__proto__") return true;
|
|
1006
|
+
if (n.get || n.set) e.a = true;
|
|
1007
|
+
const o = ensurePB(e);
|
|
1008
|
+
pendingNotify.add(e);
|
|
1009
|
+
if ("value" in n) n = {
|
|
1010
|
+
...n,
|
|
1011
|
+
value: unwrapValue(n.value)
|
|
1012
|
+
};
|
|
1013
|
+
Object.defineProperty(o, t, n);
|
|
1014
|
+
if (i) notifyWrites(e);
|
|
1015
|
+
return true;
|
|
1016
|
+
},
|
|
1017
|
+
deleteProperty(e, t) {
|
|
1018
|
+
const n = inDraft(e);
|
|
1019
|
+
const r = !n && getWriteOverride();
|
|
1020
|
+
if (!n && !r) return true;
|
|
1021
|
+
const i = ensurePB(e);
|
|
1022
|
+
pendingNotify.add(e);
|
|
1023
|
+
delete i[t];
|
|
1024
|
+
if (r) notifyWrites(e);
|
|
1025
|
+
return true;
|
|
1026
|
+
}
|
|
1027
|
+
};
|
|
1028
|
+
|
|
1029
|
+
/** Low-level setter primitive: opens write mode on a next proxy, runs `fn`,
|
|
1030
|
+
* emits write-time notifications at outermost exit, applies returned
|
|
1031
|
+
* replacements as adoptions. `guard=false` skips the owned-scope dev guard —
|
|
1032
|
+
* projection recomputes legitimately write from inside their computed. */ function storeSetterNext(e, t, n = true) {
|
|
1033
|
+
const r = e[$TARGET];
|
|
1034
|
+
const i = writeScopes;
|
|
1035
|
+
writeScopes = new Set;
|
|
1036
|
+
writeScopes.add(scopeKey(r));
|
|
1037
|
+
writing++;
|
|
1038
|
+
let o;
|
|
1039
|
+
try {
|
|
1040
|
+
// No untrack: the writing flag already disables store-node linking
|
|
1041
|
+
// (draft reads never self-track, proj R2), while EXTERNAL reads (signals
|
|
1042
|
+
// inside a projection derive) must keep tracking — they are the derive's
|
|
1043
|
+
// dependencies.
|
|
1044
|
+
o = t(e);
|
|
1045
|
+
} finally {
|
|
1046
|
+
writing--;
|
|
1047
|
+
writeScopes = i;
|
|
1048
|
+
// Outermost setter exit: emit write-time notifications (setSignal per
|
|
1049
|
+
// changed observed key) so transition holds and lanes engage now.
|
|
1050
|
+
if (writing === 0 && pendingNotify.size) {
|
|
1051
|
+
const e = [ ...pendingNotify ];
|
|
1052
|
+
pendingNotify.clear();
|
|
1053
|
+
for (const t of e) notifyWrites(t);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
if (o !== undefined && o !== e && isWrappable(o)) {
|
|
1057
|
+
// Returned replacement: on an optimistic family (outside authoritative
|
|
1058
|
+
// writes) the replacement is itself an optimistic edit — diff it against
|
|
1059
|
+
// the visible view as engine writes (reverts at settle). Otherwise it is
|
|
1060
|
+
// an adoption of the incoming object (unowned).
|
|
1061
|
+
if (r.fam?.opt && !projectionWriteActive && !getWriteOverride()) {
|
|
1062
|
+
optHooks.notifyOptimisticWrites(r, unwrapValue(o));
|
|
1063
|
+
} else {
|
|
1064
|
+
adoptPB(r, unwrapValue(o));
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
// Affects integration: the legacy affects machinery reads next targets
|
|
1070
|
+
// structurally (aliased field names); only node CREATION dispatches here.
|
|
1071
|
+
setNextAffectsNodeResolver((e, t) => t === $AFFECTS ? getNode(e, $AFFECTS, undefined) : getNode(e, t, (e.pb ?? e.v)[t]));
|
|
1072
|
+
|
|
1073
|
+
function createStoreNext(e, t = false) {
|
|
1074
|
+
const n = wrapNext(e);
|
|
1075
|
+
if (t) {
|
|
1076
|
+
n[$TARGET].s = true;
|
|
1077
|
+
markRawIngest(e);
|
|
1078
|
+
}
|
|
1079
|
+
const setter = e => storeSetterNext(n, e);
|
|
1080
|
+
return [ n, setter ];
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
/** Tracking deep snapshot (`deep()` for next targets): subscribes to the
|
|
1084
|
+
* key-set and every property node at every reachable level, then returns the
|
|
1085
|
+
* plain view. Shared references and cycles handled via the visited set. */ function deepNext(e) {
|
|
1086
|
+
const t = e?.[$TARGET];
|
|
1087
|
+
if (t === undefined || t.px !== e) return e;
|
|
1088
|
+
const n = new Set;
|
|
1089
|
+
// One membership node + one deep-witness node PER RECORD (legacy $TRACK
|
|
1090
|
+
// parity): the walk stays O(records) in subscriptions instead of O(paths)
|
|
1091
|
+
// in per-key nodes, and it walks TARGETS directly — no per-child proxy
|
|
1092
|
+
// round-trip (wrapNext → proxy → $TARGET trap) on the re-walk every
|
|
1093
|
+
// effect run performs.
|
|
1094
|
+
const walkT = e => {
|
|
1095
|
+
const t = readSource(e);
|
|
1096
|
+
if (n.has(t)) return;
|
|
1097
|
+
n.add(t);
|
|
1098
|
+
read(getKeySetNode(e));
|
|
1099
|
+
read(getDeepNode(e));
|
|
1100
|
+
const r = e.fam?.map ?? storeNextLookup;
|
|
1101
|
+
for (const n of Reflect.ownKeys(t)) {
|
|
1102
|
+
const i = Object.getOwnPropertyDescriptor(t, n);
|
|
1103
|
+
if (i === undefined) continue;
|
|
1104
|
+
if (i.get || i.set) {
|
|
1105
|
+
e.a = true;
|
|
1106
|
+
continue;
|
|
1107
|
+
// accessors track through their own reads when invoked
|
|
1108
|
+
}
|
|
1109
|
+
const o = i.value;
|
|
1110
|
+
if (o === null || typeof o !== "object") continue;
|
|
1111
|
+
// Stored proxies (chained slots) resolve through their own target;
|
|
1112
|
+
// raw children through the family map, created on first visit.
|
|
1113
|
+
let f = o[$TARGET] ?? r.get(o);
|
|
1114
|
+
if (f === undefined) {
|
|
1115
|
+
if (!isWrappable(o)) continue;
|
|
1116
|
+
wrapNext(o, e, n);
|
|
1117
|
+
f = r.get(o);
|
|
1118
|
+
if (f === undefined) continue;
|
|
1119
|
+
// raw-marked: leaf by contract
|
|
1120
|
+
}
|
|
1121
|
+
walkT(f);
|
|
1122
|
+
}
|
|
1123
|
+
};
|
|
1124
|
+
walkT(t);
|
|
1125
|
+
return snapshotNext(e);
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
/**
|
|
1129
|
+
* Snapshot with per-object registration resolution (RUL-12 DAG ruling): every
|
|
1130
|
+
* reachable wrappable resolves through its target's CURRENT backing, so
|
|
1131
|
+
* privatized subtrees are seen through any parent path. Identity-preserving:
|
|
1132
|
+
* a subtree with no substitutions below returns its own object (zero copy for
|
|
1133
|
+
* settled, never-diverged graphs).
|
|
1134
|
+
*/ function snapshotNext(e) {
|
|
1135
|
+
const t = e?.[$TARGET];
|
|
1136
|
+
return snapshotWalk(e, new Map, t?.fam ?? null);
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
function snapshotWalk(e, t, n) {
|
|
1140
|
+
if (e === null || typeof e !== "object") return e;
|
|
1141
|
+
// Resolve through the registration: proxies AND raws map to their target's
|
|
1142
|
+
// current backing (stale raw pointers through other parents resolve here).
|
|
1143
|
+
// Loops for chained backings (§7b: a projection's backing can be another
|
|
1144
|
+
// store's proxy — snapshot unwraps to the base raw).
|
|
1145
|
+
let r = e;
|
|
1146
|
+
// Chained backings can pass through several targets; optimistic overrides
|
|
1147
|
+
// on OUTER targets shadow the chain (§7b), so collect every opt target
|
|
1148
|
+
// encountered and compose their views over the resolved base, innermost
|
|
1149
|
+
// outward.
|
|
1150
|
+
let i = null;
|
|
1151
|
+
for (;;) {
|
|
1152
|
+
let e = r?.[$TARGET]?.v !== undefined ? r[$TARGET] : undefined;
|
|
1153
|
+
if (e === undefined && n !== null) e = n.map.get(r);
|
|
1154
|
+
if (e === undefined) e = storeNextLookup.get(r);
|
|
1155
|
+
if (e === undefined) break;
|
|
1156
|
+
if (e.fam !== null) n = e.fam;
|
|
1157
|
+
if (e.fam?.opt === true) (i ??= []).push(e);
|
|
1158
|
+
const t = e.pb ?? e.v;
|
|
1159
|
+
if (t === r) break;
|
|
1160
|
+
r = t;
|
|
1161
|
+
}
|
|
1162
|
+
if (!isWrappable(r)) return r;
|
|
1163
|
+
// Optimistic families: compose the visible view; a composed view is a fresh
|
|
1164
|
+
// object and snapshots via the owned/copy path (pinned `not.toBe` identity).
|
|
1165
|
+
if (i !== null) {
|
|
1166
|
+
let e = r;
|
|
1167
|
+
for (let t = i.length - 1; t >= 0; t--) e = optHooks.optimisticView(i[t], e);
|
|
1168
|
+
if (e !== r) {
|
|
1169
|
+
const i = t.get(r);
|
|
1170
|
+
if (i !== undefined) return i;
|
|
1171
|
+
const o = Array.isArray(e);
|
|
1172
|
+
const f = o ? [] : Object.create(Object.getPrototypeOf(e));
|
|
1173
|
+
t.set(r, f);
|
|
1174
|
+
for (const r of Reflect.ownKeys(e)) {
|
|
1175
|
+
if (o && r === "length") continue;
|
|
1176
|
+
const i = e[r];
|
|
1177
|
+
f[r] = i !== null && typeof i === "object" ? snapshotWalk(i, t, n) : i;
|
|
1178
|
+
}
|
|
1179
|
+
if (o) f.length = e.length;
|
|
1180
|
+
return f;
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
const o = t.get(r);
|
|
1184
|
+
if (o !== undefined) return o;
|
|
1185
|
+
// OWNED (written) subtrees snapshot as copies (§7b: identity is only for
|
|
1186
|
+
// subtrees "unmodified relative to source"): non-enumerable symbols are
|
|
1187
|
+
// excluded (recon-snap R29), and the copy registers BEFORE descent so
|
|
1188
|
+
// cycles keep identity (FINDING-3).
|
|
1189
|
+
if (ownedRaw.has(r)) {
|
|
1190
|
+
const e = Array.isArray(r);
|
|
1191
|
+
const i = e ? [] : Object.create(Object.getPrototypeOf(r));
|
|
1192
|
+
t.set(r, i);
|
|
1193
|
+
for (const o of Reflect.ownKeys(r)) {
|
|
1194
|
+
if (e && o === "length") continue;
|
|
1195
|
+
const f = Object.getOwnPropertyDescriptor(r, o);
|
|
1196
|
+
if (typeof o === "symbol" && !f.enumerable) continue;
|
|
1197
|
+
if (f.get || f.set) {
|
|
1198
|
+
Object.defineProperty(i, o, f);
|
|
1199
|
+
continue;
|
|
1200
|
+
}
|
|
1201
|
+
const u = f.value;
|
|
1202
|
+
const s = u !== null && typeof u === "object" ? snapshotWalk(u, t, n) : u;
|
|
1203
|
+
if (f.enumerable && f.writable && f.configurable) i[o] = s; else Object.defineProperty(i, o, {
|
|
1204
|
+
...f,
|
|
1205
|
+
value: s
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
if (e && i.length !== r.length) i.length = r.length;
|
|
1209
|
+
return i;
|
|
1210
|
+
}
|
|
1211
|
+
// UNOWNED (shared/user) subtrees keep identity unless a descendant
|
|
1212
|
+
// substituted; copy-on-substitution preserves the documented CoW contract.
|
|
1213
|
+
t.set(r, r);
|
|
1214
|
+
let f = null;
|
|
1215
|
+
for (const e of Reflect.ownKeys(r)) {
|
|
1216
|
+
const i = Object.getOwnPropertyDescriptor(r, e);
|
|
1217
|
+
if (!i || i.get || i.set) continue;
|
|
1218
|
+
const o = i.value;
|
|
1219
|
+
if (o === null || typeof o !== "object") continue;
|
|
1220
|
+
const u = snapshotWalk(o, t, n);
|
|
1221
|
+
if (u !== o) {
|
|
1222
|
+
if (f === null) {
|
|
1223
|
+
f = Array.isArray(r) ? [ ...r ] : Object.create(Object.getPrototypeOf(r), Object.getOwnPropertyDescriptors(r));
|
|
1224
|
+
t.set(r, f);
|
|
1225
|
+
}
|
|
1226
|
+
f[e] = u;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
return f ?? r;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
export { adoptPB, bumpDeep, createStoreNext, deepNext, getHasNode, getKeySetNode, getNode, hasAccessorFlag, hasActiveOverride, notifyFold, notifyFoldTail, notifyKeyDiff, notifyKeyValue, runAuthoritative, snapshotNext, storeSetterNext, targetsEqual, unwrapValue, wrapNext };
|