@solidjs/signals 2.0.0-beta.24 → 2.0.0-beta.25
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 +465 -81
- package/dist/node.cjs +990 -652
- package/dist/prod/core/core.js +110 -76
- package/dist/prod/core/effect.js +9 -9
- package/dist/prod/core/graph.js +3 -3
- package/dist/prod/core/heap.js +23 -23
- package/dist/prod/core/optimistic.js +6 -4
- package/dist/prod/core/owner.js +10 -10
- package/dist/prod/core/scheduler.js +12 -12
- package/dist/prod/core/verdict.js +3 -3
- package/dist/prod/store/optimistic.js +12 -5
- package/dist/prod/store/projection.js +25 -18
- package/dist/prod/store/reconcile.js +405 -211
- package/dist/prod/store/store.js +196 -102
- package/dist/prod/store/utils.js +22 -22
- package/dist/types/core/core.d.ts +15 -0
- package/dist/types/store/optimistic.d.ts +1 -1
- package/dist/types/store/store.d.ts +20 -2
- package/dist/types-cjs/core/core.d.cts +15 -0
- package/dist/types-cjs/store/optimistic.d.cts +1 -1
- package/dist/types-cjs/store/store.d.cts +20 -2
- package/package.json +1 -1
package/dist/dev.js
CHANGED
|
@@ -2230,7 +2230,21 @@ function recompute(el, create = false) {
|
|
|
2230
2230
|
value = inFlightChanged || !isAsyncResult ? fnResult : handleAsync(el, fnResult);
|
|
2231
2231
|
if (!inFlightChanged && !isAsyncResult) el._inFlight = null;
|
|
2232
2232
|
}
|
|
2233
|
-
clearStatus
|
|
2233
|
+
// On a status-free node clearStatus is a guaranteed no-op: every branch
|
|
2234
|
+
// in its body is gated on one of these fields, and with _statusFlags === 0
|
|
2235
|
+
// the flags write (create or not) stores the 0 already there.
|
|
2236
|
+
if (
|
|
2237
|
+
el._statusFlags !== 0 ||
|
|
2238
|
+
el._notifyStatus !== undefined ||
|
|
2239
|
+
el._error ||
|
|
2240
|
+
el._reask ||
|
|
2241
|
+
el._blocked ||
|
|
2242
|
+
el._pendingSources !== undefined ||
|
|
2243
|
+
el._pendingSignal !== undefined ||
|
|
2244
|
+
el._latestValueComputed !== undefined ||
|
|
2245
|
+
el._child !== null
|
|
2246
|
+
)
|
|
2247
|
+
clearStatus(el, create);
|
|
2234
2248
|
// _optimisticLane is only ever assigned by engine paths.
|
|
2235
2249
|
if (el._optimisticLane) GlobalQueue._laneAsyncSettled(el);
|
|
2236
2250
|
} catch (e) {
|
|
@@ -2291,7 +2305,15 @@ function recompute(el, create = false) {
|
|
|
2291
2305
|
if (el._error);
|
|
2292
2306
|
else if (valueChanged) {
|
|
2293
2307
|
const prevVisible = hasOverride ? el._overrideValue : undefined;
|
|
2294
|
-
if (
|
|
2308
|
+
if (
|
|
2309
|
+
create ||
|
|
2310
|
+
// Plain sync flush (no transition on either side) commits effect
|
|
2311
|
+
// values directly — the pending round-trip (queuePendingNode +
|
|
2312
|
+
// commitPendingNodes) exists to sequence transition reveals, and
|
|
2313
|
+
// paying it per effect on the plain path is pure overhead.
|
|
2314
|
+
(isEffect && (activeTransition !== el._transition || activeTransition === null)) ||
|
|
2315
|
+
isOptimisticDirty
|
|
2316
|
+
) {
|
|
2295
2317
|
el._value = value;
|
|
2296
2318
|
// Lane-propagated correction: upstream data is fresh, correct the
|
|
2297
2319
|
// override unconditionally. The direct _value commit is the lane's
|
|
@@ -2311,7 +2333,12 @@ function recompute(el, create = false) {
|
|
|
2311
2333
|
if ((activeTransition || el._transition) && GlobalQueue._syncCompanions !== null)
|
|
2312
2334
|
GlobalQueue._syncCompanions(el, value);
|
|
2313
2335
|
}
|
|
2314
|
-
|
|
2336
|
+
// insertSubs only walks _subs (no scheduling of its own), so a
|
|
2337
|
+
// subscriber-less node has nothing to notify.
|
|
2338
|
+
if (
|
|
2339
|
+
el._subs !== null &&
|
|
2340
|
+
(!hasOverride || isOptimisticDirty || el._overrideValue !== prevVisible)
|
|
2341
|
+
)
|
|
2315
2342
|
insertSubs(el, isOptimisticDirty || hasOverride);
|
|
2316
2343
|
} else if (hasOverride) {
|
|
2317
2344
|
// Unchanged value (equals the override) recomputed while the override
|
|
@@ -2610,6 +2637,39 @@ function prepareComputed(comp, refresh) {
|
|
|
2610
2637
|
updateIfNecessary(comp);
|
|
2611
2638
|
}
|
|
2612
2639
|
}
|
|
2640
|
+
/**
|
|
2641
|
+
* Sentinel returned by readNodeFast when the plain-signal fast path does not
|
|
2642
|
+
* apply and the caller must fall back to the full read().
|
|
2643
|
+
*/
|
|
2644
|
+
const READ_SLOW = Symbol("read-slow");
|
|
2645
|
+
/**
|
|
2646
|
+
* read()'s plain-signal fast path as a standalone entry for hot callers
|
|
2647
|
+
* (store traps). Safe to substitute for read() only because the bail
|
|
2648
|
+
* conditions mirror read()'s prelude and fast-path guard exactly: the
|
|
2649
|
+
* latestRead and pendingCheck windows run side-effectful hooks before the
|
|
2650
|
+
* fast path, `_fn` nodes need prepareComputed, and firewall / override /
|
|
2651
|
+
* snapshot / transition / lane / dev-strictRead state all take the full
|
|
2652
|
+
* resolution. Anything slow returns READ_SLOW; the caller then calls read().
|
|
2653
|
+
*/
|
|
2654
|
+
function readNodeFast(el) {
|
|
2655
|
+
if (
|
|
2656
|
+
latestReadActive ||
|
|
2657
|
+
pendingCheckActive ||
|
|
2658
|
+
el._fn ||
|
|
2659
|
+
el._firewall ||
|
|
2660
|
+
el._overrideValue !== undefined ||
|
|
2661
|
+
el._snapshotValue !== undefined ||
|
|
2662
|
+
activeTransition !== null ||
|
|
2663
|
+
currentOptimisticLane !== null ||
|
|
2664
|
+
snapshotCaptureActive ||
|
|
2665
|
+
strictRead
|
|
2666
|
+
)
|
|
2667
|
+
return READ_SLOW;
|
|
2668
|
+
let c = context;
|
|
2669
|
+
if (c?._root) c = c._parentComputed;
|
|
2670
|
+
if (c && tracking) link(el, c);
|
|
2671
|
+
return !c || el._pendingValue === NOT_PENDING ? el._value : el._pendingValue;
|
|
2672
|
+
}
|
|
2613
2673
|
function read(el) {
|
|
2614
2674
|
// Handle latest() mode: read from _latestValueComputed
|
|
2615
2675
|
// Checked before isPending so that isPending(() => latest(x)) checks
|
|
@@ -2805,7 +2865,12 @@ function setSignal(el, v) {
|
|
|
2805
2865
|
if (!valueChanged) return v;
|
|
2806
2866
|
if (el._pendingValue === NOT_PENDING) queuePendingNode(el);
|
|
2807
2867
|
el._pendingValue = v;
|
|
2808
|
-
|
|
2868
|
+
// syncCompanions only pokes _pendingSignal/_latestValueComputed — with
|
|
2869
|
+
// neither companion present the call is a guaranteed no-op (companions are
|
|
2870
|
+
// only ever created, never removed, and creating one installs the hook).
|
|
2871
|
+
(el._pendingSignal !== undefined || el._latestValueComputed !== undefined) &&
|
|
2872
|
+
GlobalQueue._syncCompanions !== null &&
|
|
2873
|
+
GlobalQueue._syncCompanions(el, v);
|
|
2809
2874
|
el._time = clock;
|
|
2810
2875
|
insertSubs(el);
|
|
2811
2876
|
schedule();
|
|
@@ -3147,7 +3212,11 @@ function optimisticWrite(el, v) {
|
|
|
3147
3212
|
// brand, and erasing it makes the write invisible and routes follow-up
|
|
3148
3213
|
// writes off the optimistic path into permanent commits (#2898).
|
|
3149
3214
|
el._overrideValue = v === undefined ? OVERRIDE_UNDEFINED : v;
|
|
3150
|
-
|
|
3215
|
+
// syncCompanions only pokes _pendingSignal/_latestValueComputed — with
|
|
3216
|
+
// neither companion present the call is a guaranteed no-op.
|
|
3217
|
+
(el._pendingSignal !== undefined || el._latestValueComputed !== undefined) &&
|
|
3218
|
+
GlobalQueue._syncCompanions !== null &&
|
|
3219
|
+
GlobalQueue._syncCompanions(el, v);
|
|
3151
3220
|
el._time = clock;
|
|
3152
3221
|
insertSubs(el, true);
|
|
3153
3222
|
schedule();
|
|
@@ -4591,27 +4660,70 @@ function keyedMatch(a, b, keyFn) {
|
|
|
4591
4660
|
// and `in` dependencies should follow the new value's membership. Use
|
|
4592
4661
|
// membership rather than length arithmetic so sparse arrays and named array
|
|
4593
4662
|
// props behave like normal property reads.
|
|
4663
|
+
// Inline key iteration on purpose: these loops run per array target per
|
|
4664
|
+
// reconcile pass, and a shared helper means a closure + per-key call in
|
|
4665
|
+
// instruction counts. String-only records (the common case) iterate in
|
|
4666
|
+
// place; symbol-bearing records take the nodeKeys array path.
|
|
4594
4667
|
function syncArrayNodeMembership(target, next) {
|
|
4595
4668
|
let nodes = target[STORE_NODE];
|
|
4596
4669
|
if (nodes) {
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4670
|
+
if (symbolKeyedRecords.has(nodes)) {
|
|
4671
|
+
const keys = nodeKeys(nodes);
|
|
4672
|
+
for (let i = 0, len = keys.length; i < len; i++) {
|
|
4673
|
+
keys[i] in next || setSignal(nodes[keys[i]], undefined);
|
|
4674
|
+
}
|
|
4675
|
+
} else {
|
|
4676
|
+
for (const key in nodes) {
|
|
4677
|
+
key in next || setSignal(nodes[key], undefined);
|
|
4678
|
+
}
|
|
4601
4679
|
}
|
|
4602
4680
|
}
|
|
4603
4681
|
if ((nodes = target[STORE_HAS])) {
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4682
|
+
if (symbolKeyedRecords.has(nodes)) {
|
|
4683
|
+
const keys = nodeKeys(nodes);
|
|
4684
|
+
for (let i = 0, len = keys.length; i < len; i++) {
|
|
4685
|
+
setSignal(nodes[keys[i]], keys[i] in next);
|
|
4686
|
+
}
|
|
4687
|
+
} else {
|
|
4688
|
+
for (const key in nodes) {
|
|
4689
|
+
setSignal(nodes[key], key in next);
|
|
4690
|
+
}
|
|
4608
4691
|
}
|
|
4609
4692
|
}
|
|
4610
4693
|
}
|
|
4694
|
+
// Recurse into a matched wrappable child pair without manufacturing a proxy:
|
|
4695
|
+
// resolve the child's target through the lookup and dispatch directly. A
|
|
4696
|
+
// lookup miss means the child was never observed — no proxy, no nodes, no
|
|
4697
|
+
// subscribers anywhere below — so the parent's swap making `next`
|
|
4698
|
+
// authoritative IS the whole update; a later read wraps next's child on
|
|
4699
|
+
// demand. This turns the diff from O(previous graph) into O(observed graph)
|
|
4700
|
+
// and drops the wrap()/$PROXY/$TARGET round-trip per visited child.
|
|
4701
|
+
// Wrap-family stores (projections/optimistic) own child proxy creation and
|
|
4702
|
+
// keep the proxy-based recursion.
|
|
4703
|
+
function applyStateChild(next, prevRaw, target, keyFn) {
|
|
4704
|
+
if (target[STORE_WRAP] !== undefined) {
|
|
4705
|
+
applyState(next, wrap(prevRaw, target), keyFn);
|
|
4706
|
+
return;
|
|
4707
|
+
}
|
|
4708
|
+
const childTarget = prevRaw[$TARGET] ?? storeLookup.get(prevRaw);
|
|
4709
|
+
if (childTarget === undefined) return;
|
|
4710
|
+
next = unwrap(next);
|
|
4711
|
+
if (childTarget[STORE_SHALLOW]) {
|
|
4712
|
+
applyStateShallow(next, childTarget);
|
|
4713
|
+
} else if (childTarget[STORE_OVERRIDE] || childTarget[STORE_OPTIMISTIC_OVERRIDE]) {
|
|
4714
|
+
applyStateSlow(next, childTarget, keyFn);
|
|
4715
|
+
} else {
|
|
4716
|
+
applyStateFast(next, childTarget, keyFn);
|
|
4717
|
+
}
|
|
4718
|
+
}
|
|
4611
4719
|
// Reconcile a single array slot: recurse into a wrappable pair, otherwise replace
|
|
4612
4720
|
// the node's value outright (covers object→primitive and primitive→object).
|
|
4613
4721
|
function applyArrayItem(next, previous, target, node, keyFn) {
|
|
4614
|
-
if (
|
|
4722
|
+
if (
|
|
4723
|
+
isWrappable(next) &&
|
|
4724
|
+
isWrappable(previous) &&
|
|
4725
|
+
!(rawValuesUsed && (isRawValue(previous) || isRawValue(next)))
|
|
4726
|
+
) {
|
|
4615
4727
|
const wrapped = wrap(previous, target);
|
|
4616
4728
|
node && setSignal(node, wrapped);
|
|
4617
4729
|
applyState(next, wrapped, keyFn);
|
|
@@ -4631,27 +4743,54 @@ function applyArrayItem(next, previous, target, node, keyFn) {
|
|
|
4631
4743
|
*/
|
|
4632
4744
|
function applyDescendants(previous, next, target, nodes, keyFn, override, optOverride) {
|
|
4633
4745
|
const lookup = target[STORE_LOOKUP] || storeLookup;
|
|
4634
|
-
|
|
4635
|
-
getStoreSymbols(previous, override)
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4746
|
+
if (override) {
|
|
4747
|
+
const keys = getKeys(previous, override).concat(getStoreSymbols(previous, override));
|
|
4748
|
+
for (let i = 0, len = keys.length; i < len; i++) {
|
|
4749
|
+
const key = keys[i];
|
|
4750
|
+
if (nodes?.[key]) continue; // main loop already diffed this slot
|
|
4751
|
+
const previousValue = unwrap(getOverrideValue(previous, override, key, optOverride));
|
|
4752
|
+
if (!isWrappable(previousValue)) continue;
|
|
4753
|
+
descendInto(previousValue, next[key], lookup, keyFn);
|
|
4754
|
+
}
|
|
4755
|
+
return;
|
|
4756
|
+
}
|
|
4757
|
+
// No-override path (every applyStateFast call): iterate in place instead of
|
|
4758
|
+
// building keys + symbols + concat arrays per object per pass. The cheap
|
|
4759
|
+
// bails (noded key, primitive value) stay inline — only genuine descent
|
|
4760
|
+
// candidates pay a call.
|
|
4761
|
+
for (const key in previous) {
|
|
4639
4762
|
if (nodes?.[key]) continue; // main loop already diffed this slot
|
|
4640
|
-
const previousValue = unwrap(
|
|
4641
|
-
override ? getOverrideValue(previous, override, key, optOverride) : previous[key]
|
|
4642
|
-
);
|
|
4763
|
+
const previousValue = unwrap(previous[key]);
|
|
4643
4764
|
if (!isWrappable(previousValue)) continue;
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
(
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4765
|
+
descendInto(previousValue, next[key], lookup, keyFn);
|
|
4766
|
+
}
|
|
4767
|
+
const syms = Object.getOwnPropertySymbols(previous);
|
|
4768
|
+
for (let i = 0, len = syms.length; i < len; i++) {
|
|
4769
|
+
if (Object.prototype.propertyIsEnumerable.call(previous, syms[i])) {
|
|
4770
|
+
if (nodes?.[syms[i]]) continue;
|
|
4771
|
+
const previousValue = unwrap(previous[syms[i]]);
|
|
4772
|
+
if (!isWrappable(previousValue)) continue;
|
|
4773
|
+
descendInto(previousValue, next[syms[i]], lookup, keyFn);
|
|
4774
|
+
}
|
|
4775
|
+
}
|
|
4776
|
+
}
|
|
4777
|
+
function descendInto(previousValue, rawNext, lookup, keyFn) {
|
|
4778
|
+
const childTarget = lookupTarget(previousValue, lookup);
|
|
4779
|
+
if (!childTarget?.[STORE_DESC]) return;
|
|
4780
|
+
const nextValue = unwrap(rawNext);
|
|
4781
|
+
if (
|
|
4782
|
+
previousValue === nextValue ||
|
|
4783
|
+
!isWrappable(nextValue) ||
|
|
4784
|
+
Array.isArray(previousValue) !== Array.isArray(nextValue) ||
|
|
4785
|
+
(keyFn(previousValue) != null && keyFn(previousValue) !== keyFn(nextValue))
|
|
4786
|
+
)
|
|
4787
|
+
return;
|
|
4788
|
+
if (childTarget[STORE_SHALLOW]) {
|
|
4789
|
+
applyStateShallow(nextValue, childTarget);
|
|
4790
|
+
} else if (childTarget[STORE_OVERRIDE] || childTarget[STORE_OPTIMISTIC_OVERRIDE]) {
|
|
4791
|
+
applyStateSlow(nextValue, childTarget, keyFn);
|
|
4792
|
+
} else {
|
|
4793
|
+
applyStateFast(nextValue, childTarget, keyFn);
|
|
4655
4794
|
}
|
|
4656
4795
|
}
|
|
4657
4796
|
// Dispatcher: every applyState call (including recursion) checks for the
|
|
@@ -4665,18 +4804,101 @@ function applyState(next, state, keyFn) {
|
|
|
4665
4804
|
next = unwrap(next);
|
|
4666
4805
|
const target = state?.[$TARGET];
|
|
4667
4806
|
if (!target) return;
|
|
4668
|
-
if (target[
|
|
4807
|
+
if (target[STORE_SHALLOW]) {
|
|
4808
|
+
applyStateShallow(next, target);
|
|
4809
|
+
} else if (target[STORE_OVERRIDE] || target[STORE_OPTIMISTIC_OVERRIDE]) {
|
|
4669
4810
|
applyStateSlow(next, target, keyFn);
|
|
4670
4811
|
} else {
|
|
4671
4812
|
applyStateFast(next, target, keyFn);
|
|
4672
4813
|
}
|
|
4673
4814
|
}
|
|
4815
|
+
// Shallow boundary diff: the target's own keys are reactive, its values are
|
|
4816
|
+
// raw records replaced by reference — no recursion, no wrapping. Arrays merge
|
|
4817
|
+
// positionally (below a shallow boundary the VALUE is the identity; keyed
|
|
4818
|
+
// row identity belongs to the consumer, e.g. <For keyed>). Incoming
|
|
4819
|
+
// wrappables are sticky-marked raw so they present raw through every store.
|
|
4820
|
+
// One pass over a shallow target's node record: replace changed slots with
|
|
4821
|
+
// raw next values, null out slots absent from next. Returns whether anything
|
|
4822
|
+
// differed.
|
|
4823
|
+
function shallowDiffNodes(nodes, next, prevAt, skipLength) {
|
|
4824
|
+
let changed = false;
|
|
4825
|
+
for (const key in nodes) {
|
|
4826
|
+
if (skipLength && key === "length") continue;
|
|
4827
|
+
if (key in next) {
|
|
4828
|
+
const v = next[key];
|
|
4829
|
+
if (v !== prevAt(key)) {
|
|
4830
|
+
changed = true;
|
|
4831
|
+
setSignal(nodes[key], v);
|
|
4832
|
+
}
|
|
4833
|
+
} else {
|
|
4834
|
+
changed = true;
|
|
4835
|
+
setSignal(nodes[key], undefined);
|
|
4836
|
+
}
|
|
4837
|
+
}
|
|
4838
|
+
return changed;
|
|
4839
|
+
}
|
|
4840
|
+
function applyStateShallow(next, target, keyFn) {
|
|
4841
|
+
const previous = target[STORE_VALUE];
|
|
4842
|
+
const override = target[STORE_OVERRIDE];
|
|
4843
|
+
const optOverride = target[STORE_OPTIMISTIC_OVERRIDE];
|
|
4844
|
+
if (next === previous && !override && !optOverride) return;
|
|
4845
|
+
// Setter-staged writes fold into the diff: previous values resolve through
|
|
4846
|
+
// the override layers (so a replaced slot compares against what readers
|
|
4847
|
+
// saw), and the regular override clears with the swap — reconcile makes
|
|
4848
|
+
// `next` the authoritative base, same as the deep slow path.
|
|
4849
|
+
const prevAt = key => {
|
|
4850
|
+
const v = getOverrideValue(previous, override, key, optOverride);
|
|
4851
|
+
return v === $DELETED ? undefined : v;
|
|
4852
|
+
};
|
|
4853
|
+
target[STORE_OVERRIDE] = undefined;
|
|
4854
|
+
const fam = target[STORE_LOOKUP];
|
|
4855
|
+
fam !== undefined ? fam.set(next, target[$PROXY]) : storeLookup.set(next, target);
|
|
4856
|
+
target[STORE_VALUE] = next;
|
|
4857
|
+
markRawIngest(next);
|
|
4858
|
+
const nodes = target[STORE_NODE];
|
|
4859
|
+
const tracked = nodes && nodes[$TRACK];
|
|
4860
|
+
let changed = false;
|
|
4861
|
+
if (Array.isArray(previous)) {
|
|
4862
|
+
const prevLength = override?.length ?? optOverride?.length ?? previous.length;
|
|
4863
|
+
if (nodes) {
|
|
4864
|
+
changed = shallowDiffNodes(nodes, next, prevAt, true);
|
|
4865
|
+
if (nodes.length && prevLength !== next.length) setSignal(nodes.length, next.length);
|
|
4866
|
+
}
|
|
4867
|
+
if (!changed && (tracked || target[STORE_HAS])) {
|
|
4868
|
+
// Slots without nodes still feed $TRACK enumerators / `in` probes.
|
|
4869
|
+
if (prevLength !== next.length) changed = true;
|
|
4870
|
+
else {
|
|
4871
|
+
for (let i = 0, len = next.length; i < len; i++) {
|
|
4872
|
+
if (prevAt(i) !== next[i]) {
|
|
4873
|
+
changed = true;
|
|
4874
|
+
break;
|
|
4875
|
+
}
|
|
4876
|
+
}
|
|
4877
|
+
}
|
|
4878
|
+
}
|
|
4879
|
+
} else {
|
|
4880
|
+
if (nodes) {
|
|
4881
|
+
changed = shallowDiffNodes(nodes, next, prevAt, false);
|
|
4882
|
+
}
|
|
4883
|
+
if (!changed && (tracked || target[STORE_HAS])) changed = true;
|
|
4884
|
+
}
|
|
4885
|
+
let has = target[STORE_HAS];
|
|
4886
|
+
if (has) {
|
|
4887
|
+
for (const key in has) {
|
|
4888
|
+
setSignal(has[key], key in next);
|
|
4889
|
+
}
|
|
4890
|
+
}
|
|
4891
|
+
changed && notifySelf(target);
|
|
4892
|
+
}
|
|
4674
4893
|
function applyStateFast(next, target, keyFn) {
|
|
4675
4894
|
const previous = target[STORE_VALUE];
|
|
4676
4895
|
if (next === previous) return;
|
|
4677
4896
|
const arrayNodes = target[STORE_NODE];
|
|
4678
4897
|
// swap
|
|
4679
|
-
|
|
4898
|
+
{
|
|
4899
|
+
const fam = target[STORE_LOOKUP];
|
|
4900
|
+
fam !== undefined ? fam.set(next, target[$PROXY]) : storeLookup.set(next, target);
|
|
4901
|
+
}
|
|
4680
4902
|
target[STORE_VALUE] = next;
|
|
4681
4903
|
// merge
|
|
4682
4904
|
if (Array.isArray(previous)) {
|
|
@@ -4689,10 +4911,22 @@ function applyStateFast(next, target, keyFn) {
|
|
|
4689
4911
|
start < end && keyedMatch((item = previous[start]), next[start], keyFn);
|
|
4690
4912
|
start++
|
|
4691
4913
|
) {
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4914
|
+
// keyedMatch established both sides wrappable unless they're the
|
|
4915
|
+
// SAME reference — and an identical slot is a guaranteed no-op
|
|
4916
|
+
// (the child's STORE_VALUE tracks the previous graph), so skip
|
|
4917
|
+
// the recursion dispatch entirely. Raw-marked values are leaves:
|
|
4918
|
+
// replace the slot node instead of recursing.
|
|
4919
|
+
if (item !== next[start]) {
|
|
4920
|
+
if (rawValuesUsed && (isRawValue(item) || isRawValue(next[start]))) {
|
|
4921
|
+
arrayNodes?.[start] && setSignal(arrayNodes[start], wrapValue(next[start], target));
|
|
4922
|
+
} else applyStateChild(next[start], item, target, keyFn);
|
|
4923
|
+
}
|
|
4695
4924
|
}
|
|
4925
|
+
// Every position key-matched at equal length (the steady-state shape
|
|
4926
|
+
// of a polling tick): membership, length, and order are unchanged —
|
|
4927
|
+
// nothing below could do observable work, so skip the staging
|
|
4928
|
+
// allocations and membership sync outright.
|
|
4929
|
+
if (start === next.length && start === prevLength) return;
|
|
4696
4930
|
const temp = new Array(next.length),
|
|
4697
4931
|
newIndices = new Map();
|
|
4698
4932
|
for (
|
|
@@ -4745,9 +4979,13 @@ function applyStateFast(next, target, keyFn) {
|
|
|
4745
4979
|
} else if (next.length) {
|
|
4746
4980
|
for (let i = 0, len = next.length; i < len; i++) {
|
|
4747
4981
|
const item = previous[i];
|
|
4748
|
-
if (
|
|
4749
|
-
|
|
4750
|
-
|
|
4982
|
+
if (
|
|
4983
|
+
isWrappable(item) &&
|
|
4984
|
+
isWrappable(next[i]) &&
|
|
4985
|
+
!(rawValuesUsed && (isRawValue(item) || isRawValue(next[i])))
|
|
4986
|
+
) {
|
|
4987
|
+
if (item !== next[i]) applyStateChild(next[i], item, target, keyFn);
|
|
4988
|
+
} else {
|
|
4751
4989
|
if (item !== next[i]) changed = true;
|
|
4752
4990
|
arrayNodes?.[i] && setSignal(arrayNodes[i], wrapValue(next[i], target));
|
|
4753
4991
|
}
|
|
@@ -4766,17 +5004,54 @@ function applyStateFast(next, target, keyFn) {
|
|
|
4766
5004
|
let tracked;
|
|
4767
5005
|
if (nodes) {
|
|
4768
5006
|
tracked = nodes[$TRACK];
|
|
5007
|
+
// The per-key body is duplicated across both loops on purpose: this is
|
|
5008
|
+
// the hottest object-diff site and a shared helper costs a per-key call
|
|
5009
|
+
// in instruction counts (CodSpeed regressed ~7% on deep-tree reconciles
|
|
5010
|
+
// with the extracted form).
|
|
4769
5011
|
if (tracked || symbolKeyedRecords.has(nodes)) {
|
|
4770
5012
|
const keys = tracked ? getAllKeys(previous, undefined, next) : nodeKeys(nodes);
|
|
4771
5013
|
for (let i = 0, len = keys.length; i < len; i++) {
|
|
4772
|
-
|
|
5014
|
+
const key = keys[i];
|
|
5015
|
+
const node = nodes[key];
|
|
5016
|
+
const previousValue = unwrap(previous[key]);
|
|
5017
|
+
const nextValue = unwrap(next[key]);
|
|
5018
|
+
if (previousValue === nextValue) continue;
|
|
5019
|
+
if (
|
|
5020
|
+
!previousValue ||
|
|
5021
|
+
!isWrappable(previousValue) ||
|
|
5022
|
+
!isWrappable(nextValue) ||
|
|
5023
|
+
// Raw-marked values are leaves replaced by reference — a "wrappable
|
|
5024
|
+
// pair" is only recursable when both sides are actual store children.
|
|
5025
|
+
(rawValuesUsed && (isRawValue(previousValue) || isRawValue(nextValue))) ||
|
|
5026
|
+
Array.isArray(previousValue) !== Array.isArray(nextValue) ||
|
|
5027
|
+
(keyFn(previousValue) != null && keyFn(previousValue) !== keyFn(nextValue))
|
|
5028
|
+
) {
|
|
5029
|
+
tracked && setSignal(tracked, void 0);
|
|
5030
|
+
node && setSignal(node, isWrappable(nextValue) ? wrap(nextValue, target) : nextValue);
|
|
5031
|
+
} else applyStateChild(nextValue, previousValue, target, keyFn);
|
|
4773
5032
|
}
|
|
4774
5033
|
} else {
|
|
4775
5034
|
// Untracked, string-only node records (the overwhelmingly common case)
|
|
4776
5035
|
// iterate in place — nodeKeys() allocated a fresh key array per object
|
|
4777
5036
|
// per pass, which dominates allocation on large-graph reconciles.
|
|
4778
5037
|
for (const key in nodes) {
|
|
4779
|
-
|
|
5038
|
+
const node = nodes[key];
|
|
5039
|
+
const previousValue = unwrap(previous[key]);
|
|
5040
|
+
const nextValue = unwrap(next[key]);
|
|
5041
|
+
if (previousValue === nextValue) continue;
|
|
5042
|
+
if (
|
|
5043
|
+
!previousValue ||
|
|
5044
|
+
!isWrappable(previousValue) ||
|
|
5045
|
+
!isWrappable(nextValue) ||
|
|
5046
|
+
// Raw-marked values are leaves replaced by reference — a "wrappable
|
|
5047
|
+
// pair" is only recursable when both sides are actual store children.
|
|
5048
|
+
(rawValuesUsed && (isRawValue(previousValue) || isRawValue(nextValue))) ||
|
|
5049
|
+
Array.isArray(previousValue) !== Array.isArray(nextValue) ||
|
|
5050
|
+
(keyFn(previousValue) != null && keyFn(previousValue) !== keyFn(nextValue))
|
|
5051
|
+
) {
|
|
5052
|
+
tracked && setSignal(tracked, void 0);
|
|
5053
|
+
node && setSignal(node, isWrappable(nextValue) ? wrap(nextValue, target) : nextValue);
|
|
5054
|
+
} else applyStateChild(nextValue, previousValue, target, keyFn);
|
|
4780
5055
|
}
|
|
4781
5056
|
}
|
|
4782
5057
|
}
|
|
@@ -4790,31 +5065,16 @@ function applyStateFast(next, target, keyFn) {
|
|
|
4790
5065
|
}
|
|
4791
5066
|
}
|
|
4792
5067
|
}
|
|
4793
|
-
// One node-key step of the fast object diff — shared by the array-iterating
|
|
4794
|
-
// (tracked / symbol-keyed) and for-in (plain) loops in applyStateFast.
|
|
4795
|
-
function diffNodeKey(key, nodes, previous, next, target, tracked, keyFn) {
|
|
4796
|
-
const node = nodes[key];
|
|
4797
|
-
const previousValue = unwrap(previous[key]);
|
|
4798
|
-
let nextValue = unwrap(next[key]);
|
|
4799
|
-
if (previousValue === nextValue) return;
|
|
4800
|
-
if (
|
|
4801
|
-
!previousValue ||
|
|
4802
|
-
!isWrappable(previousValue) ||
|
|
4803
|
-
!isWrappable(nextValue) ||
|
|
4804
|
-
Array.isArray(previousValue) !== Array.isArray(nextValue) ||
|
|
4805
|
-
(keyFn(previousValue) != null && keyFn(previousValue) !== keyFn(nextValue))
|
|
4806
|
-
) {
|
|
4807
|
-
tracked && setSignal(tracked, void 0);
|
|
4808
|
-
node && setSignal(node, isWrappable(nextValue) ? wrap(nextValue, target) : nextValue);
|
|
4809
|
-
} else applyState(nextValue, wrap(previousValue, target), keyFn);
|
|
4810
|
-
}
|
|
4811
5068
|
function applyStateSlow(next, target, keyFn) {
|
|
4812
5069
|
const previous = target[STORE_VALUE];
|
|
4813
5070
|
const override = target[STORE_OVERRIDE];
|
|
4814
5071
|
const optOverride = target[STORE_OPTIMISTIC_OVERRIDE];
|
|
4815
5072
|
let nodes = target[STORE_NODE];
|
|
4816
5073
|
// swap
|
|
4817
|
-
|
|
5074
|
+
{
|
|
5075
|
+
const fam = target[STORE_LOOKUP];
|
|
5076
|
+
fam !== undefined ? fam.set(next, target[$PROXY]) : storeLookup.set(next, target);
|
|
5077
|
+
}
|
|
4818
5078
|
target[STORE_VALUE] = next;
|
|
4819
5079
|
target[STORE_OVERRIDE] = undefined;
|
|
4820
5080
|
// merge
|
|
@@ -4833,9 +5093,11 @@ function applyStateSlow(next, target, keyFn) {
|
|
|
4833
5093
|
);
|
|
4834
5094
|
start++
|
|
4835
5095
|
) {
|
|
4836
|
-
isWrappable(item) &&
|
|
4837
|
-
|
|
4838
|
-
|
|
5096
|
+
if (isWrappable(item) && isWrappable(next[start]) && item !== next[start]) {
|
|
5097
|
+
if (rawValuesUsed && (isRawValue(item) || isRawValue(next[start]))) {
|
|
5098
|
+
nodes?.[start] && setSignal(nodes[start], wrapValue(next[start], target));
|
|
5099
|
+
} else applyState(next[start], wrap(item, target), keyFn);
|
|
5100
|
+
}
|
|
4839
5101
|
}
|
|
4840
5102
|
const temp = new Array(next.length),
|
|
4841
5103
|
newIndices = new Map();
|
|
@@ -4894,9 +5156,13 @@ function applyStateSlow(next, target, keyFn) {
|
|
|
4894
5156
|
} else if (next.length) {
|
|
4895
5157
|
for (let i = 0, len = next.length; i < len; i++) {
|
|
4896
5158
|
const item = getOverrideValue(previous, override, i, optOverride);
|
|
4897
|
-
if (
|
|
4898
|
-
|
|
4899
|
-
|
|
5159
|
+
if (
|
|
5160
|
+
isWrappable(item) &&
|
|
5161
|
+
isWrappable(next[i]) &&
|
|
5162
|
+
!(rawValuesUsed && (isRawValue(item) || isRawValue(next[i])))
|
|
5163
|
+
) {
|
|
5164
|
+
if (item !== next[i]) applyState(next[i], wrap(item, target), keyFn);
|
|
5165
|
+
} else {
|
|
4900
5166
|
if (item !== next[i]) changed = true;
|
|
4901
5167
|
nodes?.[i] && setSignal(nodes[i], wrapValue(next[i], target));
|
|
4902
5168
|
}
|
|
@@ -4926,6 +5192,7 @@ function applyStateSlow(next, target, keyFn) {
|
|
|
4926
5192
|
!previousValue ||
|
|
4927
5193
|
!isWrappable(previousValue) ||
|
|
4928
5194
|
!isWrappable(nextValue) ||
|
|
5195
|
+
(rawValuesUsed && (isRawValue(previousValue) || isRawValue(nextValue))) ||
|
|
4929
5196
|
Array.isArray(previousValue) !== Array.isArray(nextValue) ||
|
|
4930
5197
|
(keyFn(previousValue) != null && keyFn(previousValue) !== keyFn(nextValue))
|
|
4931
5198
|
) {
|
|
@@ -4997,9 +5264,16 @@ function reconcile(value, key = "id") {
|
|
|
4997
5264
|
function createProjectionInternal(fn, seed, options) {
|
|
4998
5265
|
let node;
|
|
4999
5266
|
const wrappedMap = new WeakMap();
|
|
5267
|
+
// A shallow projection's children are raw and never wrapped, so the
|
|
5268
|
+
// wrapper only ever runs for the root — flagging unconditionally is safe.
|
|
5269
|
+
const shallow = !!options?.shallow;
|
|
5000
5270
|
const wrapper = s => {
|
|
5001
5271
|
s[STORE_WRAP] = wrapProjection;
|
|
5002
5272
|
s[STORE_LOOKUP] = wrappedMap;
|
|
5273
|
+
if (shallow) {
|
|
5274
|
+
s[STORE_SHALLOW] = true;
|
|
5275
|
+
markRawIngest(s[STORE_VALUE]);
|
|
5276
|
+
}
|
|
5003
5277
|
Object.defineProperty(s, STORE_FIREWALL, {
|
|
5004
5278
|
get() {
|
|
5005
5279
|
return node;
|
|
@@ -5186,7 +5460,8 @@ const STORE_VALUE = "v",
|
|
|
5186
5460
|
STORE_OPTIMISTIC = "p",
|
|
5187
5461
|
STORE_OPTIMISTIC_OWNERS = "t",
|
|
5188
5462
|
STORE_PARENT = "u",
|
|
5189
|
-
STORE_DESC = "d"
|
|
5463
|
+
STORE_DESC = "d",
|
|
5464
|
+
STORE_SHALLOW = "s";
|
|
5190
5465
|
const STORE_SELF_PENDING = Symbol("STORE_SELF_PENDING");
|
|
5191
5466
|
// Every StoreNode field is initialized up front, in one fixed order, so all
|
|
5192
5467
|
// targets share a single hidden class. The traps and reconcile read these
|
|
@@ -5208,6 +5483,7 @@ function initStoreFields(newTarget) {
|
|
|
5208
5483
|
newTarget[STORE_SNAPSHOT_PROPS] = undefined;
|
|
5209
5484
|
newTarget[STORE_PARENT] = undefined;
|
|
5210
5485
|
newTarget[STORE_DESC] = undefined;
|
|
5486
|
+
newTarget[STORE_SHALLOW] = undefined;
|
|
5211
5487
|
newTarget[$PROXY] = null;
|
|
5212
5488
|
}
|
|
5213
5489
|
function createStoreProxy(value, traps = storeTraps, extend) {
|
|
@@ -5228,24 +5504,98 @@ function createStoreProxy(value, traps = storeTraps, extend) {
|
|
|
5228
5504
|
extend && extend(newTarget);
|
|
5229
5505
|
return (newTarget[$PROXY] = new Proxy(newTarget, traps));
|
|
5230
5506
|
}
|
|
5507
|
+
// The global lookup maps raw value -> StoreNode TARGET (not proxy): reconcile
|
|
5508
|
+
// and the unwrap/snapshot walks resolve targets through it constantly, and a
|
|
5509
|
+
// target hit is a plain field read away from its proxy while a proxy hit
|
|
5510
|
+
// costs a trap to get back to the target. Per-family STORE_LOOKUP maps
|
|
5511
|
+
// (projections/optimistic) still map raw -> proxy — their wrap functions own
|
|
5512
|
+
// that contract — so mixed-lookup consumers resolve through lookupTarget().
|
|
5231
5513
|
const storeLookup = new WeakMap();
|
|
5232
5514
|
// Node records that hold at least one user (non-`$TRACK`) symbol-keyed node.
|
|
5233
5515
|
// Lets reconcile enumerate symbols only for records that need it (#2851).
|
|
5234
5516
|
const symbolKeyedRecords = new WeakSet();
|
|
5517
|
+
function lookupTarget(value, lookup) {
|
|
5518
|
+
if (lookup !== undefined && lookup !== storeLookup) {
|
|
5519
|
+
const p = lookup.get(value);
|
|
5520
|
+
if (p !== undefined) return p[$TARGET];
|
|
5521
|
+
}
|
|
5522
|
+
return storeLookup.get(value);
|
|
5523
|
+
}
|
|
5524
|
+
// Values marked raw never acquire a proxy identity: wrap() serves them as-is
|
|
5525
|
+
// everywhere — deep stores hold them as leaf values replaced by reference.
|
|
5526
|
+
// Once raw, always raw (identity stays single, just unwrapped). Consulted
|
|
5527
|
+
// only on wrap-creation and ingest paths; reads never touch it.
|
|
5528
|
+
const rawValues = new WeakSet();
|
|
5529
|
+
/**
|
|
5530
|
+
* Marks a value as raw: no store will ever wrap it — every store presents it
|
|
5531
|
+
* as-is, tracked by reference at whatever slot holds it and updated by
|
|
5532
|
+
* replacement. Useful for class instances and external objects (editors,
|
|
5533
|
+
* scene graphs, Maps) and for record-shaped data updated wholesale. Sticky
|
|
5534
|
+
* for the value's lifetime.
|
|
5535
|
+
*/
|
|
5536
|
+
// Flipped on the first mark and exported as a LIVE binding: reconcile
|
|
5537
|
+
// consults it on every recursable pair, and importing the boolean directly
|
|
5538
|
+
// lets those sites skip even the function call when no shallow store or raw
|
|
5539
|
+
// mark exists anywhere in the app.
|
|
5540
|
+
let rawValuesUsed = false;
|
|
5541
|
+
function isRawValue(value) {
|
|
5542
|
+
return rawValuesUsed && rawValues.has(value);
|
|
5543
|
+
}
|
|
5544
|
+
function markRawOne(v) {
|
|
5545
|
+
if (isWrappable(v)) {
|
|
5546
|
+
if (storeLookup.has(v))
|
|
5547
|
+
throw new Error(
|
|
5548
|
+
"shallow store: an ingested record is already tracked as a deep store — one value cannot present both wrapped and raw"
|
|
5549
|
+
);
|
|
5550
|
+
rawValuesUsed = true;
|
|
5551
|
+
rawValues.add(v);
|
|
5552
|
+
}
|
|
5553
|
+
}
|
|
5554
|
+
function markRawIngest(container) {
|
|
5555
|
+
if (Array.isArray(container)) {
|
|
5556
|
+
for (let i = 0, len = container.length; i < len; i++) markRawOne(container[i]);
|
|
5557
|
+
} else {
|
|
5558
|
+
for (const k in container) markRawOne(container[k]);
|
|
5559
|
+
}
|
|
5560
|
+
}
|
|
5235
5561
|
function wrap(value, target) {
|
|
5562
|
+
// Raw is raw in every family: the mark must preempt family wrapping too,
|
|
5563
|
+
// or a shallow projection/optimistic store would proxy its raw records.
|
|
5564
|
+
if (rawValuesUsed && rawValues.has(value)) return value;
|
|
5236
5565
|
if (target?.[STORE_WRAP]) {
|
|
5237
5566
|
const p = target[STORE_WRAP](value, target);
|
|
5238
5567
|
const t = p[$TARGET];
|
|
5239
5568
|
if (t && !t[STORE_PARENT] && t !== target) t[STORE_PARENT] = target;
|
|
5240
5569
|
return p;
|
|
5241
5570
|
}
|
|
5242
|
-
|
|
5571
|
+
const t = storeLookup.get(value);
|
|
5572
|
+
if (t !== undefined) return t[$PROXY];
|
|
5573
|
+
let p = value[$PROXY];
|
|
5243
5574
|
if (!p) {
|
|
5244
|
-
|
|
5245
|
-
|
|
5575
|
+
p = createStoreProxy(value);
|
|
5576
|
+
const newTarget = p[$TARGET];
|
|
5577
|
+
storeLookup.set(value, newTarget);
|
|
5578
|
+
if (target) newTarget[STORE_PARENT] = target;
|
|
5246
5579
|
}
|
|
5247
5580
|
return p;
|
|
5248
5581
|
}
|
|
5582
|
+
// Shallow store root: the target itself is fully reactive (per-key nodes,
|
|
5583
|
+
// membership, $TRACK); its values are served raw. Seed values are marked at
|
|
5584
|
+
// creation; reconcile and the set trap mark on ingest.
|
|
5585
|
+
function wrapShallow(value) {
|
|
5586
|
+
const existing = storeLookup.get(value);
|
|
5587
|
+
if (existing !== undefined) {
|
|
5588
|
+
if (existing[STORE_SHALLOW]) return existing[$PROXY];
|
|
5589
|
+
throw new Error("createStore({ shallow }): value is already tracked as a deep store");
|
|
5590
|
+
}
|
|
5591
|
+
if (value[$TARGET]) throw new Error("createStore({ shallow }): value is already a store proxy");
|
|
5592
|
+
const p = createStoreProxy(value);
|
|
5593
|
+
const newTarget = p[$TARGET];
|
|
5594
|
+
newTarget[STORE_SHALLOW] = true;
|
|
5595
|
+
storeLookup.set(value, newTarget);
|
|
5596
|
+
markRawIngest(value);
|
|
5597
|
+
return p;
|
|
5598
|
+
}
|
|
5249
5599
|
function isWrappable(obj) {
|
|
5250
5600
|
if (obj == null || typeof obj !== "object" || Object.isFrozen(obj)) return false;
|
|
5251
5601
|
// Dynamic Node check (kept dynamic so test/SSR overrides of `globalThis.Node`
|
|
@@ -5260,7 +5610,7 @@ function writeOnly(proxy) {
|
|
|
5260
5610
|
return writeOverride || !!Writing?.has(proxy);
|
|
5261
5611
|
}
|
|
5262
5612
|
function unwrapStoreValue(value, map, lookup) {
|
|
5263
|
-
const target = value?.[$TARGET] ||
|
|
5613
|
+
const target = value?.[$TARGET] || lookupTarget(value, lookup);
|
|
5264
5614
|
if (!target) return value;
|
|
5265
5615
|
const override = target[STORE_OVERRIDE];
|
|
5266
5616
|
if (!override) return target[STORE_VALUE];
|
|
@@ -5447,7 +5797,7 @@ function walkAffectsScope(
|
|
|
5447
5797
|
visited
|
|
5448
5798
|
) {
|
|
5449
5799
|
if (!isWrappable(value)) return;
|
|
5450
|
-
const target = value[$TARGET] || (lookup
|
|
5800
|
+
const target = value[$TARGET] || lookupTarget(value, lookup);
|
|
5451
5801
|
const raw = target ? target[STORE_VALUE] : value;
|
|
5452
5802
|
if (visited.has(raw)) return;
|
|
5453
5803
|
visited.add(raw);
|
|
@@ -5814,8 +6164,25 @@ const storeTraps = {
|
|
|
5814
6164
|
const nodes = target[STORE_NODE];
|
|
5815
6165
|
const node = nodes && nodes[property];
|
|
5816
6166
|
if (node !== undefined && target[STORE_VALUE][$TARGET] === undefined) {
|
|
5817
|
-
|
|
6167
|
+
// readNodeFast is read()'s plain-signal fast path hoisted over the
|
|
6168
|
+
// call; READ_SLOW means a global read window (latest/pending-check/
|
|
6169
|
+
// transition/lane/snapshot capture) or a node layer is active, and
|
|
6170
|
+
// only then does the full read() resolution have anything to do.
|
|
6171
|
+
let value = readNodeFast(node);
|
|
6172
|
+
if (value === READ_SLOW) value = read(node);
|
|
5818
6173
|
if (value === $DELETED) value = undefined;
|
|
6174
|
+
// Every node-writing site wraps wrappables before setSignal (see the
|
|
6175
|
+
// dev assertion below), so re-wrapping on read is redundant — except
|
|
6176
|
+
// during snapshot capture, where read() can surface a raw captured
|
|
6177
|
+
// value seeded from snapshot props.
|
|
6178
|
+
if (!snapshotCaptureActive) {
|
|
6179
|
+
if (isWrappable(value) && wrap(value, target) !== value) {
|
|
6180
|
+
throw new Error(
|
|
6181
|
+
"store node invariant violated: node held an unwrapped wrappable value"
|
|
6182
|
+
);
|
|
6183
|
+
}
|
|
6184
|
+
return value;
|
|
6185
|
+
}
|
|
5819
6186
|
return isWrappable(value) ? wrap(value, target) : value;
|
|
5820
6187
|
}
|
|
5821
6188
|
}
|
|
@@ -5859,6 +6226,11 @@ const storeTraps = {
|
|
|
5859
6226
|
tracked && (overridden || !proxySource) ? visibleNodeValue(tracked) : storeValue[property];
|
|
5860
6227
|
value === $DELETED && (value = undefined);
|
|
5861
6228
|
if (!isWrappable(value)) return value;
|
|
6229
|
+
// Shallow boundary: records are replaced, never edited in place. Reads
|
|
6230
|
+
// inside a setter serve the raw so read-then-replace, filter/pop and
|
|
6231
|
+
// projection derives all work; in-place mutation of a raw is inert by
|
|
6232
|
+
// construction — the same contract as a markRaw child in a deep store.
|
|
6233
|
+
if (target[STORE_SHALLOW]) return value;
|
|
5862
6234
|
const wrapped = wrap(value, target);
|
|
5863
6235
|
Writing?.add(wrapped);
|
|
5864
6236
|
return wrapped;
|
|
@@ -5944,6 +6316,7 @@ const storeTraps = {
|
|
|
5944
6316
|
? prevLayer[property] !== $DELETED
|
|
5945
6317
|
: property in target[STORE_VALUE];
|
|
5946
6318
|
const value = unwrapStoreValue(rawValue);
|
|
6319
|
+
if (target[STORE_SHALLOW] && isWrappable(value)) rawValues.add(value);
|
|
5947
6320
|
// Symbol-keyed writes on arrays are metadata, not index writes — never run
|
|
5948
6321
|
// them through the numeric index/length machinery (`parseInt` on a symbol
|
|
5949
6322
|
// throws). #2769
|
|
@@ -6160,7 +6533,11 @@ function storeSetter(store, fn) {
|
|
|
6160
6533
|
}
|
|
6161
6534
|
function createStore(first, second, options) {
|
|
6162
6535
|
const derived = typeof first === "function",
|
|
6163
|
-
wrappedStore = derived
|
|
6536
|
+
wrappedStore = derived
|
|
6537
|
+
? createProjectionInternal(first, second, options).store
|
|
6538
|
+
: second?.shallow
|
|
6539
|
+
? wrapShallow(first)
|
|
6540
|
+
: wrap(first);
|
|
6164
6541
|
registerGraph(wrappedStore, getOwner());
|
|
6165
6542
|
return [
|
|
6166
6543
|
wrappedStore,
|
|
@@ -6324,6 +6701,8 @@ function createOptimisticStore(first, second, options) {
|
|
|
6324
6701
|
installOptimisticEngine();
|
|
6325
6702
|
GlobalQueue._clearOptimisticStores ||= clearOptimisticStores;
|
|
6326
6703
|
const derived = typeof first === "function";
|
|
6704
|
+
// Plain form: the second slot carries options.
|
|
6705
|
+
if (!derived && options === undefined) options = second;
|
|
6327
6706
|
const initialValue = derived ? second : first;
|
|
6328
6707
|
const fn = derived ? first : undefined;
|
|
6329
6708
|
// Create optimistic projection store
|
|
@@ -6425,9 +6804,14 @@ function clearOptimisticOverride(target, completing) {
|
|
|
6425
6804
|
function createOptimisticProjectionInternal(fn, initialValue, options) {
|
|
6426
6805
|
let node;
|
|
6427
6806
|
const wrappedMap = new WeakMap();
|
|
6807
|
+
const shallow = !!options?.shallow;
|
|
6428
6808
|
const wrapper = s => {
|
|
6429
6809
|
s[STORE_WRAP] = wrapProjection;
|
|
6430
6810
|
s[STORE_LOOKUP] = wrappedMap;
|
|
6811
|
+
if (shallow) {
|
|
6812
|
+
s[STORE_SHALLOW] = true;
|
|
6813
|
+
markRawIngest(s[STORE_VALUE]);
|
|
6814
|
+
}
|
|
6431
6815
|
s[STORE_OPTIMISTIC] = true; // Mark as optimistic store
|
|
6432
6816
|
Object.defineProperty(s, STORE_FIREWALL, {
|
|
6433
6817
|
get() {
|
|
@@ -6596,7 +6980,7 @@ function snapshotImpl(item, track, map, lookup) {
|
|
|
6596
6980
|
if (!isWrappable(item)) return item;
|
|
6597
6981
|
if (map && map.has(item)) return map.get(item);
|
|
6598
6982
|
if (!map) map = new Map();
|
|
6599
|
-
if ((target = item[$TARGET] ||
|
|
6983
|
+
if ((target = item[$TARGET] || lookupTarget(item, lookup))) {
|
|
6600
6984
|
if (track) {
|
|
6601
6985
|
trackSelf(target, $TRACK);
|
|
6602
6986
|
// A tracked walk reads THROUGH the record without touching the proxy
|
|
@@ -6634,7 +7018,7 @@ function snapshotImpl(item, track, map, lookup) {
|
|
|
6634
7018
|
for (let i = 0; i < len; i++) {
|
|
6635
7019
|
v = override && i in override ? override[i] : item[i];
|
|
6636
7020
|
if (v === $DELETED) continue;
|
|
6637
|
-
if (track && isWrappable(v)) wrap(v, target);
|
|
7021
|
+
if (track && isWrappable(v) && !(rawValuesUsed && isRawValue(v))) wrap(v, target);
|
|
6638
7022
|
if ((unwrapped = snapshotImpl(v, track, map, lookup)) !== v || result) {
|
|
6639
7023
|
if (!result) map.set(item, (result = [...item]));
|
|
6640
7024
|
result[i] = unwrapped;
|
|
@@ -6648,7 +7032,7 @@ function snapshotImpl(item, track, map, lookup) {
|
|
|
6648
7032
|
const desc = getPropertyDescriptor(item, override, prop);
|
|
6649
7033
|
if (!desc || desc.get) continue;
|
|
6650
7034
|
v = override && prop in override ? override[prop] : item[prop];
|
|
6651
|
-
if (track && isWrappable(v)) wrap(v, target);
|
|
7035
|
+
if (track && isWrappable(v) && !(rawValuesUsed && isRawValue(v))) wrap(v, target);
|
|
6652
7036
|
unwrapped = snapshotImpl(v, track, map, lookup);
|
|
6653
7037
|
if (unwrapped !== v || result) {
|
|
6654
7038
|
if (!result) map.set(item, (result = Object.assign([...item], item)));
|
|
@@ -6670,7 +7054,7 @@ function snapshotImpl(item, track, map, lookup) {
|
|
|
6670
7054
|
const desc = Object.getOwnPropertyDescriptor(item, prop);
|
|
6671
7055
|
if (desc.get) continue;
|
|
6672
7056
|
v = desc.value;
|
|
6673
|
-
if (track && isWrappable(v)) wrap(v, target);
|
|
7057
|
+
if (track && isWrappable(v) && !(rawValuesUsed && isRawValue(v))) wrap(v, target);
|
|
6674
7058
|
if ((unwrapped = snapshotImpl(v, track, map, lookup)) !== v || result) {
|
|
6675
7059
|
if (!result) {
|
|
6676
7060
|
result = Object.create(Object.getPrototypeOf(item));
|
|
@@ -6688,7 +7072,7 @@ function snapshotImpl(item, track, map, lookup) {
|
|
|
6688
7072
|
const desc = getPropertyDescriptor(item, override, prop);
|
|
6689
7073
|
if (desc.get) continue;
|
|
6690
7074
|
v = prop in override ? override[prop] : item[prop];
|
|
6691
|
-
if (track && isWrappable(v)) wrap(v, target);
|
|
7075
|
+
if (track && isWrappable(v) && !(rawValuesUsed && isRawValue(v))) wrap(v, target);
|
|
6692
7076
|
if ((unwrapped = snapshotImpl(v, track, map, lookup)) !== item[prop] || result) {
|
|
6693
7077
|
if (!result) {
|
|
6694
7078
|
result = Object.create(Object.getPrototypeOf(item));
|