tauri_specta/
ts.rs

1use std::path::Path;
2
3use specta::{functions::FunctionDataType, ts::TsExportError, ExportError, TypeDefs};
4
5use crate::ExportLanguage;
6
7/// Building blocks for [`export`] and [`export_with_cfg`].
8///
9/// These are made available for advanced use cases where you may combine Tauri Specta with another
10/// Specta-enabled library.
11pub mod internal {
12    use heck::ToLowerCamelCase;
13    use indoc::formatdoc;
14
15    use crate::DO_NOT_EDIT;
16    use specta::{
17        functions::FunctionDataType,
18        ts::{self, TsExportError},
19        TypeDefs,
20    };
21
22    /// Type definitions and constants that the generated functions rely on
23    pub fn globals() -> String {
24        formatdoc! {
25            r#"
26            declare global {{
27                interface Window {{
28                    __TAURI_INVOKE__<T>(cmd: string, args?: Record<string, unknown>): Promise<T>;
29                }}
30            }}
31
32            // Function avoids 'window not defined' in SSR
33            const invoke = () => window.__TAURI_INVOKE__;"#
34        }
35    }
36
37    /// Renders a collection of [`FunctionDataType`] into a TypeScript string.
38    pub fn render_functions(
39        function_types: Vec<FunctionDataType>,
40        cfg: &specta::ts::ExportConfiguration,
41    ) -> Result<String, TsExportError> {
42        function_types
43            .into_iter()
44            .map(|function| {
45                let name = &function.name;
46                let name_camel = function.name.to_lower_camel_case();
47
48                let arg_defs = function
49                    .args
50                    .iter()
51                    .map(|(name, typ)| {
52                        ts::datatype(cfg, typ)
53                            .map(|ty| format!("{}: {}", name.to_lower_camel_case(), ty))
54                    })
55                    .collect::<Result<Vec<_>, _>>()?
56                    .join(", ");
57
58                let ret_type = ts::datatype(cfg, &function.result)?;
59
60                let arg_usages = function
61                    .args
62                    .iter()
63                    .map(|(name, _)| name.to_lower_camel_case())
64                    .collect::<Vec<_>>();
65
66                let arg_usages = arg_usages
67                    .is_empty()
68                    .then(Default::default)
69                    .unwrap_or_else(|| format!(", {{ {} }}", arg_usages.join(",")));
70
71                let docs = specta::ts::js_doc(&function.docs);
72
73                Ok(formatdoc!(
74                    r#"
75                    {docs}export function {name_camel}({arg_defs}) {{
76                        return invoke()<{ret_type}>("{name}"{arg_usages})
77                    }}"#
78                ))
79            })
80            .collect::<Result<Vec<_>, _>>()
81            .map(|v| v.join("\n\n"))
82    }
83
84    /// Renders the output of [`globals`], [`render_functions`] and all dependant types into a TypeScript string.
85    pub fn render(
86        function_types: Vec<FunctionDataType>,
87        type_map: TypeDefs,
88        cfg: &specta::ts::ExportConfiguration,
89    ) -> Result<String, TsExportError> {
90        let globals = globals();
91
92        let functions = render_functions(function_types, cfg)?;
93
94        let dependant_types = type_map
95            .values()
96            .filter_map(|v| v.as_ref())
97            .map(|v| ts::export_datatype(cfg, v))
98            .collect::<Result<Vec<_>, _>>()
99            .map(|v| v.join("\n"))?;
100
101        Ok(formatdoc! {
102            r#"
103                {DO_NOT_EDIT}
104
105                {globals}
106
107                {functions}
108
109                {dependant_types}
110            "#
111        })
112    }
113}
114
115/// Implements [`ExportLanguage`] for TypeScript exporting
116pub struct Language;
117
118/// [`Exporter`](crate::Exporter) for TypeScript
119pub type Exporter = crate::Exporter<Language>;
120
121impl ExportLanguage for Language {
122    fn globals() -> String {
123        internal::globals()
124    }
125
126    fn render_functions(
127        function_types: Vec<FunctionDataType>,
128        cfg: &specta::ts::ExportConfiguration,
129    ) -> Result<String, TsExportError> {
130        internal::render_functions(function_types, cfg)
131    }
132
133    fn render(
134        function_types: Vec<FunctionDataType>,
135        type_map: TypeDefs,
136        cfg: &specta::ts::ExportConfiguration,
137    ) -> Result<String, TsExportError> {
138        internal::render(function_types, type_map, cfg)
139    }
140}
141
142/// Exports the output of [`internal::render`] for a collection of [`FunctionDataType`] into a TypeScript file.
143/// Allows for specifying a custom [`ExportConfiguration`](specta::ts::ExportConfiguration).
144pub fn export_with_cfg(
145    result: (Vec<FunctionDataType>, TypeDefs),
146    cfg: specta::ts::ExportConfiguration,
147    export_path: impl AsRef<Path>,
148) -> Result<(), TsExportError> {
149    Exporter::new(Ok(result), export_path)
150        .with_cfg(cfg)
151        .export()
152}
153
154/// Exports the output of [`internal::render`] for a collection of [`FunctionDataType`] into a TypeScript file.
155pub fn export(
156    macro_data: Result<(Vec<FunctionDataType>, TypeDefs), ExportError>,
157    export_path: impl AsRef<Path>,
158) -> Result<(), TsExportError> {
159    Exporter::new(macro_data, export_path).export()
160}