@solidjs/signals 2.0.0-rc.5 → 2.0.0-rc.7
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 +2467 -1322
- package/dist/node.cjs +2037 -2428
- package/dist/node.dev.cjs +13724 -0
- package/dist/prod/boundaries.js +3 -1
- package/dist/prod/core/async.js +47 -20
- package/dist/prod/core/attribution-hooks.js +3 -0
- package/dist/prod/core/constants.js +9 -1
- package/dist/prod/core/context.js +10 -16
- package/dist/prod/core/core.js +260 -188
- package/dist/prod/core/dev.js +2 -0
- package/dist/prod/core/effect.js +41 -32
- package/dist/prod/core/external.js +2 -2
- package/dist/prod/core/graph.js +35 -31
- package/dist/prod/core/heap.js +34 -40
- package/dist/prod/core/lanes.js +44 -30
- package/dist/prod/core/optimistic.js +55 -45
- package/dist/prod/core/owner.js +35 -35
- package/dist/prod/core/scheduler.js +176 -177
- package/dist/prod/core/verdict.js +53 -52
- package/dist/prod/index.js +0 -2
- package/dist/prod/map.js +104 -94
- package/dist/prod/signals.js +119 -35
- package/dist/prod/store/next/optimistic.js +217 -163
- package/dist/prod/store/next/projection.js +2 -2
- package/dist/prod/store/next/reconcile.js +140 -288
- package/dist/prod/store/next/store.js +338 -252
- package/dist/prod/store/store.js +2 -2
- package/dist/types/core/attribution-hooks.d.ts +75 -0
- package/dist/types/core/attribution.d.ts +313 -9
- package/dist/types/core/constants.d.ts +8 -0
- package/dist/types/core/core.d.ts +12 -3
- package/dist/types/core/dev.d.ts +56 -9
- package/dist/types/core/heap.d.ts +5 -3
- package/dist/types/core/invariants.d.ts +1 -1
- package/dist/types/core/lanes.d.ts +10 -0
- package/dist/types/core/scheduler.d.ts +14 -4
- package/dist/types/signals.d.ts +10 -11
- package/dist/types/store/index.d.ts +3 -6
- package/dist/types/store/next/optimistic.d.ts +4 -2
- package/dist/types/store/next/reconcile.d.ts +5 -12
- package/dist/types/store/next/store.d.ts +3 -9
- package/dist/types/store/next/target.d.ts +30 -46
- package/dist/types/store/store.d.ts +14 -9
- package/dist/types-cjs/core/attribution-hooks.d.cts +75 -0
- package/dist/types-cjs/core/attribution.d.cts +313 -9
- package/dist/types-cjs/core/constants.d.cts +8 -0
- package/dist/types-cjs/core/core.d.cts +12 -3
- package/dist/types-cjs/core/dev.d.cts +56 -9
- package/dist/types-cjs/core/heap.d.cts +5 -3
- package/dist/types-cjs/core/invariants.d.cts +1 -1
- package/dist/types-cjs/core/lanes.d.cts +10 -0
- package/dist/types-cjs/core/scheduler.d.cts +14 -4
- package/dist/types-cjs/signals.d.cts +10 -11
- package/dist/types-cjs/store/index.d.cts +3 -6
- package/dist/types-cjs/store/next/optimistic.d.cts +4 -2
- package/dist/types-cjs/store/next/reconcile.d.cts +5 -12
- package/dist/types-cjs/store/next/store.d.cts +3 -9
- package/dist/types-cjs/store/next/target.d.cts +30 -46
- package/dist/types-cjs/store/store.d.cts +14 -9
- package/package.json +3 -2
- package/dist/prod/store/next/patch-hooks.js +0 -13
- package/dist/prod/store/next/patch.js +0 -614
- package/dist/types/store/next/patch-hooks.d.ts +0 -41
- package/dist/types/store/next/patch.d.ts +0 -91
- package/dist/types-cjs/store/next/patch-hooks.d.cts +0 -41
- package/dist/types-cjs/store/next/patch.d.cts +0 -91
|
@@ -8,8 +8,6 @@ import "../../core/verdict.js";
|
|
|
8
8
|
|
|
9
9
|
import "../../core/effect.js";
|
|
10
10
|
|
|
11
|
-
import { patchHooks, rowHooks } from "./patch-hooks.js";
|
|
12
|
-
|
|
13
11
|
import { $TARGET, markRawIngest, isWrappable, rawValuesUsed, isRawValue, getWriteOverride } from "../store.js";
|
|
14
12
|
|
|
15
13
|
import { materializePB, adoptPB, unwrapValue, notifyFold, targetsEqual, bumpDeep, notifyKeyValue, notifyKeyDiff, notifyFoldTail, hasAccessorFlag } from "./store.js";
|
|
@@ -35,46 +33,46 @@ import { storeNextLookup, optHooks, ownedRaw } from "./target.js";
|
|
|
35
33
|
* key mismatch detaches (fresh proxy on next read, recon-snap R18);
|
|
36
34
|
* keyless items fall back positional; null/primitive slots are legal
|
|
37
35
|
* members (R11). Kind changes replace wholesale (R10).
|
|
38
|
-
*/ function reconcileNextState(e,
|
|
39
|
-
if (
|
|
40
|
-
const
|
|
41
|
-
if (
|
|
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("");
|
|
42
40
|
// Reconcile's diff walks need a REAL pending container — a prototype
|
|
43
41
|
// overlay (#3044) materializes to the clone path first (edge: reconcile
|
|
44
42
|
// inside a setter that already wrote this target).
|
|
45
|
-
if (
|
|
46
|
-
let
|
|
43
|
+
if (f.ovl) materializePB(f);
|
|
44
|
+
let l = n === null ? null : typeof n === "string" ? e => e?.[n] : n;
|
|
47
45
|
// §7b chained backing: a projection derive returning a LIVE store proxy
|
|
48
46
|
// adopts the proxy itself as the backing — reads flow through the inner
|
|
49
47
|
// store's traps, so consumers subscribe to the inner graph and updates
|
|
50
48
|
// flow with no re-derive (#2941). The adoption diff still notifies THIS
|
|
51
49
|
// store's existing subscribers of the swap.
|
|
52
|
-
if (o && e !==
|
|
53
|
-
const
|
|
54
|
-
if (
|
|
50
|
+
if (o && e !== t && e?.[$TARGET] !== undefined) {
|
|
51
|
+
const t = f.pb ?? f.v;
|
|
52
|
+
if (t === e) return;
|
|
55
53
|
// already chained to this store
|
|
56
|
-
adoptPB(
|
|
54
|
+
adoptPB(f, e);
|
|
57
55
|
return;
|
|
58
56
|
}
|
|
59
|
-
const
|
|
60
|
-
if (
|
|
57
|
+
const i = unwrapValue(e);
|
|
58
|
+
if (l) {
|
|
61
59
|
// Root identity precondition — checked before ANY mutation, so a throwing
|
|
62
60
|
// reconcile is atomic by construction (RUL-12 ruling). Projections
|
|
63
61
|
// (replace=true) relax it: a root entity change merges in place — the
|
|
64
62
|
// root proxy is stable for life (proj R5/R11) — and children are NOT
|
|
65
63
|
// key-matched across the entity change (proj R7: keyFn drops to
|
|
66
64
|
// positional so old-entity subtrees never merge into the new entity's).
|
|
67
|
-
const e =
|
|
68
|
-
const
|
|
69
|
-
if (
|
|
65
|
+
const e = f.pb ?? f.v;
|
|
66
|
+
const t = l(e);
|
|
67
|
+
if (t !== undefined && !sameKey(l(i), t)) {
|
|
70
68
|
if (!o) throw new Error("");
|
|
71
69
|
// Entity change: wholesale swap. The root proxy is stable for life
|
|
72
70
|
// (proj R5) but NOTHING below survives — children are never matched
|
|
73
71
|
// across an entity change even when their own keys align (proj R7).
|
|
74
72
|
// Displaced-raw unregistration (proj R10): the outgoing raw stops
|
|
75
73
|
// resolving to this proxy; re-handed later it wraps fresh.
|
|
76
|
-
(
|
|
77
|
-
adoptPB(
|
|
74
|
+
(f.fam?.map ?? storeNextLookup).delete(f.pb ?? f.v);
|
|
75
|
+
adoptPB(f, i);
|
|
78
76
|
return;
|
|
79
77
|
}
|
|
80
78
|
}
|
|
@@ -83,107 +81,95 @@ import { storeNextLookup, optHooks, ownedRaw } from "./target.js";
|
|
|
83
81
|
// armed nodes (reverting with their transaction); committed raw is never
|
|
84
82
|
// touched. Key-matched rows keep proxy identity by descending into the
|
|
85
83
|
// existing child targets instead of overriding their parent slots.
|
|
86
|
-
if (
|
|
87
|
-
optHooks.applyTentative(
|
|
84
|
+
if (f.fam?.opt === true && !projectionWriteActive && !getWriteOverride()) {
|
|
85
|
+
optHooks.applyTentative(f, i, l);
|
|
88
86
|
return;
|
|
89
87
|
}
|
|
90
|
-
applyAdopt(
|
|
88
|
+
applyAdopt(f, i, l, o);
|
|
91
89
|
}
|
|
92
90
|
|
|
93
|
-
function applyAdopt(e,
|
|
94
|
-
const
|
|
91
|
+
function applyAdopt(e, t, n, o = false) {
|
|
92
|
+
const f = e.pb ?? e.v;
|
|
95
93
|
// The sound identity skip (O7): same reference AND we never diverged it.
|
|
96
|
-
if (
|
|
97
|
-
const
|
|
94
|
+
if (t === f && !ownedRaw.has(f)) return;
|
|
95
|
+
const l = e.fam;
|
|
98
96
|
// §6b (R28): the diff's previous-arrangement baseline is the LANE VIEW —
|
|
99
97
|
// optimistic rows must be visible to key matching so a landing carrying the
|
|
100
98
|
// same key recycles their proxies. Raw `prev` keeps the identity/ownership
|
|
101
99
|
// roles above; only matching reads the view.
|
|
102
|
-
const
|
|
103
|
-
const
|
|
100
|
+
const i = l?.opt === true ? optHooks.optimisticView(e, f) : f;
|
|
101
|
+
const r = Array.isArray(t);
|
|
104
102
|
// Plain stores notify inline AFTER the descent (child registrations feed
|
|
105
103
|
// the fold diff's identity-preservation check); projections keep deferred
|
|
106
104
|
// folds (downstream holds can form later in the flush).
|
|
107
|
-
const
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
}
|
|
105
|
+
const u = l === null;
|
|
106
|
+
const s = e.s === true;
|
|
107
|
+
// Node-notify base is the view the nodes were last told (#3296): a draft
|
|
108
|
+
// preceding this reconcile already moved them to its pending backing at
|
|
109
|
+
// setter exit (prev, materialized above), so diffing incoming against the
|
|
110
|
+
// committed backing would skip a key the draft changed and the reconcile
|
|
111
|
+
// restores — the node would commit the superseded draft value.
|
|
112
|
+
const a = f;
|
|
113
|
+
adoptPB(e, t, u);
|
|
128
114
|
// Shallow adoption: records are slot values — sticky raw-mark the incoming
|
|
129
115
|
// set (R41) and never descend; slot notification is the positional diff.
|
|
130
|
-
if (
|
|
131
|
-
if (Array.isArray(
|
|
132
|
-
if (
|
|
116
|
+
if (s) markRawIngest(t);
|
|
117
|
+
if (Array.isArray(i) !== r) {
|
|
118
|
+
if (u) notifyFold(e, a, t);
|
|
133
119
|
return;
|
|
134
120
|
}
|
|
135
|
-
if (
|
|
136
|
-
const
|
|
137
|
-
const
|
|
121
|
+
if (r) {
|
|
122
|
+
const f = i;
|
|
123
|
+
const r = t;
|
|
138
124
|
// Fused array walk (eager mode): per-index notification rides the same
|
|
139
125
|
// loop as the descent (descend first — targetsEqual needs the child's
|
|
140
126
|
// re-registration, R9). Length, trailing removed indexes, and any other
|
|
141
127
|
// unvisited node keys land in the counted sweep below.
|
|
142
|
-
const
|
|
128
|
+
const c = u ? e.n : null;
|
|
143
129
|
let p = 0;
|
|
144
|
-
if (
|
|
130
|
+
if (n && !s) {
|
|
145
131
|
// Positional-prefix fast path (legacy keyedMatch-walk parity): while
|
|
146
132
|
// rows key-match in place — the steady-state polling shape — descend
|
|
147
133
|
// directly with zero staging. The prevByKey map is built only for the
|
|
148
134
|
// misaligned remainder, and never at all on aligned ticks.
|
|
149
|
-
const
|
|
150
|
-
const
|
|
151
|
-
let
|
|
135
|
+
const i = f.length;
|
|
136
|
+
const u = r.length;
|
|
137
|
+
let s = false;
|
|
152
138
|
let d = 0;
|
|
153
|
-
for (const y = Math.min(
|
|
139
|
+
for (const y = Math.min(i, u); d < y; d++) {
|
|
140
|
+
const i = r[d];
|
|
154
141
|
const u = f[d];
|
|
155
|
-
const s = l[d];
|
|
156
142
|
// Routing heuristic only (aligned vs keyed remainder) — both routes
|
|
157
143
|
// notify identically and descend() is the one authoritative
|
|
158
144
|
// validator, so bare typeof gates suffice here; full isWrappable
|
|
159
145
|
// per row was the walk's dominant residual cost.
|
|
160
|
-
if (
|
|
146
|
+
if (u !== i && !(u !== null && typeof u === "object" && i !== null && typeof i === "object" && sameKey(n(u), n(i)))) break;
|
|
161
147
|
// misaligned: fall to the keyed remainder below
|
|
162
148
|
// Identity skip inline (FINDING-1 guard), then descend the pair.
|
|
163
|
-
if ((
|
|
164
|
-
if (e.dk !== null && !
|
|
149
|
+
if ((u !== i || i !== null && typeof i === "object" && ownedRaw.has(i)) && i !== null && typeof i === "object") descend(unwrapValue(u), i, n, l, o);
|
|
150
|
+
if (e.dk !== null && !s && !(i !== null && typeof i === "object" ? targetsEqual(u, i) : isEqual(u, i))) {
|
|
165
151
|
bumpDeep(e);
|
|
166
|
-
|
|
152
|
+
s = true;
|
|
167
153
|
}
|
|
168
|
-
if (
|
|
169
|
-
const e =
|
|
154
|
+
if (c !== null) {
|
|
155
|
+
const e = c[d];
|
|
170
156
|
if (e !== undefined) {
|
|
171
157
|
p++;
|
|
172
|
-
notifyKeyValue(e, d,
|
|
158
|
+
notifyKeyValue(e, d, a[d], i, a, t);
|
|
173
159
|
}
|
|
174
160
|
}
|
|
175
161
|
}
|
|
176
|
-
if (e.dk !== null && !
|
|
162
|
+
if (e.dk !== null && !s && d < r.length) bumpDeep(e);
|
|
177
163
|
const y = d;
|
|
178
164
|
// misalignment point (== nlen on aligned ticks)
|
|
179
165
|
let w = null;
|
|
180
|
-
for (;d <
|
|
181
|
-
const e =
|
|
166
|
+
for (;d < r.length; d++) {
|
|
167
|
+
const e = r[d];
|
|
182
168
|
// typeof gates route; descend validates (same contract as the prefix).
|
|
183
169
|
if (e !== null && typeof e === "object") {
|
|
184
|
-
const
|
|
185
|
-
let
|
|
186
|
-
if (
|
|
170
|
+
const t = n(e);
|
|
171
|
+
let i;
|
|
172
|
+
if (t !== undefined) {
|
|
187
173
|
if (w === null) {
|
|
188
174
|
// Occurrence-aware (re-audit 2, P1-5): duplicate keys queue
|
|
189
175
|
// their prev INDICES (rows can themselves be arrays, so index
|
|
@@ -197,112 +183,67 @@ function applyAdopt(e, n, t, o = false) {
|
|
|
197
183
|
// them here let a duplicate key adopt a prefix row AGAIN while
|
|
198
184
|
// row ops (which correctly window from structStart) retained
|
|
199
185
|
// the later occurrence's DOM row against a never-adopted target.
|
|
200
|
-
for (let e = y; e <
|
|
201
|
-
const
|
|
202
|
-
if (
|
|
203
|
-
const o = t
|
|
186
|
+
for (let e = y; e < f.length; e++) {
|
|
187
|
+
const t = unwrapValue(f[e]);
|
|
188
|
+
if (t !== null && typeof t === "object") {
|
|
189
|
+
const o = n(t);
|
|
204
190
|
if (o === undefined) continue;
|
|
205
|
-
const
|
|
206
|
-
if (
|
|
191
|
+
const f = w.get(o);
|
|
192
|
+
if (f === undefined) w.set(o, e); else if (Array.isArray(f)) f.push(e); else w.set(o, [ f, e ]);
|
|
207
193
|
}
|
|
208
194
|
}
|
|
209
195
|
}
|
|
210
|
-
const e = w.get(
|
|
211
|
-
if (e === undefined)
|
|
212
|
-
|
|
213
|
-
if (e.length === 1) w.set(
|
|
196
|
+
const e = w.get(t);
|
|
197
|
+
if (e === undefined) i = undefined; else if (Array.isArray(e)) {
|
|
198
|
+
i = unwrapValue(f[e.shift()]);
|
|
199
|
+
if (e.length === 1) w.set(t, e[0]);
|
|
214
200
|
} else {
|
|
215
|
-
|
|
216
|
-
w.delete(
|
|
201
|
+
i = unwrapValue(f[e]);
|
|
202
|
+
w.delete(t);
|
|
217
203
|
}
|
|
218
204
|
} else {
|
|
219
|
-
|
|
205
|
+
i = unwrapValue(f[d]);
|
|
220
206
|
// keyless item: positional fallback
|
|
221
207
|
}
|
|
222
|
-
descend(
|
|
208
|
+
descend(i, e, n, l, o);
|
|
223
209
|
}
|
|
224
|
-
if (
|
|
225
|
-
const e =
|
|
210
|
+
if (c !== null) {
|
|
211
|
+
const e = c[d];
|
|
226
212
|
if (e !== undefined) {
|
|
227
213
|
p++;
|
|
228
|
-
notifyKeyDiff(e, d,
|
|
214
|
+
notifyKeyDiff(e, d, a, t, false);
|
|
229
215
|
}
|
|
230
216
|
}
|
|
231
217
|
}
|
|
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);
|
|
236
218
|
} else {
|
|
237
|
-
const
|
|
238
|
-
const
|
|
219
|
+
const i = Math.min(f.length, r.length);
|
|
220
|
+
const u = r.length;
|
|
239
221
|
let d = false;
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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))) {
|
|
222
|
+
for (let y = 0; y < u; y++) {
|
|
223
|
+
const u = r[y];
|
|
224
|
+
if (!s && y < i && u !== null && typeof u === "object") descend(unwrapValue(f[y]), u, n, l, o);
|
|
225
|
+
if (e.dk !== null && !d && !(u !== null && typeof u === "object" ? targetsEqual(f[y], u) : isEqual(f[y], u))) {
|
|
277
226
|
bumpDeep(e);
|
|
278
227
|
d = true;
|
|
279
228
|
}
|
|
280
|
-
if (
|
|
281
|
-
const e =
|
|
229
|
+
if (c !== null) {
|
|
230
|
+
const e = c[y];
|
|
282
231
|
if (e !== undefined) {
|
|
283
232
|
p++;
|
|
284
|
-
notifyKeyDiff(e,
|
|
233
|
+
notifyKeyDiff(e, y, a, t, false);
|
|
285
234
|
}
|
|
286
235
|
}
|
|
287
236
|
}
|
|
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
|
-
}
|
|
296
237
|
}
|
|
297
|
-
if (
|
|
298
|
-
if (
|
|
299
|
-
for (const e of Reflect.ownKeys(
|
|
238
|
+
if (u) {
|
|
239
|
+
if (c !== null && p < e.nc) {
|
|
240
|
+
for (const e of Reflect.ownKeys(c)) {
|
|
300
241
|
// visited indexes are < nextRows.length; everything else sweeps
|
|
301
|
-
const
|
|
302
|
-
if (!(
|
|
242
|
+
const n = typeof e === "string" ? +e : NaN;
|
|
243
|
+
if (!(n >= 0 && n < r.length)) notifyKeyDiff(c[e], e, a, t, false);
|
|
303
244
|
}
|
|
304
245
|
}
|
|
305
|
-
notifyFoldTail(e,
|
|
246
|
+
notifyFoldTail(e, a, t);
|
|
306
247
|
}
|
|
307
248
|
return;
|
|
308
249
|
} else {
|
|
@@ -312,68 +253,61 @@ function applyAdopt(e, n, t, o = false) {
|
|
|
312
253
|
// slots must not notify, R9). This replaces the notifyFold re-walk that
|
|
313
254
|
// doubled dbmon's diff cost. for-in covers own enumerable string keys
|
|
314
255
|
// with no key-array allocation; symbols get a pass only when present.
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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;
|
|
256
|
+
const f = u ? e.n : null;
|
|
257
|
+
let r = 0;
|
|
258
|
+
let c = false;
|
|
325
259
|
// The per-key body is inlined on purpose (legacy applyStateFast parity:
|
|
326
260
|
// an extracted helper costs a call per key on the hottest object-diff
|
|
327
261
|
// site). Reference-identical values early-continue BEFORE any other
|
|
328
262
|
// work — sound only with the ownership guard (FINDING-1: an owned
|
|
329
263
|
// backing is setter-diverged and must still diff).
|
|
330
|
-
for (const
|
|
331
|
-
const p =
|
|
332
|
-
const d =
|
|
264
|
+
for (const u in t) {
|
|
265
|
+
const p = t[u];
|
|
266
|
+
const d = a[u];
|
|
333
267
|
const y = p !== null && typeof p === "object";
|
|
334
|
-
if (d === p && (!y || !ownedRaw.has(p)) && (
|
|
335
|
-
if (
|
|
268
|
+
if (d === p && (!y || !ownedRaw.has(p)) && (f === null || f[u] === undefined || !hasAccessorFlag(f[u]))) {
|
|
269
|
+
if (f !== null && f[u] !== undefined) r++;
|
|
336
270
|
continue;
|
|
337
271
|
}
|
|
338
|
-
if (y && !
|
|
272
|
+
if (y && !s) descend(unwrapValue(i[u]), p, n, l, o);
|
|
339
273
|
// Deep-witness (dk): value changes must notify even with NO per-key
|
|
340
274
|
// node — deep() subscribes one node per record. Checked after descend
|
|
341
275
|
// so in-place adoptions (same logical slot) don't bump; child records
|
|
342
276
|
// carry their own witness. One flag + null check when unused.
|
|
343
|
-
if (e.dk !== null && !
|
|
277
|
+
if (e.dk !== null && !c && !(y ? targetsEqual(d, p) : isEqual(d, p))) {
|
|
344
278
|
bumpDeep(e);
|
|
345
|
-
|
|
279
|
+
c = true;
|
|
346
280
|
}
|
|
347
|
-
if (
|
|
348
|
-
const e =
|
|
281
|
+
if (f !== null) {
|
|
282
|
+
const e = f[u];
|
|
349
283
|
if (e !== undefined) {
|
|
350
|
-
|
|
351
|
-
notifyKeyValue(e,
|
|
284
|
+
r++;
|
|
285
|
+
notifyKeyValue(e, u, d, p, a, t);
|
|
352
286
|
}
|
|
353
287
|
}
|
|
354
288
|
}
|
|
355
|
-
const p = Object.getOwnPropertySymbols(
|
|
289
|
+
const p = Object.getOwnPropertySymbols(t);
|
|
356
290
|
for (let e = 0; e < p.length; e++) {
|
|
357
|
-
const
|
|
358
|
-
const
|
|
359
|
-
if (!
|
|
360
|
-
if (
|
|
361
|
-
const e =
|
|
291
|
+
const u = p[e];
|
|
292
|
+
const c = t[u];
|
|
293
|
+
if (!s && c !== null && typeof c === "object") descend(unwrapValue(i[u]), c, n, l, o);
|
|
294
|
+
if (f !== null) {
|
|
295
|
+
const e = f[u];
|
|
362
296
|
if (e !== undefined) {
|
|
363
|
-
|
|
364
|
-
notifyKeyValue(e,
|
|
297
|
+
r++;
|
|
298
|
+
notifyKeyValue(e, u, a[u], c, a, t);
|
|
365
299
|
}
|
|
366
300
|
}
|
|
367
301
|
}
|
|
368
|
-
if (
|
|
302
|
+
if (u) {
|
|
369
303
|
// Deleted-key nodes (in the map but absent from incoming) — counted
|
|
370
304
|
// fast-out: when every node was visited, skip the sweep entirely.
|
|
371
|
-
if (
|
|
372
|
-
for (const e of Reflect.ownKeys(
|
|
373
|
-
if (!hasOwnP.call(
|
|
305
|
+
if (f !== null && r < e.nc) {
|
|
306
|
+
for (const e of Reflect.ownKeys(f)) {
|
|
307
|
+
if (!hasOwnP.call(t, e)) notifyKeyDiff(f[e], e, a, t, false);
|
|
374
308
|
}
|
|
375
309
|
}
|
|
376
|
-
notifyFoldTail(e,
|
|
310
|
+
notifyFoldTail(e, a, t);
|
|
377
311
|
}
|
|
378
312
|
return;
|
|
379
313
|
}
|
|
@@ -383,123 +317,41 @@ const hasOwnP = Object.prototype.hasOwnProperty;
|
|
|
383
317
|
|
|
384
318
|
/** Setter-channel row ops (the fold site calls this for array targets with
|
|
385
319
|
* 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
320
|
/** Key equality for EVERY key comparison in this module (re-audit 2, P1-5):
|
|
392
|
-
* SameValueZero, matching the Map-based
|
|
393
|
-
*
|
|
394
|
-
*
|
|
395
|
-
|
|
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
|
-
};
|
|
321
|
+
* SameValueZero, matching the adoption window's Map-based matcher — NaN keys
|
|
322
|
+
* are equal to themselves, so aligned NaN rows stay aligned in the prefix
|
|
323
|
+
* walk instead of forever misaligning. */ function sameKey(e, t) {
|
|
324
|
+
return e === t || e !== e && t !== t;
|
|
473
325
|
}
|
|
474
326
|
|
|
475
|
-
function descend(e,
|
|
476
|
-
if (e === null || typeof e !== "object" ||
|
|
327
|
+
function descend(e, t, n, o, f = false) {
|
|
328
|
+
if (e === null || typeof e !== "object" || t === null || typeof t !== "object") return;
|
|
477
329
|
// Lookup FIRST: a hit implies pv was wrappable and never raw-marked (only
|
|
478
330
|
// wrappables acquire targets; rawValues never wrap) — one WeakMap get
|
|
479
331
|
// replaces isWrappable(pv) + isRawValue(pv), and a miss prunes untracked
|
|
480
332
|
// subtrees before any further checks.
|
|
481
|
-
const
|
|
482
|
-
if (
|
|
333
|
+
const l = (o?.map ?? storeNextLookup).get(e);
|
|
334
|
+
if (l === undefined) return;
|
|
483
335
|
// nothing proxied below this pair
|
|
484
336
|
// The NEW side still validates fully: a frozen/platform/markRaw'd incoming
|
|
485
337
|
// value is a leaf for reconcile — replaced by reference, never recursed
|
|
486
338
|
// into (R42); the parent's slot notification covers the change.
|
|
487
|
-
if (!isWrappable(
|
|
488
|
-
if (rawValuesUsed && isRawValue(
|
|
489
|
-
|
|
339
|
+
if (!isWrappable(t)) return;
|
|
340
|
+
if (rawValuesUsed && isRawValue(t)) return;
|
|
341
|
+
t = unwrapValue(t);
|
|
490
342
|
// Kind change replaces wholesale, never merges (R10): a target's carrier
|
|
491
343
|
// class (array vs object) is fixed at creation, so the slot detaches and a
|
|
492
344
|
// fresh proxy of the right kind wraps the incoming value on next read.
|
|
493
|
-
if (Array.isArray(e) !== Array.isArray(
|
|
494
|
-
if (
|
|
495
|
-
const o =
|
|
496
|
-
const
|
|
345
|
+
if (Array.isArray(e) !== Array.isArray(t)) return;
|
|
346
|
+
if (n) {
|
|
347
|
+
const o = n(e);
|
|
348
|
+
const f = n(t);
|
|
497
349
|
// Key mismatch detaches: the slot takes the new entity; the old proxy
|
|
498
350
|
// keeps its (old) backing and a fresh proxy wraps the new value on read.
|
|
499
351
|
// SameValueZero (re-audit 2, P1-5): NaN keys are self-equal — strict
|
|
500
352
|
// inequality detached every NaN-keyed slot on every tick while the
|
|
501
353
|
// Map-based row-ops matcher retained its DOM row (stale forever).
|
|
502
|
-
if (o !== undefined &&
|
|
354
|
+
if (o !== undefined && f !== undefined && !sameKey(o, f)) return;
|
|
503
355
|
}
|
|
504
356
|
// Reachability pruning (§6d) is MODE-dependent, both pinned:
|
|
505
357
|
// - keyed matching descends only where subscriptions exist at/below (`d`) —
|
|
@@ -511,8 +363,8 @@ function descend(e, n, t, o, l = false) {
|
|
|
511
363
|
// UNCONDITIONALLY (proj R6: the slot keeps its proxy without needing a
|
|
512
364
|
// subscriber below); plain keyed reconcile detaches unobserved captures
|
|
513
365
|
// (recon-snap R18 — staleness is the pinned pruning contract).
|
|
514
|
-
if (!
|
|
515
|
-
applyAdopt(
|
|
366
|
+
if (!f && n !== null && !l.d) return;
|
|
367
|
+
applyAdopt(l, t, n, f);
|
|
516
368
|
}
|
|
517
369
|
|
|
518
|
-
export {
|
|
370
|
+
export { reconcileNextState, sameKey };
|