Skip to content

fix(signals): require complete store seeds - #3258

Merged
ryansolid merged 2 commits into
solidjs:nextfrom
GabbeV:fix/require-complete-store-seeds
Sep 4, 2026
Merged

fix(signals): require complete store seeds#3258
ryansolid merged 2 commits into
solidjs:nextfrom
GabbeV:fix/require-complete-store-seeds

Conversation

@GabbeV

@GabbeV GabbeV commented Sep 3, 2026

Copy link
Copy Markdown

Summary

Split from #3194 to isolate the seed type-safety change.

Derived store APIs currently accept a Partial<T> seed while exposing both the returned store and the projection draft as a complete T. This allows required properties to be absent at runtime even though reads and mutations are typed as if they are present.

This changes the seed to a complete T for createStore(fn, seed), createProjection, and createOptimisticStore(fn, seed) across the signals, client, and server declarations. It also applies the existing NoFn<T> exclusion consistently, since a callable root cannot be distinguished from the function form by the runtime dispatch.

Existing tests that intentionally initialize from {} and establish the complete value through mutation now use {} as any. This preserves that pattern as an explicit unsafe escape hatch rather than claiming it is type-safe.

No runtime behavior is intended to change.

How did you test this change?

  • Ran pnpm types in packages/signals.
  • Ran pnpm exec tsc -p tsconfig.build.json --noEmit in packages/solid.
  • Compiled store.type-tests.ts independently with strict TypeScript settings.
  • Ran the focused projection, store type, and server async tests: 184 tests passed.

@changeset-bot

changeset-bot Bot commented Sep 3, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5ceea76

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 11 packages
Name Type
@solidjs/signals Patch
solid-js Patch
test-integration Patch
@solidjs/web Patch
@solidjs/element Patch
@solidjs/h Patch
@solidjs/html Patch
@solidjs/universal Patch
@solidjs/babel-plugin Patch
@solidjs/compiler Patch
@solidjs/diagnostics Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@codspeed-hq

codspeed-hq Bot commented Sep 3, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 136 untouched benchmarks


Comparing GabbeV:fix/require-complete-store-seeds (5ceea76) with next (ff96a67)

Open in CodSpeed

@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 33782694918

Warning

Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes.
Quick fix: rebase this PR. Learn more →

Coverage remained the same at 71.814%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 998
Covered Lines: 764
Line Coverage: 76.55%
Relevant Branches: 783
Covered Branches: 515
Branch Coverage: 65.77%
Branches in Coverage %: Yes
Coverage Strength: 14.94 hits per line

💛 - Coveralls

@ryansolid
ryansolid merged commit 64b8434 into solidjs:next Sep 4, 2026
7 of 8 checks passed
ryansolid added a commit that referenced this pull request Sep 4, 2026
…stores (#3258)

Derived createStore/createProjection/createOptimisticStore accept Partial<T>
seeds again. #3258 merged without maintainer review; on review the ruling is
that the requirement's ergonomic cost exceeds what it fixes: any async object
store reconciling on a non-'id' key needs the options slot and therefore the
seed slot, and there the seed is pure ceremony — reads pend until the first
resolution, so a fabricated complete T is never observable (verified: an
uninitialized derived read throws NotReadyError; the seed never leaks). The
type-honesty gain is real only for sync draft-reading callbacks and is better
served by the #3194 seedless-callback direction. #3260's overload alignment
(slot order, shallow in options, Refreshable derived returns, NoFn on plain
roots) stays. Since #3258 never shipped, its pending changeset is dropped;
the API is unchanged from 2.0.0-rc.6.

Co-authored-by: Cursor <cursoragent@cursor.com>
@ryansolid

Copy link
Copy Markdown
Member

Heads up: this has been reverted on next in de1c8b5, and we owe you an honest accounting of how it got merged in the first place.

Process first: this PR was merged by Ryan's agent without his review — a process error on the agent's side, not a maintainer approval. When Ryan reviewed the merged state, he ruled against the design. We're flagging that plainly so the reversal reads as what it is: the design question was never actually decided until now, and contributors aiming at the project's rulings shouldn't read this as instability.

The ruling on the substance: the completeness requirement costs more than it fixes. The case that decides it: any async object store that reconciles on a non-id key needs the options argument, and therefore needs the seed slot filled —

createStore(async () => fetchUser(), /* complete User required here */, { key: "email" });

and in exactly that case the seed is pure ceremony: reads of an async derived store pend with NotReadyError until the first resolution, so the fabricated complete object is never observable by any consumer (we verified this empirically while reviewing). A fetch-shaped callback returning a fresh T never reads its draft either. The type-honesty gain is real, but only for sync draft-reading callbacks — and taxing every async store to fix that corner is the wrong trade.

The concern itself is legitimate, and we think the right home for it is your #3194 direction: a callback that takes no draft parameter needs no seed at all, which fixes the awkward case and keeps the honesty for draft-taking callbacks. That conversation is still live.

#3260 is untouched by the revert — the overload alignment (slot order, shallow in options, Refreshable derived returns) stands. Thanks for the careful work here and sorry for the whiplash; the fault is on our side of the merge button.

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.

3 participants