Skip to content

feat: support boxed singular read model relationships - #231

Open
patrickleet wants to merge 2 commits into
fix/flat-event-previewfrom
feat/boxed-readmodel-relations
Open

feat: support boxed singular read model relationships#231
patrickleet wants to merge 2 commits into
fix/flat-event-previewfrom
feat/boxed-readmodel-relations

Conversation

@patrickleet

@patrickleet patrickleet commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Why

Read models can have valid cyclic one-to-one relationships, but Rust cannot store
both sides inline. Authors need Option<Box<T>> to represent that shape without
changing a singular GraphQL relationship into a collection or introducing JSON.

What changes

ReadModel now accepts a single box inside an optional belongs_to field:

#[derive(Clone, Debug, Default, ReadModel)]
struct Account {
    id: String,
    #[readmodel(belongs_to = "Profile", foreign_key = "id")]
    profile: Option<Box<Profile>>,
}

#[derive(Clone, Debug, Default, ReadModel)]
struct Profile {
    id: String,
    #[readmodel(belongs_to = "Account", foreign_key = "id")]
    account: Option<Account>,
}

The box affects only Rust storage. Schema metadata, GraphQL cardinality, typed
effect/projection relationship markers, and included rows still target the model
inside it. Hydration allocates the box; serialization reads through it.

Wrong targets, unsupported wrappers, and multiple rows for a singular include
still fail. This does not add a blanket read-model implementation for Box,
change SQL storage, or alter authorization.

Validation

  • Macro read-model tests: 30 passed, including wrapper/target rejection.
  • Relationship includes: 19 passed, including cyclic types, empty/one/multiple
    rows, included schema, and serialization.
  • SQLite GraphQL: 8 passed, including cyclic nested selection and related-row
    permission denial.
  • Full macro suite: 93 passed, 5 documentation examples ignored; compile-fail
    fixtures passed. CI is running as an additional gate.

CI-discovered startup race

The initial CI run timed out in an existing lifecycle test. Investigation found
that distributed dev captured its first watch baseline after child startup,
so a source edit during startup could be silently accepted as an unbuilt baseline.
A new held-readiness test fails on the prior code and passes after capturing the
baseline before the initial build/startup. The existing 11 lifecycle tests are
unchanged and pass alongside the new regression (12 total). The original CI
timeout did not identify which fixture wait failed, so this is a proven startup
bug rather than a claim to have conclusively explained that CI occurrence.

Stacked on #230, whose base is v5.
Documentation-only copy; no application UI or new user-facing runtime copy.

Implements [[tasks/distributed-boxed-readmodel-relations]]
@coderabbitai

coderabbitai Bot commented Sep 8, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: a0e216b3-3f5d-46b8-a904-6ad9b9a5b097

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Implements [[tasks/distributed-dev-startup-watch]]
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