Skip to main content

oag_node_client/
generator.rs

1use oag_core::config::{GeneratorConfig, GeneratorId, OutputLayout, SplitBy, ToolSetting};
2use oag_core::ir::IrSpec;
3use oag_core::{CodeGenerator, GeneratedFile, GeneratorError};
4
5use crate::emitters;
6use crate::emitters::scaffold::{NodeScaffoldConfig, ScaffoldOptions};
7
8/// TypeScript/Node code generator.
9pub struct NodeClientGenerator;
10
11impl NodeClientGenerator {
12    /// Build scaffold options from a GeneratorConfig.
13    pub fn build_scaffold_options(
14        ir: &IrSpec,
15        config: &GeneratorConfig,
16        react: bool,
17    ) -> Option<ScaffoldOptions> {
18        let raw = config.scaffold.as_ref()?;
19        let scaffold: NodeScaffoldConfig = serde_json::from_value(raw.clone()).ok()?;
20        Some(ScaffoldOptions {
21            name: ir.info.title.clone(),
22            package_name: scaffold.package_name,
23            repository: scaffold.repository,
24            formatter: ToolSetting::resolve(scaffold.formatter.as_ref(), "biome").map(String::from),
25            test_runner: ToolSetting::resolve(scaffold.test_runner.as_ref(), "vitest")
26                .map(String::from),
27            bundler: ToolSetting::resolve(scaffold.bundler.as_ref(), "tsdown").map(String::from),
28            react,
29        })
30    }
31}
32
33impl CodeGenerator for NodeClientGenerator {
34    fn id(&self) -> GeneratorId {
35        GeneratorId::NodeClient
36    }
37
38    fn generate(
39        &self,
40        ir: &IrSpec,
41        config: &GeneratorConfig,
42    ) -> Result<Vec<GeneratedFile>, GeneratorError> {
43        let no_jsdoc = config.no_jsdoc.unwrap_or(false);
44        let scaffold_options = Self::build_scaffold_options(ir, config, false);
45
46        let mut files = match config.layout {
47            OutputLayout::Bundled => {
48                let content = emitters::bundled::emit_bundled(ir, no_jsdoc);
49                vec![GeneratedFile {
50                    path: "index.ts".to_string(),
51                    content,
52                }]
53            }
54            OutputLayout::Modular => {
55                vec![
56                    GeneratedFile {
57                        path: "types.ts".to_string(),
58                        content: emitters::types::emit_types(ir),
59                    },
60                    GeneratedFile {
61                        path: "sse.ts".to_string(),
62                        content: emitters::sse::emit_sse(),
63                    },
64                    GeneratedFile {
65                        path: "client.ts".to_string(),
66                        content: emitters::client::emit_client(ir, no_jsdoc),
67                    },
68                    GeneratedFile {
69                        path: "index.ts".to_string(),
70                        content: emitters::index::emit_index(),
71                    },
72                ]
73            }
74            OutputLayout::Split => {
75                let split_by = config.split_by.unwrap_or(SplitBy::Tag);
76                emitters::split::emit_split(ir, no_jsdoc, split_by)
77            }
78        };
79
80        if let Some(ref scaffold) = scaffold_options {
81            files.extend(emitters::scaffold::emit_scaffold(scaffold));
82
83            if scaffold.test_runner.is_some() {
84                files.push(GeneratedFile {
85                    path: "client.test.ts".to_string(),
86                    content: emitters::tests::emit_client_tests(ir),
87                });
88            }
89        }
90
91        Ok(files)
92    }
93}