@solidjs/signals 2.0.0-rc.3 → 2.0.0-rc.4
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.js +1334 -94
- package/dist/node.cjs +2589 -1367
- package/dist/prod/affects.js +13 -12
- package/dist/prod/boundaries.js +39 -34
- package/dist/prod/core/action.js +3 -3
- package/dist/prod/core/async.js +48 -46
- package/dist/prod/core/core.js +99 -67
- package/dist/prod/core/effect.js +25 -28
- package/dist/prod/core/external.js +2 -2
- package/dist/prod/core/graph.js +85 -49
- package/dist/prod/core/heap.js +10 -10
- package/dist/prod/core/lanes.js +19 -19
- package/dist/prod/core/optimistic.js +36 -33
- package/dist/prod/core/owner.js +13 -13
- package/dist/prod/core/scheduler.js +131 -85
- package/dist/prod/core/verdict.js +29 -15
- package/dist/prod/index.js +4 -0
- package/dist/prod/map.js +101 -101
- package/dist/prod/signals.js +1 -1
- package/dist/prod/store/index.js +2 -0
- package/dist/prod/store/next/optimistic.js +65 -11
- package/dist/prod/store/next/patch-hooks.js +13 -0
- package/dist/prod/store/next/patch.js +614 -0
- package/dist/prod/store/next/reconcile.js +307 -120
- package/dist/prod/store/next/store.js +321 -92
- package/dist/prod/store/next/target.js +13 -4
- package/dist/prod/store/store.js +5 -5
- package/dist/types/core/core.d.ts +15 -1
- package/dist/types/core/dev.d.ts +8 -0
- package/dist/types/core/graph.d.ts +22 -0
- package/dist/types/core/scheduler.d.ts +12 -0
- package/dist/types/store/index.d.ts +2 -0
- package/dist/types/store/next/patch-hooks.d.ts +41 -0
- package/dist/types/store/next/patch.d.ts +91 -0
- package/dist/types/store/next/reconcile.d.ts +14 -0
- package/dist/types/store/next/store.d.ts +30 -2
- package/dist/types/store/next/target.d.ts +58 -8
- package/dist/types-cjs/core/core.d.cts +15 -1
- package/dist/types-cjs/core/dev.d.cts +8 -0
- package/dist/types-cjs/core/graph.d.cts +22 -0
- package/dist/types-cjs/core/scheduler.d.cts +12 -0
- package/dist/types-cjs/store/index.d.cts +2 -0
- package/dist/types-cjs/store/next/patch-hooks.d.cts +41 -0
- package/dist/types-cjs/store/next/patch.d.cts +91 -0
- package/dist/types-cjs/store/next/reconcile.d.cts +14 -0
- package/dist/types-cjs/store/next/store.d.cts +30 -2
- package/dist/types-cjs/store/next/target.d.cts +58 -8
- package/package.json +2 -2
|
@@ -8,6 +8,8 @@ import "../../core/verdict.js";
|
|
|
8
8
|
|
|
9
9
|
import "../../core/effect.js";
|
|
10
10
|
|
|
11
|
+
import { patchHooks, rowHooks } from "./patch-hooks.js";
|
|
12
|
+
|
|
11
13
|
import { $TARGET, markRawIngest, isWrappable, rawValuesUsed, isRawValue, getWriteOverride } from "../store.js";
|
|
12
14
|
|
|
13
15
|
import { materializePB, adoptPB, unwrapValue, notifyFold, targetsEqual, bumpDeep, notifyKeyValue, notifyKeyDiff, notifyFoldTail, hasAccessorFlag } from "./store.js";
|
|
@@ -33,29 +35,29 @@ import { storeNextLookup, optHooks, ownedRaw } from "./target.js";
|
|
|
33
35
|
* key mismatch detaches (fresh proxy on next read, recon-snap R18);
|
|
34
36
|
* keyless items fall back positional; null/primitive slots are legal
|
|
35
37
|
* members (R11). Kind changes replace wholesale (R10).
|
|
36
|
-
*/ function reconcileNextState(e,
|
|
37
|
-
if (
|
|
38
|
-
const l =
|
|
39
|
-
if (l === undefined || l.px !==
|
|
38
|
+
*/ function reconcileNextState(e, n, t, o = false) {
|
|
39
|
+
if (n == null) throw new Error("");
|
|
40
|
+
const l = n?.[$TARGET];
|
|
41
|
+
if (l === undefined || l.px !== n) throw new Error("");
|
|
40
42
|
// Reconcile's diff walks need a REAL pending container — a prototype
|
|
41
43
|
// overlay (#3044) materializes to the clone path first (edge: reconcile
|
|
42
44
|
// inside a setter that already wrote this target).
|
|
43
45
|
if (l.ovl) materializePB(l);
|
|
44
|
-
let
|
|
46
|
+
let i = t === null ? null : typeof t === "string" ? e => e?.[t] : t;
|
|
45
47
|
// §7b chained backing: a projection derive returning a LIVE store proxy
|
|
46
48
|
// adopts the proxy itself as the backing — reads flow through the inner
|
|
47
49
|
// store's traps, so consumers subscribe to the inner graph and updates
|
|
48
50
|
// flow with no re-derive (#2941). The adoption diff still notifies THIS
|
|
49
51
|
// store's existing subscribers of the swap.
|
|
50
|
-
if (o && e !==
|
|
51
|
-
const
|
|
52
|
-
if (
|
|
52
|
+
if (o && e !== n && e?.[$TARGET] !== undefined) {
|
|
53
|
+
const n = l.pb ?? l.v;
|
|
54
|
+
if (n === e) return;
|
|
53
55
|
// already chained to this store
|
|
54
56
|
adoptPB(l, e);
|
|
55
57
|
return;
|
|
56
58
|
}
|
|
57
|
-
const
|
|
58
|
-
if (
|
|
59
|
+
const u = unwrapValue(e);
|
|
60
|
+
if (i) {
|
|
59
61
|
// Root identity precondition — checked before ANY mutation, so a throwing
|
|
60
62
|
// reconcile is atomic by construction (RUL-12 ruling). Projections
|
|
61
63
|
// (replace=true) relax it: a root entity change merges in place — the
|
|
@@ -63,8 +65,8 @@ import { storeNextLookup, optHooks, ownedRaw } from "./target.js";
|
|
|
63
65
|
// key-matched across the entity change (proj R7: keyFn drops to
|
|
64
66
|
// positional so old-entity subtrees never merge into the new entity's).
|
|
65
67
|
const e = l.pb ?? l.v;
|
|
66
|
-
const
|
|
67
|
-
if (
|
|
68
|
+
const n = i(e);
|
|
69
|
+
if (n !== undefined && !sameKey(i(u), n)) {
|
|
68
70
|
if (!o) throw new Error("");
|
|
69
71
|
// Entity change: wholesale swap. The root proxy is stable for life
|
|
70
72
|
// (proj R5) but NOTHING below survives — children are never matched
|
|
@@ -72,7 +74,7 @@ import { storeNextLookup, optHooks, ownedRaw } from "./target.js";
|
|
|
72
74
|
// Displaced-raw unregistration (proj R10): the outgoing raw stops
|
|
73
75
|
// resolving to this proxy; re-handed later it wraps fresh.
|
|
74
76
|
(l.fam?.map ?? storeNextLookup).delete(l.pb ?? l.v);
|
|
75
|
-
adoptPB(l,
|
|
77
|
+
adoptPB(l, u);
|
|
76
78
|
return;
|
|
77
79
|
}
|
|
78
80
|
}
|
|
@@ -82,141 +84,225 @@ import { storeNextLookup, optHooks, ownedRaw } from "./target.js";
|
|
|
82
84
|
// touched. Key-matched rows keep proxy identity by descending into the
|
|
83
85
|
// existing child targets instead of overriding their parent slots.
|
|
84
86
|
if (l.fam?.opt === true && !projectionWriteActive && !getWriteOverride()) {
|
|
85
|
-
optHooks.applyTentative(l,
|
|
87
|
+
optHooks.applyTentative(l, u, i);
|
|
86
88
|
return;
|
|
87
89
|
}
|
|
88
|
-
applyAdopt(l,
|
|
90
|
+
applyAdopt(l, u, i, o);
|
|
89
91
|
}
|
|
90
92
|
|
|
91
|
-
function applyAdopt(e,
|
|
93
|
+
function applyAdopt(e, n, t, o = false) {
|
|
92
94
|
const l = e.pb ?? e.v;
|
|
93
95
|
// The sound identity skip (O7): same reference AND we never diverged it.
|
|
94
|
-
if (
|
|
95
|
-
const
|
|
96
|
+
if (n === l && !ownedRaw.has(l)) return;
|
|
97
|
+
const i = e.fam;
|
|
96
98
|
// §6b (R28): the diff's previous-arrangement baseline is the LANE VIEW —
|
|
97
99
|
// optimistic rows must be visible to key matching so a landing carrying the
|
|
98
100
|
// same key recycles their proxies. Raw `prev` keeps the identity/ownership
|
|
99
101
|
// roles above; only matching reads the view.
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
+
const u = i?.opt === true ? optHooks.optimisticView(e, l) : l;
|
|
103
|
+
const f = Array.isArray(n);
|
|
102
104
|
// Plain stores notify inline AFTER the descent (child registrations feed
|
|
103
105
|
// the fold diff's identity-preservation check); projections keep deferred
|
|
104
106
|
// folds (downstream holds can form later in the flush).
|
|
105
|
-
const
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
adoptPB(e,
|
|
107
|
+
const s = i === null;
|
|
108
|
+
const r = e.s === true;
|
|
109
|
+
const c = e.v;
|
|
110
|
+
adoptPB(e, n, s);
|
|
111
|
+
// Patch channel (adoption site): this record transitioned — queue its
|
|
112
|
+
// patches with the pre-adopt prev. No bubbling walk: the adoption walk
|
|
113
|
+
// visits parents before children, so ancestors emitted already. EAGER
|
|
114
|
+
// only — family targets' visibility moment is their fold commit
|
|
115
|
+
// (drainFolds emits there; emitting here too would double-fire).
|
|
116
|
+
if (patchHooks !== null && s && e.pc !== null && e.pc.p !== null) {
|
|
117
|
+
// Accessor demotion at the ADOPTION seam is DEV-ONLY (prod principle:
|
|
118
|
+
// explicitly-odd input must not cost correct-input prod — the
|
|
119
|
+
// per-adoption scan was ~12% of dbmon's tick since adoptPB resets the
|
|
120
|
+
// verdict every adoption). Dev demotes AND warns; prod emits directly,
|
|
121
|
+
// so a getter adoptee's OUTSIDE deps (signals) won't re-apply in prod —
|
|
122
|
+
// caught loudly during development instead. Registration-time admission
|
|
123
|
+
// (patchableRaw) keeps its full one-time scan in both modes.
|
|
124
|
+
{
|
|
125
|
+
patchHooks.emitPatchLocal(e, n, c);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
109
128
|
// Shallow adoption: records are slot values — sticky raw-mark the incoming
|
|
110
129
|
// set (R41) and never descend; slot notification is the positional diff.
|
|
111
|
-
if (
|
|
112
|
-
if (Array.isArray(
|
|
113
|
-
if (
|
|
130
|
+
if (r) markRawIngest(n);
|
|
131
|
+
if (Array.isArray(u) !== f) {
|
|
132
|
+
if (s) notifyFold(e, c, n);
|
|
114
133
|
return;
|
|
115
134
|
}
|
|
116
|
-
if (
|
|
117
|
-
const l =
|
|
118
|
-
const
|
|
135
|
+
if (f) {
|
|
136
|
+
const l = u;
|
|
137
|
+
const f = n;
|
|
119
138
|
// Fused array walk (eager mode): per-index notification rides the same
|
|
120
139
|
// loop as the descent (descend first — targetsEqual needs the child's
|
|
121
140
|
// re-registration, R9). Length, trailing removed indexes, and any other
|
|
122
141
|
// unvisited node keys land in the counted sweep below.
|
|
123
|
-
const
|
|
142
|
+
const a = s ? e.n : null;
|
|
124
143
|
let p = 0;
|
|
125
|
-
if (
|
|
144
|
+
if (t && !r) {
|
|
126
145
|
// Positional-prefix fast path (legacy keyedMatch-walk parity): while
|
|
127
146
|
// rows key-match in place — the steady-state polling shape — descend
|
|
128
147
|
// directly with zero staging. The prevByKey map is built only for the
|
|
129
148
|
// misaligned remainder, and never at all on aligned ticks.
|
|
130
|
-
const
|
|
131
|
-
const
|
|
132
|
-
let
|
|
149
|
+
const u = l.length;
|
|
150
|
+
const s = f.length;
|
|
151
|
+
let r = false;
|
|
133
152
|
let d = 0;
|
|
134
|
-
for (const y = Math.min(
|
|
135
|
-
const
|
|
136
|
-
const
|
|
153
|
+
for (const y = Math.min(u, s); d < y; d++) {
|
|
154
|
+
const u = f[d];
|
|
155
|
+
const s = l[d];
|
|
137
156
|
// Routing heuristic only (aligned vs keyed remainder) — both routes
|
|
138
157
|
// notify identically and descend() is the one authoritative
|
|
139
158
|
// validator, so bare typeof gates suffice here; full isWrappable
|
|
140
159
|
// per row was the walk's dominant residual cost.
|
|
141
|
-
if (
|
|
160
|
+
if (s !== u && !(s !== null && typeof s === "object" && u !== null && typeof u === "object" && sameKey(t(s), t(u)))) break;
|
|
142
161
|
// misaligned: fall to the keyed remainder below
|
|
143
162
|
// Identity skip inline (FINDING-1 guard), then descend the pair.
|
|
144
|
-
if ((
|
|
145
|
-
if (e.dk !== null && !
|
|
163
|
+
if ((s !== u || u !== null && typeof u === "object" && ownedRaw.has(u)) && u !== null && typeof u === "object") descend(unwrapValue(s), u, t, i, o);
|
|
164
|
+
if (e.dk !== null && !r && !(u !== null && typeof u === "object" ? targetsEqual(s, u) : isEqual(s, u))) {
|
|
146
165
|
bumpDeep(e);
|
|
147
|
-
|
|
166
|
+
r = true;
|
|
148
167
|
}
|
|
149
|
-
if (
|
|
150
|
-
const e =
|
|
168
|
+
if (a !== null) {
|
|
169
|
+
const e = a[d];
|
|
151
170
|
if (e !== undefined) {
|
|
152
171
|
p++;
|
|
153
|
-
notifyKeyValue(e, d,
|
|
172
|
+
notifyKeyValue(e, d, c[d], u, c, n);
|
|
154
173
|
}
|
|
155
174
|
}
|
|
156
175
|
}
|
|
157
|
-
if (e.dk !== null && !
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
176
|
+
if (e.dk !== null && !r && d < f.length) bumpDeep(e);
|
|
177
|
+
const y = d;
|
|
178
|
+
// misalignment point (== nlen on aligned ticks)
|
|
179
|
+
let w = null;
|
|
180
|
+
for (;d < f.length; d++) {
|
|
181
|
+
const e = f[d];
|
|
161
182
|
// typeof gates route; descend validates (same contract as the prefix).
|
|
162
183
|
if (e !== null && typeof e === "object") {
|
|
163
|
-
const
|
|
164
|
-
let
|
|
165
|
-
if (
|
|
166
|
-
if (
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
184
|
+
const n = t(e);
|
|
185
|
+
let u;
|
|
186
|
+
if (n !== undefined) {
|
|
187
|
+
if (w === null) {
|
|
188
|
+
// Occurrence-aware (re-audit 2, P1-5): duplicate keys queue
|
|
189
|
+
// their prev INDICES (rows can themselves be arrays, so index
|
|
190
|
+
// queues are the unambiguous encoding — same as buildRowOps)
|
|
191
|
+
// and each is consumed ONCE. First-wins would adopt two next
|
|
192
|
+
// rows into the SAME prev target while row ops retain two
|
|
193
|
+
// separate DOM rows (the second one stale).
|
|
194
|
+
w = new Map;
|
|
195
|
+
// From structStart, not 0 (re-audit 3, P1-2): prefix-aligned
|
|
196
|
+
// rows already adopted their incoming counterparts — re-offering
|
|
197
|
+
// them here let a duplicate key adopt a prefix row AGAIN while
|
|
198
|
+
// row ops (which correctly window from structStart) retained
|
|
199
|
+
// the later occurrence's DOM row against a never-adopted target.
|
|
200
|
+
for (let e = y; e < l.length; e++) {
|
|
201
|
+
const n = unwrapValue(l[e]);
|
|
202
|
+
if (n !== null && typeof n === "object") {
|
|
203
|
+
const o = t(n);
|
|
204
|
+
if (o === undefined) continue;
|
|
205
|
+
const l = w.get(o);
|
|
206
|
+
if (l === undefined) w.set(o, e); else if (Array.isArray(l)) l.push(e); else w.set(o, [ l, e ]);
|
|
173
207
|
}
|
|
174
208
|
}
|
|
175
209
|
}
|
|
176
|
-
|
|
210
|
+
const e = w.get(n);
|
|
211
|
+
if (e === undefined) u = undefined; else if (Array.isArray(e)) {
|
|
212
|
+
u = unwrapValue(l[e.shift()]);
|
|
213
|
+
if (e.length === 1) w.set(n, e[0]);
|
|
214
|
+
} else {
|
|
215
|
+
u = unwrapValue(l[e]);
|
|
216
|
+
w.delete(n);
|
|
217
|
+
}
|
|
177
218
|
} else {
|
|
178
|
-
|
|
219
|
+
u = unwrapValue(l[d]);
|
|
179
220
|
// keyless item: positional fallback
|
|
180
221
|
}
|
|
181
|
-
descend(
|
|
222
|
+
descend(u, e, t, i, o);
|
|
182
223
|
}
|
|
183
|
-
if (
|
|
184
|
-
const e =
|
|
224
|
+
if (a !== null) {
|
|
225
|
+
const e = a[d];
|
|
185
226
|
if (e !== undefined) {
|
|
186
227
|
p++;
|
|
187
|
-
notifyKeyDiff(e, d,
|
|
228
|
+
notifyKeyDiff(e, d, c, n, false);
|
|
188
229
|
}
|
|
189
230
|
}
|
|
190
231
|
}
|
|
232
|
+
// Row ops (PR-B): emit structural ops ONLY when structure changed —
|
|
233
|
+
// aligned value ticks pay nothing. Built after the walk so retained
|
|
234
|
+
// rows' value patches queue first (adds bind at op-apply).
|
|
235
|
+
if (rowHooks !== null && e.pc !== null && e.pc.ro !== null && (y < s || u !== s)) buildAndEmitRowOps(e, l, f, y, t);
|
|
191
236
|
} else {
|
|
192
|
-
const
|
|
193
|
-
const
|
|
237
|
+
const u = Math.min(l.length, f.length);
|
|
238
|
+
const s = f.length;
|
|
194
239
|
let d = false;
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
240
|
+
const y = rowHooks !== null && e.pc !== null ? e.pc.sp : null;
|
|
241
|
+
// Row ops for shallow/positional lists: track the key-aligned prefix
|
|
242
|
+
// (keyed) so aligned value ticks emit nothing; keyless lists emit only
|
|
243
|
+
// on length change (append/truncate). Slot-patch consumers need the
|
|
244
|
+
// alignment tracking too (aligned = value tick, misaligned = ops).
|
|
245
|
+
const w = rowHooks !== null && e.pc !== null ? e.pc.ro : null;
|
|
246
|
+
let b = t !== null && (w !== null || y !== null);
|
|
247
|
+
let m = 0;
|
|
248
|
+
for (let w = 0; w < s; w++) {
|
|
249
|
+
const s = f[w];
|
|
250
|
+
if (b && w < u) {
|
|
251
|
+
const e = l[w];
|
|
252
|
+
if (e !== null && typeof e === "object" && s !== null && typeof s === "object" &&
|
|
253
|
+
// SameValueZero (self-sweep): strict === here broke slot
|
|
254
|
+
// alignment on NaN keys while buildRowOps retained the row —
|
|
255
|
+
// retained DOM with suppressed value ticks (the round-1 NaN
|
|
256
|
+
// staleness, in the shallow branch).
|
|
257
|
+
sameKey(t(e), t(s))) m++; else b = false;
|
|
258
|
+
}
|
|
259
|
+
// Slot-patch dispatch (shallow): a KEY-ALIGNED slot whose value was
|
|
260
|
+
// replaced by reference is a value tick — emit through the queue.
|
|
261
|
+
// Misaligned/appended slots are STRUCTURE (row ops rebuild or move
|
|
262
|
+
// them; new rows initial-apply at bind), so they emit nothing here.
|
|
263
|
+
// Keyless positional lists treat same-index replacement as the value
|
|
264
|
+
// tick for indices below the common length.
|
|
265
|
+
// `i < dlen` is load-bearing for BOTH modes: an appended position
|
|
266
|
+
// past a fully-aligned prefix (vacuously aligned when prev is empty)
|
|
267
|
+
// has no previous slot — emitting a slot tick for it races the row
|
|
268
|
+
// ops that CREATE the row (the slot queue applies first, indexing a
|
|
269
|
+
// row that does not exist yet). Equivalence-matrix finding:
|
|
270
|
+
// clear-then-refill and pure appends crashed the driver.
|
|
271
|
+
if (y !== null && w < u && (t === null || b)) {
|
|
272
|
+
const n = l[w];
|
|
273
|
+
if (n !== s) rowHooks.emitSlotPatch(e, w, s, n);
|
|
274
|
+
}
|
|
275
|
+
if (!r && w < u && s !== null && typeof s === "object") descend(unwrapValue(l[w]), s, t, i, o);
|
|
276
|
+
if (e.dk !== null && !d && !(s !== null && typeof s === "object" ? targetsEqual(l[w], s) : isEqual(l[w], s))) {
|
|
199
277
|
bumpDeep(e);
|
|
200
278
|
d = true;
|
|
201
279
|
}
|
|
202
|
-
if (
|
|
203
|
-
const e =
|
|
280
|
+
if (a !== null) {
|
|
281
|
+
const e = a[w];
|
|
204
282
|
if (e !== undefined) {
|
|
205
283
|
p++;
|
|
206
|
-
notifyKeyDiff(e,
|
|
284
|
+
notifyKeyDiff(e, w, c, n, false);
|
|
207
285
|
}
|
|
208
286
|
}
|
|
209
287
|
}
|
|
288
|
+
if (w !== null) {
|
|
289
|
+
const n = l.length;
|
|
290
|
+
if (t !== null) {
|
|
291
|
+
if (m < s || n !== s) buildAndEmitRowOps(e, l, f, m, t);
|
|
292
|
+
} else if (n !== s) {
|
|
293
|
+
buildAndEmitRowOps(e, l, f, u, null);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
210
296
|
}
|
|
211
|
-
if (
|
|
212
|
-
if (
|
|
213
|
-
for (const e of Reflect.ownKeys(
|
|
297
|
+
if (s) {
|
|
298
|
+
if (a !== null && p < e.nc) {
|
|
299
|
+
for (const e of Reflect.ownKeys(a)) {
|
|
214
300
|
// visited indexes are < nextRows.length; everything else sweeps
|
|
215
|
-
const
|
|
216
|
-
if (!(
|
|
301
|
+
const t = typeof e === "string" ? +e : NaN;
|
|
302
|
+
if (!(t >= 0 && t < f.length)) notifyKeyDiff(a[e], e, c, n, false);
|
|
217
303
|
}
|
|
218
304
|
}
|
|
219
|
-
notifyFoldTail(e,
|
|
305
|
+
notifyFoldTail(e, c, n);
|
|
220
306
|
}
|
|
221
307
|
return;
|
|
222
308
|
} else {
|
|
@@ -226,61 +312,68 @@ function applyAdopt(e, t, n, o = false) {
|
|
|
226
312
|
// slots must not notify, R9). This replaces the notifyFold re-walk that
|
|
227
313
|
// doubled dbmon's diff cost. for-in covers own enumerable string keys
|
|
228
314
|
// with no key-array allocation; symbols get a pass only when present.
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
315
|
+
// PROTOTYPE compiled-patch fast path: a pure-patch record (no nodes,
|
|
316
|
+
// no presence/key-set/deep subscribers, no family) adopts and hands the
|
|
317
|
+
// (next, prev) pair to its compiled patch — no per-key walk at all.
|
|
318
|
+
if (e.pc !== null && e.pc.p !== null && s && e.n === null && e.h === null && e.k === null && e.dk === null && i === null) {
|
|
319
|
+
// Adoption already ran at applyAdopt entry; emission was queued there.
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
const l = s ? e.n : null;
|
|
323
|
+
let f = 0;
|
|
324
|
+
let a = false;
|
|
232
325
|
// The per-key body is inlined on purpose (legacy applyStateFast parity:
|
|
233
326
|
// an extracted helper costs a call per key on the hottest object-diff
|
|
234
327
|
// site). Reference-identical values early-continue BEFORE any other
|
|
235
328
|
// work — sound only with the ownership guard (FINDING-1: an owned
|
|
236
329
|
// backing is setter-diverged and must still diff).
|
|
237
|
-
for (const
|
|
238
|
-
const p =
|
|
239
|
-
const d =
|
|
330
|
+
for (const s in n) {
|
|
331
|
+
const p = n[s];
|
|
332
|
+
const d = c[s];
|
|
240
333
|
const y = p !== null && typeof p === "object";
|
|
241
|
-
if (d === p && (!y || !ownedRaw.has(p)) && (l === null || l[
|
|
242
|
-
if (l !== null && l[
|
|
334
|
+
if (d === p && (!y || !ownedRaw.has(p)) && (l === null || l[s] === undefined || !hasAccessorFlag(l[s]))) {
|
|
335
|
+
if (l !== null && l[s] !== undefined) f++;
|
|
243
336
|
continue;
|
|
244
337
|
}
|
|
245
|
-
if (y && !
|
|
338
|
+
if (y && !r) descend(unwrapValue(u[s]), p, t, i, o);
|
|
246
339
|
// Deep-witness (dk): value changes must notify even with NO per-key
|
|
247
340
|
// node — deep() subscribes one node per record. Checked after descend
|
|
248
341
|
// so in-place adoptions (same logical slot) don't bump; child records
|
|
249
342
|
// carry their own witness. One flag + null check when unused.
|
|
250
|
-
if (e.dk !== null && !
|
|
343
|
+
if (e.dk !== null && !a && !(y ? targetsEqual(d, p) : isEqual(d, p))) {
|
|
251
344
|
bumpDeep(e);
|
|
252
|
-
|
|
345
|
+
a = true;
|
|
253
346
|
}
|
|
254
347
|
if (l !== null) {
|
|
255
|
-
const e = l[
|
|
348
|
+
const e = l[s];
|
|
256
349
|
if (e !== undefined) {
|
|
257
|
-
|
|
258
|
-
notifyKeyValue(e,
|
|
350
|
+
f++;
|
|
351
|
+
notifyKeyValue(e, s, d, p, c, n);
|
|
259
352
|
}
|
|
260
353
|
}
|
|
261
354
|
}
|
|
262
|
-
const p = Object.getOwnPropertySymbols(
|
|
355
|
+
const p = Object.getOwnPropertySymbols(n);
|
|
263
356
|
for (let e = 0; e < p.length; e++) {
|
|
264
|
-
const
|
|
265
|
-
const
|
|
266
|
-
if (!
|
|
357
|
+
const s = p[e];
|
|
358
|
+
const a = n[s];
|
|
359
|
+
if (!r && a !== null && typeof a === "object") descend(unwrapValue(u[s]), a, t, i, o);
|
|
267
360
|
if (l !== null) {
|
|
268
|
-
const e = l[
|
|
361
|
+
const e = l[s];
|
|
269
362
|
if (e !== undefined) {
|
|
270
|
-
|
|
271
|
-
notifyKeyValue(e,
|
|
363
|
+
f++;
|
|
364
|
+
notifyKeyValue(e, s, c[s], a, c, n);
|
|
272
365
|
}
|
|
273
366
|
}
|
|
274
367
|
}
|
|
275
|
-
if (
|
|
368
|
+
if (s) {
|
|
276
369
|
// Deleted-key nodes (in the map but absent from incoming) — counted
|
|
277
370
|
// fast-out: when every node was visited, skip the sweep entirely.
|
|
278
|
-
if (l !== null &&
|
|
371
|
+
if (l !== null && f < e.nc) {
|
|
279
372
|
for (const e of Reflect.ownKeys(l)) {
|
|
280
|
-
if (!hasOwnP.call(
|
|
373
|
+
if (!hasOwnP.call(n, e)) notifyKeyDiff(l[e], e, c, n, false);
|
|
281
374
|
}
|
|
282
375
|
}
|
|
283
|
-
notifyFoldTail(e,
|
|
376
|
+
notifyFoldTail(e, c, n);
|
|
284
377
|
}
|
|
285
378
|
return;
|
|
286
379
|
}
|
|
@@ -288,31 +381,125 @@ function applyAdopt(e, t, n, o = false) {
|
|
|
288
381
|
|
|
289
382
|
const hasOwnP = Object.prototype.hasOwnProperty;
|
|
290
383
|
|
|
291
|
-
|
|
292
|
-
|
|
384
|
+
/** Setter-channel row ops (the fold site calls this for array targets with
|
|
385
|
+
* ops consumers): structural mutation through the setter — push/splice/index
|
|
386
|
+
* assignment/permutation — is a visibility transition for the list container
|
|
387
|
+
* just like a reconcile walk, and drivers consuming registerRowOps must see
|
|
388
|
+
* it. Setter mutations move the SAME row objects around, so RAW IDENTITY is
|
|
389
|
+
* the key. Aligned arrays (value-only folds) emit nothing. */ const identityKey = e => unwrapValue(e);
|
|
390
|
+
|
|
391
|
+
/** Key equality for EVERY key comparison in this module (re-audit 2, P1-5):
|
|
392
|
+
* SameValueZero, matching the Map-based matchers (buildRowOps, the adoption
|
|
393
|
+
* window) — NaN keys are equal to themselves, so aligned NaN rows stay
|
|
394
|
+
* aligned in the prefix walk instead of forever misaligning. Adoption and
|
|
395
|
+
* row ops MUST agree on key equality or retained DOM rows go stale. */ function sameKey(e, n) {
|
|
396
|
+
return e === n || e !== e && n !== n;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function emitSetterRowOps(e, n, t) {
|
|
400
|
+
const o = buildIdentityRowOps(n, t);
|
|
401
|
+
if (o !== null) rowHooks.emitRowOps(e, t, o);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/** Identity-keyed structural diff, returned rather than emitted: shared by
|
|
405
|
+
* the setter channel (regular queue) and the OPTIMISTIC write channel (lane
|
|
406
|
+
* queue) — same retention semantics, different dispatch timing. Returns
|
|
407
|
+
* null when the lists are identity-aligned (no structure changed). */ function buildIdentityRowOps(e, n) {
|
|
408
|
+
let t = 0;
|
|
409
|
+
const o = e.length < n.length ? e.length : n.length;
|
|
410
|
+
while (t < o && unwrapValue(e[t]) === unwrapValue(n[t])) t++;
|
|
411
|
+
if (t === e.length && t === n.length) return null;
|
|
412
|
+
return buildRowOps(e, n, t, identityKey);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/** Shared row-ops builder (keyed deep branch + shallow/positional branch):
|
|
416
|
+
* key-matches the misaligned window into { prefix, sources, removed }.
|
|
417
|
+
* `keyFn === null` degrades to positional ops (append/truncate only). */ function buildAndEmitRowOps(e, n, t, o, l) {
|
|
418
|
+
rowHooks.emitRowOps(e, t, buildRowOps(n, t, o, l));
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function buildRowOps(e, n, t, o) {
|
|
422
|
+
const l = e.length;
|
|
423
|
+
const i = n.length;
|
|
424
|
+
const u = new Array(i - t);
|
|
425
|
+
// Occurrence-aware matching (re-audit): duplicate keys queue their old
|
|
426
|
+
// indices and each is consumed ONCE — first-wins reuse would hand the same
|
|
427
|
+
// source (and its one DOM row) to multiple next positions. The no-dup fast
|
|
428
|
+
// shape stays a bare number; collisions upgrade to a queue.
|
|
429
|
+
let f = null;
|
|
430
|
+
if (o !== null && t < l) {
|
|
431
|
+
f = new Map;
|
|
432
|
+
for (let n = t; n < l; n++) {
|
|
433
|
+
const t = unwrapValue(e[n]);
|
|
434
|
+
if (t !== null && typeof t === "object") {
|
|
435
|
+
const e = o(t);
|
|
436
|
+
if (e === undefined) continue;
|
|
437
|
+
const l = f.get(e);
|
|
438
|
+
if (l === undefined) f.set(e, n); else if (Array.isArray(l)) l.push(n); else f.set(e, [ l, n ]);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
const s = f !== null ? new Set : null;
|
|
443
|
+
for (let e = t; e < i; e++) {
|
|
444
|
+
const l = n[e];
|
|
445
|
+
let i = -1;
|
|
446
|
+
if (l !== null && typeof l === "object" && f !== null) {
|
|
447
|
+
const e = o(l);
|
|
448
|
+
if (e !== undefined) {
|
|
449
|
+
const n = f.get(e);
|
|
450
|
+
if (n !== undefined) {
|
|
451
|
+
if (Array.isArray(n)) {
|
|
452
|
+
i = n.shift();
|
|
453
|
+
if (n.length === 1) f.set(e, n[0]);
|
|
454
|
+
} else {
|
|
455
|
+
i = n;
|
|
456
|
+
f.delete(e);
|
|
457
|
+
}
|
|
458
|
+
s.add(i);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
u[e - t] = i;
|
|
463
|
+
}
|
|
464
|
+
const r = [];
|
|
465
|
+
for (let n = t; n < l; n++) {
|
|
466
|
+
if (s === null || !s.has(n)) r.push(unwrapValue(e[n]));
|
|
467
|
+
}
|
|
468
|
+
return {
|
|
469
|
+
prefix: t,
|
|
470
|
+
sources: u,
|
|
471
|
+
removed: r
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function descend(e, n, t, o, l = false) {
|
|
476
|
+
if (e === null || typeof e !== "object" || n === null || typeof n !== "object") return;
|
|
293
477
|
// Lookup FIRST: a hit implies pv was wrappable and never raw-marked (only
|
|
294
478
|
// wrappables acquire targets; rawValues never wrap) — one WeakMap get
|
|
295
479
|
// replaces isWrappable(pv) + isRawValue(pv), and a miss prunes untracked
|
|
296
480
|
// subtrees before any further checks.
|
|
297
|
-
const
|
|
298
|
-
if (
|
|
481
|
+
const i = (o?.map ?? storeNextLookup).get(e);
|
|
482
|
+
if (i === undefined) return;
|
|
299
483
|
// nothing proxied below this pair
|
|
300
484
|
// The NEW side still validates fully: a frozen/platform/markRaw'd incoming
|
|
301
485
|
// value is a leaf for reconcile — replaced by reference, never recursed
|
|
302
486
|
// into (R42); the parent's slot notification covers the change.
|
|
303
|
-
if (!isWrappable(
|
|
304
|
-
if (rawValuesUsed && isRawValue(
|
|
305
|
-
|
|
487
|
+
if (!isWrappable(n)) return;
|
|
488
|
+
if (rawValuesUsed && isRawValue(n)) return;
|
|
489
|
+
n = unwrapValue(n);
|
|
306
490
|
// Kind change replaces wholesale, never merges (R10): a target's carrier
|
|
307
491
|
// class (array vs object) is fixed at creation, so the slot detaches and a
|
|
308
492
|
// fresh proxy of the right kind wraps the incoming value on next read.
|
|
309
|
-
if (Array.isArray(e) !== Array.isArray(
|
|
310
|
-
if (
|
|
311
|
-
const o =
|
|
312
|
-
const l = n
|
|
493
|
+
if (Array.isArray(e) !== Array.isArray(n)) return;
|
|
494
|
+
if (t) {
|
|
495
|
+
const o = t(e);
|
|
496
|
+
const l = t(n);
|
|
313
497
|
// Key mismatch detaches: the slot takes the new entity; the old proxy
|
|
314
498
|
// keeps its (old) backing and a fresh proxy wraps the new value on read.
|
|
315
|
-
|
|
499
|
+
// SameValueZero (re-audit 2, P1-5): NaN keys are self-equal — strict
|
|
500
|
+
// inequality detached every NaN-keyed slot on every tick while the
|
|
501
|
+
// Map-based row-ops matcher retained its DOM row (stale forever).
|
|
502
|
+
if (o !== undefined && l !== undefined && !sameKey(o, l)) return;
|
|
316
503
|
}
|
|
317
504
|
// Reachability pruning (§6d) is MODE-dependent, both pinned:
|
|
318
505
|
// - keyed matching descends only where subscriptions exist at/below (`d`) —
|
|
@@ -324,8 +511,8 @@ function descend(e, t, n, o, l = false) {
|
|
|
324
511
|
// UNCONDITIONALLY (proj R6: the slot keeps its proxy without needing a
|
|
325
512
|
// subscriber below); plain keyed reconcile detaches unobserved captures
|
|
326
513
|
// (recon-snap R18 — staleness is the pinned pruning contract).
|
|
327
|
-
if (!l &&
|
|
328
|
-
applyAdopt(
|
|
514
|
+
if (!l && t !== null && !i.d) return;
|
|
515
|
+
applyAdopt(i, n, t, l);
|
|
329
516
|
}
|
|
330
517
|
|
|
331
|
-
export { reconcileNextState };
|
|
518
|
+
export { buildIdentityRowOps, emitSetterRowOps, reconcileNextState, sameKey };
|