forge-codegen 0.0.1-alpha

TypeScript code generator for the Forge framework
Documentation
use std::path::PathBuf;

use forge_core::schema::SchemaRegistry;

use super::Error;

/// Generates TypeScript type definitions from Rust schema.
pub struct TypeGenerator {
    #[allow(dead_code)]
    output_dir: PathBuf,
}

impl TypeGenerator {
    /// Create a new type generator.
    pub fn new(output_dir: impl Into<PathBuf>) -> Self {
        Self {
            output_dir: output_dir.into(),
        }
    }

    /// Generate TypeScript types from the schema registry.
    pub fn generate(&self, registry: &SchemaRegistry) -> Result<String, Error> {
        let mut output = String::new();

        // Header
        output.push_str("// Auto-generated by FORGE - DO NOT EDIT\n\n");

        // Generate interfaces for each table
        for table in registry.all_tables() {
            output.push_str(&format!("export interface {} {{\n", table.struct_name));
            for field in &table.fields {
                output.push_str(&field.to_typescript());
                output.push('\n');
            }
            output.push_str("}\n\n");
        }

        // Generate enums
        for enum_def in registry.all_enums() {
            output.push_str(&enum_def.to_typescript());
            output.push_str("\n\n");
        }

        // Generate common types
        output.push_str(&self.generate_common_types());

        Ok(output)
    }

    /// Generate common utility types.
    fn generate_common_types(&self) -> String {
        r#"
// Common Types
export interface Paginated<T> {
  data: T[];
  total: number;
  page: number;
  pageSize: number;
  hasMore: boolean;
}

export interface Page {
  page: number;
  pageSize: number;
}

export interface SortOrder {
  field: string;
  direction: 'asc' | 'desc';
}

export interface QueryResult<T> {
  loading: boolean;
  data: T | null;
  error: ForgeError | null;
}

export interface SubscriptionResult<T> extends QueryResult<T> {
  stale: boolean;
}

export interface ForgeError {
  code: string;
  message: string;
  details?: Record<string, unknown>;
}
"#
        .to_string()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use forge_core::schema::{EnumDef, EnumVariant, FieldDef, RustType, TableDef};

    #[test]
    fn test_generator_creation() {
        let gen = TypeGenerator::new("/tmp");
        assert_eq!(gen.output_dir, PathBuf::from("/tmp"));
    }

    #[test]
    fn test_generate_with_table() {
        let gen = TypeGenerator::new("/tmp");
        let registry = SchemaRegistry::new();

        let mut table = TableDef::new("users", "User");
        table.fields.push(FieldDef::new("id", RustType::Uuid));
        table.fields.push(FieldDef::new("email", RustType::String));
        table.fields.push(FieldDef::new(
            "avatar_url",
            RustType::Option(Box::new(RustType::String)),
        ));
        registry.register_table(table);

        let output = gen.generate(&registry).unwrap();
        assert!(output.contains("export interface User {"));
        assert!(output.contains("id: string;"));
        assert!(output.contains("email: string;"));
        assert!(output.contains("avatarUrl?: string | null;"));
    }

    #[test]
    fn test_generate_with_enum() {
        let gen = TypeGenerator::new("/tmp");
        let registry = SchemaRegistry::new();

        let mut enum_def = EnumDef::new("ProjectStatus");
        enum_def.variants.push(EnumVariant::new("Draft"));
        enum_def.variants.push(EnumVariant::new("Active"));
        registry.register_enum(enum_def);

        let output = gen.generate(&registry).unwrap();
        assert!(output.contains("export type ProjectStatus"));
        assert!(output.contains("'draft'"));
        assert!(output.contains("'active'"));
    }

    #[test]
    fn test_generate_common_types() {
        let gen = TypeGenerator::new("/tmp");
        let result = gen.generate_common_types();
        assert!(result.contains("export interface Paginated<T>"));
        assert!(result.contains("export interface ForgeError"));
    }
}