tauri-specta

repository·main·Indexed 20 days ago

https://github.com/specta-rs/tauri-specta

Provides completely typesafe Tauri commands, enabling seamless type synchronization between Rust backend commands and TypeScript frontend calls. It includes support for generating type-safe TanStack Query helpers via tauri-specta-query for frameworks like React, Solid, Vue, Angular, Preact, and Svelte. Version 2.0.0-rc.25 supports Tauri v2 and includes additional support for generating types for events.

Tokens
8.9K
Snippets
30
Records
39
Agent score
72%

What's inside tauri-specta

  1. Use Tauri Specta Query to generate TanStack Query helpers

    main

    Tauri Specta Query is used to generate type-safe TanStack Query helpers for commands exported by tauri-specta. It allows you to bridge your Tauri backend commands with TanStack Query on the frontend, ensuring type safety across the IPC boundary.

    Commands are organized into CommandSet, which distinguishes between queries and mutations.

    // This package generates TanStack Query helpers for commands
    // exported by tauri-specta.
  2. Choose the correct Tauri Specta version

    main

    Before starting, ensure you select the version of tauri-specta that matches your Tauri version. Compatibility is strictly tied to both the Tauri version and the Specta version:

    • Tauri v1: Use Tauri Specta v1 (compatible with Specta v1).
    • Tauri v2: Use Tauri Specta v2 (compatible with Specta v2). Tauri Specta v2 includes additional support for generating types for events.

    Note: Specta v1 is unsupported on Tauri v2, and Specta v2 is unsupported on Tauri v1.

  3. Configure naming casing for generated accessors

    main

    Tauri Specta allows you to control how Rust identifiers (like command and event names) are transformed into JavaScript property names during code generation.

    By default, Tauri Specta converts Rust snake_case identifiers to JavaScript-idiomatic camelCase. You can override this using the Casing enum via the Builder::function_casing method.

    Important: This setting only affects the generated frontend property name. It does not rename command arguments or change the actual runtime IPC command string sent to Tauri.

    // Example of how this concept is applied via the Builder
    // (Note: Builder usage depends on the specific Builder implementation)
    let builder = Builder::new()
        .function_casing(Casing::SnakeCase);
  4. Understand Serde Phase-specific types

    main

    By default, Tauri Specta exports separate TypeScript aliases for serialization and deserialization phases if the shapes differ (e.g., due to #[serde(rename(...))] or skip_serializing).

    • MyType_Serialize: The shape used when sending data from Rust to JS.
    • MyType_Deserialize: The shape used when receiving data from JS to Rust.
    • MyType: An alias representing the serialization shape.

    If you want to disable this behavior and use a single type, use Builder::disable_serde_phases.

  5. How CommandSet works in tauri-specta-query

    main

    The CommandSet is the central orchestrator for generating TanStack Query bindings. It manages the lifecycle of command registration, type registration, and constant definition.

    Workflow

    1. Initialization: Create a set via CommandSet::new(queries, mutations). Commands must be unique to one collection to avoid ambiguous helpers.
    2. Augmentation: Before building, you can add:
      • Standalone Types: Use .typ::<T>() for types not reachable via commands/events.
      • Constants: Use .constant(key, value) to export serializable values.
      • Events: Use .events(events) to register frontend event bindings.
      • Casing: Use .function_casing(casing) to control the naming convention of the generated TypeScript helpers.
    3. Finalization: Call .build(framework) which returns a tuple: (String, tauri_specta::Builder<R>). The String is the raw TS code for the helpers, and the Builder is used for the actual Tauri integration.
  6. Quickstart: Configure Tauri Specta in your application

    main

    To get started with Tauri Specta, use the Builder to register commands and events, then export the bindings to a file. You must also register the invoke_handler and call mount_events during the Tauri app setup for the bindings to function correctly.

    TypeScript Export Example

    use tauri_specta::{collect_commands, collect_events, Builder};
    use specta_typescript::Typescript;
    
    let mut builder = Builder::new()
        .commands(collect_commands![])
        .events(collect_events![]);
    
    #[cfg(debug_assertions)]
    builder
        .export(Typescript::default(), "../src/bindings.ts")
        .expect("Failed to export typescript bindings");
    
    tauri::Builder::default()
        .invoke_handler(builder.invoke_handler()) // Required for commands to work
        .setup(move |app| {
            builder.mount_events(app); // Required for events to work
    
            Ok(())
        })
        .run(tauri::test::mock_context(tauri::test::noop_assets()))
        .expect("error while running tauri application");

    JSDoc Export Example

    use tauri_specta::{collect_commands,collect_events,Builder};
    use specta_typescript::JSDoc;
    
    let mut builder = Builder::new()
        .commands(collect_commands![])
        .events(collect_events![]);
    
    // exporting to JsDoc
    #[cfg(debug_assertions)]
    builder
        .export(JSDoc::default(), "../src/bindings.js")
        .expect("Failed to export jsdoc bindings");
    
    tauri::Builder::default()
        .invoke_handler(builder.invoke_handler()) // Required for commands to work
        .setup(move |app| {
            builder.mount_events(app); // Required for events to work
    
            Ok(())
        })
        .run(tauri::test::mock_context(tauri::test::noop_assets()))
        .expect("error while running tauri application");
    use tauri_specta::{collect_commands, collect_events, Builder};
    use specta_typescript::Typescript;
    
    let mut builder = Builder::new()
        .commands(collect_commands![])
        .events(collect_events![]);
    
    #[cfg(debug_assertions)]
    builder
        .export(Typescript::default(), "../src/bindings.ts")
        .expect("Failed to export typescript bindings");
    
    tauri::Builder::default()
        .invoke_handler(builder.invoke_handler())
        .setup(move |app| {
            builder.mount_events(app);
            Ok(())
        })
        .run(tauri::test::mock_context(tauri::test::noop_assets()))
        .expect("error while running tauri application");
  7. Set up Tauri Specta with TypeScript

    main

    To use TypeScript bindings, follow these steps:

    1. Annotate your Tauri commands with #[specta::specta].
    2. Initialize a Builder and register your commands using collect_commands![].
    3. Export the bindings using builder.export() (typically on non-release builds).
    4. Register the builder's invoke_handler() with your Tauri application.
    5. If using events, call builder.mount_events(app) in the Tauri setup closure.
    #![cfg_attr(
        all(not(debug_assertions), target_os = "windows"),
        windows_subsystem = "windows"
    )]
    
    use serde::{Deserialize, Serialize};
    use specta_typescript::Typescript;
    use tauri_specta::{collect_commands, Builder};
    
    #[tauri::command]
    #[specta::specta] // < You must annotate your commands
    fn hello_world(my_name: String) -> String {
        format!("Hello, {my_name}! You've been greeted from Rust!")
    }
    
    fn main() {
        let mut builder = Builder::new()
            // Then register them (separated by a comma)
            .commands(collect_commands![hello_world,]);
    
        #[cfg(debug_assertions)] // <- Only export on non-release builds
        builder
            .export(Typescript::default(), "../src/bindings.ts")
            .expect("Failed to export typescript bindings");
    
        tauri::Builder::default()
            // and finally tell Tauri how to invoke them
            .invoke_handler(builder.invoke_handler())
            .setup(move |app| {
                // This is also required if you want to use events
                builder.mount_events(app);
    
                Ok(())
            })
            .run(tauri::test::mock_context(tauri::test::noop_assets()))
            .expect("error while running tauri application");
    }
  8. How to use type-safe Events

    main

    To make events type-safe:

    1. Derive tauri_specta::Event on your event struct.
    2. Register the event using collect_events![] on the builder.
    3. Call builder.mount_events(app) in the Tauri setup closure.
    4. Use the generated events object on the frontend to listen or emit.
    use serde::{Serialize, Deserialize};
    use specta::Type;
    use tauri_specta::{Builder, collect_commands, collect_events, Event};
    
    // Add tauri_specta::Event to your event
    #[derive(Serialize, Deserialize, Debug, Clone, Type, Event)]
    pub struct DemoEvent(String);
    
    let mut builder = Builder::new()
            // And then register it to your builder
            .events(collect_events![DemoEvent]);
    
    // ... in Tauri setup ...
    builder.mount_events(app);
    
    // Backend usage:
    DemoEvent("Test".into()).emit(app).unwrap();

    Frontend usage:

    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")
  9. Register custom types with Specta

    main

    To allow Specta to understand your custom structs, you must derive specta::Type (alongside Serialize and Deserialize). You can also manually register types using .typ::<T>() on the builder.

    use serde::{Serialize, Deserialize};
    use specta::Type;
    
    #[derive(Serialize, Deserialize, Type)]
    pub struct MyStruct {
        a: String
    }
    
    // Register the type manually
    let mut builder = tauri_specta::Builder::<tauri::Wry>::new().typ::<MyStruct>();
  10. Install Tauri Specta

    main

    To get started, add the required dependencies to your Cargo.toml.

    Note: Tauri Specta v2 is in beta and requires using Specta v2 beta. It is highly recommended to use = before your version numbers to prevent breaking changes during the beta period.

    Run the following commands:

    # Add Tauri and Specta beta dependencies
    cargo add tauri@2.0 specta@=2.0.0-rc.25 specta-typescript@0.0.12
    
    # Add tauri-specta with required features
    # Use `javascript` for JSDoc support, or `typescript` for TypeScript support
    cargo add tauri-specta@=2.0.0-rc.25 --features derive,typescript,javascript
    cargo add tauri@2.0 specta@=2.0.0-rc.25 specta-typescript@0.0.12
    cargo add tauri-specta@=2.0.0-rc.25 --features derive,typescript,javascript