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