Engineering

Type-safe props, from Rust to TypeScript

Suprnova's Inertia bridge has one rule: the props a handler sends and the props a component receives are the same type, and a machine checks it.

On the Rust side, a page's props are a struct:

#[derive(InertiaProps)]
pub struct DocsPageProps {
    pub groups: Vec<DocsGroup>,
    pub chapter: DocsChapterProps,
}

inertia_response!("docs/Index", DocsPageProps { .. }) refuses to compile if frontend/src/pages/docs/Index.vue doesn't exist - dead page references are a build error, not a 3 a.m. discovery.

Then suprnova generate-types walks every InertiaProps struct and emits TypeScript interfaces:

export interface DocsPageProps {
  groups: Array<DocsGroup>;
  chapter: DocsChapterProps;
}

Rename a Rust field and the TypeScript compiler points at every component that needs updating. suprnova serve runs the generator as a watcher, so the loop is: edit the struct, save, watch vue-tsc light up.

Nested types ride along

The generator resolves nested DTOs transitively - any struct your project defines gets a precise named interface when a prop reaches it, whether or not it derives InertiaProps or Data. A plain struct DocsGroup two levels down still comes out as Array<DocsGroup>, not unknown. Only types the generator genuinely can't see into - external crate types, enums, tuple structs - degrade to unknown, and each one warns loudly:

⚠ Prop type `serde_json::Value` (referenced by `AdminProps.payload`)
  isn't a struct this project defines - emitting `unknown`.

This site is built with Suprnova, and the whole surface - docs, blog, community, resources, admin - runs on exactly this pipeline. The generated file is committed, so a type drift shows up in review as a diff, not as a runtime surprise.

Read more in the Frontend chapter of the manual.

Comments 0