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            existing_repo: scaffold.existing_repo.unwrap_or(false),
30        })
31    }
32}
33
34impl CodeGenerator for NodeClientGenerator {
35    fn id(&self) -> GeneratorId {
36        GeneratorId::NodeClient
37    }
38
39    fn generate(
40        &self,
41        ir: &IrSpec,
42        config: &GeneratorConfig,
43    ) -> Result<Vec<GeneratedFile>, GeneratorError> {
44        let no_jsdoc = config.no_jsdoc.unwrap_or(false);
45        let scaffold_options = Self::build_scaffold_options(ir, config, false);
46
47        let mut files = match config.layout {
48            OutputLayout::Bundled => {
49                let content = emitters::bundled::emit_bundled(ir, no_jsdoc);
50                vec![GeneratedFile {
51                    path: "src/index.ts".to_string(),
52                    content,
53                }]
54            }
55            OutputLayout::Modular => {
56                vec![
57                    GeneratedFile {
58                        path: "src/types.ts".to_string(),
59                        content: emitters::types::emit_types(ir),
60                    },
61                    GeneratedFile {
62                        path: "src/sse.ts".to_string(),
63                        content: emitters::sse::emit_sse(),
64                    },
65                    GeneratedFile {
66                        path: "src/client.ts".to_string(),
67                        content: emitters::client::emit_client(ir, no_jsdoc),
68                    },
69                    GeneratedFile {
70                        path: "src/index.ts".to_string(),
71                        content: emitters::index::emit_index(),
72                    },
73                ]
74            }
75            OutputLayout::Split => {
76                let split_by = config.split_by.unwrap_or(SplitBy::Tag);
77                emitters::split::emit_split(ir, no_jsdoc, split_by)
78            }
79        };
80
81        if let Some(ref scaffold) = scaffold_options {
82            files.extend(emitters::scaffold::emit_scaffold(scaffold));
83
84            if scaffold.test_runner.is_some() {
85                files.push(GeneratedFile {
86                    path: "src/client.test.ts".to_string(),
87                    content: emitters::tests::emit_client_tests(ir),
88                });
89            }
90        }
91
92        Ok(files)
93    }
94}