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