@solidjs/signals
Advanced tools
+110
-76
@@ -85,3 +85,3 @@ import { handleAsync, clearStatus, notifyStatus } from "./async.js"; | ||
| e.se |= REACTIVE_DIRTY; | ||
| if (dirtyQueue.He > e.Fe) dirtyQueue.He = e.Fe; | ||
| if (dirtyQueue.Fe > e.He) dirtyQueue.Fe = e.He; | ||
| insertIntoHeap(e, dirtyQueue); | ||
@@ -118,4 +118,4 @@ } | ||
| markDisposal(e); | ||
| e.ye = e.xe; | ||
| e.Le = e.me; | ||
| e.Le = e.xe; | ||
| e.ye = e.me; | ||
| e.xe = null; | ||
@@ -127,4 +127,4 @@ e.me = null; | ||
| let i = !!(e.se & REACTIVE_OPTIMISTIC_DIRTY); | ||
| const l = e.Ee !== undefined && e.Ee !== NOT_PENDING; | ||
| const u = !!(e.S & STATUS_UNINITIALIZED); | ||
| const u = e.Ee !== undefined && e.Ee !== NOT_PENDING; | ||
| const l = !!(e.S & STATUS_UNINITIALIZED); | ||
| // Re-ask classification lives in the verdict module; capture the flag before | ||
@@ -136,7 +136,7 @@ // the recompute wipes _flags below. | ||
| e.qe = null; | ||
| e.Ye++; | ||
| e.We++; | ||
| e.se = REACTIVE_RECOMPUTING_DEPS; | ||
| e.ce = clock; | ||
| let a = e.Ne === NOT_PENDING ? e.Pe : e.Ne; | ||
| let r = e.Fe; | ||
| let r = e.He; | ||
| let c = tracking; | ||
@@ -151,3 +151,3 @@ let _ = currentOptimisticLane; | ||
| // covers creation-time computes and flushes that run inside the window. | ||
| const E = latestReadActive; | ||
| const f = latestReadActive; | ||
| latestReadActive = false; | ||
@@ -158,3 +158,3 @@ // Lane posture lives with the engine: OPTIMISTIC_DIRTY is only ever set by | ||
| if (i) { | ||
| const t = GlobalQueue.We(e, true); | ||
| const t = GlobalQueue.Ye(e, true); | ||
| if (t) currentOptimisticLane = t; | ||
@@ -165,3 +165,3 @@ } else if (activeTransition && !t && activeTransition.Ze.length) { | ||
| // recomputes under the right posture and propagates correctly. | ||
| const t = GlobalQueue.We(e, false); | ||
| const t = GlobalQueue.Ye(e, false); | ||
| if (t) { | ||
@@ -172,5 +172,5 @@ i = true; | ||
| } | ||
| const f = n && n !== EFFECT_USER; | ||
| const E = n && n !== EFFECT_USER; | ||
| const N = stale; | ||
| if (f) stale = true; | ||
| if (E) stale = true; | ||
| try { | ||
@@ -189,7 +189,10 @@ if (!false && e.T & CONFIG_SYNC) { | ||
| const i = typeof n === "object" && n !== null; | ||
| const l = e.Te !== t; | ||
| a = l || !i ? n : handleAsync(e, n); | ||
| if (!l && !i) e.Te = null; | ||
| const u = e.Te !== t; | ||
| a = u || !i ? n : handleAsync(e, n); | ||
| if (!u && !i) e.Te = null; | ||
| } | ||
| clearStatus(e, t); | ||
| // On a status-free node clearStatus is a guaranteed no-op: every branch | ||
| // in its body is gated on one of these fields, and with _statusFlags === 0 | ||
| // the flags write (create or not) stores the 0 already there. | ||
| if (e.S !== 0 || e.i !== undefined || e._ || e._e || e.Se || e.oe !== undefined || e.be !== undefined || e.ge !== undefined || e.u !== null) clearStatus(e, t); | ||
| // _optimisticLane is only ever assigned by engine paths. | ||
@@ -210,4 +213,4 @@ if (e.je) GlobalQueue.Me(e); | ||
| tracking = c; | ||
| latestReadActive = E; | ||
| if (f) stale = N; | ||
| latestReadActive = f; | ||
| if (E) stale = N; | ||
| e.se = REACTIVE_NONE | (t ? e.se & REACTIVE_SNAPSHOT_STALE : 0); | ||
@@ -218,6 +221,6 @@ context = s; | ||
| trimStaleDeps(e); | ||
| const o = l ? unwrapOverride(e.Ee) : e.Ne === NOT_PENDING ? e.Pe : e.Ne; | ||
| const o = u ? unwrapOverride(e.Ee) : e.Ne === NOT_PENDING ? e.Pe : e.Ne; | ||
| let s = false; | ||
| try { | ||
| s = !n && u || !e.Re || !e.Re(o, a); | ||
| s = !n && l || !e.Re || !e.Re(o, a); | ||
| } catch (t) { | ||
@@ -242,4 +245,9 @@ // A throwing user comparator is an error of this node's computation. | ||
| if (e._) ; else if (s) { | ||
| const u = l ? e.Ee : undefined; | ||
| if (t || n && activeTransition !== e.Ue || i) { | ||
| const l = u ? e.Ee : undefined; | ||
| if (t || | ||
| // Plain sync flush (no transition on either side) commits effect | ||
| // values directly — the pending round-trip (queuePendingNode + | ||
| // commitPendingNodes) exists to sequence transition reveals, and | ||
| // paying it per effect on the plain path is pure overhead. | ||
| n && (activeTransition !== e.Ue || activeTransition === null) || i) { | ||
| e.Pe = a; | ||
@@ -250,3 +258,3 @@ // Lane-propagated correction: upstream data is fresh, correct the | ||
| // commit can't clobber the fresh value. | ||
| if (l && i) { | ||
| if (u && i) { | ||
| e.Ee = a === undefined ? OVERRIDE_UNDEFINED : a; | ||
@@ -264,4 +272,6 @@ e.Ne = NOT_PENDING; | ||
| } | ||
| if (!l || i || e.Ee !== u) insertSubs(e, i || l); | ||
| } else if (l) { | ||
| // insertSubs only walks _subs (no scheduling of its own), so a | ||
| // subscriber-less node has nothing to notify. | ||
| if (e.o !== null && (!u || i || e.Ee !== l)) insertSubs(e, i || u); | ||
| } else if (u) { | ||
| // Unchanged value (equals the override) recomputed while the override | ||
@@ -272,3 +282,3 @@ // is active: _value may still be stale, so hold the authoritative value | ||
| e.Ne = a; | ||
| } else if (e.Fe != r) { | ||
| } else if (e.He != r) { | ||
| for (let t = e.o; t !== null; t = t.ue) { | ||
@@ -280,3 +290,3 @@ insertIntoHeapHeight(t.le, queueFor(t.le)); | ||
| currentOptimisticLane = _; | ||
| const T = e.Ne !== NOT_PENDING || e.Le !== null || e.ye !== null || (e.S & (STATUS_PENDING | STATUS_UNINITIALIZED)) !== 0; | ||
| const T = e.Ne !== NOT_PENDING || e.ye !== null || e.Le !== null || (e.S & (STATUS_PENDING | STATUS_UNINITIALIZED)) !== 0; | ||
| // Override-covered holds (hasOverride) always queue: their commit belongs | ||
@@ -287,3 +297,3 @@ // to their own transition's schedule (A18 re-rule) and is unobservable | ||
| // _transition. | ||
| T && (!t || e.S & STATUS_PENDING) && (!e.Ue || l) && queuePendingNode(e); | ||
| T && (!t || e.S & STATUS_PENDING) && (!e.Ue || u) && queuePendingNode(e); | ||
| e.Ue && n && activeTransition !== e.Ue && runInTransition(e.Ue, () => recompute(e)); | ||
@@ -324,9 +334,9 @@ } | ||
| Pe: undefined, | ||
| Fe: 0, | ||
| He: 0, | ||
| u: null, | ||
| lt: undefined, | ||
| ut: null, | ||
| ut: undefined, | ||
| lt: null, | ||
| Xe: null, | ||
| qe: null, | ||
| Ye: 0, | ||
| We: 0, | ||
| o: null, | ||
@@ -342,4 +352,4 @@ ot: null, | ||
| Ne: NOT_PENDING, | ||
| Le: null, | ||
| ye: null, | ||
| Le: null, | ||
| Te: null, | ||
@@ -358,9 +368,9 @@ Ue: null, | ||
| * the auto-dispose CONFIG bit (effect() previously cleared it post-construction). | ||
| */ function createEffectNode(e, t, n, i, l, u) { | ||
| const o = u?.transparent ?? false; | ||
| */ function createEffectNode(e, t, n, i, u, l) { | ||
| const o = l?.transparent ?? false; | ||
| const s = { | ||
| id: inheritId(u, o, context), | ||
| T: (o ? CONFIG_TRANSPARENT : 0) | (u?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (u?.sync ? CONFIG_SYNC : 0) | (snapshotCaptureActive && ownerInSnapshotScope(context) ? CONFIG_IN_SNAPSHOT_SCOPE : 0), | ||
| id: inheritId(l, o, context), | ||
| T: (o ? CONFIG_TRANSPARENT : 0) | (l?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (l?.sync ? CONFIG_SYNC : 0) | (snapshotCaptureActive && ownerInSnapshotScope(context) ? CONFIG_IN_SNAPSHOT_SCOPE : 0), | ||
| Re: false, | ||
| it: u?.unobserved, | ||
| it: l?.unobserved, | ||
| xe: null, | ||
@@ -372,9 +382,9 @@ C: context?.C ?? globalQueue, | ||
| Pe: undefined, | ||
| Fe: 0, | ||
| He: 0, | ||
| u: null, | ||
| lt: undefined, | ||
| ut: null, | ||
| ut: undefined, | ||
| lt: null, | ||
| Xe: null, | ||
| qe: null, | ||
| Ye: 0, | ||
| We: 0, | ||
| o: null, | ||
@@ -390,4 +400,4 @@ ot: null, | ||
| Ne: NOT_PENDING, | ||
| Le: null, | ||
| ye: null, | ||
| Le: null, | ||
| Te: null, | ||
@@ -400,5 +410,5 @@ Ue: null, | ||
| _t: n, | ||
| Et: undefined, | ||
| ft: undefined, | ||
| Ae: i, | ||
| i: l | ||
| i: u | ||
| }; | ||
@@ -414,4 +424,4 @@ setupComputedNode(s, lazyOptions); | ||
| function setupComputedNode(e, t) { | ||
| e.ut = e; | ||
| const n = context?.ft ? context.Nt : context; | ||
| e.lt = e; | ||
| const n = context?.Et ? context.Nt : context; | ||
| if (context) { | ||
@@ -427,3 +437,3 @@ const t = context.me; | ||
| } | ||
| if (n) e.Fe = n.Fe + 1; | ||
| if (n) e.He = n.He + 1; | ||
| if (GlobalQueue.Tt !== null) GlobalQueue.Tt(e); | ||
@@ -524,2 +534,23 @@ !t?.lazy && recompute(e, true); | ||
| /** | ||
| * Sentinel returned by readNodeFast when the plain-signal fast path does not | ||
| * apply and the caller must fall back to the full read(). | ||
| */ const READ_SLOW = Symbol("read-slow"); | ||
| /** | ||
| * read()'s plain-signal fast path as a standalone entry for hot callers | ||
| * (store traps). Safe to substitute for read() only because the bail | ||
| * conditions mirror read()'s prelude and fast-path guard exactly: the | ||
| * latestRead and pendingCheck windows run side-effectful hooks before the | ||
| * fast path, `_fn` nodes need prepareComputed, and firewall / override / | ||
| * snapshot / transition / lane / dev-strictRead state all take the full | ||
| * resolution. Anything slow returns READ_SLOW; the caller then calls read(). | ||
| */ function readNodeFast(e) { | ||
| if (latestReadActive || pendingCheckActive || e.ke || e.nt || e.Ee !== undefined || e.Ve !== undefined || activeTransition !== null || currentOptimisticLane !== null || snapshotCaptureActive || false) return READ_SLOW; | ||
| let t = context; | ||
| if (t?.Et) t = t.Nt; | ||
| if (t && tracking) link(e, t); | ||
| return !t || e.Ne === NOT_PENDING ? e.Pe : e.Ne; | ||
| } | ||
| function read(e) { | ||
@@ -530,8 +561,8 @@ // Handle latest() mode: read from _latestValueComputed | ||
| // than the original node (which stays "pending" while held in a transition). | ||
| if (latestReadActive) return GlobalQueue.St(e); | ||
| if (latestReadActive) return GlobalQueue.dt(e); | ||
| let t = context; | ||
| if (t?.ft) t = t.Nt; | ||
| if (t?.Et) t = t.Nt; | ||
| const n = e; | ||
| const i = e.nt; | ||
| const l = i || e; | ||
| const u = i || e; | ||
| // Handle isPending() mode: collect pending state while preserving normal read semantics. | ||
@@ -541,7 +572,7 @@ // Probe mode is suspended while preparing the node so nested reads during a | ||
| if (pendingCheckActive) { | ||
| GlobalQueue.dt(e, t, l, i); | ||
| GlobalQueue.St(e, t, u, i); | ||
| } else if (typeof n.ke === "function") { | ||
| prepareComputed(e, false); | ||
| } | ||
| if (!n.ke && l === e && e.Ee === undefined && e.Ve === undefined && activeTransition === null && currentOptimisticLane === null && !snapshotCaptureActive && true) { | ||
| if (!n.ke && u === e && e.Ee === undefined && e.Ve === undefined && activeTransition === null && currentOptimisticLane === null && !snapshotCaptureActive && true) { | ||
| if (t && tracking) link(e, t); | ||
@@ -552,30 +583,30 @@ return !t || e.Ne === NOT_PENDING ? e.Pe : e.Ne; | ||
| link(e, t, pendingCheckActive); | ||
| if (l.ke) { | ||
| if (u.ke) { | ||
| const n = queueFor(e); | ||
| if (l.Fe >= n.He) { | ||
| if (u.He >= n.Fe) { | ||
| markNode(t); | ||
| markHeap(n); | ||
| updateIfNecessary(l); | ||
| updateIfNecessary(u); | ||
| } | ||
| const i = l.Fe; | ||
| const i = u.He; | ||
| // parent check is shallow, might need to be recursive | ||
| if (i >= t.Fe && e.Ge !== t) { | ||
| t.Fe = i + 1; | ||
| if (i >= t.He && e.Ge !== t) { | ||
| t.He = i + 1; | ||
| } | ||
| } | ||
| } | ||
| if (l.S & STATUS_PENDING) { | ||
| if (t && !(stale && l.Ue && activeTransition !== l.Ue)) { | ||
| if (u.S & STATUS_PENDING) { | ||
| if (t && !(stale && u.Ue && activeTransition !== u.Ue)) { | ||
| // Per-lane suspension lives with the engine (a non-null lane implies it | ||
| // is installed): under a lane, only same-lane pending async without an | ||
| // active override throws. | ||
| if (currentOptimisticLane === null || GlobalQueue.At(l)) { | ||
| if (currentOptimisticLane === null || GlobalQueue.At(u)) { | ||
| if (!tracking && e !== t) link(e, t); | ||
| throw l._; | ||
| throw u._; | ||
| } | ||
| } else if (t && l !== e && l.S & STATUS_UNINITIALIZED) { | ||
| } else if (t && u !== e && u.S & STATUS_UNINITIALIZED) { | ||
| if (!tracking && e !== t) link(e, t); | ||
| throw l._; | ||
| } else if (!t && l.S & STATUS_UNINITIALIZED) { | ||
| throw l._; | ||
| throw u._; | ||
| } else if (!t && u.S & STATUS_UNINITIALIZED) { | ||
| throw u._; | ||
| } | ||
@@ -597,4 +628,4 @@ } | ||
| const i = n === NO_SNAPSHOT ? undefined : n; | ||
| const l = e.Ne !== NOT_PENDING ? e.Ne : e.Pe; | ||
| if (l !== i) t.se |= REACTIVE_SNAPSHOT_STALE; | ||
| const u = e.Ne !== NOT_PENDING ? e.Ne : e.Pe; | ||
| if (u !== i) t.se |= REACTIVE_SNAPSHOT_STALE; | ||
| return i; | ||
@@ -614,3 +645,3 @@ } | ||
| // engine — a non-null lane implies it is installed.) | ||
| if (currentOptimisticLane !== null && activeTransition !== null && t !== null && GlobalQueue.Ct(e, l, t)) { | ||
| if (currentOptimisticLane !== null && activeTransition !== null && t !== null && GlobalQueue.Ct(e, u, t)) { | ||
| return e.Pe; | ||
@@ -622,10 +653,10 @@ } | ||
| // (The lane-context clause lives with the engine.) | ||
| const u = !t || currentOptimisticLane !== null && GlobalQueue.Ot(e, l, t) || e.Ne === NOT_PENDING || stale && e.Ue && activeTransition !== e.Ue ? e.Pe : e.Ne; | ||
| const l = !t || currentOptimisticLane !== null && GlobalQueue.Ot(e, u, t) || e.Ne === NOT_PENDING || stale && e.Ue && activeTransition !== e.Ue ? e.Pe : e.Ne; | ||
| // Record that this isPending() probe observed the fresh pending value, so | ||
| // the probe doesn't pair "pending" with the new value (#2831). | ||
| if (pendingCheckActive) GlobalQueue.Rt(e, u); | ||
| if (!t && l === e && typeof n.ke === "function" && e.T & CONFIG_AUTO_DISPOSE && !(l.S & STATUS_PENDING) && !e.o) { | ||
| if (pendingCheckActive) GlobalQueue.Rt(e, l); | ||
| if (!t && u === e && typeof n.ke === "function" && e.T & CONFIG_AUTO_DISPOSE && !(u.S & STATUS_PENDING) && !e.o) { | ||
| unobserved(e); | ||
| } | ||
| return u; | ||
| return l; | ||
| } | ||
@@ -648,3 +679,6 @@ | ||
| e.Ne = t; | ||
| GlobalQueue.Ie !== null && GlobalQueue.Ie(e, t); | ||
| // syncCompanions only pokes _pendingSignal/_latestValueComputed — with | ||
| // neither companion present the call is a guaranteed no-op (companions are | ||
| // only ever created, never removed, and creating one installs the hook). | ||
| (e.be !== undefined || e.ge !== undefined) && GlobalQueue.Ie !== null && GlobalQueue.Ie(e, t); | ||
| e.ce = clock; | ||
@@ -765,2 +799,2 @@ insertSubs(e); | ||
| export { clearSnapshots, computed, context, createEffectNode, currentOptimisticLane, isEqual, latestReadActive, markSnapshotScope, optimisticComputed, optimisticSignal, pendingCheckActive, prepareComputed, read, recompute, refresh, releaseSnapshotScope, runWithOwner, setContextInternal, setLatestReadActive, setMemo, setPendingCheckActive, setSignal, setSnapshotCapture, signal, snapshotCaptureActive, snapshotSources, stale, staleValues, suppressComputedRecompute, tracking, untrack }; | ||
| export { READ_SLOW, clearSnapshots, computed, context, createEffectNode, currentOptimisticLane, isEqual, latestReadActive, markSnapshotScope, optimisticComputed, optimisticSignal, pendingCheckActive, prepareComputed, read, readNodeFast, recompute, refresh, releaseSnapshotScope, runWithOwner, setContextInternal, setLatestReadActive, setMemo, setPendingCheckActive, setSignal, setSnapshotCapture, signal, snapshotCaptureActive, snapshotSources, stale, staleValues, suppressComputedRecompute, tracking, untrack }; |
@@ -69,4 +69,4 @@ import { REACTIVE_DISPOSED, STATUS_ERROR, EFFECT_USER, CONFIG_AUTO_DISPOSE, CONFIG_CHILDREN_FORBIDDEN, EFFECT_TRACKED, STATUS_PENDING, EFFECT_RENDER } from "./constants.js"; | ||
| t._t ? t._t(E, () => { | ||
| const E = t.Et; | ||
| t.Et = undefined; | ||
| const E = t.ft; | ||
| t.ft = undefined; | ||
| E?.(); | ||
@@ -82,4 +82,4 @@ }) : console.error(E); | ||
| } | ||
| const E = t.Et; | ||
| t.Et = undefined; | ||
| const E = t.ft; | ||
| t.ft = undefined; | ||
| try { | ||
@@ -90,3 +90,3 @@ E?.(); | ||
| // The final cleanup is invoked by disposeChildren at true disposal. | ||
| t.Et = e; | ||
| t.ft = e; | ||
| } catch (E) { | ||
@@ -120,7 +120,7 @@ t._ = new StatusError(t, E); | ||
| const e = computed(() => { | ||
| const E = e.Et; | ||
| e.Et = undefined; | ||
| const E = e.ft; | ||
| e.ft = undefined; | ||
| E?.(); | ||
| const R = staleValues(t); | ||
| e.Et = R; | ||
| e.ft = R; | ||
| }, { | ||
@@ -130,3 +130,3 @@ ...E, | ||
| }); | ||
| e.Et = undefined; | ||
| e.ft = undefined; | ||
| e.T = e.T & ~CONFIG_AUTO_DISPOSE | CONFIG_CHILDREN_FORBIDDEN; | ||
@@ -133,0 +133,0 @@ e.$e = true; |
@@ -72,3 +72,3 @@ import { CONFIG_AUTO_DISPOSE, REACTIVE_ZOMBIE, STATUS_PENDING, REACTIVE_RECOMPUTING_DEPS } from "./constants.js"; | ||
| if (s !== null && s.tt === l) { | ||
| s.nl = n.Ye; | ||
| s.nl = n.We; | ||
| n.qe = s; | ||
@@ -86,3 +86,3 @@ // First touch of this pass: the previous pass's label is stale. | ||
| const i = l.ot; | ||
| if (i !== null && i.le === n && (!t || i.nl === n.Ye)) { | ||
| if (i !== null && i.le === n && (!t || i.nl === n.We)) { | ||
| // Gen-matched during a recompute = repeat touch this pass (AND); outside | ||
@@ -99,3 +99,3 @@ // a recompute there is no pass boundary, so the latest read labels it. | ||
| ue: null, | ||
| nl: n.Ye, | ||
| nl: n.We, | ||
| De: e | ||
@@ -102,0 +102,0 @@ }; |
+23
-23
@@ -23,3 +23,3 @@ import { REACTIVE_IN_HEAP, EFFECT_TRACKED, EFFECT_USER, REACTIVE_IN_HEAP_HEIGHT, REACTIVE_RECOMPUTING_DEPS, REACTIVE_MANUAL_WRITE, REACTIVE_ZOMBIE, REACTIVE_CHECK, REACTIVE_DIRTY } from "./constants.js"; | ||
| const E = queueFor(e); | ||
| if (E.He > e.Fe) E.He = e.Fe; | ||
| if (E.Fe > e.He) E.Fe = e.He; | ||
| insertIntoHeap(e, E); | ||
@@ -29,11 +29,11 @@ } | ||
| function actualInsertIntoHeap(e, E) { | ||
| const t = (e.Ge?.ft ? e.Ge.Nt?.Fe : e.Ge?.Fe) ?? -1; | ||
| if (t >= e.Fe) e.Fe = t + 1; | ||
| const n = e.Fe; | ||
| const t = (e.Ge?.Et ? e.Ge.Nt?.He : e.Ge?.He) ?? -1; | ||
| if (t >= e.He) e.He = t + 1; | ||
| const n = e.He; | ||
| const I = E.eE[n]; | ||
| if (I === undefined) E.eE[n] = e; else { | ||
| const E = I.ut; | ||
| E.lt = e; | ||
| e.ut = E; | ||
| I.ut = e; | ||
| const E = I.lt; | ||
| E.ut = e; | ||
| e.lt = E; | ||
| I.lt = e; | ||
| } | ||
@@ -71,12 +71,12 @@ if (n > E.EE) E.EE = n; | ||
| e.se = t & -25; | ||
| const n = e.Fe; | ||
| if (e.ut === e) E.eE[n] = undefined; else { | ||
| const t = e.lt; | ||
| const n = e.He; | ||
| if (e.lt === e) E.eE[n] = undefined; else { | ||
| const t = e.ut; | ||
| const I = E.eE[n]; | ||
| const o = t ?? I; | ||
| if (e === I) E.eE[n] = t; else e.ut.lt = t; | ||
| o.ut = e.ut; | ||
| if (e === I) E.eE[n] = t; else e.lt.ut = t; | ||
| o.lt = e.lt; | ||
| } | ||
| e.ut = e; | ||
| e.lt = undefined; | ||
| e.lt = e; | ||
| e.ut = undefined; | ||
| } | ||
@@ -88,3 +88,3 @@ | ||
| for (let E = 0; E <= e.EE; E++) { | ||
| for (let t = e.eE[E]; t !== undefined; t = t.lt) { | ||
| for (let t = e.eE[E]; t !== undefined; t = t.ut) { | ||
| if (t.se & REACTIVE_IN_HEAP) markNode(t); | ||
@@ -113,7 +113,7 @@ } | ||
| e.tE = false; | ||
| for (e.He = 0; e.He <= e.EE; e.He++) { | ||
| let t = e.eE[e.He]; | ||
| for (e.Fe = 0; e.Fe <= e.EE; e.Fe++) { | ||
| let t = e.eE[e.Fe]; | ||
| while (t !== undefined) { | ||
| if (t.se & REACTIVE_IN_HEAP) E(t); else adjustHeight(t, e); | ||
| t = e.eE[e.He]; | ||
| t = e.eE[e.Fe]; | ||
| } | ||
@@ -126,10 +126,10 @@ } | ||
| deleteFromHeap(e, E); | ||
| let t = e.Fe; | ||
| let t = e.He; | ||
| for (let E = e.Xe; E; E = E.et) { | ||
| const e = E.tt; | ||
| const n = e.nt || e; | ||
| if (n.ke && n.Fe >= t) t = n.Fe + 1; | ||
| if (n.ke && n.He >= t) t = n.He + 1; | ||
| } | ||
| if (e.Fe !== t) { | ||
| e.Fe = t; | ||
| if (e.He !== t) { | ||
| e.He = t; | ||
| for (let E = e.o; E !== null; E = E.ue) { | ||
@@ -136,0 +136,0 @@ // Route each subscriber by its own zombie flag, mirroring the |
@@ -55,3 +55,5 @@ import { NOT_PENDING, unwrapOverride, STATUS_UNINITIALIZED, OVERRIDE_UNDEFINED, STATUS_PENDING, REACTIVE_MANUAL_WRITE, REACTIVE_OPTIMISTIC_DIRTY, EFFECT_RENDER, EFFECT_USER } from "./constants.js"; | ||
| e.Ee = n === undefined ? OVERRIDE_UNDEFINED : n; | ||
| GlobalQueue.Ie !== null && GlobalQueue.Ie(e, n); | ||
| // syncCompanions only pokes _pendingSignal/_latestValueComputed — with | ||
| // neither companion present the call is a guaranteed no-op. | ||
| (e.be !== undefined || e.ge !== undefined) && GlobalQueue.Ie !== null && GlobalQueue.Ie(e, n); | ||
| e.ce = clock; | ||
@@ -220,4 +222,4 @@ insertSubs(e, true); | ||
| GlobalQueue.En = resolveOptimisticNodes; | ||
| GlobalQueue.Tn = transitionBlocked; | ||
| GlobalQueue.dn = cleanupCompletedLanes; | ||
| GlobalQueue.dn = transitionBlocked; | ||
| GlobalQueue.Tn = cleanupCompletedLanes; | ||
| GlobalQueue.In = runLaneEffects; | ||
@@ -227,3 +229,3 @@ GlobalQueue.Ct = gatedRead; | ||
| GlobalQueue.Ot = laneReadsCommitted; | ||
| GlobalQueue.We = recomputeLane; | ||
| GlobalQueue.Ye = recomputeLane; | ||
| GlobalQueue.Ke = laneAsyncPending; | ||
@@ -230,0 +232,0 @@ GlobalQueue.Me = laneAsyncSettled; |
+10
-10
@@ -57,3 +57,3 @@ import { REACTIVE_DISPOSED, REACTIVE_ZOMBIE, REACTIVE_IN_HEAP, REACTIVE_IN_HEAP_HEIGHT, CONFIG_TRANSPARENT, defaultContext } from "./constants.js"; | ||
| if (n && e.ke) e.Te = null; | ||
| let l = t ? e.Le : e.me; | ||
| let l = t ? e.ye : e.me; | ||
| while (l) { | ||
@@ -75,3 +75,3 @@ const e = l.ve; | ||
| if (t) { | ||
| e.Le = null; | ||
| e.ye = null; | ||
| } else { | ||
@@ -95,5 +95,5 @@ e.me = null; | ||
| // to mirror rerun ordering (compute-phase teardown first, cleanup last). | ||
| if (n && e.Et) { | ||
| const n = e.Et; | ||
| e.Et = undefined; | ||
| if (n && e.ft) { | ||
| const n = e.ft; | ||
| e.ft = undefined; | ||
| n(); | ||
@@ -104,3 +104,3 @@ } | ||
| function runDisposal(e, n) { | ||
| let t = n ? e.ye : e.xe; | ||
| let t = n ? e.Le : e.xe; | ||
| if (!t) return; | ||
@@ -115,3 +115,3 @@ if (Array.isArray(t)) { | ||
| } | ||
| n ? e.ye = null : e.xe = null; | ||
| n ? e.Le = null : e.xe = null; | ||
| } | ||
@@ -243,4 +243,4 @@ | ||
| T: t ? CONFIG_TRANSPARENT : 0, | ||
| ft: true, | ||
| Nt: n?.ft ? n.Nt : n, | ||
| Et: true, | ||
| Nt: n?.Et ? n.Nt : n, | ||
| me: null, | ||
@@ -253,4 +253,4 @@ ve: null, | ||
| Qe: 0, | ||
| Le: null, | ||
| ye: null, | ||
| Le: null, | ||
| Ge: n, | ||
@@ -257,0 +257,0 @@ dispose: disposeRootSelf |
@@ -22,3 +22,3 @@ import { EFFECT_RENDER, EFFECT_USER, STATUS_PENDING, REACTIVE_DISPOSED, REACTIVE_ZOMBIE, NOT_PENDING, EFFECT_TRACKED, CONFIG_IN_SNAPSHOT_SCOPE, REACTIVE_SNAPSHOT_STALE, REACTIVE_OPTIMISTIC_DIRTY, REACTIVE_REASK, REACTIVE_MANUAL_WRITE, STATUS_UNINITIALIZED } from "./constants.js"; | ||
| tE: false, | ||
| He: 0, | ||
| Fe: 0, | ||
| EE: 0 | ||
@@ -30,3 +30,3 @@ }; | ||
| tE: false, | ||
| He: 0, | ||
| Fe: 0, | ||
| EE: 0 | ||
@@ -286,4 +286,4 @@ }; | ||
| static un=null; | ||
| static dt=null; | ||
| static St=null; | ||
| static dt=null; | ||
| static Rt=null; | ||
@@ -302,4 +302,4 @@ static ze=null; | ||
| static En=null; | ||
| static dn=null; | ||
| static Tn=null; | ||
| static dn=null; | ||
| static In=null; | ||
@@ -309,3 +309,3 @@ static Ct=null; | ||
| static Ot=null; | ||
| static We=null; | ||
| static Ye=null; | ||
| static Ke=null; | ||
@@ -342,3 +342,3 @@ static Me=null; | ||
| // flush path instead of leaving them until the next natural flush. | ||
| scheduled = dirtyQueue.EE >= dirtyQueue.He || this.m.Qt.length > 0; | ||
| scheduled = dirtyQueue.EE >= dirtyQueue.Fe || this.m.Qt.length > 0; | ||
| reassignPendingTransition(e.Qt); | ||
@@ -371,3 +371,3 @@ activeTransition = null; | ||
| commitPendingNodes(); | ||
| if (dirtyQueue.EE >= dirtyQueue.He) { | ||
| if (dirtyQueue.EE >= dirtyQueue.Fe) { | ||
| runHeap(dirtyQueue, GlobalQueue.pe); | ||
@@ -383,3 +383,3 @@ commitPendingNodes(); | ||
| // Check if finalization added items to the heap (from optimistic reversion) | ||
| scheduled = dirtyQueue.EE >= dirtyQueue.He; | ||
| scheduled = dirtyQueue.EE >= dirtyQueue.Fe; | ||
| // Run lane effects first (for ready lanes), then regular effects | ||
@@ -520,3 +520,3 @@ activeLanes.size && GlobalQueue.In(EFFECT_RENDER); | ||
| if (!(t.S & STATUS_PENDING)) t.S &= ~STATUS_UNINITIALIZED; | ||
| if (t.Le !== null || t.ye !== null) GlobalQueue.Ce(t, false, true); | ||
| if (t.ye !== null || t.Le !== null) GlobalQueue.Ce(t, false, true); | ||
| if (e.be || e.ge) GlobalQueue.un(e); | ||
@@ -539,3 +539,3 @@ } | ||
| if (!t && globalQueue.vt.length) checkBoundaryChildren(globalQueue); | ||
| const n = dirtyQueue.EE >= dirtyQueue.He; | ||
| const n = dirtyQueue.EE >= dirtyQueue.Fe; | ||
| if (n) runHeap(dirtyQueue, GlobalQueue.pe); | ||
@@ -575,3 +575,3 @@ if (i) { | ||
| // Lanes only enter activeLanes through the engine's getOrCreateLane. | ||
| if (activeLanes.size) GlobalQueue.dn(e); | ||
| if (activeLanes.size) GlobalQueue.Tn(e); | ||
| } | ||
@@ -682,3 +682,3 @@ } | ||
| // _optimisticNodes, so without the engine the loop was vacuous anyway. | ||
| if (t && e.Ze.length && GlobalQueue.Tn(e)) t = false; | ||
| if (t && e.Ze.length && GlobalQueue.dn(e)) t = false; | ||
| t && (e.fn = true); | ||
@@ -685,0 +685,0 @@ return t; |
@@ -235,3 +235,3 @@ import { NOT_PENDING, REACTIVE_DISPOSED, REACTIVE_DIRTY, REACTIVE_CHECK, unwrapOverride, REACTIVE_ZOMBIE, STATUS_UNINITIALIZED, STATUS_PENDING, REACTIVE_MANUAL_WRITE, STATUS_ERROR, REACTIVE_RECOMPUTING_DEPS } from "./constants.js"; | ||
| const e = queueFor(t); | ||
| if (t.Fe >= e.He && !(t.se & (REACTIVE_DISPOSED | REACTIVE_ZOMBIE))) { | ||
| if (t.He >= e.Fe && !(t.se & (REACTIVE_DISPOSED | REACTIVE_ZOMBIE))) { | ||
| markHeap(e); | ||
@@ -347,5 +347,5 @@ prepareComputed(t, true); | ||
| GlobalQueue.St = latestRead; | ||
| GlobalQueue.dt = latestRead; | ||
| GlobalQueue.dt = pendingCheckRead; | ||
| GlobalQueue.St = pendingCheckRead; | ||
@@ -352,0 +352,0 @@ GlobalQueue.Rt = recordFreshRead; |
@@ -17,3 +17,3 @@ import { computed } from "../core/core.js"; | ||
| import { storeSetter, $TARGET, STORE_OPTIMISTIC_OVERRIDE, STORE_NODE, STORE_OPTIMISTIC_OWNERS, getOverlayLayer, STORE_VALUE, $DELETED, isWrappable, wrap, visibleNodeValue, $TRACK, notifySelf, STORE_WRAP, createStoreProxy, storeTraps, STORE_LOOKUP, STORE_OPTIMISTIC, STORE_FIREWALL } from "./store.js"; | ||
| import { storeSetter, $TARGET, STORE_OPTIMISTIC_OVERRIDE, STORE_NODE, STORE_OPTIMISTIC_OWNERS, getOverlayLayer, STORE_VALUE, $DELETED, isWrappable, wrap, visibleNodeValue, $TRACK, notifySelf, STORE_WRAP, createStoreProxy, storeTraps, STORE_LOOKUP, STORE_SHALLOW, markRawIngest, STORE_OPTIMISTIC, STORE_FIREWALL } from "./store.js"; | ||
@@ -27,2 +27,4 @@ function createOptimisticStore(e, t, i) { | ||
| const r = typeof e === "function"; | ||
| // Plain form: the second slot carries options. | ||
| if (!r && i === undefined) i = t; | ||
| const o = r ? t : e; | ||
@@ -131,5 +133,10 @@ const n = r ? e : undefined; | ||
| const o = new WeakMap; | ||
| const n = !!i?.shallow; | ||
| const wrapper = e => { | ||
| e[STORE_WRAP] = wrapProjection; | ||
| e[STORE_LOOKUP] = o; | ||
| if (n) { | ||
| e[STORE_SHALLOW] = true; | ||
| markRawIngest(e[STORE_VALUE]); | ||
| } | ||
| e[STORE_OPTIMISTIC] = true; | ||
@@ -151,3 +158,3 @@ // Mark as optimistic store | ||
| }; | ||
| const n = wrapProjection(t); | ||
| const c = wrapProjection(t); | ||
| // If there's a projection function, create a computed to drive it | ||
@@ -161,3 +168,3 @@ if (e) { | ||
| const clearProjectionOverride = () => { | ||
| const e = n[$TARGET]; | ||
| const e = c[$TARGET]; | ||
| if (e?.[STORE_OPTIMISTIC_OVERRIDE]) clearOptimisticOverride(e); | ||
@@ -178,3 +185,3 @@ }; | ||
| try { | ||
| runProjectionComputed(n, e, i?.key || "id", wrapCommit, clearProjectionOverride); | ||
| runProjectionComputed(c, e, i?.key || "id", wrapCommit, clearProjectionOverride); | ||
| } finally { | ||
@@ -187,3 +194,3 @@ setProjectionWriteActive(false); | ||
| return { | ||
| store: n, | ||
| store: c, | ||
| node: r | ||
@@ -190,0 +197,0 @@ }; |
@@ -17,10 +17,17 @@ import { computed } from "../core/core.js"; | ||
| import { storeSetter, $TARGET, STORE_WRAP, createStoreProxy, storeTraps, setWriteOverride, STORE_LOOKUP, STORE_FIREWALL } from "./store.js"; | ||
| import { storeSetter, $TARGET, STORE_WRAP, createStoreProxy, storeTraps, setWriteOverride, STORE_LOOKUP, STORE_SHALLOW, markRawIngest, STORE_VALUE, STORE_FIREWALL } from "./store.js"; | ||
| function createProjectionInternal(e, r, t) { | ||
| let o; | ||
| const i = new WeakMap; | ||
| const n = new WeakMap; | ||
| // A shallow projection's children are raw and never wrapped, so the | ||
| // wrapper only ever runs for the root — flagging unconditionally is safe. | ||
| const i = !!t?.shallow; | ||
| const wrapper = e => { | ||
| e[STORE_WRAP] = wrapProjection; | ||
| e[STORE_LOOKUP] = i; | ||
| e[STORE_LOOKUP] = n; | ||
| if (i) { | ||
| e[STORE_SHALLOW] = true; | ||
| markRawIngest(e[STORE_VALUE]); | ||
| } | ||
| Object.defineProperty(e, STORE_FIREWALL, { | ||
@@ -34,16 +41,16 @@ get() { | ||
| const wrapProjection = e => { | ||
| if (i.has(e)) return i.get(e); | ||
| if (n.has(e)) return n.get(e); | ||
| if (e[$TARGET]?.[STORE_WRAP] === wrapProjection) return e; | ||
| const r = createStoreProxy(e, storeTraps, wrapper); | ||
| i.set(e, r); | ||
| n.set(e, r); | ||
| return r; | ||
| }; | ||
| const n = wrapProjection(r); | ||
| const c = wrapProjection(r); | ||
| o = computed(() => { | ||
| if (!o) o = getOwner(); | ||
| runProjectionComputed(n, e, t?.key || "id"); | ||
| runProjectionComputed(c, e, t?.key || "id"); | ||
| }, undefined); | ||
| o.T &= ~CONFIG_AUTO_DISPOSE; | ||
| return { | ||
| store: n, | ||
| store: c, | ||
| node: o | ||
@@ -111,18 +118,18 @@ }; | ||
| * the outer `setProjectionWriteActive` scope. | ||
| */ function runProjectionComputed(e, r, t, o, i) { | ||
| const n = getOwner(); | ||
| */ function runProjectionComputed(e, r, t, o, n) { | ||
| const i = getOwner(); | ||
| let c = false; | ||
| let s; | ||
| const u = new Proxy(e, createWriteTraps(() => !c || n.Te === s, i)); | ||
| storeSetter(u, i => { | ||
| s = r(i); | ||
| const u = new Proxy(e, createWriteTraps(() => !c || i.Te === s, n)); | ||
| storeSetter(u, n => { | ||
| s = r(n); | ||
| c = true; | ||
| const commit = r => { | ||
| if (r === i || r === undefined) return; | ||
| if (r === n || r === undefined) return; | ||
| const write = () => storeSetter(e, reconcile(r, t)); | ||
| o ? o(write) : write(); | ||
| }; | ||
| commit(handleAsync(n, s, commit)); | ||
| commit(handleAsync(i, s, commit)); | ||
| }); | ||
| return n; | ||
| return i; | ||
| } | ||
@@ -157,3 +164,3 @@ | ||
| }, | ||
| set(t, o, i) { | ||
| set(t, o, n) { | ||
| if (e && !e()) return true; | ||
@@ -163,3 +170,3 @@ setWriteOverride(true); | ||
| try { | ||
| t[o] = i; | ||
| t[o] = n; | ||
| r?.(); | ||
@@ -166,0 +173,0 @@ } finally { |
+405
-211
@@ -11,3 +11,3 @@ import { setSignal, untrack } from "../core/core.js"; | ||
| import { $TARGET, STORE_OVERRIDE, STORE_OPTIMISTIC_OVERRIDE, STORE_VALUE, STORE_NODE, storeLookup, STORE_LOOKUP, $PROXY, isWrappable, wrap, notifySelf, $TRACK, STORE_DESC, STORE_HAS, symbolKeyedRecords, getKeys, $DELETED, getStoreSymbols } from "./store.js"; | ||
| import { $TARGET, STORE_SHALLOW, STORE_OVERRIDE, STORE_OPTIMISTIC_OVERRIDE, STORE_VALUE, STORE_LOOKUP, $PROXY, storeLookup, markRawIngest, STORE_NODE, $TRACK, STORE_HAS, notifySelf, isWrappable, wrap, rawValuesUsed, STORE_DESC, symbolKeyedRecords, $DELETED, isRawValue, getKeys, getStoreSymbols, STORE_WRAP, lookupTarget } from "./store.js"; | ||
@@ -21,5 +21,5 @@ // Enumerate a node record's keys. Keeps the common string-key path on the | ||
| if (symbolKeyedRecords.has(e)) { | ||
| const r = Object.getOwnPropertySymbols(e); | ||
| for (let e = 0, n = r.length; e < n; e++) { | ||
| if (r[e] !== $TRACK) t.push(r[e]); | ||
| const n = Object.getOwnPropertySymbols(e); | ||
| for (let e = 0, a = n.length; e < a; e++) { | ||
| if (n[e] !== $TRACK) t.push(n[e]); | ||
| } | ||
@@ -37,30 +37,30 @@ } | ||
| function getOverrideValue(e, t, r, n) { | ||
| if (n && r in n) return n[r]; | ||
| return t && r in t ? t[r] : e[r]; | ||
| function getOverrideValue(e, t, n, a) { | ||
| if (a && n in a) return a[n]; | ||
| return t && n in t ? t[n] : e[n]; | ||
| } | ||
| // Append `o`'s *enumerable* own symbol keys from a pre-fetched symbol list. | ||
| function addEnumSymbols(e, t, r) { | ||
| for (let n = 0, a = t.length; n < a; n++) { | ||
| if (Object.prototype.propertyIsEnumerable.call(e, t[n])) r.add(t[n]); | ||
| function addEnumSymbols(e, t, n) { | ||
| for (let a = 0, r = t.length; a < r; a++) { | ||
| if (Object.prototype.propertyIsEnumerable.call(e, t[a])) n.add(t[a]); | ||
| } | ||
| } | ||
| function getAllKeys(e, t, r) { | ||
| function getAllKeys(e, t, n) { | ||
| // Symbols are merged explicitly below; keep the common string-key path on | ||
| // Object.keys() and avoid reflecting the base symbols twice. | ||
| const n = getKeys(e, t); | ||
| const a = Object.keys(r); | ||
| const a = getKeys(e, t); | ||
| const r = Object.keys(n); | ||
| // `value` can be a wrapped store (store-in-store) whose ownKeys trap tracks; | ||
| // mirror `getKeys` and enumerate its symbols untracked in that case. | ||
| const i = e[$TARGET] ? untrack(() => Object.getOwnPropertySymbols(e)) : Object.getOwnPropertySymbols(e); | ||
| const o = Object.getOwnPropertySymbols(r); | ||
| const s = Object.getOwnPropertySymbols(n); | ||
| // Symbol-free diff (the overwhelmingly common case) stays on the exact | ||
| // pre-#2851 path, including the identical-key-sets fast path from #2756. | ||
| if (i.length === 0 && o.length === 0) { | ||
| if (n.length === a.length) { | ||
| if (i.length === 0 && s.length === 0) { | ||
| if (a.length === r.length) { | ||
| let e = true; | ||
| for (let t = 0; t < n.length; t++) { | ||
| if (n[t] !== a[t]) { | ||
| for (let t = 0; t < a.length; t++) { | ||
| if (a[t] !== r[t]) { | ||
| e = false; | ||
@@ -70,6 +70,6 @@ break; | ||
| } | ||
| if (e) return n; | ||
| if (e) return a; | ||
| } | ||
| const e = new Set(n); | ||
| for (let t = 0; t < a.length; t++) e.add(a[t]); | ||
| const e = new Set(a); | ||
| for (let t = 0; t < r.length; t++) e.add(r[t]); | ||
| return Array.from(e); | ||
@@ -80,3 +80,3 @@ } | ||
| // `next`'s keys (a key present in next is never deleted by the diff). | ||
| const l = new Set(n); | ||
| const l = new Set(a); | ||
| addEnumSymbols(e, i, l); | ||
@@ -88,4 +88,4 @@ if (t) { | ||
| } | ||
| for (let e = 0; e < a.length; e++) l.add(a[e]); | ||
| addEnumSymbols(r, o, l); | ||
| for (let e = 0; e < r.length; e++) l.add(r[e]); | ||
| addEnumSymbols(n, s, l); | ||
| return Array.from(l); | ||
@@ -105,4 +105,4 @@ } | ||
| function keyedMatch(e, t, r) { | ||
| return e === t || isWrappable(e) && isWrappable(t) && r(e) === r(t); | ||
| function keyedMatch(e, t, n) { | ||
| return e === t || isWrappable(e) && isWrappable(t) && n(e) === n(t); | ||
| } | ||
@@ -115,16 +115,30 @@ | ||
| // props behave like normal property reads. | ||
| // Inline key iteration on purpose: these loops run per array target per | ||
| // reconcile pass, and a shared helper means a closure + per-key call in | ||
| // instruction counts. String-only records (the common case) iterate in | ||
| // place; symbol-bearing records take the nodeKeys array path. | ||
| function syncArrayNodeMembership(e, t) { | ||
| let r = e[STORE_NODE]; | ||
| if (r) { | ||
| const e = nodeKeys(r); | ||
| for (let n = 0, a = e.length; n < a; n++) { | ||
| const a = e[n]; | ||
| a in t || setSignal(r[a], undefined); | ||
| let n = e[STORE_NODE]; | ||
| if (n) { | ||
| if (symbolKeyedRecords.has(n)) { | ||
| const e = nodeKeys(n); | ||
| for (let a = 0, r = e.length; a < r; a++) { | ||
| e[a] in t || setSignal(n[e[a]], undefined); | ||
| } | ||
| } else { | ||
| for (const e in n) { | ||
| e in t || setSignal(n[e], undefined); | ||
| } | ||
| } | ||
| } | ||
| if (r = e[STORE_HAS]) { | ||
| const e = nodeKeys(r); | ||
| for (let n = 0, a = e.length; n < a; n++) { | ||
| const a = e[n]; | ||
| setSignal(r[a], a in t); | ||
| if (n = e[STORE_HAS]) { | ||
| if (symbolKeyedRecords.has(n)) { | ||
| const e = nodeKeys(n); | ||
| for (let a = 0, r = e.length; a < r; a++) { | ||
| setSignal(n[e[a]], e[a] in t); | ||
| } | ||
| } else { | ||
| for (const e in n) { | ||
| setSignal(n[e], e in t); | ||
| } | ||
| } | ||
@@ -134,10 +148,36 @@ } | ||
| // Recurse into a matched wrappable child pair without manufacturing a proxy: | ||
| // resolve the child's target through the lookup and dispatch directly. A | ||
| // lookup miss means the child was never observed — no proxy, no nodes, no | ||
| // subscribers anywhere below — so the parent's swap making `next` | ||
| // authoritative IS the whole update; a later read wraps next's child on | ||
| // demand. This turns the diff from O(previous graph) into O(observed graph) | ||
| // and drops the wrap()/$PROXY/$TARGET round-trip per visited child. | ||
| // Wrap-family stores (projections/optimistic) own child proxy creation and | ||
| // keep the proxy-based recursion. | ||
| function applyStateChild(e, t, n, a) { | ||
| if (n[STORE_WRAP] !== undefined) { | ||
| applyState(e, wrap(t, n), a); | ||
| return; | ||
| } | ||
| const r = t[$TARGET] ?? storeLookup.get(t); | ||
| if (r === undefined) return; | ||
| e = unwrap(e); | ||
| if (r[STORE_SHALLOW]) { | ||
| applyStateShallow(e, r); | ||
| } else if (r[STORE_OVERRIDE] || r[STORE_OPTIMISTIC_OVERRIDE]) { | ||
| applyStateSlow(e, r, a); | ||
| } else { | ||
| applyStateFast(e, r, a); | ||
| } | ||
| } | ||
| // Reconcile a single array slot: recurse into a wrappable pair, otherwise replace | ||
| // the node's value outright (covers object→primitive and primitive→object). | ||
| function applyArrayItem(e, t, r, n, a) { | ||
| if (isWrappable(e) && isWrappable(t)) { | ||
| const i = wrap(t, r); | ||
| n && setSignal(n, i); | ||
| applyState(e, i, a); | ||
| } else n && setSignal(n, wrapValue(e, r)); | ||
| function applyArrayItem(e, t, n, a, r) { | ||
| if (isWrappable(e) && isWrappable(t) && !(rawValuesUsed && (isRawValue(t) || isRawValue(e)))) { | ||
| const i = wrap(t, n); | ||
| a && setSignal(a, i); | ||
| applyState(e, i, r); | ||
| } else a && setSignal(a, wrapValue(e, n)); | ||
| } | ||
@@ -156,19 +196,52 @@ | ||
| * `$TRACK` absence (an enumeration-tracked record already diffs every key). | ||
| */ function applyDescendants(e, t, r, n, a, i, o) { | ||
| const l = r[STORE_LOOKUP] || storeLookup; | ||
| const s = (i ? getKeys(e, i) : Object.keys(e)).concat(getStoreSymbols(e, i)); | ||
| for (let p = 0, f = s.length; p < f; p++) { | ||
| const f = s[p]; | ||
| if (n?.[f]) continue; | ||
| */ function applyDescendants(e, t, n, a, r, i, s) { | ||
| const l = n[STORE_LOOKUP] || storeLookup; | ||
| if (i) { | ||
| const n = getKeys(e, i).concat(getStoreSymbols(e, i)); | ||
| for (let o = 0, f = n.length; o < f; o++) { | ||
| const f = n[o]; | ||
| if (a?.[f]) continue; | ||
| // main loop already diffed this slot | ||
| const c = unwrap(i ? getOverrideValue(e, i, f, o) : e[f]); | ||
| if (!isWrappable(c)) continue; | ||
| const u = (l.get(c) ?? storeLookup.get(c))?.[$TARGET]; | ||
| if (!u?.[STORE_DESC]) continue; | ||
| const y = unwrap(t[f]); | ||
| if (c === y || !isWrappable(y) || Array.isArray(c) !== Array.isArray(y) || a(c) != null && a(c) !== a(y)) continue; | ||
| applyState(y, wrap(c, r), a); | ||
| const p = unwrap(getOverrideValue(e, i, f, s)); | ||
| if (!isWrappable(p)) continue; | ||
| descendInto(p, t[f], l, r); | ||
| } | ||
| return; | ||
| } | ||
| // No-override path (every applyStateFast call): iterate in place instead of | ||
| // building keys + symbols + concat arrays per object per pass. The cheap | ||
| // bails (noded key, primitive value) stay inline — only genuine descent | ||
| // candidates pay a call. | ||
| for (const n in e) { | ||
| if (a?.[n]) continue; | ||
| // main loop already diffed this slot | ||
| const i = unwrap(e[n]); | ||
| if (!isWrappable(i)) continue; | ||
| descendInto(i, t[n], l, r); | ||
| } | ||
| const o = Object.getOwnPropertySymbols(e); | ||
| for (let n = 0, i = o.length; n < i; n++) { | ||
| if (Object.prototype.propertyIsEnumerable.call(e, o[n])) { | ||
| if (a?.[o[n]]) continue; | ||
| const i = unwrap(e[o[n]]); | ||
| if (!isWrappable(i)) continue; | ||
| descendInto(i, t[o[n]], l, r); | ||
| } | ||
| } | ||
| } | ||
| function descendInto(e, t, n, a) { | ||
| const r = lookupTarget(e, n); | ||
| if (!r?.[STORE_DESC]) return; | ||
| const i = unwrap(t); | ||
| if (e === i || !isWrappable(i) || Array.isArray(e) !== Array.isArray(i) || a(e) != null && a(e) !== a(i)) return; | ||
| if (r[STORE_SHALLOW]) { | ||
| applyStateShallow(i, r); | ||
| } else if (r[STORE_OVERRIDE] || r[STORE_OPTIMISTIC_OVERRIDE]) { | ||
| applyStateSlow(i, r, a); | ||
| } else { | ||
| applyStateFast(i, r, a); | ||
| } | ||
| } | ||
| // Dispatcher: every applyState call (including recursion) checks for the | ||
@@ -179,80 +252,180 @@ // presence of override / optimistic-override slots once and routes to the | ||
| // shape for the overwhelmingly common case of plain stores. | ||
| function applyState(e, t, r) { | ||
| function applyState(e, t, n) { | ||
| // Array items and root calls can pass a store proxy as `next`; normalize to | ||
| // its raw value or the swap would set a store's STORE_VALUE to its own proxy. | ||
| e = unwrap(e); | ||
| const n = t?.[$TARGET]; | ||
| if (!n) return; | ||
| if (n[STORE_OVERRIDE] || n[STORE_OPTIMISTIC_OVERRIDE]) { | ||
| applyStateSlow(e, n, r); | ||
| const a = t?.[$TARGET]; | ||
| if (!a) return; | ||
| if (a[STORE_SHALLOW]) { | ||
| applyStateShallow(e, a); | ||
| } else if (a[STORE_OVERRIDE] || a[STORE_OPTIMISTIC_OVERRIDE]) { | ||
| applyStateSlow(e, a, n); | ||
| } else { | ||
| applyStateFast(e, n, r); | ||
| applyStateFast(e, a, n); | ||
| } | ||
| } | ||
| function applyStateFast(e, t, r) { | ||
| const n = t[STORE_VALUE]; | ||
| if (e === n) return; | ||
| const a = t[STORE_NODE]; | ||
| // Shallow boundary diff: the target's own keys are reactive, its values are | ||
| // raw records replaced by reference — no recursion, no wrapping. Arrays merge | ||
| // positionally (below a shallow boundary the VALUE is the identity; keyed | ||
| // row identity belongs to the consumer, e.g. <For keyed>). Incoming | ||
| // wrappables are sticky-marked raw so they present raw through every store. | ||
| // One pass over a shallow target's node record: replace changed slots with | ||
| // raw next values, null out slots absent from next. Returns whether anything | ||
| // differed. | ||
| function shallowDiffNodes(e, t, n, a) { | ||
| let r = false; | ||
| for (const i in e) { | ||
| if (a && i === "length") continue; | ||
| if (i in t) { | ||
| const a = t[i]; | ||
| if (a !== n(i)) { | ||
| r = true; | ||
| setSignal(e[i], a); | ||
| } | ||
| } else { | ||
| r = true; | ||
| setSignal(e[i], undefined); | ||
| } | ||
| } | ||
| return r; | ||
| } | ||
| function applyStateShallow(e, t, n) { | ||
| const a = t[STORE_VALUE]; | ||
| const r = t[STORE_OVERRIDE]; | ||
| const i = t[STORE_OPTIMISTIC_OVERRIDE]; | ||
| if (e === a && !r && !i) return; | ||
| // Setter-staged writes fold into the diff: previous values resolve through | ||
| // the override layers (so a replaced slot compares against what readers | ||
| // saw), and the regular override clears with the swap — reconcile makes | ||
| // `next` the authoritative base, same as the deep slow path. | ||
| const prevAt = e => { | ||
| const t = getOverrideValue(a, r, e, i); | ||
| return t === $DELETED ? undefined : t; | ||
| }; | ||
| t[STORE_OVERRIDE] = undefined; | ||
| const s = t[STORE_LOOKUP]; | ||
| s !== undefined ? s.set(e, t[$PROXY]) : storeLookup.set(e, t); | ||
| t[STORE_VALUE] = e; | ||
| markRawIngest(e); | ||
| const l = t[STORE_NODE]; | ||
| const o = l && l[$TRACK]; | ||
| let f = false; | ||
| if (Array.isArray(a)) { | ||
| const n = r?.length ?? i?.length ?? a.length; | ||
| if (l) { | ||
| f = shallowDiffNodes(l, e, prevAt, true); | ||
| if (l.length && n !== e.length) setSignal(l.length, e.length); | ||
| } | ||
| if (!f && (o || t[STORE_HAS])) { | ||
| // Slots without nodes still feed $TRACK enumerators / `in` probes. | ||
| if (n !== e.length) f = true; else { | ||
| for (let t = 0, n = e.length; t < n; t++) { | ||
| if (prevAt(t) !== e[t]) { | ||
| f = true; | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } else { | ||
| if (l) { | ||
| f = shallowDiffNodes(l, e, prevAt, false); | ||
| } | ||
| if (!f && (o || t[STORE_HAS])) f = true; | ||
| } | ||
| let p = t[STORE_HAS]; | ||
| if (p) { | ||
| for (const t in p) { | ||
| setSignal(p[t], t in e); | ||
| } | ||
| } | ||
| f && notifySelf(t); | ||
| } | ||
| function applyStateFast(e, t, n) { | ||
| const a = t[STORE_VALUE]; | ||
| if (e === a) return; | ||
| const r = t[STORE_NODE]; | ||
| // swap | ||
| (t[STORE_LOOKUP] || storeLookup).set(e, t[$PROXY]); | ||
| { | ||
| const n = t[STORE_LOOKUP]; | ||
| n !== undefined ? n.set(e, t[$PROXY]) : storeLookup.set(e, t); | ||
| } | ||
| t[STORE_VALUE] = e; | ||
| // merge | ||
| if (Array.isArray(n)) { | ||
| if (Array.isArray(a)) { | ||
| let i = false; | ||
| const o = n.length; | ||
| if (e.length && o && isWrappable(e[0]) && r(e[0]) != null) { | ||
| let l, s, p, f, c, u, y, S; | ||
| for (p = 0, f = Math.min(o, e.length); p < f && keyedMatch(u = n[p], e[p], r); p++) { | ||
| isWrappable(u) && isWrappable(e[p]) && applyState(e[p], wrap(u, t), r); | ||
| const s = a.length; | ||
| if (e.length && s && isWrappable(e[0]) && n(e[0]) != null) { | ||
| let l, o, f, p, u, c, S, R; | ||
| for (f = 0, p = Math.min(s, e.length); f < p && keyedMatch(c = a[f], e[f], n); f++) { | ||
| // keyedMatch established both sides wrappable unless they're the | ||
| // SAME reference — and an identical slot is a guaranteed no-op | ||
| // (the child's STORE_VALUE tracks the previous graph), so skip | ||
| // the recursion dispatch entirely. Raw-marked values are leaves: | ||
| // replace the slot node instead of recursing. | ||
| if (c !== e[f]) { | ||
| if (rawValuesUsed && (isRawValue(c) || isRawValue(e[f]))) { | ||
| r?.[f] && setSignal(r[f], wrapValue(e[f], t)); | ||
| } else applyStateChild(e[f], c, t, n); | ||
| } | ||
| } | ||
| // Every position key-matched at equal length (the steady-state shape | ||
| // of a polling tick): membership, length, and order are unchanged — | ||
| // nothing below could do observable work, so skip the staging | ||
| // allocations and membership sync outright. | ||
| if (f === e.length && f === s) return; | ||
| const E = new Array(e.length), O = new Map; | ||
| for (f = o - 1, c = e.length - 1; f >= p && c >= p && keyedMatch(u = n[f], e[c], r); f--, | ||
| c--) { | ||
| E[c] = u; | ||
| for (p = s - 1, u = e.length - 1; p >= f && u >= f && keyedMatch(c = a[p], e[u], n); p--, | ||
| u--) { | ||
| E[u] = c; | ||
| } | ||
| if (p > c || p > f) { | ||
| for (s = p; s <= c; s++) { | ||
| if (f > u || f > p) { | ||
| for (o = f; o <= u; o++) { | ||
| i = true; | ||
| a?.[s] && setSignal(a[s], wrapValue(e[s], t)); | ||
| r?.[o] && setSignal(r[o], wrapValue(e[o], t)); | ||
| } | ||
| for (;s < e.length; s++) { | ||
| for (;o < e.length; o++) { | ||
| i = true; | ||
| applyArrayItem(e[s], E[s], t, a?.[s], r); | ||
| applyArrayItem(e[o], E[o], t, r?.[o], n); | ||
| } | ||
| syncArrayNodeMembership(t, e); | ||
| (i || o !== e.length) && notifySelf(t); | ||
| o !== e.length && a?.length && setSignal(a.length, e.length); | ||
| (i || s !== e.length) && notifySelf(t); | ||
| s !== e.length && r?.length && setSignal(r.length, e.length); | ||
| return; | ||
| } | ||
| y = new Array(c + 1); | ||
| for (s = c; s >= p; s--) { | ||
| u = e[s]; | ||
| S = itemKey(u, r); | ||
| l = O.get(S); | ||
| y[s] = l === undefined ? -1 : l; | ||
| O.set(S, s); | ||
| S = new Array(u + 1); | ||
| for (o = u; o >= f; o--) { | ||
| c = e[o]; | ||
| R = itemKey(c, n); | ||
| l = O.get(R); | ||
| S[o] = l === undefined ? -1 : l; | ||
| O.set(R, o); | ||
| } | ||
| for (l = p; l <= f; l++) { | ||
| u = n[l]; | ||
| S = itemKey(u, r); | ||
| s = O.get(S); | ||
| if (s !== undefined && s !== -1) { | ||
| E[s] = u; | ||
| s = y[s]; | ||
| O.set(S, s); | ||
| for (l = f; l <= p; l++) { | ||
| c = a[l]; | ||
| R = itemKey(c, n); | ||
| o = O.get(R); | ||
| if (o !== undefined && o !== -1) { | ||
| E[o] = c; | ||
| o = S[o]; | ||
| O.set(R, o); | ||
| } | ||
| } | ||
| for (s = p; s < e.length; s++) { | ||
| if (s in E) { | ||
| applyArrayItem(e[s], E[s], t, a?.[s], r); | ||
| } else a?.[s] && setSignal(a[s], wrapValue(e[s], t)); | ||
| for (o = f; o < e.length; o++) { | ||
| if (o in E) { | ||
| applyArrayItem(e[o], E[o], t, r?.[o], n); | ||
| } else r?.[o] && setSignal(r[o], wrapValue(e[o], t)); | ||
| } | ||
| if (p < e.length) i = true; | ||
| if (f < e.length) i = true; | ||
| } else if (e.length) { | ||
| for (let o = 0, l = e.length; o < l; o++) { | ||
| const l = n[o]; | ||
| if (isWrappable(l) && isWrappable(e[o])) applyState(e[o], wrap(l, t), r); else { | ||
| if (l !== e[o]) i = true; | ||
| a?.[o] && setSignal(a[o], wrapValue(e[o], t)); | ||
| for (let s = 0, l = e.length; s < l; s++) { | ||
| const l = a[s]; | ||
| if (isWrappable(l) && isWrappable(e[s]) && !(rawValuesUsed && (isRawValue(l) || isRawValue(e[s])))) { | ||
| if (l !== e[s]) applyStateChild(e[s], l, t, n); | ||
| } else { | ||
| if (l !== e[s]) i = true; | ||
| r?.[s] && setSignal(r[s], wrapValue(e[s], t)); | ||
| } | ||
@@ -262,5 +435,5 @@ } | ||
| syncArrayNodeMembership(t, e); | ||
| if (o !== e.length) { | ||
| if (s !== e.length) { | ||
| i = true; | ||
| a?.length && setSignal(a.length, e.length); | ||
| r?.length && setSignal(r.length, e.length); | ||
| } | ||
@@ -272,9 +445,24 @@ i && notifySelf(t); | ||
| let i = t[STORE_NODE]; | ||
| let o; | ||
| let s; | ||
| if (i) { | ||
| o = i[$TRACK]; | ||
| if (o || symbolKeyedRecords.has(i)) { | ||
| const a = o ? getAllKeys(n, undefined, e) : nodeKeys(i); | ||
| for (let l = 0, s = a.length; l < s; l++) { | ||
| diffNodeKey(a[l], i, n, e, t, o, r); | ||
| s = i[$TRACK]; | ||
| // The per-key body is duplicated across both loops on purpose: this is | ||
| // the hottest object-diff site and a shared helper costs a per-key call | ||
| // in instruction counts (CodSpeed regressed ~7% on deep-tree reconciles | ||
| // with the extracted form). | ||
| if (s || symbolKeyedRecords.has(i)) { | ||
| const r = s ? getAllKeys(a, undefined, e) : nodeKeys(i); | ||
| for (let l = 0, o = r.length; l < o; l++) { | ||
| const o = r[l]; | ||
| const f = i[o]; | ||
| const p = unwrap(a[o]); | ||
| const u = unwrap(e[o]); | ||
| if (p === u) continue; | ||
| if (!p || !isWrappable(p) || !isWrappable(u) || | ||
| // Raw-marked values are leaves replaced by reference — a "wrappable | ||
| // pair" is only recursable when both sides are actual store children. | ||
| rawValuesUsed && (isRawValue(p) || isRawValue(u)) || Array.isArray(p) !== Array.isArray(u) || n(p) != null && n(p) !== n(u)) { | ||
| s && setSignal(s, void 0); | ||
| f && setSignal(f, isWrappable(u) ? wrap(u, t) : u); | ||
| } else applyStateChild(u, p, t, n); | ||
| } | ||
@@ -285,14 +473,24 @@ } else { | ||
| // per pass, which dominates allocation on large-graph reconciles. | ||
| for (const a in i) { | ||
| diffNodeKey(a, i, n, e, t, o, r); | ||
| for (const r in i) { | ||
| const l = i[r]; | ||
| const o = unwrap(a[r]); | ||
| const f = unwrap(e[r]); | ||
| if (o === f) continue; | ||
| if (!o || !isWrappable(o) || !isWrappable(f) || | ||
| // Raw-marked values are leaves replaced by reference — a "wrappable | ||
| // pair" is only recursable when both sides are actual store children. | ||
| rawValuesUsed && (isRawValue(o) || isRawValue(f)) || Array.isArray(o) !== Array.isArray(f) || n(o) != null && n(o) !== n(f)) { | ||
| s && setSignal(s, void 0); | ||
| l && setSignal(l, isWrappable(f) ? wrap(f, t) : f); | ||
| } else applyStateChild(f, o, t, n); | ||
| } | ||
| } | ||
| } | ||
| if (!o && t[STORE_DESC]) applyDescendants(n, e, t, i, r); | ||
| if (!s && t[STORE_DESC]) applyDescendants(a, e, t, i, n); | ||
| // has | ||
| if (i = t[STORE_HAS]) { | ||
| const t = nodeKeys(i); | ||
| for (let r = 0, n = t.length; r < n; r++) { | ||
| const n = t[r]; | ||
| setSignal(i[n], n in e); | ||
| for (let n = 0, a = t.length; n < a; n++) { | ||
| const a = t[n]; | ||
| setSignal(i[a], a in e); | ||
| } | ||
@@ -302,91 +500,87 @@ } | ||
| // One node-key step of the fast object diff — shared by the array-iterating | ||
| // (tracked / symbol-keyed) and for-in (plain) loops in applyStateFast. | ||
| function diffNodeKey(e, t, r, n, a, i, o) { | ||
| const l = t[e]; | ||
| const s = unwrap(r[e]); | ||
| let p = unwrap(n[e]); | ||
| if (s === p) return; | ||
| if (!s || !isWrappable(s) || !isWrappable(p) || Array.isArray(s) !== Array.isArray(p) || o(s) != null && o(s) !== o(p)) { | ||
| i && setSignal(i, void 0); | ||
| l && setSignal(l, isWrappable(p) ? wrap(p, a) : p); | ||
| } else applyState(p, wrap(s, a), o); | ||
| } | ||
| function applyStateSlow(e, t, r) { | ||
| const n = t[STORE_VALUE]; | ||
| const a = t[STORE_OVERRIDE]; | ||
| function applyStateSlow(e, t, n) { | ||
| const a = t[STORE_VALUE]; | ||
| const r = t[STORE_OVERRIDE]; | ||
| const i = t[STORE_OPTIMISTIC_OVERRIDE]; | ||
| let o = t[STORE_NODE]; | ||
| let s = t[STORE_NODE]; | ||
| // swap | ||
| (t[STORE_LOOKUP] || storeLookup).set(e, t[$PROXY]); | ||
| { | ||
| const n = t[STORE_LOOKUP]; | ||
| n !== undefined ? n.set(e, t[$PROXY]) : storeLookup.set(e, t); | ||
| } | ||
| t[STORE_VALUE] = e; | ||
| t[STORE_OVERRIDE] = undefined; | ||
| // merge | ||
| if (Array.isArray(n)) { | ||
| if (Array.isArray(a)) { | ||
| let l = false; | ||
| const s = getOverrideValue(n, a, "length", i); | ||
| if (e.length && s && isWrappable(e[0]) && r(e[0]) != null) { | ||
| let p, f, c, u, y, S, E, O; | ||
| for (c = 0, u = Math.min(s, e.length); c < u && keyedMatch(S = getOverrideValue(n, a, c, i), e[c], r); c++) { | ||
| isWrappable(S) && isWrappable(e[c]) && applyState(e[c], wrap(S, t), r); | ||
| const o = getOverrideValue(a, r, "length", i); | ||
| if (e.length && o && isWrappable(e[0]) && n(e[0]) != null) { | ||
| let f, p, u, c, S, R, E, O; | ||
| for (u = 0, c = Math.min(o, e.length); u < c && keyedMatch(R = getOverrideValue(a, r, u, i), e[u], n); u++) { | ||
| if (isWrappable(R) && isWrappable(e[u]) && R !== e[u]) { | ||
| if (rawValuesUsed && (isRawValue(R) || isRawValue(e[u]))) { | ||
| s?.[u] && setSignal(s[u], wrapValue(e[u], t)); | ||
| } else applyState(e[u], wrap(R, t), n); | ||
| } | ||
| } | ||
| const d = new Array(e.length), R = new Map; | ||
| for (u = s - 1, y = e.length - 1; u >= c && y >= c && keyedMatch(S = getOverrideValue(n, a, u, i), e[y], r); u--, | ||
| y--) { | ||
| d[y] = S; | ||
| const y = new Array(e.length), d = new Map; | ||
| for (c = o - 1, S = e.length - 1; c >= u && S >= u && keyedMatch(R = getOverrideValue(a, r, c, i), e[S], n); c--, | ||
| S--) { | ||
| y[S] = R; | ||
| } | ||
| if (c > y || c > u) { | ||
| for (f = c; f <= y; f++) { | ||
| if (u > S || u > c) { | ||
| for (p = u; p <= S; p++) { | ||
| l = true; | ||
| o?.[f] && setSignal(o[f], wrapValue(e[f], t)); | ||
| s?.[p] && setSignal(s[p], wrapValue(e[p], t)); | ||
| } | ||
| for (;f < e.length; f++) { | ||
| for (;p < e.length; p++) { | ||
| l = true; | ||
| applyArrayItem(e[f], d[f], t, o?.[f], r); | ||
| applyArrayItem(e[p], y[p], t, s?.[p], n); | ||
| } | ||
| const n = e.length; | ||
| const a = e.length; | ||
| syncArrayNodeMembership(t, e); | ||
| (l || s !== n) && notifySelf(t); | ||
| s !== n && o?.length && setSignal(o.length, n); | ||
| (l || o !== a) && notifySelf(t); | ||
| o !== a && s?.length && setSignal(s.length, a); | ||
| return; | ||
| } | ||
| E = new Array(y + 1); | ||
| for (f = y; f >= c; f--) { | ||
| S = e[f]; | ||
| O = itemKey(S, r); | ||
| p = R.get(O); | ||
| E[f] = p === undefined ? -1 : p; | ||
| R.set(O, f); | ||
| E = new Array(S + 1); | ||
| for (p = S; p >= u; p--) { | ||
| R = e[p]; | ||
| O = itemKey(R, n); | ||
| f = d.get(O); | ||
| E[p] = f === undefined ? -1 : f; | ||
| d.set(O, p); | ||
| } | ||
| for (p = c; p <= u; p++) { | ||
| S = getOverrideValue(n, a, p, i); | ||
| O = itemKey(S, r); | ||
| f = R.get(O); | ||
| if (f !== undefined && f !== -1) { | ||
| d[f] = S; | ||
| f = E[f]; | ||
| R.set(O, f); | ||
| for (f = u; f <= c; f++) { | ||
| R = getOverrideValue(a, r, f, i); | ||
| O = itemKey(R, n); | ||
| p = d.get(O); | ||
| if (p !== undefined && p !== -1) { | ||
| y[p] = R; | ||
| p = E[p]; | ||
| d.set(O, p); | ||
| } | ||
| } | ||
| for (f = c; f < e.length; f++) { | ||
| if (f in d) { | ||
| applyArrayItem(e[f], d[f], t, o?.[f], r); | ||
| } else o?.[f] && setSignal(o[f], wrapValue(e[f], t)); | ||
| for (p = u; p < e.length; p++) { | ||
| if (p in y) { | ||
| applyArrayItem(e[p], y[p], t, s?.[p], n); | ||
| } else s?.[p] && setSignal(s[p], wrapValue(e[p], t)); | ||
| } | ||
| if (c < e.length) l = true; | ||
| if (u < e.length) l = true; | ||
| } else if (e.length) { | ||
| for (let s = 0, p = e.length; s < p; s++) { | ||
| const p = getOverrideValue(n, a, s, i); | ||
| if (isWrappable(p) && isWrappable(e[s])) applyState(e[s], wrap(p, t), r); else { | ||
| if (p !== e[s]) l = true; | ||
| o?.[s] && setSignal(o[s], wrapValue(e[s], t)); | ||
| for (let o = 0, f = e.length; o < f; o++) { | ||
| const f = getOverrideValue(a, r, o, i); | ||
| if (isWrappable(f) && isWrappable(e[o]) && !(rawValuesUsed && (isRawValue(f) || isRawValue(e[o])))) { | ||
| if (f !== e[o]) applyState(e[o], wrap(f, t), n); | ||
| } else { | ||
| if (f !== e[o]) l = true; | ||
| s?.[o] && setSignal(s[o], wrapValue(e[o], t)); | ||
| } | ||
| } | ||
| } | ||
| const p = e.length; | ||
| const f = e.length; | ||
| syncArrayNodeMembership(t, e); | ||
| if (s !== p) { | ||
| if (o !== f) { | ||
| l = true; | ||
| o?.length && setSignal(o.length, p); | ||
| s?.length && setSignal(s.length, f); | ||
| } | ||
@@ -398,24 +592,24 @@ l && notifySelf(t); | ||
| let l; | ||
| if (o) { | ||
| l = o[$TRACK]; | ||
| const s = l ? getAllKeys(n, a, e) : nodeKeys(o); | ||
| for (let p = 0, f = s.length; p < f; p++) { | ||
| const f = s[p]; | ||
| const c = o[f]; | ||
| const u = unwrap(getOverrideValue(n, a, f, i)); | ||
| let y = unwrap(e[f]); | ||
| if (u === y) continue; | ||
| if (!u || !isWrappable(u) || !isWrappable(y) || Array.isArray(u) !== Array.isArray(y) || r(u) != null && r(u) !== r(y)) { | ||
| if (s) { | ||
| l = s[$TRACK]; | ||
| const o = l ? getAllKeys(a, r, e) : nodeKeys(s); | ||
| for (let f = 0, p = o.length; f < p; f++) { | ||
| const p = o[f]; | ||
| const u = s[p]; | ||
| const c = unwrap(getOverrideValue(a, r, p, i)); | ||
| let S = unwrap(e[p]); | ||
| if (c === S) continue; | ||
| if (!c || !isWrappable(c) || !isWrappable(S) || rawValuesUsed && (isRawValue(c) || isRawValue(S)) || Array.isArray(c) !== Array.isArray(S) || n(c) != null && n(c) !== n(S)) { | ||
| l && setSignal(l, void 0); | ||
| c && setSignal(c, isWrappable(y) ? wrap(y, t) : y); | ||
| } else applyState(y, wrap(u, t), r); | ||
| u && setSignal(u, isWrappable(S) ? wrap(S, t) : S); | ||
| } else applyState(S, wrap(c, t), n); | ||
| } | ||
| } | ||
| if (!l && t[STORE_DESC]) applyDescendants(n, e, t, o, r, a, i); | ||
| if (!l && t[STORE_DESC]) applyDescendants(a, e, t, s, n, r, i); | ||
| // has | ||
| if (o = t[STORE_HAS]) { | ||
| const t = nodeKeys(o); | ||
| for (let r = 0, n = t.length; r < n; r++) { | ||
| const n = t[r]; | ||
| setSignal(o[n], n in e); | ||
| if (s = t[STORE_HAS]) { | ||
| const t = nodeKeys(s); | ||
| for (let n = 0, a = t.length; n < a; n++) { | ||
| const a = t[n]; | ||
| setSignal(s[a], a in e); | ||
| } | ||
@@ -460,12 +654,12 @@ } | ||
| */ function reconcile(e, t = "id") { | ||
| return r => { | ||
| if (r == null) throw new Error(""); | ||
| return n => { | ||
| if (n == null) throw new Error(""); | ||
| if (t === null) { | ||
| applyState(e, r, NOKEY); | ||
| applyState(e, n, NOKEY); | ||
| return; | ||
| } | ||
| const n = typeof t === "string" ? e => e[t] : t; | ||
| const a = n(r); | ||
| if (a !== undefined && n(e) !== a) throw new Error(""); | ||
| applyState(e, r, n); | ||
| const a = typeof t === "string" ? e => e[t] : t; | ||
| const r = a(n); | ||
| if (r !== undefined && a(e) !== r) throw new Error(""); | ||
| applyState(e, n, a); | ||
| }; | ||
@@ -472,0 +666,0 @@ } |
+196
-102
| import { $REFRESH, STORE_SNAPSHOT_PROPS, NOT_PENDING, unwrapOverride, STATUS_UNINITIALIZED, STATUS_PENDING, NO_SNAPSHOT } from "../core/constants.js"; | ||
| import { suppressComputedRecompute, isEqual, signal, pendingCheckActive, untrack, setSignal, read, snapshotCaptureActive, snapshotSources } from "../core/core.js"; | ||
| import { suppressComputedRecompute, isEqual, signal, pendingCheckActive, untrack, setSignal, read, readNodeFast, READ_SLOW, snapshotCaptureActive, snapshotSources } from "../core/core.js"; | ||
@@ -31,3 +31,3 @@ import { DEV } from "../core/dev.js"; | ||
| const STORE_VALUE = "v", STORE_OVERRIDE = "o", STORE_OPTIMISTIC_OVERRIDE = "x", STORE_NODE = "n", STORE_HAS = "h", STORE_CUSTOM_PROTO = "c", STORE_WRAP = "w", STORE_LOOKUP = "l", STORE_FIREWALL = "f", STORE_OPTIMISTIC = "p", STORE_OPTIMISTIC_OWNERS = "t", STORE_PARENT = "u", STORE_DESC = "d"; | ||
| const STORE_VALUE = "v", STORE_OVERRIDE = "o", STORE_OPTIMISTIC_OVERRIDE = "x", STORE_NODE = "n", STORE_HAS = "h", STORE_CUSTOM_PROTO = "c", STORE_WRAP = "w", STORE_LOOKUP = "l", STORE_FIREWALL = "f", STORE_OPTIMISTIC = "p", STORE_OPTIMISTIC_OWNERS = "t", STORE_PARENT = "u", STORE_DESC = "d", STORE_SHALLOW = "s"; | ||
@@ -56,2 +56,3 @@ const STORE_SELF_PENDING = Symbol(0); | ||
| e[STORE_DESC] = undefined; | ||
| e[STORE_SHALLOW] = undefined; | ||
| e[$PROXY] = null; | ||
@@ -81,2 +82,8 @@ } | ||
| // The global lookup maps raw value -> StoreNode TARGET (not proxy): reconcile | ||
| // and the unwrap/snapshot walks resolve targets through it constantly, and a | ||
| // target hit is a plain field read away from its proxy while a proxy hit | ||
| // costs a trap to get back to the target. Per-family STORE_LOOKUP maps | ||
| // (projections/optimistic) still map raw -> proxy — their wrap functions own | ||
| // that contract — so mixed-lookup consumers resolve through lookupTarget(). | ||
| const storeLookup = new WeakMap; | ||
@@ -88,3 +95,52 @@ | ||
| function lookupTarget(e, t) { | ||
| if (t !== undefined && t !== storeLookup) { | ||
| const r = t.get(e); | ||
| if (r !== undefined) return r[$TARGET]; | ||
| } | ||
| return storeLookup.get(e); | ||
| } | ||
| // Values marked raw never acquire a proxy identity: wrap() serves them as-is | ||
| // everywhere — deep stores hold them as leaf values replaced by reference. | ||
| // Once raw, always raw (identity stays single, just unwrapped). Consulted | ||
| // only on wrap-creation and ingest paths; reads never touch it. | ||
| const rawValues = new WeakSet; | ||
| /** | ||
| * Marks a value as raw: no store will ever wrap it — every store presents it | ||
| * as-is, tracked by reference at whatever slot holds it and updated by | ||
| * replacement. Useful for class instances and external objects (editors, | ||
| * scene graphs, Maps) and for record-shaped data updated wholesale. Sticky | ||
| * for the value's lifetime. | ||
| */ | ||
| // Flipped on the first mark and exported as a LIVE binding: reconcile | ||
| // consults it on every recursable pair, and importing the boolean directly | ||
| // lets those sites skip even the function call when no shallow store or raw | ||
| // mark exists anywhere in the app. | ||
| let rawValuesUsed = false; | ||
| function isRawValue(e) { | ||
| return rawValuesUsed && rawValues.has(e); | ||
| } | ||
| function markRawOne(e) { | ||
| if (isWrappable(e)) { | ||
| rawValuesUsed = true; | ||
| rawValues.add(e); | ||
| } | ||
| } | ||
| function markRawIngest(e) { | ||
| if (Array.isArray(e)) { | ||
| for (let t = 0, r = e.length; t < r; t++) markRawOne(e[t]); | ||
| } else { | ||
| for (const t in e) markRawOne(e[t]); | ||
| } | ||
| } | ||
| function wrap(e, t) { | ||
| // Raw is raw in every family: the mark must preempt family wrapping too, | ||
| // or a shallow projection/optimistic store would proxy its raw records. | ||
| if (rawValuesUsed && rawValues.has(e)) return e; | ||
| if (t?.[STORE_WRAP]) { | ||
@@ -96,7 +152,27 @@ const r = t[STORE_WRAP](e, t); | ||
| } | ||
| let r = e[$PROXY] || storeLookup.get(e); | ||
| if (!r) { | ||
| storeLookup.set(e, r = createStoreProxy(e)); | ||
| if (t) r[$TARGET][STORE_PARENT] = t; | ||
| const r = storeLookup.get(e); | ||
| if (r !== undefined) return r[$PROXY]; | ||
| let n = e[$PROXY]; | ||
| if (!n) { | ||
| n = createStoreProxy(e); | ||
| const r = n[$TARGET]; | ||
| storeLookup.set(e, r); | ||
| if (t) r[STORE_PARENT] = t; | ||
| } | ||
| return n; | ||
| } | ||
| // Shallow store root: the target itself is fully reactive (per-key nodes, | ||
| // membership, $TRACK); its values are served raw. Seed values are marked at | ||
| // creation; reconcile and the set trap mark on ingest. | ||
| function wrapShallow(e) { | ||
| const t = storeLookup.get(e); | ||
| if (t !== undefined) { | ||
| if (t[STORE_SHALLOW]) return t[$PROXY]; | ||
| } | ||
| const r = createStoreProxy(e); | ||
| const n = r[$TARGET]; | ||
| n[STORE_SHALLOW] = true; | ||
| storeLookup.set(e, n); | ||
| markRawIngest(e); | ||
| return r; | ||
@@ -123,3 +199,3 @@ } | ||
| function unwrapStoreValue(e, t, r) { | ||
| const n = e?.[$TARGET] || r?.get(e)?.[$TARGET]; | ||
| const n = e?.[$TARGET] || lookupTarget(e, r); | ||
| if (!n) return e; | ||
@@ -131,13 +207,13 @@ const o = n[STORE_OVERRIDE]; | ||
| const i = n[STORE_VALUE]; | ||
| const E = Array.isArray(i); | ||
| const O = E ? [] : Object.create(Object.getPrototypeOf(i)); | ||
| t.set(e, O); | ||
| const O = Array.isArray(i); | ||
| const s = O ? [] : Object.create(Object.getPrototypeOf(i)); | ||
| t.set(e, s); | ||
| r = n[STORE_LOOKUP] ?? storeLookup; | ||
| for (const e of getStoreKeys(i, o)) { | ||
| if (E && e === "length") continue; | ||
| if (O && e === "length") continue; | ||
| const n = e in o ? o[e] : i[e]; | ||
| if (n !== $DELETED) O[e] = unwrapStoreValue(n, t, r); | ||
| if (n !== $DELETED) s[e] = unwrapStoreValue(n, t, r); | ||
| } | ||
| if (E) O.length = o.length ?? i.length; | ||
| return O; | ||
| if (O) s.length = o.length ?? i.length; | ||
| return s; | ||
| } | ||
@@ -217,6 +293,6 @@ | ||
| if (t[r]) return t[r]; | ||
| const E = signal(n, { | ||
| const O = signal(n, { | ||
| equals: o, | ||
| unobserved() { | ||
| if (t[r] === E) { | ||
| if (t[r] === O) { | ||
| delete t[r]; | ||
@@ -241,8 +317,8 @@ // Drop the symbol-record mark once the last user symbol node is | ||
| if (e[STORE_OPTIMISTIC]) { | ||
| E.Ee = NOT_PENDING; | ||
| O.Ee = NOT_PENDING; | ||
| } | ||
| if (i && r in i) { | ||
| const e = i[r]; | ||
| E.Ve = e === undefined ? NO_SNAPSHOT : e; | ||
| snapshotSources?.add(E); | ||
| O.Ve = e === undefined ? NO_SNAPSHOT : e; | ||
| snapshotSources?.add(O); | ||
| } | ||
@@ -253,3 +329,3 @@ if (typeof r === "symbol" && r !== $TRACK && r !== $AFFECTS) symbolKeyedRecords.add(t); | ||
| // record's own $AFFECTS carrier is the mark's channel, never a member. | ||
| if (r !== $AFFECTS && affectsScopes.size) inheritAffectsMarks(E, e[STORE_VALUE], r); | ||
| if (r !== $AFFECTS && affectsScopes.size) inheritAffectsMarks(O, e[STORE_VALUE], r); | ||
| // Node presence bubbles up the wrap chain (sticky), so reconcile can see | ||
@@ -259,8 +335,8 @@ // "subscribers live somewhere below" through node-less intermediate | ||
| // the first already-flagged ancestor. | ||
| let O = e; | ||
| while (O && !O[STORE_DESC]) { | ||
| O[STORE_DESC] = true; | ||
| O = O[STORE_PARENT]; | ||
| let s = e; | ||
| while (s && !s[STORE_DESC]) { | ||
| s[STORE_DESC] = true; | ||
| s = s[STORE_PARENT]; | ||
| } | ||
| return t[r] = E; | ||
| return t[r] = O; | ||
| } | ||
@@ -301,12 +377,12 @@ | ||
| if (!isWrappable(e)) return; | ||
| const i = e[$TARGET] || (n ?? storeLookup).get(e)?.[$TARGET]; | ||
| const E = i ? i[STORE_VALUE] : e; | ||
| if (o.has(E)) return; | ||
| o.add(E); | ||
| t.scope.add(E); | ||
| let O; | ||
| const i = e[$TARGET] || lookupTarget(e, n); | ||
| const O = i ? i[STORE_VALUE] : e; | ||
| if (o.has(O)) return; | ||
| o.add(O); | ||
| t.scope.add(O); | ||
| let s; | ||
| if (i) { | ||
| collectRecordNodes(i[STORE_NODE], r); | ||
| collectRecordNodes(i[STORE_HAS], r); | ||
| O = mergedOverlay(i); | ||
| s = mergedOverlay(i); | ||
| // Carry the effective lookup into untouched descendants. Default stores | ||
@@ -317,6 +393,6 @@ // use the global lookup just like snapshotImpl; without it, nested raw | ||
| } | ||
| if (Array.isArray(E)) { | ||
| const e = O?.length ?? E.length; | ||
| if (Array.isArray(O)) { | ||
| const e = s?.length ?? O.length; | ||
| for (let i = 0; i < e; i++) { | ||
| const e = O && i in O ? O[i] : E[i]; | ||
| const e = s && i in s ? s[i] : O[i]; | ||
| if (e !== $DELETED) walkAffectsScope(e, t, r, n, o); | ||
@@ -327,6 +403,6 @@ } | ||
| // existing index-only walk. | ||
| const s = i || n ? getStoreSymbols(E, O) : []; | ||
| for (let e = 0, i = s.length; e < i; e++) { | ||
| const i = s[e]; | ||
| const S = getPropertyDescriptor(E, O, i); | ||
| const E = i || n ? getStoreSymbols(O, s) : []; | ||
| for (let e = 0, i = E.length; e < i; e++) { | ||
| const i = E[e]; | ||
| const S = getPropertyDescriptor(O, s, i); | ||
| if (!S || S.get) continue; | ||
@@ -336,7 +412,7 @@ walkAffectsScope(S.value, t, r, n, o); | ||
| } else { | ||
| const e = i || n ? getStoreKeys(E, O) : getKeys(E, O); | ||
| for (let i = 0, s = e.length; i < s; i++) { | ||
| const s = getPropertyDescriptor(E, O, e[i]); | ||
| if (!s || s.get) continue; | ||
| walkAffectsScope(s.value, t, r, n, o); | ||
| const e = i || n ? getStoreKeys(O, s) : getKeys(O, s); | ||
| for (let i = 0, E = e.length; i < E; i++) { | ||
| const E = getPropertyDescriptor(O, s, e[i]); | ||
| if (!E || E.get) continue; | ||
| walkAffectsScope(E.value, t, r, n, o); | ||
| } | ||
@@ -532,6 +608,6 @@ } | ||
| const i = e[STORE_OPTIMISTIC] && !projectionWriteActive; | ||
| const E = i ? STORE_OPTIMISTIC_OVERRIDE : STORE_OVERRIDE; | ||
| const O = i ? STORE_OPTIMISTIC_OVERRIDE : STORE_OVERRIDE; | ||
| return { | ||
| base: o, | ||
| overrideKey: E, | ||
| overrideKey: O, | ||
| state: n | ||
@@ -570,5 +646,5 @@ }; | ||
| const i = isWrappable(n) ? wrap(n, e) : n; | ||
| const E = getNode(e, t, r, i, isEqual, o); | ||
| registerTransientStoreNode(E); | ||
| return E; | ||
| const O = getNode(e, t, r, i, isEqual, o); | ||
| registerTransientStoreNode(O); | ||
| return O; | ||
| } | ||
@@ -580,10 +656,10 @@ | ||
| // (whose whole purpose is immediate visibility via STORE_OPTIMISTIC_OVERRIDE). | ||
| const E = projectionWriteActive || e[STORE_OPTIMISTIC]; | ||
| const O = r !== "delete"; | ||
| const s = e[STORE_HAS]?.[t]; | ||
| if (s) { | ||
| setSignal(s, O); | ||
| } else if (!E && r !== "invalidate" && i !== O) { | ||
| const O = projectionWriteActive || e[STORE_OPTIMISTIC]; | ||
| const s = r !== "delete"; | ||
| const E = e[STORE_HAS]?.[t]; | ||
| if (E) { | ||
| setSignal(E, s); | ||
| } else if (!O && r !== "invalidate" && i !== s) { | ||
| const r = upsertStoreNode(e, getNodes(e, STORE_HAS), t, i); | ||
| setSignal(r, O); | ||
| setSignal(r, s); | ||
| } | ||
@@ -594,3 +670,3 @@ const S = getNodes(e, STORE_NODE); | ||
| setSignal(S[t], () => isWrappable(n) ? wrap(n, e) : n); | ||
| } else if (!E) { | ||
| } else if (!O) { | ||
| const r = upsertStoreNode(e, S, t, o, e[STORE_SNAPSHOT_PROPS]); | ||
@@ -607,3 +683,3 @@ setSignal(r, () => isWrappable(n) ? wrap(n, e) : n); | ||
| setSignal(S[t], undefined); | ||
| } else if (!E) { | ||
| } else if (!O) { | ||
| const r = upsertStoreNode(e, S, t, o, e[STORE_SNAPSHOT_PROPS]); | ||
@@ -651,4 +727,16 @@ setSignal(r, undefined); | ||
| if (n !== undefined && e[STORE_VALUE][$TARGET] === undefined) { | ||
| let t = read(n); | ||
| // readNodeFast is read()'s plain-signal fast path hoisted over the | ||
| // call; READ_SLOW means a global read window (latest/pending-check/ | ||
| // transition/lane/snapshot capture) or a node layer is active, and | ||
| // only then does the full read() resolution have anything to do. | ||
| let t = readNodeFast(n); | ||
| if (t === READ_SLOW) t = read(n); | ||
| if (t === $DELETED) t = undefined; | ||
| // Every node-writing site wraps wrappables before setSignal (see the | ||
| // dev assertion below), so re-wrapping on read is redundant — except | ||
| // during snapshot capture, where read() can surface a raw captured | ||
| // value seeded from snapshot props. | ||
| if (!snapshotCaptureActive) { | ||
| return t; | ||
| } | ||
| return isWrappable(t) ? wrap(t, e) : t; | ||
@@ -660,17 +748,17 @@ } | ||
| const i = n ? undefined : o[t]; | ||
| const E = e[STORE_VALUE]; | ||
| if (!i && !e[STORE_OVERRIDE] && !e[STORE_OPTIMISTIC_OVERRIDE] && !e[STORE_CUSTOM_PROTO] && !e[STORE_OPTIMISTIC] && !e[STORE_SNAPSHOT_PROPS] && !E[$TARGET] && !(t in E) && getObserver() && !n && !writeOnly(r)) { | ||
| const O = e[STORE_VALUE]; | ||
| if (!i && !e[STORE_OVERRIDE] && !e[STORE_OPTIMISTIC_OVERRIDE] && !e[STORE_CUSTOM_PROTO] && !e[STORE_OPTIMISTIC] && !e[STORE_SNAPSHOT_PROPS] && !O[$TARGET] && !(t in O) && getObserver() && !n && !writeOnly(r)) { | ||
| return read(getNode(e, o, t, undefined)); | ||
| } | ||
| const O = getOverlayLayer(e, t); | ||
| const s = !!O; | ||
| const s = getOverlayLayer(e, t); | ||
| const E = !!s; | ||
| const S = !!e[STORE_VALUE][$TARGET]; | ||
| const c = O ?? e[STORE_VALUE]; | ||
| const f = s ?? e[STORE_VALUE]; | ||
| if (!i) { | ||
| const n = Object.getOwnPropertyDescriptor(c, t); | ||
| const n = Object.getOwnPropertyDescriptor(f, t); | ||
| if (n && n.get) return n.get.call(r); | ||
| if (!n && !s && e[STORE_CUSTOM_PROTO]) { | ||
| const e = unwrapStoreValue(c); | ||
| if (!n && !E && e[STORE_CUSTOM_PROTO]) { | ||
| const e = unwrapStoreValue(f); | ||
| if (hasInheritedAccessor(e, t)) { | ||
| return Reflect.get(c, t, r); | ||
| return Reflect.get(f, t, r); | ||
| } | ||
@@ -681,5 +769,10 @@ } | ||
| if (isPrototypePollutionKey(t) && !hasOwnStoreProperty(e, t)) return undefined; | ||
| let r = i && (s || !S) ? visibleNodeValue(i) : c[t]; | ||
| let r = i && (E || !S) ? visibleNodeValue(i) : f[t]; | ||
| r === $DELETED && (r = undefined); | ||
| if (!isWrappable(r)) return r; | ||
| // Shallow boundary: records are replaced, never edited in place. Reads | ||
| // inside a setter serve the raw so read-then-replace, filter/pop and | ||
| // projection derives all work; in-place mutation of a raw is inert by | ||
| // construction — the same contract as a markRaw child in a deep store. | ||
| if (e[STORE_SHALLOW]) return r; | ||
| const n = wrap(r, e); | ||
@@ -689,10 +782,10 @@ Writing?.add(n); | ||
| } | ||
| let T = i ? s || !S ? read(o[t]) : (read(o[t]), c[t]) : c[t]; | ||
| T === $DELETED && (T = undefined); | ||
| let c = i ? E || !S ? read(o[t]) : (read(o[t]), f[t]) : f[t]; | ||
| c === $DELETED && (c = undefined); | ||
| if (!i) { | ||
| if (!s && typeof T === "function" && !Object.prototype.hasOwnProperty.call(c, t)) { | ||
| if (!E && typeof c === "function" && !Object.prototype.hasOwnProperty.call(f, t)) { | ||
| let t; | ||
| return !Array.isArray(e[STORE_VALUE]) && (t = Object.getPrototypeOf(e[STORE_VALUE])) && t !== Object.prototype ? T.bind(c) : T; | ||
| return !Array.isArray(e[STORE_VALUE]) && (t = Object.getPrototypeOf(e[STORE_VALUE])) && t !== Object.prototype ? c.bind(f) : c; | ||
| } else if (getObserver() && !n) { | ||
| return read(getNode(e, o, t, isWrappable(T) ? wrap(T, e) : T, isEqual, e[STORE_SNAPSHOT_PROPS])); | ||
| return read(getNode(e, o, t, isWrappable(c) ? wrap(c, e) : c, isEqual, e[STORE_SNAPSHOT_PROPS])); | ||
| } | ||
@@ -703,3 +796,3 @@ } | ||
| if (!n) throwIfUninitialized(e); | ||
| return isWrappable(T) ? wrap(T, e) : T; | ||
| return isWrappable(c) ? wrap(c, e) : c; | ||
| }, | ||
@@ -731,18 +824,19 @@ has(e, t) { | ||
| untrack(() => { | ||
| const {base: o, overrideKey: i, state: E} = prepareStoreWrite(e, n, t); | ||
| const O = getOverlayLayer(e, t); | ||
| const s = O ? O[t] : o; | ||
| const S = O ? O[t] !== $DELETED : t in e[STORE_VALUE]; | ||
| const c = unwrapStoreValue(r); | ||
| const {base: o, overrideKey: i, state: O} = prepareStoreWrite(e, n, t); | ||
| const s = getOverlayLayer(e, t); | ||
| const E = s ? s[t] : o; | ||
| const S = s ? s[t] !== $DELETED : t in e[STORE_VALUE]; | ||
| const f = unwrapStoreValue(r); | ||
| if (e[STORE_SHALLOW] && isWrappable(f)) rawValues.add(f); | ||
| // Symbol-keyed writes on arrays are metadata, not index writes — never run | ||
| // them through the numeric index/length machinery (`parseInt` on a symbol | ||
| // throws). #2769 | ||
| const T = typeof t === "string" ? Number(t) : -1; | ||
| const R = Array.isArray(E) && Number.isInteger(T) && T >= 0 && T < 4294967295 && String(T) === t; | ||
| const f = R ? T + 1 : 0; | ||
| const u = R && (getOverlayLayer(e, "length") ?? E).length; | ||
| const l = R && f > u ? f : undefined; | ||
| if (s === c && l === undefined) return true; | ||
| const c = typeof t === "string" ? Number(t) : -1; | ||
| const R = Array.isArray(O) && Number.isInteger(c) && c >= 0 && c < 4294967295 && String(c) === t; | ||
| const T = R ? c + 1 : 0; | ||
| const u = R && (getOverlayLayer(e, "length") ?? O).length; | ||
| const a = R && T > u ? T : undefined; | ||
| if (E === f && a === undefined) return true; | ||
| armOptimisticStoreWrite(e, n); | ||
| if (c !== undefined && c === o && l === undefined) { | ||
| if (f !== undefined && f === o && a === undefined) { | ||
| delete e[i]?.[t]; | ||
@@ -752,19 +846,19 @@ if (i === STORE_OPTIMISTIC_OVERRIDE) delete e[STORE_OPTIMISTIC_OWNERS]?.[t]; | ||
| const r = e[i] || (e[i] = Object.create(null)); | ||
| r[t] = c; | ||
| r[t] = f; | ||
| stampOptimisticOwner(e, i, t); | ||
| if (l !== undefined) { | ||
| r.length = l; | ||
| if (a !== undefined) { | ||
| r.length = a; | ||
| stampOptimisticOwner(e, i, "length"); | ||
| } | ||
| } | ||
| notifyStoreProperty(e, t, "set", c, s, S); | ||
| notifyStoreProperty(e, t, "set", f, E, S); | ||
| // Shrinking an array's length must remove the truncated indices, otherwise | ||
| // they leak through `has`, `ownKeys`, and (tracked) index reads from the | ||
| // underlying value. Mark each as deleted and notify so reactive reads update. #2768 | ||
| if (Array.isArray(E) && t === "length" && typeof c === "number" && typeof s === "number" && c < s) { | ||
| if (Array.isArray(O) && t === "length" && typeof f === "number" && typeof E === "number" && f < E) { | ||
| const t = e[i] || (e[i] = Object.create(null)); | ||
| for (let r = c; r < s; r++) { | ||
| for (let r = f; r < E; r++) { | ||
| if (t[r] === $DELETED) continue; | ||
| const n = r in t ? t[r] : E[r]; | ||
| if (!(r in t) && !(r in E)) continue; | ||
| const n = r in t ? t[r] : O[r]; | ||
| if (!(r in t) && !(r in O)) continue; | ||
| t[r] = $DELETED; | ||
@@ -776,9 +870,9 @@ stampOptimisticOwner(e, i, r); | ||
| // notify length change | ||
| if (Array.isArray(E) && t !== "length" && l !== undefined) { | ||
| if (Array.isArray(O) && t !== "length" && a !== undefined) { | ||
| const t = getNodes(e, STORE_NODE); | ||
| if (t.length) { | ||
| setSignal(t.length, l); | ||
| setSignal(t.length, a); | ||
| } else if (!projectionWriteActive && !e[STORE_OPTIMISTIC]) { | ||
| const r = upsertStoreNode(e, t, "length", u, e[STORE_SNAPSHOT_PROPS]); | ||
| setSignal(r, l); | ||
| setSignal(r, a); | ||
| } | ||
@@ -798,7 +892,7 @@ } | ||
| armOptimisticStoreWrite(e, n); | ||
| const E = "value" in r ? { | ||
| const O = "value" in r ? { | ||
| ...r, | ||
| value: unwrapStoreValue(r.value) | ||
| } : r; | ||
| Object.defineProperty(e[i] || (e[i] = Object.create(null)), t, E); | ||
| Object.defineProperty(e[i] || (e[i] = Object.create(null)), t, O); | ||
| stampOptimisticOwner(e, i, t); | ||
@@ -931,3 +1025,3 @@ notifyStoreProperty(e, t, "invalidate"); | ||
| function createStore(e, t, r) { | ||
| const n = typeof e === "function", o = n ? createProjectionInternal(e, t, r).store : wrap(e); | ||
| const n = typeof e === "function", o = n ? createProjectionInternal(e, t, r).store : t?.shallow ? wrapShallow(e) : wrap(e); | ||
| return [ o, n ? e => { | ||
@@ -940,2 +1034,2 @@ // Mark the projection as manually written before notifying property nodes. | ||
| export { $AFFECTS, $DELETED, $PROXY, $TARGET, $TRACK, STORE_CUSTOM_PROTO, STORE_DESC, STORE_FIREWALL, STORE_HAS, STORE_LOOKUP, STORE_NODE, STORE_OPTIMISTIC, STORE_OPTIMISTIC_OVERRIDE, STORE_OPTIMISTIC_OWNERS, STORE_OVERRIDE, STORE_PARENT, STORE_VALUE, STORE_WRAP, createStore, createStoreProxy, getKeys, getOverlayLayer, getPropertyDescriptor, getStoreAffectsNodes, getStoreKeys, getStoreSymbols, isWrappable, mergedOverlay, notifySelf, ownEnumerableKeys, setWriteOverride, storeLookup, storeSetter, storeTraps, symbolKeyedRecords, trackSelf, visibleNodeValue, witnessAffectsMark, wrap }; | ||
| export { $AFFECTS, $DELETED, $PROXY, $TARGET, $TRACK, STORE_CUSTOM_PROTO, STORE_DESC, STORE_FIREWALL, STORE_HAS, STORE_LOOKUP, STORE_NODE, STORE_OPTIMISTIC, STORE_OPTIMISTIC_OVERRIDE, STORE_OPTIMISTIC_OWNERS, STORE_OVERRIDE, STORE_PARENT, STORE_SHALLOW, STORE_VALUE, STORE_WRAP, createStore, createStoreProxy, getKeys, getOverlayLayer, getPropertyDescriptor, getStoreAffectsNodes, getStoreKeys, getStoreSymbols, isRawValue, isWrappable, lookupTarget, markRawIngest, mergedOverlay, notifySelf, ownEnumerableKeys, rawValuesUsed, setWriteOverride, storeLookup, storeSetter, storeTraps, symbolKeyedRecords, trackSelf, visibleNodeValue, witnessAffectsMark, wrap, wrapShallow }; |
+22
-22
@@ -15,3 +15,3 @@ import { pendingCheckActive } from "../core/core.js"; | ||
| import { ownEnumerableKeys, $PROXY, isWrappable, $TARGET, trackSelf, witnessAffectsMark, mergedOverlay, STORE_VALUE, storeLookup, STORE_LOOKUP, $DELETED, wrap, getStoreSymbols, getPropertyDescriptor, getStoreKeys, getKeys, $TRACK } from "./store.js"; | ||
| import { ownEnumerableKeys, $PROXY, isWrappable, $TARGET, lookupTarget, trackSelf, witnessAffectsMark, mergedOverlay, STORE_VALUE, storeLookup, STORE_LOOKUP, $DELETED, wrap, getStoreSymbols, getPropertyDescriptor, getStoreKeys, getKeys, $TRACK, rawValuesUsed, isRawValue } from "./store.js"; | ||
@@ -23,3 +23,3 @@ function snapshotImpl(e, t, r, n) { | ||
| if (!r) r = new Map; | ||
| if (o = e[$TARGET] || n?.get(e)?.[$TARGET]) { | ||
| if (o = e[$TARGET] || lookupTarget(e, n)) { | ||
| if (t) { | ||
@@ -54,9 +54,9 @@ trackSelf(o, $TRACK); | ||
| const s = i?.length ?? e.length; | ||
| for (let p = 0; p < s; p++) { | ||
| u = i && p in i ? i[p] : e[p]; | ||
| for (let a = 0; a < s; a++) { | ||
| u = i && a in i ? i[a] : e[a]; | ||
| if (u === $DELETED) continue; | ||
| if (t && isWrappable(u)) wrap(u, o); | ||
| if (t && isWrappable(u) && !(rawValuesUsed && isRawValue(u))) wrap(u, o); | ||
| if ((f = snapshotImpl(u, t, r, n)) !== u || c) { | ||
| if (!c) r.set(e, c = [ ...e ]); | ||
| c[p] = f; | ||
| c[a] = f; | ||
| } | ||
@@ -66,9 +66,9 @@ } | ||
| // Spread copies omit symbols, so assign them after the numeric walk. | ||
| const p = n ? getStoreSymbols(e, i) : []; | ||
| for (let s = 0, l = p.length; s < l; s++) { | ||
| const l = p[s]; | ||
| const a = getPropertyDescriptor(e, i, l); | ||
| if (!a || a.get) continue; | ||
| const a = n ? getStoreSymbols(e, i) : []; | ||
| for (let s = 0, l = a.length; s < l; s++) { | ||
| const l = a[s]; | ||
| const p = getPropertyDescriptor(e, i, l); | ||
| if (!p || p.get) continue; | ||
| u = i && l in i ? i[l] : e[l]; | ||
| if (t && isWrappable(u)) wrap(u, o); | ||
| if (t && isWrappable(u) && !(rawValuesUsed && isRawValue(u))) wrap(u, o); | ||
| f = snapshotImpl(u, t, r, n); | ||
@@ -90,8 +90,8 @@ if (f !== u || c) { | ||
| const s = n ? getStoreKeys(e, undefined) : getKeys(e, undefined); | ||
| for (let i = 0, p = s.length; i < p; i++) { | ||
| const p = s[i]; | ||
| const l = Object.getOwnPropertyDescriptor(e, p); | ||
| for (let i = 0, a = s.length; i < a; i++) { | ||
| const a = s[i]; | ||
| const l = Object.getOwnPropertyDescriptor(e, a); | ||
| if (l.get) continue; | ||
| u = l.value; | ||
| if (t && isWrappable(u)) wrap(u, o); | ||
| if (t && isWrappable(u) && !(rawValuesUsed && isRawValue(u))) wrap(u, o); | ||
| if ((f = snapshotImpl(u, t, r, n)) !== u || c) { | ||
@@ -102,3 +102,3 @@ if (!c) { | ||
| } | ||
| c[p] = f; | ||
| c[a] = f; | ||
| } | ||
@@ -110,8 +110,8 @@ } | ||
| const s = getStoreKeys(e, i); | ||
| for (let p = 0, l = s.length; p < l; p++) { | ||
| let l = s[p]; | ||
| const a = getPropertyDescriptor(e, i, l); | ||
| if (a.get) continue; | ||
| for (let a = 0, l = s.length; a < l; a++) { | ||
| let l = s[a]; | ||
| const p = getPropertyDescriptor(e, i, l); | ||
| if (p.get) continue; | ||
| u = l in i ? i[l] : e[l]; | ||
| if (t && isWrappable(u)) wrap(u, o); | ||
| if (t && isWrappable(u) && !(rawValuesUsed && isRawValue(u))) wrap(u, o); | ||
| if ((f = snapshotImpl(u, t, r, n)) !== e[l] || c) { | ||
@@ -118,0 +118,0 @@ if (!c) { |
@@ -75,2 +75,17 @@ import { type Refreshable } from "./constants.cjs"; | ||
| export declare function prepareComputed(comp: Computed<unknown>, refresh: boolean): void; | ||
| /** | ||
| * Sentinel returned by readNodeFast when the plain-signal fast path does not | ||
| * apply and the caller must fall back to the full read(). | ||
| */ | ||
| export declare const READ_SLOW: unique symbol; | ||
| /** | ||
| * read()'s plain-signal fast path as a standalone entry for hot callers | ||
| * (store traps). Safe to substitute for read() only because the bail | ||
| * conditions mirror read()'s prelude and fast-path guard exactly: the | ||
| * latestRead and pendingCheck windows run side-effectful hooks before the | ||
| * fast path, `_fn` nodes need prepareComputed, and firewall / override / | ||
| * snapshot / transition / lane / dev-strictRead state all take the full | ||
| * resolution. Anything slow returns READ_SLOW; the caller then calls read(). | ||
| */ | ||
| export declare function readNodeFast<T>(el: Signal<T>): T | typeof READ_SLOW; | ||
| export declare function read<T>(el: Signal<T> | Computed<T>): T; | ||
@@ -77,0 +92,0 @@ export declare function setSignal<T>(el: Signal<T> | Computed<T>, v: T | ((prev: T) => T)): T; |
@@ -44,3 +44,3 @@ import { type Refreshable } from "../core/index.cjs"; | ||
| */ | ||
| export declare function createOptimisticStore<T extends object = {}>(store: NoFn<T> | Store<NoFn<T>>): [get: Store<T>, set: StoreSetter<T>]; | ||
| export declare function createOptimisticStore<T extends object = {}>(store: NoFn<T> | Store<NoFn<T>>, options?: ProjectionOptions): [get: Store<T>, set: StoreSetter<T>]; | ||
| export declare function createOptimisticStore<T extends object = {}>(fn: (store: T) => void | T | Promise<void | T> | AsyncIterable<void | T>, store: Partial<T> | Store<NoFn<T>>, options?: ProjectionOptions): [get: Refreshable<Store<T>>, set: StoreSetter<T>]; |
@@ -34,2 +34,4 @@ import { STORE_SNAPSHOT_PROPS, type Computed, type Refreshable, type Signal } from "../core/index.cjs"; | ||
| key?: string | ((item: NonNullable<any>) => any); | ||
| /** Single-layer store: root keys reactive, values raw records replaced by reference */ | ||
| shallow?: boolean; | ||
| } | ||
@@ -46,3 +48,3 @@ export type NoFn<T> = T extends Function ? never : T; | ||
| export declare const $TRACK: unique symbol, $TARGET: unique symbol, $PROXY: unique symbol, $DELETED: unique symbol, $AFFECTS: unique symbol; | ||
| export declare const STORE_VALUE = "v", STORE_OVERRIDE = "o", STORE_OPTIMISTIC_OVERRIDE = "x", STORE_NODE = "n", STORE_HAS = "h", STORE_CUSTOM_PROTO = "c", STORE_WRAP = "w", STORE_LOOKUP = "l", STORE_FIREWALL = "f", STORE_OPTIMISTIC = "p", STORE_OPTIMISTIC_OWNERS = "t", STORE_PARENT = "u", STORE_DESC = "d"; | ||
| export declare const STORE_VALUE = "v", STORE_OVERRIDE = "o", STORE_OPTIMISTIC_OVERRIDE = "x", STORE_NODE = "n", STORE_HAS = "h", STORE_CUSTOM_PROTO = "c", STORE_WRAP = "w", STORE_LOOKUP = "l", STORE_FIREWALL = "f", STORE_OPTIMISTIC = "p", STORE_OPTIMISTIC_OWNERS = "t", STORE_PARENT = "u", STORE_DESC = "d", STORE_SHALLOW = "s"; | ||
| export type StoreNode = { | ||
@@ -63,2 +65,3 @@ [$PROXY]: any; | ||
| [STORE_PARENT]?: StoreNode; | ||
| [STORE_SHALLOW]?: boolean; | ||
| [STORE_DESC]?: boolean; | ||
@@ -74,3 +77,16 @@ }; | ||
| export declare const symbolKeyedRecords: WeakSet<object>; | ||
| export declare function lookupTarget(value: any, lookup?: WeakMap<any, any>): StoreNode | undefined; | ||
| /** | ||
| * Marks a value as raw: no store will ever wrap it — every store presents it | ||
| * as-is, tracked by reference at whatever slot holds it and updated by | ||
| * replacement. Useful for class instances and external objects (editors, | ||
| * scene graphs, Maps) and for record-shaped data updated wholesale. Sticky | ||
| * for the value's lifetime. | ||
| */ | ||
| export declare let rawValuesUsed: boolean; | ||
| export declare function isRawValue(value: any): boolean; | ||
| export declare function markRaw<T>(value: T): T; | ||
| export declare function markRawIngest(container: any): void; | ||
| export declare function wrap<T extends Record<PropertyKey, any>>(value: T, target?: StoreNode): T; | ||
| export declare function wrapShallow<T extends Record<PropertyKey, any>>(value: T): T; | ||
| export declare function isWrappable<T>(obj: T | NotWrappable): obj is T; | ||
@@ -183,4 +199,6 @@ export declare function setWriteOverride(value: boolean): void; | ||
| */ | ||
| export declare function createStore<T extends object = {}>(store: NoFn<T> | Store<NoFn<T>>): StoreReturn<T>; | ||
| export declare function createStore<T extends object = {}>(store: NoFn<T> | Store<NoFn<T>>, options?: StoreOptions & { | ||
| shallow?: boolean; | ||
| }): StoreReturn<T>; | ||
| export declare function createStore<T extends object = {}>(fn: (store: T) => void | T | Promise<void | T> | AsyncIterable<void | T>, store: Partial<T> | Store<NoFn<T>>, options?: ProjectionOptions): ProjectionStoreReturn<T>; | ||
| export {}; |
@@ -75,2 +75,17 @@ import { type Refreshable } from "./constants.js"; | ||
| export declare function prepareComputed(comp: Computed<unknown>, refresh: boolean): void; | ||
| /** | ||
| * Sentinel returned by readNodeFast when the plain-signal fast path does not | ||
| * apply and the caller must fall back to the full read(). | ||
| */ | ||
| export declare const READ_SLOW: unique symbol; | ||
| /** | ||
| * read()'s plain-signal fast path as a standalone entry for hot callers | ||
| * (store traps). Safe to substitute for read() only because the bail | ||
| * conditions mirror read()'s prelude and fast-path guard exactly: the | ||
| * latestRead and pendingCheck windows run side-effectful hooks before the | ||
| * fast path, `_fn` nodes need prepareComputed, and firewall / override / | ||
| * snapshot / transition / lane / dev-strictRead state all take the full | ||
| * resolution. Anything slow returns READ_SLOW; the caller then calls read(). | ||
| */ | ||
| export declare function readNodeFast<T>(el: Signal<T>): T | typeof READ_SLOW; | ||
| export declare function read<T>(el: Signal<T> | Computed<T>): T; | ||
@@ -77,0 +92,0 @@ export declare function setSignal<T>(el: Signal<T> | Computed<T>, v: T | ((prev: T) => T)): T; |
@@ -44,3 +44,3 @@ import { type Refreshable } from "../core/index.js"; | ||
| */ | ||
| export declare function createOptimisticStore<T extends object = {}>(store: NoFn<T> | Store<NoFn<T>>): [get: Store<T>, set: StoreSetter<T>]; | ||
| export declare function createOptimisticStore<T extends object = {}>(store: NoFn<T> | Store<NoFn<T>>, options?: ProjectionOptions): [get: Store<T>, set: StoreSetter<T>]; | ||
| export declare function createOptimisticStore<T extends object = {}>(fn: (store: T) => void | T | Promise<void | T> | AsyncIterable<void | T>, store: Partial<T> | Store<NoFn<T>>, options?: ProjectionOptions): [get: Refreshable<Store<T>>, set: StoreSetter<T>]; |
@@ -34,2 +34,4 @@ import { STORE_SNAPSHOT_PROPS, type Computed, type Refreshable, type Signal } from "../core/index.js"; | ||
| key?: string | ((item: NonNullable<any>) => any); | ||
| /** Single-layer store: root keys reactive, values raw records replaced by reference */ | ||
| shallow?: boolean; | ||
| } | ||
@@ -46,3 +48,3 @@ export type NoFn<T> = T extends Function ? never : T; | ||
| export declare const $TRACK: unique symbol, $TARGET: unique symbol, $PROXY: unique symbol, $DELETED: unique symbol, $AFFECTS: unique symbol; | ||
| export declare const STORE_VALUE = "v", STORE_OVERRIDE = "o", STORE_OPTIMISTIC_OVERRIDE = "x", STORE_NODE = "n", STORE_HAS = "h", STORE_CUSTOM_PROTO = "c", STORE_WRAP = "w", STORE_LOOKUP = "l", STORE_FIREWALL = "f", STORE_OPTIMISTIC = "p", STORE_OPTIMISTIC_OWNERS = "t", STORE_PARENT = "u", STORE_DESC = "d"; | ||
| export declare const STORE_VALUE = "v", STORE_OVERRIDE = "o", STORE_OPTIMISTIC_OVERRIDE = "x", STORE_NODE = "n", STORE_HAS = "h", STORE_CUSTOM_PROTO = "c", STORE_WRAP = "w", STORE_LOOKUP = "l", STORE_FIREWALL = "f", STORE_OPTIMISTIC = "p", STORE_OPTIMISTIC_OWNERS = "t", STORE_PARENT = "u", STORE_DESC = "d", STORE_SHALLOW = "s"; | ||
| export type StoreNode = { | ||
@@ -63,2 +65,3 @@ [$PROXY]: any; | ||
| [STORE_PARENT]?: StoreNode; | ||
| [STORE_SHALLOW]?: boolean; | ||
| [STORE_DESC]?: boolean; | ||
@@ -74,3 +77,16 @@ }; | ||
| export declare const symbolKeyedRecords: WeakSet<object>; | ||
| export declare function lookupTarget(value: any, lookup?: WeakMap<any, any>): StoreNode | undefined; | ||
| /** | ||
| * Marks a value as raw: no store will ever wrap it — every store presents it | ||
| * as-is, tracked by reference at whatever slot holds it and updated by | ||
| * replacement. Useful for class instances and external objects (editors, | ||
| * scene graphs, Maps) and for record-shaped data updated wholesale. Sticky | ||
| * for the value's lifetime. | ||
| */ | ||
| export declare let rawValuesUsed: boolean; | ||
| export declare function isRawValue(value: any): boolean; | ||
| export declare function markRaw<T>(value: T): T; | ||
| export declare function markRawIngest(container: any): void; | ||
| export declare function wrap<T extends Record<PropertyKey, any>>(value: T, target?: StoreNode): T; | ||
| export declare function wrapShallow<T extends Record<PropertyKey, any>>(value: T): T; | ||
| export declare function isWrappable<T>(obj: T | NotWrappable): obj is T; | ||
@@ -183,4 +199,6 @@ export declare function setWriteOverride(value: boolean): void; | ||
| */ | ||
| export declare function createStore<T extends object = {}>(store: NoFn<T> | Store<NoFn<T>>): StoreReturn<T>; | ||
| export declare function createStore<T extends object = {}>(store: NoFn<T> | Store<NoFn<T>>, options?: StoreOptions & { | ||
| shallow?: boolean; | ||
| }): StoreReturn<T>; | ||
| export declare function createStore<T extends object = {}>(fn: (store: T) => void | T | Promise<void | T> | AsyncIterable<void | T>, store: Partial<T> | Store<NoFn<T>>, options?: ProjectionOptions): ProjectionStoreReturn<T>; | ||
| export {}; |
+1
-1
| { | ||
| "name": "@solidjs/signals", | ||
| "version": "2.0.0-beta.24", | ||
| "version": "2.0.0-beta.25", | ||
| "description": "Solid's reactive primitives: signals, memos, effects, stores, and async-aware computations.", | ||
@@ -5,0 +5,0 @@ "author": "Ryan Carniato", |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
1129786
4.81%24827
4.52%