Describe the bug
After a plain setStore write, reading the property through the proxy returns the new value, but Object.getOwnPropertyDescriptor(store, key).value (and therefore Object.getOwnPropertyDescriptors(store)) still reports the pre-write value — permanently, not just until the next flush. This breaks descriptor-driven consumers (shallow-clone utilities, Object.defineProperties-based copying, devtools/serialization helpers) that trust the descriptor over a get.
Related to but distinct from #2797 (fixed in 5894f2a): that fix addressed the configurable proxy invariant in the same trap. The optimistic-override branch of the trap already patches value: correctly; the regular STORE_OVERRIDE path (where every plain setStore write lands) does not.
Your Example Website or App
https://stackblitz.com/edit/solidjs-templates-k8dgpbb6?file=src%2FApp.tsx
import { createSignal, createStore, flush, Show } from "solid-js";
type Verdict = { ok: boolean; actual: string };
export default function App() {
const [store, setStore] = createStore({ count: 1 });
const [verdict, setVerdict] = createSignal<Verdict>();
function increment() {
setStore(draft => {
draft.count++;
});
flush();
const value = store.count;
const descriptorValue = Object.getOwnPropertyDescriptor(store, "count")?.value;
setVerdict({
ok: descriptorValue === value,
actual: `store.count = ${value}; descriptor.value = ${descriptorValue}`
});
}
return (
<main style={{ "font-family": "system-ui", padding: "16px" }}>
<h2>Store descriptor stale value</h2>
<p>count: {store.count}</p>
<button onClick={increment}>increment</button>
<Show when={verdict()}>
{v => (
<section
style={{
padding: "12px",
"margin-top": "12px",
color: "white",
background: v().ok ? "#137333" : "#c5221f"
}}
>
<b>{v().ok ? "PASS - bug is fixed" : "FAIL - bug reproduced"}</b>
<p>Expected descriptor.value to match store.count after setStore.</p>
<pre>{v().actual}</pre>
</section>
)}
</Show>
</main>
);
}
Steps to Reproduce the Bug or Issue
- Click increment. The rendered count updates to
2 — the proxy read is correct.
- Actual — the verdict banner shows the descriptor still reporting the pre-write value:
FAIL - bug reproduced
store.count = 2; descriptor.value = 1
The proxy get and the descriptor disagree about the same property, and they keep disagreeing on every later read (clicking again yields store.count = 3; descriptor.value = 1).
Expected behavior
The descriptor agrees with the proxy read:
PASS - bug is fixed
store.count = 2; descriptor.value = 2
Screenshots or Videos
No response
Platform
- OS: macOS
- Browser: Chrome
- Version: 2.0.0-beta.15 (verified at
next @ 0e8672ab)
Additional context
Written values live in STORE_OVERRIDE, not in the base object. Root cause: getPropertyDescriptor in packages/solid-signals/src/store/store.ts:300-311:
if (override && property in override) {
if (override[property] === $DELETED) return void 0;
const overrideDesc = Reflect.getOwnPropertyDescriptor(override, property);
if (overrideDesc?.get || overrideDesc?.set || !(property in source)) return overrideDesc;
}
return Reflect.getOwnPropertyDescriptor(source, property); // ← stale base value
For a plain data write to a property that also exists on the base object (the common case), overrideDesc has no getter/setter and property in source is true, so the function falls through to the base descriptor with the pre-write value. The trap's optimistic branch shows the intended behavior one screen up (store.ts:722-727): it fetches the base descriptor for structure and overrides just the value —
return { ...baseDesc, configurable, value: target[STORE_OPTIMISTIC_OVERRIDE][property] };
Suggested fix direction: in getPropertyDescriptor, when the override holds a plain data value for a property that also exists on the source, return the base descriptor with value: override[property] patched in (mirroring the optimistic branch), instead of falling through.
Does this exist in Solid 1.x?
Regression from 1.x. Verified against solid-js 1.9.14: after setState("n", 2), Object.getOwnPropertyDescriptor(state, "n") reflects the written value, and Object.getOwnPropertyDescriptors(state) agrees. (1.x hands out a live getter descriptor for store properties — reading through it always observes the current value, so it can never go stale; 2.0 returns a plain data descriptor whose value is the pre-write value forever.)
Working 1.x comparison: https://playground.solidjs.com/anonymous/407e15da-2432-4e27-a06c-1128d5a6db42
Describe the bug
After a plain
setStorewrite, reading the property through the proxy returns the new value, butObject.getOwnPropertyDescriptor(store, key).value(and thereforeObject.getOwnPropertyDescriptors(store)) still reports the pre-write value — permanently, not just until the next flush. This breaks descriptor-driven consumers (shallow-clone utilities,Object.defineProperties-based copying, devtools/serialization helpers) that trust the descriptor over aget.Related to but distinct from #2797 (fixed in 5894f2a): that fix addressed the
configurableproxy invariant in the same trap. The optimistic-override branch of the trap already patchesvalue:correctly; the regularSTORE_OVERRIDEpath (where every plainsetStorewrite lands) does not.Your Example Website or App
https://stackblitz.com/edit/solidjs-templates-k8dgpbb6?file=src%2FApp.tsx
Steps to Reproduce the Bug or Issue
2— the proxy read is correct.The proxy
getand the descriptor disagree about the same property, and they keep disagreeing on every later read (clicking again yieldsstore.count = 3; descriptor.value = 1).Expected behavior
The descriptor agrees with the proxy read:
Screenshots or Videos
No response
Platform
next@0e8672ab)Additional context
Written values live in
STORE_OVERRIDE, not in the base object. Root cause:getPropertyDescriptorinpackages/solid-signals/src/store/store.ts:300-311:For a plain data write to a property that also exists on the base object (the common case),
overrideDeschas no getter/setter andproperty in sourceis true, so the function falls through to the base descriptor with the pre-writevalue. The trap's optimistic branch shows the intended behavior one screen up (store.ts:722-727): it fetches the base descriptor for structure and overrides just the value —Suggested fix direction: in
getPropertyDescriptor, when the override holds a plain data value for a property that also exists on the source, return the base descriptor withvalue: override[property]patched in (mirroring the optimistic branch), instead of falling through.Does this exist in Solid 1.x?
Regression from 1.x. Verified against solid-js 1.9.14: after
setState("n", 2),Object.getOwnPropertyDescriptor(state, "n")reflects the written value, andObject.getOwnPropertyDescriptors(state)agrees. (1.x hands out a live getter descriptor for store properties — reading through it always observes the current value, so it can never go stale; 2.0 returns a plain data descriptor whosevalueis the pre-write value forever.)Working 1.x comparison: https://playground.solidjs.com/anonymous/407e15da-2432-4e27-a06c-1128d5a6db42