Skip to content

2.0.0-beta.18: snapshot() / store-to-store writes drop symbol-keyed properties once the object has been written #2894

Description

@yumemi-thomas

Describe the bug

After any write to a store subtree, snapshot() of it and copying it into another store both drop symbol-keyed properties. Before the first write they're preserved (identity return path), so the loss appears only once the object has an override.

Symbol keys are the standard way a data layer attaches metadata that cannot collide with API fields (cache etags, dirty flags). Dropping them breaks save/undo flows that snapshot edited store state before sending it to a worker, cache, or another store — and because the failure only appears after a normal field edit, initial tests with untouched records pass. The internal store-to-store materialization path uses the same key enumeration, so copying a previously written subtree into another store drops its symbol keys too.

Your Example Website or App

Live reproduction — opens the standalone browser repro in StackBlitz.

The repro keeps an invoice record with a symbol-keyed cache tag, performs one ordinary field edit, and checks the tag on the resulting snapshot.

import { createSignal, createStore, flush, snapshot } from "solid-js";

const cacheMeta = Symbol("cacheMeta");

export default function App() {
  const [invoice, setInvoice] = createStore({ total: 100, [cacheMeta]: "etag-v1" } as any);
  const [result, setResult] = createSignal("Click the button to run the repro.");

  function editAndSnapshot() {
    setInvoice((draft: any) => {
      draft.total = 125;
    });
    flush();

    const copy = snapshot(invoice) as any;
    setResult(
      copy[cacheMeta] === "etag-v1"
        ? "PASS - snapshot kept the symbol key"
        : `FAIL - snapshot[cacheMeta] is ${String(copy[cacheMeta])}`
    );
  }

  return (
    <main style={{ "font-family": "system-ui", padding: "16px" }}>
      <h2>snapshot() drops symbol keys after a write</h2>
      <p>total: {invoice.total}</p>
      <button onClick={editAndSnapshot}>edit total and snapshot</button>
      <pre>{result()}</pre>
    </main>
  );
}

Steps to Reproduce the Bug or Issue

  1. Click edit total and snapshot. The handler writes total = 125, flushes, and snapshots the record.
  2. The edited total is present, but the symbol-keyed cache tag is missing.

On 2.0.0-beta.18 the page shows:

FAIL - snapshot[cacheMeta] is undefined

Expected behavior

Symbol keys survive snapshot() and internal store-to-store materialization after writes, as in Solid 1.x's equivalent unwrap() behavior:

PASS - snapshot kept the symbol key

Screenshots or Videos

No response

Platform

  • OS: macOS
  • Browser: Chrome
  • Version: 2.0.0-beta.18 (re-verified at next @ 1dc0b45e)

Additional context

Root cause: getKeys() (packages/solid-signals/src/store/store.ts:511) seeds base keys from Object.keys(source) (strings only), and drives both snapshotImpl (utils.ts:23) and unwrapStoreValue (store.ts:176). Residual gap of #2769, which added ownEnumerableKeys only to the setter/merge/omit/set-trap sites — not these internal materialization paths.

Because unwrapStoreValue shares the same enumeration, writing a written store subtree into another store also drops its symbol-keyed props (third case in the repro test).

Repro test: packages/solid-signals/tests/store/hunt2-snapshot-symbol-keys.test.ts (2 failing + control). 1.x check: hunt-1x-checks/checks/w2-store-unwrap-symbol-keys.test.ts.

Does this exist in Solid 1.x?

Regression. Verified against 1.9.14: unwrap after a write keeps symbol keys, and copying a written subtree into another store keeps them.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions