Skip to content

Refactor: Automate Rust-to-TypeScript generation using ts-rs #153

@mmogr

Description

@mmogr

Problem

Manual synchronization between Rust backend types and TypeScript frontend interfaces is fragile and error-prone. The recent regression in #151 (commit e1af0cd) occurred because #[serde(rename_all = "camelCase")] was added to Rust structs but the TypeScript interfaces weren't fully updated to match.

Proposed Solution

Replace manual TypeScript interface synchronization with automated ts-rs generation to prevent serialization mismatches.

Why ts-rs?

  • ✅ Works seamlessly with existing serde attributes (respects rename_all = "camelCase")
  • ✅ Generates TypeScript via derive macro at compile time
  • ✅ Minimal runtime overhead (generation happens during build)
  • ✅ Generates .d.ts or .ts files directly into src/types/ directory
  • ✅ Handles Rust type mappings well (Option → ?, Vec → [], etc.)

Implementation Tasks

1. Add ts-rs dependency

Add to crates/gglib-gui/Cargo.toml:

[dependencies]
ts-rs = "7.1"  # or latest stable

2. Update Rust structs

In crates/gglib-gui/src/types.rs, add #[derive(TS)] to all API types:

use ts_rs::TS;

#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export, export_to = "../../../src/types/generated/")]
#[serde(rename_all = "camelCase")]
pub struct GuiModel {
    // ... fields
}

Apply to:

  • GuiModel
  • StartServerRequest
  • ServerHealthStatus
  • GuiSettings
  • HfToolSupportResponse
  • Any other types exposed via API

3. Configure generation

  • Types generate to src/types/generated/ on cargo build
  • Check generated files into git for visibility
  • Update .gitignore if needed (consider tracking generated files for transparency)

4. Update TypeScript imports

Replace manual interfaces in src/types/index.ts:

// Before:
export interface GgufModel { /* manual sync */ }

// After:
export { GuiModel as GgufModel } from './generated/GuiModel';
// Or extend if needed:
export interface GgufModel extends GuiModel { /* client-only fields */ }

5. Add CI validation

Add check to CI pipeline:

# Verify generated types match committed files
cargo build --package gglib-gui
git diff --exit-code src/types/generated/

Fails if developers forget to regenerate types after Rust changes.

6. Documentation

Update CONTRIBUTING.md or docs/ARCHITECTURE.md:

  • Explain ts-rs workflow
  • Document when/how types regenerate
  • Note that Rust structs are source of truth

Benefits

Trade-offs

  • Generated files add noise to git diffs (mitigated by separating to generated/)
  • Build process couples frontend types to Rust compilation
  • Developers must run cargo build before frontend work (document in README)

Related Issues

Alternative Considered

specta: More runtime-focused, better for tRPC-style workflows. Overkill for this project's simpler HTTP API pattern.

Metadata

Metadata

Assignees

Labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions