TypeScript Bindings with tsify

This document describes the implementation of automatic TypeScript binding generation from Rust types using the tsify crate.

Overview

We have implemented a comprehensive TypeScript bindings system that automatically generates TypeScript definitions from Rust structs and enums. This eliminates manual type definitions and ensures type safety between the Rust backend and TypeScript frontend.

Implementation Details

1. Added tsify Dependencies

Core Crates with TypeScript Support:

  • crates/terraphim_config - Configuration types
  • crates/terraphim_types - Core domain types
  • crates/terraphim_automata - Automata and path types
  • desktop/src-tauri - Tauri command response types

Cargo.toml Updates:

# In each crate
tsify = { version = "0.4", features = ["js"], optional = true }
wasm-bindgen = { version = "0.2", optional = true }

[features]
typescript = ["tsify", "wasm-bindgen"]

2. Added tsify Derives to Rust Types

Configuration Types (terraphim_config):

  • ServiceType - Ripgrep vs Atomic service selection
  • Haystack - Individual document collection with service and parameters
  • KnowledgeGraph - Knowledge graph configuration
  • KnowledgeGraphLocal - Local KG configuration
  • Role - User role with settings and haystacks
  • Config - Complete application configuration
  • ConfigId - Configuration type identifier

Core Domain Types (terraphim_types):

  • RoleName - Role name with normalization
  • NormalizedTermValue - Normalized search terms
  • Document - Document representation
  • SearchQuery - Search request structure
  • RelevanceFunction - Scoring algorithm selection
  • KnowledgeGraphInputType - Input format types

Automata Types (terraphim_automata):

  • AutomataPath - Local vs Remote automata file paths

Command Response Types (desktop/src-tauri/cmd.rs):

  • Status - Success/error status
  • ConfigResponse - Configuration API response
  • SearchResponse - Search API response
  • DocumentResponse - Document API response
  • InitialSettings - Application startup settings

3. Automatic Binding Generation System

Generation Binary (desktop/src-tauri/src/bin/generate-bindings.rs):

use terraphim_ai_desktop::generate_typescript_bindings;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    println!("πŸ”§ Generating TypeScript bindings from Rust types...");
    generate_typescript_bindings()?;
    println!("βœ… Done! Check desktop/src/lib/generated/types.ts");
    Ok(())
}

Generation Logic (desktop/src-tauri/src/bindings.rs):

  • Uses tsify DECL constants to extract TypeScript definitions
  • Organizes types by category (Core, Configuration, Command Response)
  • Writes to desktop/src/lib/generated/types.ts
  • Includes header with regeneration instructions

4. Generated TypeScript Types

Output Location: desktop/src/lib/generated/types.ts

Key Generated Types:

// Service and configuration types
export type ServiceType = "Ripgrep" | "Atomic";
export type ConfigId = "Server" | "Desktop" | "Embedded";
export type RelevanceFunction = "terraphim-graph" | "title-scorer";

// Haystack with full feature support
export interface Haystack {
    location: string;
    service: ServiceType;
    read_only?: boolean;
    atomic_server_secret?: string | undefined;
    extra_parameters?: Map<string, string>;
}

// Complete configuration structure
export interface Config {
    id: ConfigId;
    global_shortcut: string;
    roles: AHashMap<RoleName, Role>;
    default_role: RoleName;
    selected_role: RoleName;
}

// API response types
export interface ConfigResponse {
    status: Status;
    config: Config;
}

5. Frontend Integration

ConfigWizard.svelte Updates:

  • Replaced manual type definitions with generated imports
  • Added type safety for service selection
  • Proper support for RelevanceFunction values ("title-scorer" vs "terraphim-graph")
  • Full compatibility with new haystack structure

Import Pattern:

import type {
  Config,
  Role,
  Haystack,
  ServiceType,
  ConfigId,
  RelevanceFunction,
  KnowledgeGraphInputType
} from "./generated/types";

Benefits Achieved

βœ… Type Safety

  • Zero Type Mismatches: Frontend automatically stays in sync with backend
  • Compile-Time Verification: TypeScript catches type errors immediately
  • IDE Autocomplete: Full IntelliSense support for all Rust-defined types

βœ… Maintenance Efficiency

  • Single Source of Truth: Types defined once in Rust, used everywhere
  • Automatic Updates: Changes to Rust types automatically propagate to frontend
  • No Manual Sync: Eliminates handcrafted TypeScript definitions

βœ… Developer Experience

  • Consistent Naming: Rust conventions automatically translated to TypeScript
  • Documentation: Type comments from Rust appear in TypeScript
  • Refactoring Safety: Changes to Rust types cause TypeScript compilation errors if incompatible

βœ… Feature Completeness

  • Full Haystack Support: Service types, extra parameters, atomic secrets
  • Configuration Wizard: Complete UI support for all configuration options
  • API Compatibility: Perfect alignment between Tauri commands and frontend types

Usage Instructions

Regenerating Bindings

When Rust types change, regenerate TypeScript bindings:

cd desktop/src-tauri
cargo run --bin generate-bindings

Adding New Types

  1. Add tsify derive to Rust type:
#[derive(Serialize, Deserialize, ...)]
#[cfg_attr(feature = "typescript", derive(Tsify))]
#[cfg_attr(feature = "typescript", tsify(into_wasm_abi, from_wasm_abi))]
pub struct NewType {
    // fields
}
  1. Add to generation script in bindings.rs:
all_bindings.push_str(&NewType::DECL);
  1. Regenerate bindings:
cargo run --bin generate-bindings
  1. Import in frontend:
import type { NewType } from "./generated/types";

Handling External Types

For types from external crates (like AHashMap), add type aliases:

// In generated/types.ts
export type AHashMap<K, V> = Record<string, V>;
export type Value = any;

Build Integration

The bindings generation is integrated into the development workflow:

  1. Development: Run cargo run --bin generate-bindings when types change
  2. CI/CD: Can be automated to ensure frontend stays current
  3. Frontend Build: TypeScript compilation validates all generated types

Testing

The implementation is validated by:

  • βœ… Backend Compilation: All Rust code compiles with tsify features
  • βœ… Frontend Compilation: TypeScript build succeeds with generated types
  • βœ… ConfigWizard Integration: UI properly uses all new type features
  • βœ… Type Safety: No manual type casting required in frontend

Future Enhancements

  1. Automatic Regeneration: Watch Rust files and auto-regenerate on changes
  2. Documentation Generation: Extract Rust doc comments to TypeScript JSDoc
  3. Validation: Add runtime type validation for API boundaries
  4. Optimization: Tree-shake unused types for smaller bundles

Conclusion

The tsify implementation provides a robust, maintainable solution for TypeScript bindings that:

  • Eliminates manual type maintenance
  • Ensures perfect type safety
  • Supports all configuration features
  • Integrates seamlessly with existing workflow

This foundation enables confident frontend development with guaranteed backend compatibility.