@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
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { computed, suppressComputedRecompute } from "../../core/core.js";
|
|
2
|
+
|
|
3
|
+
import { getOwner } from "../../core/owner.js";
|
|
4
|
+
|
|
5
|
+
import { setProjectionWriteActive, projectionWriteActive } from "../../core/scheduler.js";
|
|
6
|
+
|
|
7
|
+
import { handleAsync } from "../../core/async.js";
|
|
8
|
+
|
|
9
|
+
import "../../core/verdict.js";
|
|
10
|
+
|
|
11
|
+
import "../../core/effect.js";
|
|
12
|
+
|
|
13
|
+
import { CONFIG_AUTO_DISPOSE } from "../../core/constants.js";
|
|
14
|
+
|
|
15
|
+
import { $TARGET, STORE_VALUE, markRawIngest, setWriteOverride } from "../store.js";
|
|
16
|
+
|
|
17
|
+
import { reconcileNextState } from "./reconcile.js";
|
|
18
|
+
|
|
19
|
+
import { storeSetterNext, wrapNext } from "./store.js";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Store rewrite — projections (§7/§7b): a projection is a computed store.
|
|
23
|
+
* The derive runs inside a computed whose recompute merges its output into
|
|
24
|
+
* the projection's backing through the adoption channel (replace-mode root:
|
|
25
|
+
* entity changes merge in place, the root proxy is stable for life). Children
|
|
26
|
+
* wrap into the projection's own FAMILY (writes land here, never in a source
|
|
27
|
+
* family), and every family node carries the projection computed as its
|
|
28
|
+
* firewall — reads link the derive's status and lifecycle natively. The §6c
|
|
29
|
+
* status gate in the traps makes an uninitialized async derive's seed
|
|
30
|
+
* unobservable through every read surface.
|
|
31
|
+
*
|
|
32
|
+
* Mirrors the legacy runProjectionComputed shape (shadow runs for open
|
|
33
|
+
* loading windows, handleAsync landings, commit-through-setter) on next
|
|
34
|
+
* primitives; the generic draft write-traps are reused from the legacy
|
|
35
|
+
* module unchanged.
|
|
36
|
+
*/
|
|
37
|
+
/**
|
|
38
|
+
* Wrap a store proxy as a projection DRAFT: every operation carries the write
|
|
39
|
+
* override (the derive is the author — its ops must not hit the §6c firewall
|
|
40
|
+
* gate, even in a continuation after an `await`/`yield` where the sync write
|
|
41
|
+
* scope has closed).
|
|
42
|
+
*
|
|
43
|
+
* FAKE TARGET, not the store proxy itself (#3060): after a proxy trap
|
|
44
|
+
* returns, the engine runs spec invariant validation against the proxy's
|
|
45
|
+
* TARGET — [[OwnPropertyKeys]] after ownKeys, [[GetOwnProperty]] after
|
|
46
|
+
* set/getOwnPropertyDescriptor/defineProperty. With the store proxy as
|
|
47
|
+
* target those checks re-enter the store's traps OUTSIDE the override
|
|
48
|
+
* bracket (the trap's finally has already run), so `Object.keys(state)` in
|
|
49
|
+
* a derive continuation fired the firewall gate and re-threw the
|
|
50
|
+
* projection's own pending NotReadyError into the derive. A dummy of
|
|
51
|
+
* matching kind (array/object, same trick as the store's own TargetShape)
|
|
52
|
+
* keeps invariant validation away from the store entirely; the traps
|
|
53
|
+
* forward to the closed-over inner proxy inside the bracket.
|
|
54
|
+
*
|
|
55
|
+
* Save/restore projectionWriteActive, never hard-reset: the draft can be
|
|
56
|
+
* driven from inside an enclosing authoritative-write scope (next-store
|
|
57
|
+
* optimistic derives), and a hard `false` would clobber it mid-derive.
|
|
58
|
+
*/ function wrapDraft(e, t, r) {
|
|
59
|
+
const write = e => r ? r(e) : e();
|
|
60
|
+
const i = {
|
|
61
|
+
get(i, o) {
|
|
62
|
+
let n;
|
|
63
|
+
const c = projectionWriteActive;
|
|
64
|
+
setWriteOverride(true);
|
|
65
|
+
setProjectionWriteActive(true);
|
|
66
|
+
try {
|
|
67
|
+
n = e[o];
|
|
68
|
+
} finally {
|
|
69
|
+
setWriteOverride(false);
|
|
70
|
+
setProjectionWriteActive(c);
|
|
71
|
+
}
|
|
72
|
+
if (o === $TARGET) return n;
|
|
73
|
+
return typeof n === "object" && n !== null ? wrapDraft(n, t, r) : n;
|
|
74
|
+
},
|
|
75
|
+
has(t, r) {
|
|
76
|
+
let i;
|
|
77
|
+
const o = projectionWriteActive;
|
|
78
|
+
setWriteOverride(true);
|
|
79
|
+
setProjectionWriteActive(true);
|
|
80
|
+
try {
|
|
81
|
+
i = r in e;
|
|
82
|
+
} finally {
|
|
83
|
+
setWriteOverride(false);
|
|
84
|
+
setProjectionWriteActive(o);
|
|
85
|
+
}
|
|
86
|
+
return i;
|
|
87
|
+
},
|
|
88
|
+
set(r, i, o) {
|
|
89
|
+
if (t && !t()) return true;
|
|
90
|
+
const n = projectionWriteActive;
|
|
91
|
+
setWriteOverride(true);
|
|
92
|
+
setProjectionWriteActive(true);
|
|
93
|
+
try {
|
|
94
|
+
write(() => {
|
|
95
|
+
e[i] = o;
|
|
96
|
+
});
|
|
97
|
+
} finally {
|
|
98
|
+
setWriteOverride(false);
|
|
99
|
+
setProjectionWriteActive(n);
|
|
100
|
+
}
|
|
101
|
+
return true;
|
|
102
|
+
},
|
|
103
|
+
deleteProperty(r, i) {
|
|
104
|
+
if (t && !t()) return true;
|
|
105
|
+
const o = projectionWriteActive;
|
|
106
|
+
setWriteOverride(true);
|
|
107
|
+
setProjectionWriteActive(true);
|
|
108
|
+
try {
|
|
109
|
+
write(() => {
|
|
110
|
+
delete e[i];
|
|
111
|
+
});
|
|
112
|
+
} finally {
|
|
113
|
+
setWriteOverride(false);
|
|
114
|
+
setProjectionWriteActive(o);
|
|
115
|
+
}
|
|
116
|
+
return true;
|
|
117
|
+
},
|
|
118
|
+
ownKeys() {
|
|
119
|
+
const t = projectionWriteActive;
|
|
120
|
+
setWriteOverride(true);
|
|
121
|
+
setProjectionWriteActive(true);
|
|
122
|
+
try {
|
|
123
|
+
return Reflect.ownKeys(e);
|
|
124
|
+
} finally {
|
|
125
|
+
setWriteOverride(false);
|
|
126
|
+
setProjectionWriteActive(t);
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
getOwnPropertyDescriptor(t, r) {
|
|
130
|
+
let i;
|
|
131
|
+
const o = projectionWriteActive;
|
|
132
|
+
setWriteOverride(true);
|
|
133
|
+
setProjectionWriteActive(true);
|
|
134
|
+
try {
|
|
135
|
+
i = Reflect.getOwnPropertyDescriptor(e, r);
|
|
136
|
+
} finally {
|
|
137
|
+
setWriteOverride(false);
|
|
138
|
+
setProjectionWriteActive(o);
|
|
139
|
+
}
|
|
140
|
+
// The dummy target doesn't hold the key, so a non-configurable report
|
|
141
|
+
// would violate the proxy invariant. Store descriptors are already
|
|
142
|
+
// normalized configurable; enforce it for raw leaves too.
|
|
143
|
+
if (i) i.configurable = true;
|
|
144
|
+
return i;
|
|
145
|
+
},
|
|
146
|
+
defineProperty(r, i, o) {
|
|
147
|
+
if (t && !t()) return true;
|
|
148
|
+
const n = projectionWriteActive;
|
|
149
|
+
setWriteOverride(true);
|
|
150
|
+
setProjectionWriteActive(true);
|
|
151
|
+
try {
|
|
152
|
+
write(() => {
|
|
153
|
+
Reflect.defineProperty(e, i, o);
|
|
154
|
+
});
|
|
155
|
+
} finally {
|
|
156
|
+
setWriteOverride(false);
|
|
157
|
+
setProjectionWriteActive(n);
|
|
158
|
+
}
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
// Matching-kind dummy so Array.isArray(draft) answers like the store.
|
|
163
|
+
return new Proxy(Array.isArray(e) ? [] : {}, i);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function createProjectionNextInternal(e, t, r) {
|
|
167
|
+
const i = {
|
|
168
|
+
map: new WeakMap,
|
|
169
|
+
node: null,
|
|
170
|
+
shallow: !!r?.shallow
|
|
171
|
+
};
|
|
172
|
+
const o = wrapNext(t, null, null, i);
|
|
173
|
+
if (i.shallow) {
|
|
174
|
+
// Shallow projection: the root is the only wrapped level — slot values
|
|
175
|
+
// serve raw, ingests sticky raw-mark (same t.s machinery as plain).
|
|
176
|
+
o[$TARGET].s = true;
|
|
177
|
+
markRawIngest(t);
|
|
178
|
+
}
|
|
179
|
+
let n;
|
|
180
|
+
if (r?.seedLoadingValue) n = {
|
|
181
|
+
loadingValue: undefined
|
|
182
|
+
};
|
|
183
|
+
if (r?.name) n = {
|
|
184
|
+
...n,
|
|
185
|
+
name: r.name
|
|
186
|
+
};
|
|
187
|
+
const c = computed(() => {
|
|
188
|
+
if (!i.node) i.node = getOwner();
|
|
189
|
+
runProjectionComputedNext(o, e, r?.key === undefined ? "id" : r.key);
|
|
190
|
+
}, n);
|
|
191
|
+
c.T &= ~CONFIG_AUTO_DISPOSE;
|
|
192
|
+
i.node = c;
|
|
193
|
+
return {
|
|
194
|
+
store: o,
|
|
195
|
+
node: c
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function createProjectionNext(e, t, r) {
|
|
200
|
+
return createProjectionNextInternal(e, t, r).store;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Derived writable store (legacy parity): a projection whose public setter
|
|
204
|
+
* masks the recompute for the tick (core R31 — the manual write wins over a
|
|
205
|
+
* same-flush dependency change). */ function createStoreDerivedNext(e, t, r) {
|
|
206
|
+
const {store: i, node: o} = createProjectionNextInternal(e, t, r);
|
|
207
|
+
return [ i, e => {
|
|
208
|
+
// Mark the projection as manually written before notifying nodes.
|
|
209
|
+
suppressComputedRecompute(o);
|
|
210
|
+
storeSetterNext(i, e);
|
|
211
|
+
} ];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function runProjectionComputedNext(e, t, r, i, o) {
|
|
215
|
+
const n = getOwner();
|
|
216
|
+
let c = false;
|
|
217
|
+
let s;
|
|
218
|
+
// Open loading window (seedLoadingValue): the observable store IS commit #0
|
|
219
|
+
// for the whole first flight — the derive works a detached shadow of the
|
|
220
|
+
// seed so draft writes cannot tear through to readers (#2988). Every commit
|
|
221
|
+
// point reconciles the shadow through the normal commit path.
|
|
222
|
+
const u = n.de ? JSON.parse(JSON.stringify(e[$TARGET][STORE_VALUE])) : null;
|
|
223
|
+
const l = wrapDraft(e, () => !c || n.o?.Se === s, o);
|
|
224
|
+
storeSetterNext(l, o => {
|
|
225
|
+
s = t(u ?? o);
|
|
226
|
+
c = true;
|
|
227
|
+
const commit = t => {
|
|
228
|
+
// Shadow run: commit a detached snapshot, never the shadow itself
|
|
229
|
+
// (adoption takes the value by identity — handing it the live shadow
|
|
230
|
+
// would fuse the draft to the observable store).
|
|
231
|
+
if (u && (t === undefined || t === u)) t = JSON.parse(JSON.stringify(u));
|
|
232
|
+
if (t === o || t === undefined) return;
|
|
233
|
+
const write = () => storeSetterNext(e, e => reconcileNextState(t, e, r, true), false);
|
|
234
|
+
i ? i(write, t) : write();
|
|
235
|
+
};
|
|
236
|
+
const l = handleAsync(n, s, commit);
|
|
237
|
+
if (!n.de) commit(l);
|
|
238
|
+
}, false);
|
|
239
|
+
return n;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export { createProjectionNext, createStoreDerivedNext, runProjectionComputedNext };
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { isEqual } from "../../core/core.js";
|
|
2
|
+
|
|
3
|
+
import { projectionWriteActive } from "../../core/scheduler.js";
|
|
4
|
+
|
|
5
|
+
import "../../core/invariants.js";
|
|
6
|
+
|
|
7
|
+
import "../../core/verdict.js";
|
|
8
|
+
|
|
9
|
+
import "../../core/effect.js";
|
|
10
|
+
|
|
11
|
+
import { $TARGET, markRawIngest, isWrappable, rawValuesUsed, isRawValue, getWriteOverride } from "../store.js";
|
|
12
|
+
|
|
13
|
+
import { materializePB, adoptPB, unwrapValue, notifyFold, targetsEqual, bumpDeep, notifyKeyValue, notifyKeyDiff, notifyFoldTail, hasAccessorFlag } from "./store.js";
|
|
14
|
+
|
|
15
|
+
import { isOwned, $OWNER, storeNextLookup, optHooks, lookupTarget } from "./target.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Store rewrite — reconcile, the adoption channel (INTERNALS-STORE-STATE.md
|
|
19
|
+
* §3, decision 2026-08-16c). Reconcile never merge-writes: it adopts `next`
|
|
20
|
+
* as the authoritative pending backing at every proxied level (pointer swap
|
|
21
|
+
* folded at flush commit), notification riding the fold's descriptor diff.
|
|
22
|
+
*
|
|
23
|
+
* Structural optimizations (all kept, per 2026-08-17 morning ruling):
|
|
24
|
+
* - Identity skip with completed proof: `incoming === backing && !owned` —
|
|
25
|
+
* sound because input is immutable by convention (R2a) and ownership marks
|
|
26
|
+
* the only writer the convention doesn't cover (us). Fixes FINDING-1.
|
|
27
|
+
* - Reachability pruning: descent happens only where a child TARGET exists
|
|
28
|
+
* (proxies exist only where read) — never-subscribed subtrees are never
|
|
29
|
+
* walked (recon-snap R17), while a subscriber deep below an untracked path
|
|
30
|
+
* keeps its chain walkable because wrapping created the intermediate
|
|
31
|
+
* targets (recon-snap R16).
|
|
32
|
+
* - Keyed matching ported semantics: key-matched rows keep proxy identity;
|
|
33
|
+
* key mismatch detaches (fresh proxy on next read, recon-snap R18);
|
|
34
|
+
* keyless items fall back positional; null/primitive slots are legal
|
|
35
|
+
* members (R11). Kind changes replace wholesale (R10).
|
|
36
|
+
*/ function reconcileNextState(e, t, n, o = false) {
|
|
37
|
+
if (t == null) throw new Error("");
|
|
38
|
+
const f = t?.[$TARGET];
|
|
39
|
+
if (f === undefined || f.px !== t) throw new Error("");
|
|
40
|
+
// Reconcile's diff walks need a REAL pending container — a prototype
|
|
41
|
+
// overlay (#3044) materializes to the clone path first (edge: reconcile
|
|
42
|
+
// inside a setter that already wrote this target).
|
|
43
|
+
if (f.ovl) materializePB(f);
|
|
44
|
+
let i = n === null ? null : typeof n === "string" ? e => e?.[n] : n;
|
|
45
|
+
// §7b chained backing: a projection derive returning a LIVE store proxy
|
|
46
|
+
// adopts the proxy itself as the backing — reads flow through the inner
|
|
47
|
+
// store's traps, so consumers subscribe to the inner graph and updates
|
|
48
|
+
// flow with no re-derive (#2941). The adoption diff still notifies THIS
|
|
49
|
+
// store's existing subscribers of the swap.
|
|
50
|
+
if (o && e !== t && e?.[$TARGET] !== undefined) {
|
|
51
|
+
const t = f.pb ?? f.v;
|
|
52
|
+
if (t === e) return;
|
|
53
|
+
// already chained to this store
|
|
54
|
+
adoptPB(f, e);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const l = unwrapValue(e);
|
|
58
|
+
if (i) {
|
|
59
|
+
// Root identity precondition — checked before ANY mutation, so a throwing
|
|
60
|
+
// reconcile is atomic by construction (RUL-12 ruling). Projections
|
|
61
|
+
// (replace=true) relax it: a root entity change merges in place — the
|
|
62
|
+
// root proxy is stable for life (proj R5/R11) — and children are NOT
|
|
63
|
+
// key-matched across the entity change (proj R7: keyFn drops to
|
|
64
|
+
// positional so old-entity subtrees never merge into the new entity's).
|
|
65
|
+
const e = f.pb ?? f.v;
|
|
66
|
+
const t = i(e);
|
|
67
|
+
if (t !== undefined && !sameKey(i(l), t)) {
|
|
68
|
+
if (!o) throw new Error("");
|
|
69
|
+
// Entity change: wholesale swap. The root proxy is stable for life
|
|
70
|
+
// (proj R5) but NOTHING below survives — children are never matched
|
|
71
|
+
// across an entity change even when their own keys align (proj R7).
|
|
72
|
+
// Displaced-raw unregistration (proj R10): the outgoing raw stops
|
|
73
|
+
// resolving to this proxy; re-handed later it wraps fresh.
|
|
74
|
+
const e = f.pb ?? f.v;
|
|
75
|
+
if (isOwned(e)) delete e[$OWNER]; // disown: wraps fresh if re-handed
|
|
76
|
+
else (f.fam?.map ?? storeNextLookup).delete(e);
|
|
77
|
+
adoptPB(f, l);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Tentative channel (§6b, RUL-5): a user-context reconcile on an optimistic
|
|
82
|
+
// family parks as engine overrides — values, membership, and length ride
|
|
83
|
+
// armed nodes (reverting with their transaction); committed raw is never
|
|
84
|
+
// touched. Key-matched rows keep proxy identity by descending into the
|
|
85
|
+
// existing child targets instead of overriding their parent slots.
|
|
86
|
+
if (f.fam?.opt === true && !projectionWriteActive && !getWriteOverride()) {
|
|
87
|
+
optHooks.applyTentative(f, l, i);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
applyAdopt(f, l, i, o);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function applyAdopt(e, t, n, o = false) {
|
|
94
|
+
const f = e.pb ?? e.v;
|
|
95
|
+
// The sound identity skip (O7): same reference AND we never diverged it.
|
|
96
|
+
if (t === f && !isOwned(f)) return;
|
|
97
|
+
const i = e.fam;
|
|
98
|
+
// §6b (R28): the diff's previous-arrangement baseline is the LANE VIEW —
|
|
99
|
+
// optimistic rows must be visible to key matching so a landing carrying the
|
|
100
|
+
// same key recycles their proxies. Raw `prev` keeps the identity/ownership
|
|
101
|
+
// roles above; only matching reads the view.
|
|
102
|
+
const l = i?.opt === true ? optHooks.optimisticView(e, f) : f;
|
|
103
|
+
const r = Array.isArray(t);
|
|
104
|
+
// Plain stores notify inline AFTER the descent (child registrations feed
|
|
105
|
+
// the fold diff's identity-preservation check); projections keep deferred
|
|
106
|
+
// folds (downstream holds can form later in the flush).
|
|
107
|
+
const s = i === null;
|
|
108
|
+
const u = e.s === true;
|
|
109
|
+
// Node-notify base is the view the nodes were last told (#3296): a draft
|
|
110
|
+
// preceding this reconcile already moved them to its pending backing at
|
|
111
|
+
// setter exit (prev, materialized above), so diffing incoming against the
|
|
112
|
+
// committed backing would skip a key the draft changed and the reconcile
|
|
113
|
+
// restores — the node would commit the superseded draft value.
|
|
114
|
+
const a = f;
|
|
115
|
+
adoptPB(e, t, s);
|
|
116
|
+
// Shallow adoption: records are slot values — sticky raw-mark the incoming
|
|
117
|
+
// set (R41) and never descend; slot notification is the positional diff.
|
|
118
|
+
if (u) markRawIngest(t);
|
|
119
|
+
if (Array.isArray(l) !== r) {
|
|
120
|
+
if (s) notifyFold(e, a, t);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (r) {
|
|
124
|
+
const f = l;
|
|
125
|
+
const r = t;
|
|
126
|
+
// Fused array walk (eager mode): per-index notification rides the same
|
|
127
|
+
// loop as the descent (descend first — targetsEqual needs the child's
|
|
128
|
+
// re-registration, R9). Length, trailing removed indexes, and any other
|
|
129
|
+
// unvisited node keys land in the counted sweep below.
|
|
130
|
+
const c = s ? e.n : null;
|
|
131
|
+
let d = 0;
|
|
132
|
+
if (n && !u) {
|
|
133
|
+
// Positional-prefix fast path (legacy keyedMatch-walk parity): while
|
|
134
|
+
// rows key-match in place — the steady-state polling shape — descend
|
|
135
|
+
// directly with zero staging. The prevByKey map is built only for the
|
|
136
|
+
// misaligned remainder, and never at all on aligned ticks.
|
|
137
|
+
const l = f.length;
|
|
138
|
+
const s = r.length;
|
|
139
|
+
let u = false;
|
|
140
|
+
let p = 0;
|
|
141
|
+
for (const y = Math.min(l, s); p < y; p++) {
|
|
142
|
+
const l = r[p];
|
|
143
|
+
const s = f[p];
|
|
144
|
+
// Routing heuristic only (aligned vs keyed remainder) — both routes
|
|
145
|
+
// notify identically and descend() is the one authoritative
|
|
146
|
+
// validator, so bare typeof gates suffice here; full isWrappable
|
|
147
|
+
// per row was the walk's dominant residual cost.
|
|
148
|
+
if (s !== l && !(s !== null && typeof s === "object" && l !== null && typeof l === "object" && sameKey(n(s), n(l)))) break;
|
|
149
|
+
// misaligned: fall to the keyed remainder below
|
|
150
|
+
// Identity skip inline (FINDING-1 guard), then descend the pair.
|
|
151
|
+
if ((s !== l || l !== null && typeof l === "object" && isOwned(l)) && l !== null && typeof l === "object") descend(unwrapValue(s), l, n, i, o);
|
|
152
|
+
if (e.dk !== null && !u && !(l !== null && typeof l === "object" ? targetsEqual(s, l) : isEqual(s, l))) {
|
|
153
|
+
bumpDeep(e);
|
|
154
|
+
u = true;
|
|
155
|
+
}
|
|
156
|
+
if (c !== null) {
|
|
157
|
+
const e = c[p];
|
|
158
|
+
if (e !== undefined) {
|
|
159
|
+
d++;
|
|
160
|
+
notifyKeyValue(e, p, a[p], l, a, t);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (e.dk !== null && !u && p < r.length) bumpDeep(e);
|
|
165
|
+
const y = p;
|
|
166
|
+
// misalignment point (== nlen on aligned ticks)
|
|
167
|
+
let w = null;
|
|
168
|
+
for (;p < r.length; p++) {
|
|
169
|
+
const e = r[p];
|
|
170
|
+
// typeof gates route; descend validates (same contract as the prefix).
|
|
171
|
+
if (e !== null && typeof e === "object") {
|
|
172
|
+
const t = n(e);
|
|
173
|
+
let l;
|
|
174
|
+
if (t !== undefined) {
|
|
175
|
+
if (w === null) {
|
|
176
|
+
// Occurrence-aware (re-audit 2, P1-5): duplicate keys queue
|
|
177
|
+
// their prev INDICES (rows can themselves be arrays, so index
|
|
178
|
+
// queues are the unambiguous encoding — same as buildRowOps)
|
|
179
|
+
// and each is consumed ONCE. First-wins would adopt two next
|
|
180
|
+
// rows into the SAME prev target while row ops retain two
|
|
181
|
+
// separate DOM rows (the second one stale).
|
|
182
|
+
w = new Map;
|
|
183
|
+
// From structStart, not 0 (re-audit 3, P1-2): prefix-aligned
|
|
184
|
+
// rows already adopted their incoming counterparts — re-offering
|
|
185
|
+
// them here let a duplicate key adopt a prefix row AGAIN while
|
|
186
|
+
// row ops (which correctly window from structStart) retained
|
|
187
|
+
// the later occurrence's DOM row against a never-adopted target.
|
|
188
|
+
for (let e = y; e < f.length; e++) {
|
|
189
|
+
const t = unwrapValue(f[e]);
|
|
190
|
+
if (t !== null && typeof t === "object") {
|
|
191
|
+
const o = n(t);
|
|
192
|
+
if (o === undefined) continue;
|
|
193
|
+
const f = w.get(o);
|
|
194
|
+
if (f === undefined) w.set(o, e); else if (Array.isArray(f)) f.push(e); else w.set(o, [ f, e ]);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const e = w.get(t);
|
|
199
|
+
if (e === undefined) l = undefined; else if (Array.isArray(e)) {
|
|
200
|
+
l = unwrapValue(f[e.shift()]);
|
|
201
|
+
if (e.length === 1) w.set(t, e[0]);
|
|
202
|
+
} else {
|
|
203
|
+
l = unwrapValue(f[e]);
|
|
204
|
+
w.delete(t);
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
l = unwrapValue(f[p]);
|
|
208
|
+
// keyless item: positional fallback
|
|
209
|
+
}
|
|
210
|
+
descend(l, e, n, i, o);
|
|
211
|
+
}
|
|
212
|
+
if (c !== null) {
|
|
213
|
+
const e = c[p];
|
|
214
|
+
if (e !== undefined) {
|
|
215
|
+
d++;
|
|
216
|
+
notifyKeyDiff(e, p, a, t, false);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
} else {
|
|
221
|
+
const l = Math.min(f.length, r.length);
|
|
222
|
+
const s = r.length;
|
|
223
|
+
let p = false;
|
|
224
|
+
for (let y = 0; y < s; y++) {
|
|
225
|
+
const s = r[y];
|
|
226
|
+
if (!u && y < l && s !== null && typeof s === "object") descend(unwrapValue(f[y]), s, n, i, o);
|
|
227
|
+
if (e.dk !== null && !p && !(s !== null && typeof s === "object" ? targetsEqual(f[y], s) : isEqual(f[y], s))) {
|
|
228
|
+
bumpDeep(e);
|
|
229
|
+
p = true;
|
|
230
|
+
}
|
|
231
|
+
if (c !== null) {
|
|
232
|
+
const e = c[y];
|
|
233
|
+
if (e !== undefined) {
|
|
234
|
+
d++;
|
|
235
|
+
notifyKeyDiff(e, y, a, t, false);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (s) {
|
|
241
|
+
if (c !== null && d < e.nc) {
|
|
242
|
+
for (const e of Reflect.ownKeys(c)) {
|
|
243
|
+
// visited indexes are < nextRows.length; everything else sweeps
|
|
244
|
+
const n = typeof e === "string" ? +e : NaN;
|
|
245
|
+
if (!(n >= 0 && n < r.length)) notifyKeyDiff(c[e], e, a, t, false);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
notifyFoldTail(e, a, t);
|
|
249
|
+
}
|
|
250
|
+
return;
|
|
251
|
+
} else {
|
|
252
|
+
// FUSED adoption walk (eager mode): one pass fetches each key's pair,
|
|
253
|
+
// descends, then notifies its node inline — descend runs FIRST so the
|
|
254
|
+
// child's re-registration is visible to targetsEqual (identity-preserved
|
|
255
|
+
// slots must not notify, R9). This replaces the notifyFold re-walk that
|
|
256
|
+
// doubled dbmon's diff cost. for-in covers own enumerable string keys
|
|
257
|
+
// with no key-array allocation; symbols get a pass only when present.
|
|
258
|
+
const f = s ? e.n : null;
|
|
259
|
+
let r = 0;
|
|
260
|
+
let c = false;
|
|
261
|
+
// The per-key body is inlined on purpose (legacy applyStateFast parity:
|
|
262
|
+
// an extracted helper costs a call per key on the hottest object-diff
|
|
263
|
+
// site). Reference-identical values early-continue BEFORE any other
|
|
264
|
+
// work — sound only with the ownership guard (FINDING-1: an owned
|
|
265
|
+
// backing is setter-diverged and must still diff).
|
|
266
|
+
for (const s in t) {
|
|
267
|
+
const d = t[s];
|
|
268
|
+
const p = a[s];
|
|
269
|
+
const y = d !== null && typeof d === "object";
|
|
270
|
+
if (p === d && (!y || !isOwned(d)) && (f === null || f[s] === undefined || !hasAccessorFlag(f[s]))) {
|
|
271
|
+
if (f !== null && f[s] !== undefined) r++;
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
if (y && !u) descend(unwrapValue(l[s]), d, n, i, o);
|
|
275
|
+
// Deep-witness (dk): value changes must notify even with NO per-key
|
|
276
|
+
// node — deep() subscribes one node per record. Checked after descend
|
|
277
|
+
// so in-place adoptions (same logical slot) don't bump; child records
|
|
278
|
+
// carry their own witness. One flag + null check when unused.
|
|
279
|
+
if (e.dk !== null && !c && !(y ? targetsEqual(p, d) : isEqual(p, d))) {
|
|
280
|
+
bumpDeep(e);
|
|
281
|
+
c = true;
|
|
282
|
+
}
|
|
283
|
+
if (f !== null) {
|
|
284
|
+
const e = f[s];
|
|
285
|
+
if (e !== undefined) {
|
|
286
|
+
r++;
|
|
287
|
+
notifyKeyValue(e, s, p, d, a, t);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
const d = Object.getOwnPropertySymbols(t);
|
|
292
|
+
for (let e = 0; e < d.length; e++) {
|
|
293
|
+
const s = d[e];
|
|
294
|
+
if (s === $OWNER) continue;
|
|
295
|
+
const c = t[s];
|
|
296
|
+
if (!u && c !== null && typeof c === "object") descend(unwrapValue(l[s]), c, n, i, o);
|
|
297
|
+
if (f !== null) {
|
|
298
|
+
const e = f[s];
|
|
299
|
+
if (e !== undefined) {
|
|
300
|
+
r++;
|
|
301
|
+
notifyKeyValue(e, s, a[s], c, a, t);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (s) {
|
|
306
|
+
// Deleted-key nodes (in the map but absent from incoming) — counted
|
|
307
|
+
// fast-out: when every node was visited, skip the sweep entirely.
|
|
308
|
+
if (f !== null && r < e.nc) {
|
|
309
|
+
for (const e of Reflect.ownKeys(f)) {
|
|
310
|
+
if (!hasOwnP.call(t, e)) notifyKeyDiff(f[e], e, a, t, false);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
notifyFoldTail(e, a, t);
|
|
314
|
+
}
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const hasOwnP = Object.prototype.hasOwnProperty;
|
|
320
|
+
|
|
321
|
+
/** Setter-channel row ops (the fold site calls this for array targets with
|
|
322
|
+
* ops consumers): structural mutation through the setter — push/splice/index
|
|
323
|
+
/** Key equality for EVERY key comparison in this module (re-audit 2, P1-5):
|
|
324
|
+
* SameValueZero, matching the adoption window's Map-based matcher — NaN keys
|
|
325
|
+
* are equal to themselves, so aligned NaN rows stay aligned in the prefix
|
|
326
|
+
* walk instead of forever misaligning. */ function sameKey(e, t) {
|
|
327
|
+
return e === t || e !== e && t !== t;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function descend(e, t, n, o, f = false) {
|
|
331
|
+
if (e === null || typeof e !== "object" || t === null || typeof t !== "object") return;
|
|
332
|
+
// Lookup FIRST: a hit implies pv was wrappable and never raw-marked (only
|
|
333
|
+
// wrappables acquire targets; rawValues never wrap) — one WeakMap get
|
|
334
|
+
// replaces isWrappable(pv) + isRawValue(pv), and a miss prunes untracked
|
|
335
|
+
// subtrees before any further checks.
|
|
336
|
+
const i = lookupTarget(e, o);
|
|
337
|
+
if (i === undefined) return;
|
|
338
|
+
// nothing proxied below this pair
|
|
339
|
+
// The NEW side still validates fully: a frozen/platform/markRaw'd incoming
|
|
340
|
+
// value is a leaf for reconcile — replaced by reference, never recursed
|
|
341
|
+
// into (R42); the parent's slot notification covers the change.
|
|
342
|
+
if (!isWrappable(t)) return;
|
|
343
|
+
if (rawValuesUsed && isRawValue(t)) return;
|
|
344
|
+
t = unwrapValue(t);
|
|
345
|
+
// Kind change replaces wholesale, never merges (R10): a target's carrier
|
|
346
|
+
// class (array vs object) is fixed at creation, so the slot detaches and a
|
|
347
|
+
// fresh proxy of the right kind wraps the incoming value on next read.
|
|
348
|
+
if (Array.isArray(e) !== Array.isArray(t)) return;
|
|
349
|
+
if (n) {
|
|
350
|
+
const o = n(e);
|
|
351
|
+
const f = n(t);
|
|
352
|
+
// Key mismatch detaches: the slot takes the new entity; the old proxy
|
|
353
|
+
// keeps its (old) backing and a fresh proxy wraps the new value on read.
|
|
354
|
+
// SameValueZero (re-audit 2, P1-5): NaN keys are self-equal — strict
|
|
355
|
+
// inequality detached every NaN-keyed slot on every tick while the
|
|
356
|
+
// Map-based row-ops matcher retained its DOM row (stale forever).
|
|
357
|
+
if (o !== undefined && f !== undefined && !sameKey(o, f)) return;
|
|
358
|
+
}
|
|
359
|
+
// Reachability pruning (§6d) is MODE-dependent, both pinned:
|
|
360
|
+
// - keyed matching descends only where subscriptions exist at/below (`d`) —
|
|
361
|
+
// captured-but-unobserved proxies deliberately detach and go stale
|
|
362
|
+
// (recon-snap R18; subscribing is what buys liveness);
|
|
363
|
+
// - positional (key: null) pairing preserves slot identity unconditionally
|
|
364
|
+
// (recon-snap R8 — the fixed-shape dashboard pattern).
|
|
365
|
+
// Projection merges (replace mode) preserve key-matched identity
|
|
366
|
+
// UNCONDITIONALLY (proj R6: the slot keeps its proxy without needing a
|
|
367
|
+
// subscriber below); plain keyed reconcile detaches unobserved captures
|
|
368
|
+
// (recon-snap R18 — staleness is the pinned pruning contract).
|
|
369
|
+
if (!f && n !== null && !i.d) return;
|
|
370
|
+
applyAdopt(i, t, n, f);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export { reconcileNextState, sameKey };
|