Skip to content

Read query and mutation state as properties, not accessors - #285

Merged
pathscale merged 1 commit into
masterfrom
feat/data-primitives-property-shape
Sep 2, 2026
Merged

Read query and mutation state as properties, not accessors#285
pathscale merged 1 commit into
masterfrom
feat/data-primitives-property-shape

Conversation

@pathscale

Copy link
Copy Markdown
Owner

Corrects the API shape of the primitives added in #284, before any application is written against them.

Why this is needed

I designed createQuery around accessors — query.data() — without first measuring how the code that will consume it is written. solid-query returns a store, so every existing consumer reads properties:

read occurrences (5 sites sampled)
.data 2097
.isLoading 612
.isSuccess 134
.isError 123
.isPending 182

Roughly 3300 reads. Keeping accessors would have meant editing every file that reads a query, not just the ~300 that define one — and for no benefit, since a getter over a signal subscribes identically.

So this is a straight shape change: getters over the same signals.

What changed

  • QueryResult and MutationResult expose data, error, isLoading, isError, isSuccess, isReady (and isPending) as properties.
  • refetch, mutate, mutateAsync, reset stay methods, matching what call sites already write.

isPending is carried over deliberately with different meaning, and this is the one thing to look at closely. In solid-query it meant "has no data", which stayed true forever for a query held back by enabled: false — so if (isPending) return <Spinner/> spun for the life of the page. That is the defect that took honey.id's authenticated routes down. Here it means "a fetch is in flight", so a disabled query reads as not pending and its consumer renders instead of hanging. Call sites migrate without edits, but they behave better rather than identically.

Verification

  • bun test --conditions=browser src/hooks/data — 8 pass.
  • The new one is the load-bearing claim: a createRenderEffect reading q.data re-runs when the fetch resolves. It asserts the subscription, not just the value, because "getters still track" is the whole basis for this change.
  • bunx tsc --noEmit clean, biome lint clean on the changed files.

Compatibility

Breaking against 2.12.0. Nothing consumes it yet — 2.12.0 published about a minute before this branch — so the blast radius is zero if it lands before the site migrations start.

2.12.0 exposed accessors -- `query.data()`. `solid-query`, which this replaces,
returned a store, so every consumer that will be migrated onto it is written
`query.data`: about 3300 such reads across these applications, 2097 of them
`.data` alone.

Keeping accessors would have made the migration touch every file that *reads*
a query rather than the ~300 that define one, for no gain. These are getters
over the same signals, so a read inside a tracked scope subscribes exactly as
an accessor call did -- there is a test asserting the subscription rather than
just the value.

`isPending`, `isError` and `isSuccess` come along for the same reason, so
carried-over call sites keep working. `isPending` deliberately does not mean
what it meant: there it was "has no data", which stayed true forever for a
query held back by `enabled: false`, so `if (isPending) return <Spinner/>`
spun for the life of the page. Here it means a fetch is in flight.

Breaking against 2.12.0, which no application consumes yet.
@pathscale
pathscale merged commit 5266e27 into master Sep 2, 2026
1 check passed
@pathscale
pathscale deleted the feat/data-primitives-property-shape branch September 2, 2026 09:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant