@solidjs/signals 2.0.0-rc.11 → 2.0.0-rc.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dev-shared.js +100 -49
- package/dist/dev.attribution.js +27 -12
- package/dist/dev.js +163 -22
- package/dist/observe/core/attribution.js +36 -21
- package/dist/observe/core/constants.js +21 -12
- package/dist/observe/core/core.js +44 -34
- package/dist/observe/core/dev.js +38 -15
- package/dist/observe/core/optimistic.js +5 -5
- package/dist/observe/core/owner.js +11 -11
- package/dist/observe/core/scheduler.js +6 -6
- package/dist/observe/core/verdict.js +2 -2
- package/dist/observe/store/next/store.js +159 -31
- package/dist/prod/core/constants.js +21 -12
- package/dist/prod/core/core.js +27 -17
- package/dist/prod/store/next/store.js +201 -73
- package/dist/types/core/attribution-hooks.d.ts +28 -0
- package/dist/types/core/attribution.d.ts +9 -0
- package/dist/types/core/constants.d.ts +21 -12
- package/dist/types/core/dev.d.ts +59 -6
- package/dist/types/core/index.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/signals.d.ts +18 -1
- package/dist/types/store/next/store.d.ts +4 -2
- package/package.json +1 -1
package/dist/dev.js
CHANGED
|
@@ -4794,6 +4794,8 @@ function getNode(
|
|
|
4794
4794
|
// committed is the held view `hv[key]`, staged is `v[key]`.
|
|
4795
4795
|
const fold = heldFoldTransition(target);
|
|
4796
4796
|
let held = heldAdoptionTransition(target);
|
|
4797
|
+
// A key the adoption left unchanged is not born holding (#3706).
|
|
4798
|
+
if (held !== null && !heldKey(target, key)) held = null;
|
|
4797
4799
|
if (held !== null) current = target.hv[key];
|
|
4798
4800
|
else if ((held = fold) !== null) current = target.v[key];
|
|
4799
4801
|
// Create-floor diet: slotSignal bakes the whole node into one literal —
|
|
@@ -4916,6 +4918,45 @@ function holdVisible(txn, c) {
|
|
|
4916
4918
|
enterStagedRead(null, txn);
|
|
4917
4919
|
return true;
|
|
4918
4920
|
}
|
|
4921
|
+
// #3706: the keys an adoption under a live hold changed against the held
|
|
4922
|
+
// view — the adoption twin of `wk` (#3688), keyed on the target. adoptPB
|
|
4923
|
+
// records `[adopted object]`; the keys are diffed against IT, once, on the
|
|
4924
|
+
// first held read (its children are re-pointed by then), and replace the
|
|
4925
|
+
// entry. Never against the live backing — a mainline write during the hold
|
|
4926
|
+
// replaces that, and its key is not the adoption's. WK_ALL holds the whole
|
|
4927
|
+
// container.
|
|
4928
|
+
const heldKeys = new WeakMap();
|
|
4929
|
+
function heldKey(target, key) {
|
|
4930
|
+
let keys = heldKeys.get(target);
|
|
4931
|
+
if (Array.isArray(keys)) heldKeys.set(target, (keys = adoptionChangedKeys(target, keys[0])));
|
|
4932
|
+
return keys === WK_ALL || keys.has(key);
|
|
4933
|
+
}
|
|
4934
|
+
function adoptionChangedKeys(target, v) {
|
|
4935
|
+
const hv = target.hv;
|
|
4936
|
+
// A chained backing, an optimistic family, a chained held view, a swapped
|
|
4937
|
+
// or non-plain prototype (inherited accessors read through `this`): whole.
|
|
4938
|
+
if (
|
|
4939
|
+
v[$TARGET] !== undefined ||
|
|
4940
|
+
target.fam?.opt === true ||
|
|
4941
|
+
hv[$TARGET] !== undefined ||
|
|
4942
|
+
Object.getPrototypeOf(hv) !== Object.getPrototypeOf(v) ||
|
|
4943
|
+
!plainProto(v)
|
|
4944
|
+
)
|
|
4945
|
+
return WK_ALL;
|
|
4946
|
+
const keys = new Set();
|
|
4947
|
+
// Accessors are never invoked: they and a flipped enumerability are changes.
|
|
4948
|
+
for (const key of Reflect.ownKeys(hv))
|
|
4949
|
+
if (
|
|
4950
|
+
!hasOwn.call(v, key) ||
|
|
4951
|
+
isOwnAccessor(hv, key) ||
|
|
4952
|
+
isOwnAccessor(v, key) ||
|
|
4953
|
+
propertyIsEnumerable.call(hv, key) !== propertyIsEnumerable.call(v, key) ||
|
|
4954
|
+
!(isEqual(hv[key], v[key]) || sameLogicalSlot(target, hv[key], v[key]))
|
|
4955
|
+
)
|
|
4956
|
+
keys.add(key);
|
|
4957
|
+
for (const key of Reflect.ownKeys(v)) if (!hasOwn.call(hv, key)) keys.add(key);
|
|
4958
|
+
return keys;
|
|
4959
|
+
}
|
|
4919
4960
|
function stageHeldKey(node, nv, txn) {
|
|
4920
4961
|
if (slotNodeEquals.call(node, node._value, nv)) return;
|
|
4921
4962
|
node._pendingValue = nv;
|
|
@@ -5116,6 +5157,23 @@ function cloneRaw(source, t) {
|
|
|
5116
5157
|
clone[$OWNER] = t;
|
|
5117
5158
|
return clone;
|
|
5118
5159
|
}
|
|
5160
|
+
/** Shallow copy of a WIDE plain-data (sc 2) backing for the overlay rebuild
|
|
5161
|
+
* (#3689). Own keys land on a null-prototype object — V8 creates those in
|
|
5162
|
+
* dictionary mode, so the copy is O(keys) hash inserts — and the prototype
|
|
5163
|
+
* is attached after. A spread builds a fast-mode object one map transition
|
|
5164
|
+
* per key; from a source in dictionary mode (a backing that took deletes)
|
|
5165
|
+
* whose key order differs from the last copy's, every transition is a fresh
|
|
5166
|
+
* map with an O(keys) descriptor copy: measured 270 µs vs 28 µs at 400 keys,
|
|
5167
|
+
* 1.3 ms vs 75 µs at 1000. Narrow, stable-shaped backings keep the spread
|
|
5168
|
+
* (cloneRaw): there the clone IC copies the property store at once and is
|
|
5169
|
+
* 3–8× cheaper than any per-key loop. */
|
|
5170
|
+
function wideClone(source, t) {
|
|
5171
|
+
const clone = Object.create(null);
|
|
5172
|
+
for (const key of Reflect.ownKeys(source)) clone[key] = source[key];
|
|
5173
|
+
Object.setPrototypeOf(clone, Object.prototype);
|
|
5174
|
+
clone[$OWNER] = t;
|
|
5175
|
+
return clone;
|
|
5176
|
+
}
|
|
5119
5177
|
/** Copy own `key` from `from` onto `to`. A plain data slot (enumerable,
|
|
5120
5178
|
* writable, configurable, no accessor) is a bare assignment — the common case
|
|
5121
5179
|
* and the cheap one; anything else goes through defineProperty so accessors
|
|
@@ -5154,22 +5212,68 @@ function scanAccessorsOnce(target) {
|
|
|
5154
5212
|
* backing into a prototype, and every later flatten writes into that
|
|
5155
5213
|
* prototype); above it the clone's O(keys) copy dominates (#3044). */
|
|
5156
5214
|
const OVERLAY_MIN_KEYS = 32;
|
|
5215
|
+
/** Overlay commit choice (#3689): flatten in place, or rebuild the backing
|
|
5216
|
+
* (materializePB, then the clone path's swap)? Measured on V8 (Node 26): a
|
|
5217
|
+
* committed backing that has served as an overlay prototype is kept as a
|
|
5218
|
+
* FAST-MODE prototype object up to V8's descriptor limit (1020 own
|
|
5219
|
+
* properties), and there every in-place DELETE is O(keys) — normalize to
|
|
5220
|
+
* dictionary plus re-optimize as a prototype, ~0.08 µs × keys — and every
|
|
5221
|
+
* in-place ADD is a map transition copying the descriptors, ~0.006 µs ×
|
|
5222
|
+
* keys. Detaching the overlay first does not help: prototype-ness is a
|
|
5223
|
+
* property of the object's map. One rebuild (wideClone, deletes, writes) is
|
|
5224
|
+
* ~0.07–0.09 µs × keys — the price of ONE in-place delete. So a fold that
|
|
5225
|
+
* deleted anything, or added more than ~16 keys, commits cheaper rebuilt (a
|
|
5226
|
+
* 400-key record churning 100 keys per commit, 10,000 membership readers:
|
|
5227
|
+
* 4.7 ms → 0.22 ms per step); a pure rewrite of existing keys — the
|
|
5228
|
+
* overlay's home case — stays in place at O(written).
|
|
5229
|
+
* Past the descriptor limit V8 keeps the backing in dictionary mode where
|
|
5230
|
+
* every in-place op is O(1), and a rebuild would be a pure O(keys) loss on
|
|
5231
|
+
* exactly the growing-record pattern the overlay exists for (#3044) — the
|
|
5232
|
+
* count gate keeps those in place. The gate reads `kc`, which the set trap
|
|
5233
|
+
* counts up for keys new to the container and the delete commit counts down. */
|
|
5234
|
+
const OVERLAY_REBUILD_MAX_KEYS = 1024;
|
|
5235
|
+
const OVERLAY_REBUILD_MIN_ADDS = 16;
|
|
5236
|
+
function overlayRebuilds(t, pb) {
|
|
5237
|
+
if (t.kc > OVERLAY_REBUILD_MAX_KEYS) return false;
|
|
5238
|
+
if (t.del !== null && t.del.size !== 0) return true;
|
|
5239
|
+
// Every trap write records its key, so the written-key count bounds the
|
|
5240
|
+
// adds — the common few-key fold (#3044's growing record) answers here
|
|
5241
|
+
// without enumerating the overlay.
|
|
5242
|
+
const wk = t.wk;
|
|
5243
|
+
if (wk !== null && wk !== WK_ALL && wk.size <= OVERLAY_REBUILD_MIN_ADDS) return false;
|
|
5244
|
+
const v = t.v;
|
|
5245
|
+
let adds = 0;
|
|
5246
|
+
for (const key of Reflect.ownKeys(pb))
|
|
5247
|
+
if (!hasOwn.call(v, key) && ++adds > OVERLAY_REBUILD_MIN_ADDS) return true;
|
|
5248
|
+
return false;
|
|
5249
|
+
}
|
|
5157
5250
|
/** Downgrade a prototype-overlay pending backing to the clone path: builds
|
|
5158
5251
|
* the real container (committed + overlay writes − deletes) that fold will
|
|
5159
5252
|
* SWAP in as the committed backing, exactly as if the draft had started on
|
|
5160
5253
|
* the clone path. Consumers that need a complete container (reconcile's
|
|
5161
|
-
* diff walks, drafts escaping into other storage) call this
|
|
5254
|
+
* diff walks, drafts escaping into other storage) call this, and so does the
|
|
5255
|
+
* commit itself when the fold changed the key set (overlayRebuilds). Returns
|
|
5256
|
+
* the pending backing (the clone, or the pb as-is when not an overlay). */
|
|
5162
5257
|
function materializePB(target) {
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
const clone = cloneRaw(target.v, target);
|
|
5166
|
-
|
|
5258
|
+
const pb = target.pb;
|
|
5259
|
+
if (!target.ovl) return pb;
|
|
5260
|
+
const clone = target.sc === 2 ? wideClone(target.v, target) : cloneRaw(target.v, target);
|
|
5261
|
+
// Deletes before writes (#3689): on a fast-mode clone (the descriptor
|
|
5262
|
+
// path) the first delete normalizes it once, and the writes then land as
|
|
5263
|
+
// O(1) dictionary adds instead of O(keys) map transitions each. Order is
|
|
5264
|
+
// free semantically — a rewritten key left `del` at write time, so no
|
|
5265
|
+
// write targets a deleted key.
|
|
5167
5266
|
if (target.del !== null) {
|
|
5168
5267
|
for (const key of target.del) delete clone[key];
|
|
5268
|
+
target.kc -= target.del.size;
|
|
5169
5269
|
target.del = null;
|
|
5170
5270
|
}
|
|
5271
|
+
// Plain-data grade (sc 2): bare assignment, as flattenOverlay does.
|
|
5272
|
+
for (const key of Reflect.ownKeys(pb))
|
|
5273
|
+
target.sc === 2 ? (clone[key] = pb[key]) : copyOwn(clone, pb, key);
|
|
5171
5274
|
target.pb = clone;
|
|
5172
5275
|
target.ovl = false;
|
|
5276
|
+
return clone;
|
|
5173
5277
|
}
|
|
5174
5278
|
function ensurePB(target) {
|
|
5175
5279
|
let pb = target.pb;
|
|
@@ -5321,6 +5425,7 @@ function adoptPB(target, incoming, eager = false) {
|
|
|
5321
5425
|
if (heldMaskView(target) === null) target.hv = target.v;
|
|
5322
5426
|
target.ht = activeTransition ?? PLAIN_HOLD;
|
|
5323
5427
|
if (!eager && activeTransition !== null) heldAdoptions.add(target);
|
|
5428
|
+
heldKeys.set(target, [incoming]);
|
|
5324
5429
|
}
|
|
5325
5430
|
target.pb = null;
|
|
5326
5431
|
// Overlay and accessor-scan state describe the OUTGOING backing — a
|
|
@@ -5450,6 +5555,7 @@ function flattenOverlay(t, pb) {
|
|
|
5450
5555
|
for (const key of Reflect.ownKeys(pb)) t.sc === 2 ? (v[key] = pb[key]) : copyOwn(v, pb, key);
|
|
5451
5556
|
if (t.del !== null) {
|
|
5452
5557
|
for (const key of t.del) delete v[key];
|
|
5558
|
+
t.kc -= t.del.size;
|
|
5453
5559
|
t.del = null;
|
|
5454
5560
|
}
|
|
5455
5561
|
t.pb = null;
|
|
@@ -5510,11 +5616,13 @@ function drainFolds() {
|
|
|
5510
5616
|
foldOlds.set(t, old); // re-queue: commit happens when the hold settles
|
|
5511
5617
|
continue;
|
|
5512
5618
|
}
|
|
5513
|
-
if (t.ovl) {
|
|
5619
|
+
if (t.ovl && (t.v !== old || !overlayRebuilds(t, pb))) {
|
|
5514
5620
|
// Overlay flatten (#3044): the backing keeps its identity, so the
|
|
5515
5621
|
// `t.v === old` gate below skips path copying (the parent slot
|
|
5516
5622
|
// already points here) and the adopted-notify (setter notifications
|
|
5517
|
-
// happened at write time).
|
|
5623
|
+
// happened at write time). A backing privatized mid-batch is a fresh
|
|
5624
|
+
// clone, never a prototype — in-place writes into it are cheap, and
|
|
5625
|
+
// the merge branch below is what a materialized pb would need anyway.
|
|
5518
5626
|
flattenOverlay(t, pb);
|
|
5519
5627
|
} else if (t.v !== old) {
|
|
5520
5628
|
// Privatized mid-batch (#3271): an earlier fold in this drain
|
|
@@ -5557,7 +5665,10 @@ function drainFolds() {
|
|
|
5557
5665
|
t.pb = null;
|
|
5558
5666
|
t.wk = null; // written-keys window closes with the fold commit
|
|
5559
5667
|
} else {
|
|
5560
|
-
|
|
5668
|
+
// An overlay whose fold changed the key set rebuilds (#3689) and
|
|
5669
|
+
// takes the swap like a clone-path draft: the parent slot re-points
|
|
5670
|
+
// in the path copy below.
|
|
5671
|
+
t.v = t.ovl ? materializePB(t) : pb;
|
|
5561
5672
|
t.ch = false; // pb is always a plain clone
|
|
5562
5673
|
t.pb = null;
|
|
5563
5674
|
t.wk = null; // written-keys window closes with the fold commit
|
|
@@ -5823,8 +5934,12 @@ function notifyWrites(t) {
|
|
|
5823
5934
|
if (t.ht !== null) t.ht = t.hv = null;
|
|
5824
5935
|
// Overlay landing (#3352): flatten in place — identity-stable, and
|
|
5825
5936
|
// privatizeCommitted re-slots the parent itself when it has to clone an
|
|
5826
|
-
// unowned seed, so no path copy is needed.
|
|
5827
|
-
|
|
5937
|
+
// unowned seed, so no path copy is needed. A landing that changed the
|
|
5938
|
+
// key set rebuilds instead (#3689) and swaps like a clone-path landing.
|
|
5939
|
+
if (t.ovl) {
|
|
5940
|
+
if (!overlayRebuilds(t, pb)) return flattenOverlay(t, pb);
|
|
5941
|
+
pb = materializePB(t);
|
|
5942
|
+
}
|
|
5828
5943
|
const oldBacking = t.v;
|
|
5829
5944
|
t.pb = null;
|
|
5830
5945
|
t.v = pb;
|
|
@@ -6082,7 +6197,10 @@ function foldHeld(target) {
|
|
|
6082
6197
|
}
|
|
6083
6198
|
return false;
|
|
6084
6199
|
}
|
|
6085
|
-
|
|
6200
|
+
/** The backing a reader is served. `key` (the get/has/descriptor traps)
|
|
6201
|
+
* scopes a fold hold to the keys the fold touched — see pendingBackingVisible —
|
|
6202
|
+
* and an adoption hold to the keys the adoption changed (heldKey). */
|
|
6203
|
+
function readSource(target, key) {
|
|
6086
6204
|
// Adoption hold first (#3074): an adoption staged under a live transaction
|
|
6087
6205
|
// (or a latest()-pull, PLAIN_HOLD) serves the pre-hold committed view to
|
|
6088
6206
|
// committed-visibility readers — context-free and children-forbidden ones,
|
|
@@ -6091,7 +6209,9 @@ function readSource(target) {
|
|
|
6091
6209
|
const ht = target.ht;
|
|
6092
6210
|
if (ht !== null && !latestReadActive && !inDraft(target) && !getWriteOverride()) {
|
|
6093
6211
|
const hv = heldMaskView(target);
|
|
6094
|
-
|
|
6212
|
+
// A key the adoption left unchanged derives nothing from the hold, for
|
|
6213
|
+
// any reader (#3706): the backing serves it.
|
|
6214
|
+
if (hv !== null && (key === undefined || heldKey(target, key))) {
|
|
6095
6215
|
const c = readerContext();
|
|
6096
6216
|
if (
|
|
6097
6217
|
c === null ||
|
|
@@ -6101,7 +6221,7 @@ function readSource(target) {
|
|
|
6101
6221
|
return hv;
|
|
6102
6222
|
}
|
|
6103
6223
|
}
|
|
6104
|
-
return pendingBackingVisible(target, false) ? target.pb : target.v;
|
|
6224
|
+
return pendingBackingVisible(target, false, key) ? target.pb : target.v;
|
|
6105
6225
|
}
|
|
6106
6226
|
/** The single pb-vs-committed visibility decision (#3147), shared by per-key
|
|
6107
6227
|
* backing reads (readSource) and deep()/snapshot composition (snapshotWalk)
|
|
@@ -6118,7 +6238,7 @@ function readSource(target) {
|
|
|
6118
6238
|
* ordinary pending staging regardless of owner context (the documented
|
|
6119
6239
|
* divergence from context-free per-key reads) — but never through a hold:
|
|
6120
6240
|
* held truth stays masked exactly as it is for per-key readers. */
|
|
6121
|
-
function pendingBackingVisible(target, speculative) {
|
|
6241
|
+
function pendingBackingVisible(target, speculative, key) {
|
|
6122
6242
|
if (target.pb === null) return false;
|
|
6123
6243
|
// The writer's own channels compose on the pending backing regardless.
|
|
6124
6244
|
if (inDraft(target) || getWriteOverride()) return true;
|
|
@@ -6142,7 +6262,26 @@ function pendingBackingVisible(target, speculative) {
|
|
|
6142
6262
|
if (speculative) return txn === null || ownsHold(txn);
|
|
6143
6263
|
return target.fam !== null && c === null && !foldHeld(target) && txn === null;
|
|
6144
6264
|
}
|
|
6145
|
-
|
|
6265
|
+
const txn = liveFoldTransition(target);
|
|
6266
|
+
// A hold changes what a reader of ONE key sees only through the fold's
|
|
6267
|
+
// writes to that key. A key the fold left alone reads the same from either
|
|
6268
|
+
// backing, so a pass reading it derives nothing from the hold: serve
|
|
6269
|
+
// committed, with no transaction entry and no stale replay (#3688 — a memo
|
|
6270
|
+
// reading an unchanged key beside an independent signal was held with
|
|
6271
|
+
// someone else's action). Per-node reads already have this precision (a
|
|
6272
|
+
// node with nothing staged enters nothing; stageHeldKey skips an equal
|
|
6273
|
+
// value); this is the same rule at the container gate. `wk` is the trap's
|
|
6274
|
+
// record of every write and delete this batch (deletes included, on the
|
|
6275
|
+
// overlay and clone paths alike); null (a fold with no trap writes) or
|
|
6276
|
+
// WK_ALL (an array length write) leaves the whole container held. Same
|
|
6277
|
+
// exclusions as heldFoldTransition: an optimistic family's draft is seeded
|
|
6278
|
+
// from node overrides the trap never saw, and a chained backing's
|
|
6279
|
+
// committed layer is a live proxy, not a frozen twin of the clone.
|
|
6280
|
+
if (txn !== null && key !== undefined && !target.ch && target.fam?.opt !== true) {
|
|
6281
|
+
const wk = target.wk;
|
|
6282
|
+
if (wk != null && wk !== WK_ALL && !wk.has(key)) return false;
|
|
6283
|
+
}
|
|
6284
|
+
return holdVisible(txn, c);
|
|
6146
6285
|
}
|
|
6147
6286
|
/** #3164 fold: HELD truth on an optimistic family — a pending backing
|
|
6148
6287
|
* stamped by a live transition that retains optimism — is masked from LANE
|
|
@@ -6456,7 +6595,7 @@ const traps = {
|
|
|
6456
6595
|
// memo parity for latest() reads through a projection.
|
|
6457
6596
|
if (target.fam !== null && latestReadActive && !inDraft(target) && !getWriteOverride())
|
|
6458
6597
|
pullProjectionForLatest(target);
|
|
6459
|
-
const src = readSource(target);
|
|
6598
|
+
const src = readSource(target, key);
|
|
6460
6599
|
// Overlay delete (#3044): a prototype overlay cannot shadow a delete, so
|
|
6461
6600
|
// deleted keys are tracked aside and read as absent in the pending view.
|
|
6462
6601
|
if (target.del !== null && src === target.pb && target.del.has(key)) {
|
|
@@ -6632,7 +6771,7 @@ const traps = {
|
|
|
6632
6771
|
if (key === $OWNER || key === $RECORD) return false;
|
|
6633
6772
|
if (pendingCheckActive) witnessAffectsMark(target, key);
|
|
6634
6773
|
if (target.fam !== null && getObserver() === null && !inDraft(target)) firewallGate(target);
|
|
6635
|
-
const src = readSource(target);
|
|
6774
|
+
const src = readSource(target, key);
|
|
6636
6775
|
let present = key in src;
|
|
6637
6776
|
// Overlay deletes read as absent in the pending view (#3044).
|
|
6638
6777
|
if (present && target.del !== null && src === target.pb && target.del.has(key)) present = false;
|
|
@@ -6689,7 +6828,7 @@ const traps = {
|
|
|
6689
6828
|
if (pendingCheckActive) witnessAffectsMark(target, key);
|
|
6690
6829
|
const obs = getObserver();
|
|
6691
6830
|
if (target.fam !== null && obs === null && !inDraft(target)) firewallGate(target);
|
|
6692
|
-
const src = readSource(target);
|
|
6831
|
+
const src = readSource(target, key);
|
|
6693
6832
|
const desc = visibleDescriptor(target, src, key);
|
|
6694
6833
|
if (!inDraft(target) && obs !== null && !observerHoldsKeySet(target, obs)) {
|
|
6695
6834
|
// The node is born from the source's presence (as `has` births it),
|
|
@@ -6735,10 +6874,12 @@ const traps = {
|
|
|
6735
6874
|
}
|
|
6736
6875
|
} else {
|
|
6737
6876
|
if (target.wk !== WK_ALL) (target.wk ??= new Set()).add(key);
|
|
6738
|
-
// Live own-key estimate for the overlay/clone choice (#3360)
|
|
6739
|
-
// sees through an overlay to the
|
|
6740
|
-
//
|
|
6741
|
-
//
|
|
6877
|
+
// Live own-key estimate for the overlay/clone choice (#3360) and the
|
|
6878
|
+
// overlay's commit choice (#3689): `in` sees through an overlay to the
|
|
6879
|
+
// committed keys, so this counts keys NEW to the container. Overlay
|
|
6880
|
+
// deletes are counted down when they commit (`del` is exact); clone-
|
|
6881
|
+
// path deletes are not (a stale high count on a narrow container only
|
|
6882
|
+
// picks the overlay a little early).
|
|
6742
6883
|
if (!(key in pb)) target.kc++;
|
|
6743
6884
|
}
|
|
6744
6885
|
// Own data keys literally named "prototype"/"constructor" land as data —
|
|
@@ -7,12 +7,13 @@ import { OBSERVE, liveRootOwners, emitDiagnostic, ownerPath, reportDiagnostic, i
|
|
|
7
7
|
/** A fallback shown for less than this is a flash: feedback for a wait too short to need it. */ const FALLBACK_FLASH_MS = 150;
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Under an owner the observer marked as its own (`OBSERVE.exclude
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* exists, before that a flag
|
|
10
|
+
* Under an owner the observer marked as its own (`OBSERVE.exclude`, and not
|
|
11
|
+
* re-admitted by a nearer `OBSERVE.include`), or framework plumbing itself
|
|
12
|
+
* (`CONFIG_PLUMBING` — the HMR memo between a component's root and its
|
|
13
|
+
* body, which is nobody's node): the engine records nothing about the node.
|
|
14
|
+
* Plumbing is a bit read and excludes the node alone, not what it owns; the
|
|
15
|
+
* observer exclusion is cached per node once any exists, before that a flag
|
|
16
|
+
* read.
|
|
16
17
|
*/ function excludedNode(e) {
|
|
17
18
|
if ((e.C & CONFIG_PLUMBING) !== 0) return true;
|
|
18
19
|
if (!anyExcluded()) return false;
|
|
@@ -373,15 +374,22 @@ function popFrame(e) {
|
|
|
373
374
|
return;
|
|
374
375
|
}
|
|
375
376
|
}
|
|
376
|
-
|
|
377
|
+
// The initial navigation is the document's: its request is the time origin
|
|
378
|
+
// unless the router says otherwise, and it left nowhere.
|
|
379
|
+
const n = e.initial === true;
|
|
380
|
+
const t = {
|
|
377
381
|
kind: e.kind,
|
|
378
|
-
at: e.at ?? now()
|
|
382
|
+
at: e.at ?? (n ? 0 : now())
|
|
379
383
|
};
|
|
380
|
-
if (e.from !== undefined)
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
384
|
+
if (!n && e.from !== undefined) t.from = e.from;
|
|
385
|
+
// Declared beats ambient: a router that awaited before writing hands back
|
|
386
|
+
// the origin it captured in the request (`undefined` when the request ran
|
|
387
|
+
// under none — still a declaration); what is on the stack now is whatever
|
|
388
|
+
// happened to be running.
|
|
389
|
+
const o = "interaction" in e ? interactionOf(e.interaction) : enclosingInteraction();
|
|
390
|
+
if (o !== undefined) t.interaction = o;
|
|
391
|
+
originFrames.push(t);
|
|
392
|
+
openNavigation(t, e);
|
|
385
393
|
}
|
|
386
394
|
|
|
387
395
|
function originEnd() {
|
|
@@ -415,7 +423,8 @@ function originEnd() {
|
|
|
415
423
|
if (e.to !== undefined && e.to !== n) t.push(e.to);
|
|
416
424
|
const o = navStates.get(e)?.event.redirects;
|
|
417
425
|
if (o !== undefined) t.push(`redirected from ${o.map(e => e.to ?? e.name ?? "?").join(" → ")}`);
|
|
418
|
-
|
|
426
|
+
const i = navStates.get(e)?.event.initial === true;
|
|
427
|
+
return `${i ? "initial " : ""}navigation to ${n}${t.length > 0 ? ` (${t.join(", ")})` : ""}`;
|
|
419
428
|
}
|
|
420
429
|
|
|
421
430
|
default:
|
|
@@ -1778,8 +1787,10 @@ function checkLongHold(e, n) {
|
|
|
1778
1787
|
// derived override promotes rather than reverts. Same predicate the hold
|
|
1779
1788
|
// census uses to skip them.
|
|
1780
1789
|
if (!options.optimisticReverts || isCompanion(e)) return;
|
|
1781
|
-
|
|
1782
|
-
|
|
1790
|
+
// Method call, as every commit path does: store slot nodes share one
|
|
1791
|
+
// comparator that reads `this._host` (#3687).
|
|
1792
|
+
const i = e.Fe;
|
|
1793
|
+
if (i && i.call(e, n, t)) return;
|
|
1783
1794
|
const s = nodeName(e);
|
|
1784
1795
|
// The two values are user data: quoted only under `values: "full"`; the
|
|
1785
1796
|
// other levels say what happened without saying what was shown.
|
|
@@ -1841,17 +1852,17 @@ function checkLongHold(e, n) {
|
|
|
1841
1852
|
for (const t of e) {
|
|
1842
1853
|
n.owners++;
|
|
1843
1854
|
// Iterative: a deep tree must not grow the call stack.
|
|
1844
|
-
let e = t.
|
|
1855
|
+
let e = t.On;
|
|
1845
1856
|
for (;;) {
|
|
1846
1857
|
while (e !== null) {
|
|
1847
1858
|
n.owners++;
|
|
1848
1859
|
if (typeof e.Se === "function") meet(e);
|
|
1849
|
-
if (e.
|
|
1850
|
-
e = e.
|
|
1860
|
+
if (e.On !== null) i.push(e);
|
|
1861
|
+
e = e._n;
|
|
1851
1862
|
}
|
|
1852
1863
|
const t = i.pop();
|
|
1853
1864
|
if (t === undefined) break;
|
|
1854
|
-
e = t.
|
|
1865
|
+
e = t.On;
|
|
1855
1866
|
}
|
|
1856
1867
|
}
|
|
1857
1868
|
for (let e = 0; e < o.length; e++) {
|
|
@@ -2070,6 +2081,7 @@ function openNavigation(e, n) {
|
|
|
2070
2081
|
writes: 0,
|
|
2071
2082
|
origin: e
|
|
2072
2083
|
};
|
|
2084
|
+
if (n.initial === true) t.initial = true;
|
|
2073
2085
|
if (e.from !== undefined) t.from = e.from;
|
|
2074
2086
|
if (e.interaction !== undefined) t.interaction = e.interaction;
|
|
2075
2087
|
const o = {
|
|
@@ -2295,7 +2307,10 @@ function settleNavigation(e, n, t) {
|
|
|
2295
2307
|
o.settledMs = now() - o.at;
|
|
2296
2308
|
o.outcome = n;
|
|
2297
2309
|
if (t !== undefined) o.hold = t;
|
|
2298
|
-
|
|
2310
|
+
// The initial declaration is not a navigation the person waited on: its
|
|
2311
|
+
// `settledMs` is document start → router built, which would read as the
|
|
2312
|
+
// route's responsiveness in the feedback tables. Consumers get the record.
|
|
2313
|
+
if (o.initial !== true) for (const e of folds) e.navigation?.(o);
|
|
2299
2314
|
records.emit("navigation", o, undefined);
|
|
2300
2315
|
trackGraph(o);
|
|
2301
2316
|
// The interaction that performed it may have been waiting only on this.
|
|
@@ -180,20 +180,29 @@ const CONFIG_HAS_LANE = 1 << 10;
|
|
|
180
180
|
* transaction. A parked node (status propagation stamps `_transition`
|
|
181
181
|
* without recomputing) recomputed when its source lands otherwise disposed
|
|
182
182
|
* its committed children mid-hold, running their cleanups before the
|
|
183
|
-
* transaction's atomic reveal. Cleared by `commitPendingNode`.
|
|
183
|
+
* transaction's atomic reveal. Cleared by `commitPendingNode`. Transaction
|
|
184
|
+
* work only (A15 lane work and transaction work, #3698): zombies are parked
|
|
185
|
+
* by a pass under a held transaction; a pass over a lane parks a LANE frame
|
|
186
|
+
* instead (`CONFIG_LANE_FRAME`), whatever the node's kind. */ const CONFIG_HELD_CHILDREN = 1 << 20;
|
|
184
187
|
|
|
185
188
|
/** The frame parked in `_pendingFirstChild` / `_pendingDisposal` is a LANE
|
|
186
|
-
* frame (#3662, A15
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
189
|
+
* frame (#3662, #3698; A15 lane work and transaction work): a lane pass —
|
|
190
|
+
* on an effect or a memo alike — publishes into the lane's frame (an
|
|
191
|
+
* effect's run; a memo's derived override, A17), so the frame it replaces
|
|
192
|
+
* leaves the screen when the lane applies (A30) — not at the action's
|
|
193
|
+
* commit like #3404's transaction zombies, and not at the pass (a held lane
|
|
194
|
+
* defers the apply with the frame still displayed). Drained by the lane's
|
|
195
|
+
* render entry the parking site pushed ahead of the new frame's effects
|
|
196
|
+
* (cleanups before side effects), by `commitPendingNode` if a hold commits
|
|
197
|
+
* the node first, or with the owner's death. While set the parked frame is
|
|
198
|
+
* not a hold (the node is not queued or stamped for it — lane work never
|
|
199
|
+
* makes its node transaction work), a superseding pass disposes the
|
|
200
|
+
* never-shown live children on the spot, and a lane-channel dirty on a
|
|
201
|
+
* member is cancelled (`laneZombie`). Ruled 2026-09-28 (#3698): a pass is
|
|
202
|
+
* lane work or transaction work by its owner, never by node kind. #3662
|
|
203
|
+
* flagged effects only, and a memo's lane pass parked a transaction zombie
|
|
204
|
+
* that queued and stamped the memo as the action's pending node, so its
|
|
205
|
+
* next mainline recompute re-entered the hold. */ const CONFIG_LANE_FRAME = 1 << 26;
|
|
197
206
|
|
|
198
207
|
/** In-flight async node whose inputs were PUBLISHED while it was pending: a
|
|
199
208
|
* batch or transaction committed with the node still `STATUS_PENDING` (an
|