/tauri-specta

Completely typesafe Tauri commands

Primary LanguageRustMIT LicenseMIT

Specta Logo

Tauri Specta

Typesafe Tauri commands

Discord Crates.io crates.io docs.rs License

This branch contains the code for tauri-specta v2. You can check the v1.0.2 git tag for the v1 code.

Install

Specta v1

cargo add specta
cargo add tauri-specta --features javascript,typescript

Specta v2

Specta v2 hasn't officially launched yet but it can be used through the release candidate (rc) versions.

You must ensure you lock your Specta version to avoid breaking changes.

cargo add specta@=2.0.0-rc.7
cargo add tauri-specta@=2.0.0-rc.4 --features javascript,typescript

Adding Specta to custom types

use specta::Type;
use serde::{Deserialize, Serialize};

// The `specta::Type` macro allows us to understand your types
// We implement `specta::Type` on primitive types for you.
// If you want to use a type from an external crate you may need to enable the feature on Specta.
#[derive(Serialize, Type)]
pub struct MyCustomReturnType {
    pub some_field: String,
}

#[derive(Deserialize, Type)]
pub struct MyCustomArgumentType {
    pub foo: String,
    pub bar: i32,
}

Annotate your Tauri commands with Specta

#[tauri::command]
#[specta::specta] // <-- This bit here
fn greet3() -> MyCustomReturnType {
    MyCustomReturnType {
        some_field: "Hello World".into(),
    }
}

#[tauri::command]
#[specta::specta] // <-- This bit here
fn greet(name: String) -> String {
  format!("Hello {name}!")
}

Export your bindings

use specta::collect_types;
use tauri_specta::{ts, js};

// this example exports your types on startup when in debug mode. You can do whatever.

fn main() {
    let specta_builder = {
        // You can use `tauri_specta::js::builder` for exporting JS Doc instead of Typescript!`
        let specta_builder = tauri_specta::ts::builder()
            .commands(tauri_specta::collect_commands![greet, greet2, greet3 ]); // <- Each of your comments


        #[cfg(debug_assertions)] // <- Only export on non-release builds
        let specta_builder = specta_builder.path("../src/bindings.ts");

        specta_builder.into_plugin()
    };

    tauri::Builder::default()
        .plugin(specta_builder)
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

Usage on frontend

import * as commands from "./bindings"; // This should point to the file we export from Rust

await commands.greet("Brendan");

Events

To use Events you must be using Specta v2 and Tauri Specta v2.

Firstly you have to define your event types. You can add as many of these as you want.

#[derive(Debug, Clone, Serialize, Deserialize, specta::Type, tauri_specta::Event)]
pub struct DemoEvent(String);

Next you must add it to the builder like the following:

let specta_builder = ts::builder()
        .events(tauri_specta::collect_events![DemoEvent]); // This should contain all your events.

Then it can be used in Rust like the following:

tauri::Builder::default()
    .setup(|app| {
        let handle = app.handle();

        DemoEvent::listen_global(&handle, |event| {
            dbg!(event.payload);
        });

        DemoEvent("Test".to_string()).emit_all(&handle).unwrap();
    });

and it can be used in TS like the following:

import { commands, events } from "./bindings";
import { appWindow } from "@tauri-apps/api/window";

// For all windows
events.demoEvent.listen((e) => console.log(e));

// For a single window
events.demoEvent(appWindow).listen((e) => console.log(e));

// Emit to the backend and all windows
await events.demoEvent.emit("Test")

// Emit to a window
await events.demoEvent(appWindow).emit("Test")

Known limitations

  • Your command can only take up to 10 arguments. Any more and you'll get a compile error. If you need more just use a struct.
  • Exporting your schema within a directory tracked by Tauri's hot reload will cause an infinite reload loop.

Development

Run the example:

pnpm i
cd examples/app/
pnpm dev

Credit

Created by oscartbeaumont and Brendonovich.