Skip to content

fix: materialize placeholder-backed hasOne creating relations on persist#23

Merged
vparys merged 3 commits intomainfrom
fix/placeholder-hasone-materialization
Apr 21, 2026
Merged

fix: materialize placeholder-backed hasOne creating relations on persist#23
vparys merged 3 commits intomainfrom
fix/placeholder-hasone-materialization

Conversation

@vparys
Copy link
Copy Markdown
Member

@vparys vparys commented Apr 20, 2026

Summary

Fixes a persist bug where a hasOne relation in creating state backed by PlaceholderHandle data (typical admin create form) was never materialized into a real store entity. As a result extractNestedResultsFromNode saw currentId = null and skipped ID mapping — the server persisted the child correctly, but the client store stayed with state: 'creating' and empty placeholderData, so HasOneHandle kept returning a blank PlaceholderHandle until refresh.

  • Placeholder materialization moved into a dedicated pass (materializePlaceholderRelations) called at the top of collectUpdateData, and folded into the existing materializeEntityRelations on the create path.
  • Collection methods are back to a pure read over the store (the old collectUpdateOneRelation had a hidden createEntity + setRelation side-effect mid-collection).
  • Both create and update flows now go through the shared materialization + standard tempId inline-create path, so post-persist ID mapping works uniformly.

Test plan

  • New regression suite tests/hasOneCreatingMaterialization.test.ts:
    • collector: placeholder-backed creating hasOne gets materialized on both create and update of parent
    • collector: empty placeholderData does not mutate the store
    • end-to-end through BatchPersister: after persist the relation is connected to a server-persisted User with mapped ID and cleared placeholder
  • Existing tests/mutationCollector.test.ts passes (one assertion loosened to not depend on operation ordering)
  • bun test green

vparys and others added 3 commits April 20, 2026 14:30
Without materialization, extractNestedResultsFromNode sees a relation with
currentId = null and skips it, so the server-assigned ID is never mapped
back — the relation stays in 'creating' with empty placeholderData and
HasOneHandle keeps returning a blank PlaceholderHandle until refresh.

Move materialization into a dedicated pass (materializePlaceholderRelations)
called at the top of collectUpdateData, and extend the existing
materializeEntityRelations used by collectCreateData. The collection phase
is back to a pure read over the store, and both create and update flows
go through the shared materialization + standard tempId inline-create path
so post-persist ID mapping works uniformly.
Rename materializeEntityRelations/materializePlaceholderRelations to
materializeForCreate/materializeForUpdate so call sites express when
they run, not what they walk.

Replace the silent fallback in collectHasOneOperation 'creating' branch
with an invariant throw for non-empty placeholderData — reaching it
means materialization was skipped, which would regenerate the pre-fix
ghost-create payload the whole PR is meant to prevent. Empty
placeholderData remains a legitimate no-op.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- nested placeholder hasOne (creating → creating) recursively materializes
- placeholder hasOne carried on a hasMany-created item
- invariant throw when a broken MutationSchemaProvider bypasses
  materialization with a non-empty placeholderData

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vparys vparys merged commit 7bd0d63 into main Apr 21, 2026
3 checks passed
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.

2 participants