gear_mesh/
inventory_collect.rs

1/// Type information for automatic collection via inventory
2pub struct TypeInfo {
3    pub get_type: fn() -> crate::GearMeshType,
4    pub type_name: &'static str,
5}
6
7inventory::collect!(TypeInfo);
8
9/// Generate TypeScript definitions for all registered types
10///
11/// This function automatically collects all types that have `#[derive(GearMesh)]`
12/// and generates TypeScript definitions.
13///
14/// # Example
15///
16/// ```no_run
17/// use gear_mesh::generate_types;
18///
19/// generate_types("../frontend/src/types/generated.ts")
20///     .expect("Failed to generate types");
21/// ```
22pub fn generate_types(output_path: impl AsRef<std::path::Path>) -> std::io::Result<()> {
23    use crate::{GeneratorConfig, TypeScriptGenerator};
24    use std::fs;
25
26    // Collect all registered types
27    let types: Vec<_> = inventory::iter::<TypeInfo>()
28        .map(|info| (info.get_type)())
29        .collect();
30
31    if types.is_empty() {
32        eprintln!(
33            "⚠️  Warning: No types found. Make sure you have #[derive(GearMesh)] on your types."
34        );
35    }
36
37    // Generate TypeScript with Zod schemas
38    let config = GeneratorConfig::new().with_zod(true).with_validation(true);
39    let mut generator = TypeScriptGenerator::new(config);
40    let output_content = generator.generate(&types);
41
42    // Create output directory
43    let output_path = output_path.as_ref();
44    if let Some(parent) = output_path.parent() {
45        fs::create_dir_all(parent)?;
46    }
47
48    // Write to file
49    fs::write(output_path, output_content)?;
50
51    println!("✅ Generated TypeScript types: {}", output_path.display());
52    println!("   {} types exported", types.len());
53
54    Ok(())
55}
56
57/// Generate TypeScript definitions to separate files
58///
59/// Each type will be generated in its own file, with an index.ts that re-exports all types.
60///
61/// # Example
62///
63/// ```no_run
64/// use gear_mesh::generate_types_to_dir;
65///
66/// generate_types_to_dir("../frontend/src/types")
67///     .expect("Failed to generate types");
68/// ```
69pub fn generate_types_to_dir(output_dir: impl AsRef<std::path::Path>) -> std::io::Result<()> {
70    use crate::{GeneratorConfig, TypeScriptGenerator};
71    use std::fs;
72
73    let output_dir = output_dir.as_ref();
74
75    // Collect all registered types
76    let types: Vec<_> = inventory::iter::<TypeInfo>()
77        .map(|info| (info.get_type)())
78        .collect();
79
80    if types.is_empty() {
81        eprintln!(
82            "⚠️  Warning: No types found. Make sure you have #[derive(GearMesh)] on your types."
83        );
84        return Ok(());
85    }
86
87    // Create output directory
88    fs::create_dir_all(output_dir)?;
89
90    // Generate config
91    let config = GeneratorConfig::new().with_zod(true).with_validation(true);
92
93    let validator = crate::ValidationGenerator::new(config.clone());
94    let mut exports = Vec::new();
95
96    for ty in &types {
97        let file_name = format!("{}.ts", ty.name);
98        let file_path = output_dir.join(&file_name);
99
100        let mut content = String::new();
101
102        // Zod import
103        if config.generate_zod {
104            content.push_str("import { z } from 'zod';\n");
105        }
106
107        // Extract type dependencies and generate imports
108        let deps = crate::extract_type_dependencies(ty);
109        let mut sorted_deps: Vec<_> = deps.iter().collect();
110        sorted_deps.sort();
111
112        for dep in sorted_deps {
113            // Skip self-reference
114            if dep != &ty.name {
115                // Import both type and schema
116                if config.generate_zod {
117                    content.push_str(&format!(
118                        "import type {{ {} }} from './{}';\nimport {{ {}Schema }} from './{}';\n",
119                        dep, dep, dep, dep
120                    ));
121                } else {
122                    content.push_str(&format!("import type {{ {} }} from './{}';\n", dep, dep));
123                }
124            }
125        }
126
127        if !deps.is_empty() {
128            content.push('\n');
129        }
130
131        // Branded Type helper if this type needs it
132        if config.generate_branded && ty.attributes.branded {
133            content.push_str("\n// Branded Type helper\n");
134            content.push_str("type Brand<T, B> = T & { readonly __brand: B };\n");
135        }
136
137        content.push('\n');
138
139        // Generate the type
140        let mut generator = TypeScriptGenerator::new(config.clone());
141        generator.generate_type(ty);
142        content.push_str(&generator.output);
143
144        // Generate Zod schema
145        if config.generate_zod {
146            // Branded typeの場合は専用のスキーマ生成
147            if ty.attributes.branded {
148                let branded_gen = crate::BrandedTypeGenerator::new(config.clone());
149                if let Some(schema) = branded_gen.generate_zod_schema(ty) {
150                    content.push_str("\n// Zod Schema\n\n");
151                    content.push_str(&schema);
152                }
153            } else if let Some(schema) = validator.generate_zod_schema(ty) {
154                content.push_str("\n// Zod Schema\n\n");
155                content.push_str(&schema);
156            }
157        }
158
159        fs::write(&file_path, content)?;
160        exports.push(ty.name.clone());
161        println!("  ✓ {}", file_name);
162    }
163
164    // Generate index.ts
165    let mut index_content = String::new();
166    index_content.push_str("// Auto-generated index file\n");
167    index_content.push_str("// Re-exports all types\n\n");
168
169    for type_name in &exports {
170        index_content.push_str(&format!("export * from './{}';\n", type_name));
171    }
172
173    fs::write(output_dir.join("index.ts"), index_content)?;
174
175    println!("✅ Generated TypeScript types to: {}", output_dir.display());
176    println!("   {} types exported", types.len());
177    println!("   📄 index.ts created");
178
179    Ok(())
180}