tauri_specta/
js.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    };
20
21    /// Constants that the generated functions rely on
22    pub fn globals() -> String {
23        formatdoc! {
24            r#"
25            // Late bind to avoid 'window not defined' in SSR
26            const invoke = () => window.__TAURI_INVOKE__;"#
27        }
28    }
29
30    /// Renders a collection of [`FunctionDataType`] into a JavaScript string.
31    pub fn render_functions(
32        function_types: Vec<FunctionDataType>,
33        cfg: &specta::ts::ExportConfiguration,
34    ) -> Result<String, TsExportError> {
35        function_types
36            .into_iter()
37            .map(|function| {
38                let name = &function.name;
39                let name_camel = function.name.to_lower_camel_case();
40
41                let arg_list = function
42                    .args
43                    .iter()
44                    .map(|(name, _)| name.to_lower_camel_case())
45                    .collect::<Vec<_>>();
46
47                let arg_defs = arg_list.join(", ");
48
49                let arg_usages = arg_list
50                    .is_empty()
51                    .then(Default::default)
52                    .unwrap_or_else(|| format!(", {{ {} }}", arg_list.join(", ")));
53
54                let ret_type = ts::datatype(cfg, &function.result)?;
55
56                let jsdoc = {
57                    let vec = []
58                        .into_iter()
59                        .chain(
60                            function
61                                .docs
62                                .into_iter()
63                                .map(str::to_owned)
64                                .collect::<Vec<_>>(),
65                        )
66                        .chain(function.args.iter().flat_map(|(name, typ)| {
67                            ts::datatype(cfg, typ).map(|typ| {
68                                let name = name.to_lower_camel_case();
69
70                                format!("@param {{ {typ} }} {name}")
71                            })
72                        }))
73                        .chain([format!("@returns {{ Promise<{ret_type}> }}")])
74                        .collect::<Vec<_>>();
75
76                    specta::ts::js_doc(&vec.iter().map(|s| s.as_str()).collect::<Vec<_>>())
77                };
78
79                Ok(formatdoc! {
80                    r#"
81                    {jsdoc} export function {name_camel}({arg_defs}) {{
82                        return invoke()("{name}"{arg_usages})
83                    }}"#
84                })
85            })
86            .collect::<Result<Vec<_>, _>>()
87            .map(|v| v.join("\n\n"))
88    }
89
90    /// Renders the output of [`globals`] and [`render_functions`] into a TypeScript string.
91    pub fn render(
92        function_types: Vec<FunctionDataType>,
93        cfg: &specta::ts::ExportConfiguration,
94    ) -> Result<String, TsExportError> {
95        let globals = globals();
96
97        let functions = render_functions(function_types, cfg)?;
98
99        Ok(formatdoc! {
100            r#"
101                {DO_NOT_EDIT}
102
103                {globals}
104
105                {functions}
106            "#
107        })
108    }
109}
110
111/// Implements [`ExportLanguage`] for JS exporting
112pub struct Language;
113
114/// [`Exporter`](crate::Exporter) for JavaScript
115pub type Exporter = crate::Exporter<Language>;
116
117impl ExportLanguage for Language {
118    fn globals() -> String {
119        internal::globals()
120    }
121
122    fn render_functions(
123        function_types: Vec<FunctionDataType>,
124        cfg: &specta::ts::ExportConfiguration,
125    ) -> Result<String, TsExportError> {
126        internal::render_functions(function_types, cfg)
127    }
128
129    fn render(
130        function_types: Vec<FunctionDataType>,
131        _: TypeDefs,
132        cfg: &specta::ts::ExportConfiguration,
133    ) -> Result<String, TsExportError> {
134        internal::render(function_types, cfg)
135    }
136}
137
138/// Exports the output of [`internal::render`] for a collection of [`FunctionDataType`] into a JavaScript file.
139/// Allows for specifying a custom [`ExportConfiguration`](specta::ts::ExportConfiguration).
140pub fn export_with_cfg(
141    result: (Vec<FunctionDataType>, TypeDefs),
142    export_path: impl AsRef<Path>,
143    cfg: specta::ts::ExportConfiguration,
144) -> Result<(), TsExportError> {
145    Exporter::new(Ok(result), export_path)
146        .with_cfg(cfg)
147        .export()
148}
149
150/// Exports the output of [`internal::render`] for a collection of [`FunctionDataType`] into a JavaScript file.
151pub fn export(
152    macro_data: Result<(Vec<FunctionDataType>, TypeDefs), ExportError>,
153    export_path: impl AsRef<Path>,
154) -> Result<(), TsExportError> {
155    Exporter::new(macro_data, export_path).export()
156}