Git Product home page Git Product logo

tauri-specta's Introduction

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.

tauri-specta's People

Contributors

brendonovich avatar dependabot[bot] avatar linden-dg avatar oscartbeaumont avatar probablykasper avatar suyashtnt avatar tbrockman avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

tauri-specta's Issues

Cannot bind tauri::Window inside a custom plugin

Version v2.0.0-rc.4

To reproduce

  1. In the custom-plugin example, add a parameter to add_numbers with the type tauri::Window:
#[tauri::command]
#[specta::specta]
fn add_numbers(
  a: i32,
  b: i32,
  window: tauri::Window // <--
) -> i32 {
    a + b
}
  1. Try to compile the example. It will fail with this error message:
   Compiling tauri-specta-example-custom-plugin v0.1.0 (C:\Users\jtiinane\Downloads\tauri-specta\examples\custom-plugin\plugin)
error[E0308]: mismatched types
  --> examples\custom-plugin\plugin\src\lib.rs:30:5
   |
27 |   pub fn init<R: Runtime>() -> TauriPlugin<R> {
   |               -                -------------- expected `TauriPlugin<R>` because of return type
   |               |
   |               this type parameter
...
30 | /     Builder::new(PLUGIN_NAME)
31 | |         .invoke_handler(plugin_utils.invoke_handler)
32 | |         .setup(move |app| {
33 | |             let app = app.clone();
...  |
42 | |         })
43 | |         .build()
   | |________________^ expected `TauriPlugin<R>`, found `TauriPlugin<Wry<EventLoopMessage>, _>`
   |
   = note: expected struct `TauriPlugin<R, ()>`
              found struct `TauriPlugin<tauri_runtime_wry::Wry<EventLoopMessage>, _>`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `tauri-specta-example-custom-plugin` (lib) due to previous error
warning: build failed, waiting for other jobs to finish...
error: could not compile `tauri-specta-example-custom-plugin` (lib test) due to previous error

Crash when maximizing window in command

I've tried to make a minimal example from the issue I was facing. The main problem is with the function close_splashscreen. The code below makes the app freeze and crash on startup.

Changing fn close_splashscreen to async close_splashscreen fixes it. Not using specta, and instead just calling normal tauri commands also fixes it, so I believe this is a tauri-specta specific issue.

(tauri.conf.json)
...
    "windows": [
      {
        "title": "test-maximize",
        "maximized": false
      }
    ],
...
import { useEffect } from "react";
import { commands } from "./bindings";

function App() {
  useEffect(() => {
    commands.closeSplashscreen();
  }, []);

  return <h1>Test</h1>;
}

export default App;
use tauri::{Manager, Window};

#[tauri::command]
#[specta::specta]
fn close_splashscreen(window: Window) -> Result<(), String> {
    window
        .get_window("main")
        .expect("no window labeled 'main' found")
        .maximize()
        .unwrap();
    Ok(())
}

fn main() {
    let specta_builder = {
        let specta_builder = tauri_specta::ts::builder()
            .commands(tauri_specta::collect_commands!(close_splashscreen,));

        #[cfg(debug_assertions)]
        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");
}

Versions

tauri = { version = "1.5", features = ["shell-open"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tauri-specta = { version = "=2.0.0-rc.4", features = ["javascript", "typescript"] }
specta = "=2.0.0-rc.7"

Optional input argument

Not sure how it would be done, but would be great to have the input be optional when a function has no argument. Currently you have to supply null. Setting the type to void might be an option.

Build-time type generation

Hi,

does tauri-specta support build-time generation of types? generating them at runtime seems... sketchy, and requires a development build and run of that development build before a production build in order to guarantee typesafety. is there any way around this (via build-time generation of types for example)?

cheers & thanks

error[E0053]: method `inline` has an incompatible type for trait

Hello , using v2.0.0-rc.5 i am getting this error

  Compiling specta v2.0.0-rc.5
error[E0053]: method `inline` has an incompatible type for trait
   --> C:\Users\User\.cargo\registry\src\index.crates.io-6f17d22bba15001f\specta-2.0.0-rc.5\src\type\impls.rs:169:10
    |
169 | #[derive(Type)]
    |          ^^^^
    |          |
    |          expected `datatype::DefOpts<'_>`, found `&mut BTreeMap<SpectaID, ...>`
    |          help: change the parameter type to match the trait: `datatype::DefOpts<'_>`
    |
note: type in trait
   --> C:\Users\User\.cargo\registry\src\index.crates.io-6f17d22bba15001f\specta-2.0.0-rc.5\src\type\mod.rs:22:21
    |
22  |     fn inline(opts: DefOpts, generics: &[DataType]) -> DataType;
    |                     ^^^^^^^
    = note: expected signature `fn(datatype::DefOpts<'_>, &[datatype::DataType]) -> datatype::DataType`
               found signature `fn(&mut std::collections::BTreeMap<specta_id::SpectaID, Option<named::NamedDataType>>, &[datatype::DataType]) -> datatype::DataType`
    = note: this error originates in the derive macro `Type` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0053]: method `definition` has an incompatible type for trait
   --> C:\Users\User\.cargo\registry\src\index.crates.io-6f17d22bba15001f\specta-2.0.0-rc.5\src\type\impls.rs:169:10
    |
169 | #[derive(Type)]
    |          ^^^^
    |          |
    |          expected `datatype::DefOpts<'_>`, found `&mut BTreeMap<SpectaID, ...>`
    |          help: change the parameter type to match the trait: `datatype::DefOpts<'_>`
    |
note: type in trait
   --> C:\Users\User\.cargo\registry\src\index.crates.io-6f17d22bba15001f\specta-2.0.0-rc.5\src\type\mod.rs:37:25
    |
37  |     fn definition(opts: DefOpts) -> DataType {
    |                         ^^^^^^^
    = note: expected signature `fn(datatype::DefOpts<'_>) -> datatype::DataType`
               found signature `fn(&mut std::collections::BTreeMap<specta_id::SpectaID, Option<named::NamedDataType>>) -> datatype::DataType`
    = note: this error originates in the derive macro `Type` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0053]: method `reference` has an incompatible type for trait
   --> C:\Users\User\.cargo\registry\src\index.crates.io-6f17d22bba15001f\specta-2.0.0-rc.5\src\type\impls.rs:169:10
    |
169 | #[derive(Type)]
    |          ^^^^
    |          |
    |          expected `datatype::DefOpts<'_>`, found `&mut BTreeMap<SpectaID, ...>`
    |          help: change the parameter type to match the trait: `datatype::DefOpts<'_>`
    |
note: type in trait
   --> C:\Users\User\.cargo\registry\src\index.crates.io-6f17d22bba15001f\specta-2.0.0-rc.5\src\type\mod.rs:51:24
    |
51  |     fn reference(opts: DefOpts, generics: &[DataType]) -> Reference {
    |                        ^^^^^^^
    = note: expected signature `fn(datatype::DefOpts<'_>, &[datatype::DataType]) -> Reference`
               found signature `fn(&mut std::collections::BTreeMap<specta_id::SpectaID, Option<named::NamedDataType>>, &[datatype::DataType]) -> Reference`
    = note: this error originates in the derive macro `Type` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0053]: method `named_data_type` has an incompatible type for trait
   --> C:\Users\User\.cargo\registry\src\index.crates.io-6f17d22bba15001f\specta-2.0.0-rc.5\src\type\impls.rs:169:10
    |
169 | #[derive(Type)]
    |          ^^^^
    |          |
    |          expected `datatype::DefOpts<'_>`, found `&mut BTreeMap<SpectaID, ...>`
    |          help: change the parameter type to match the trait: `datatype::DefOpts<'_>`
    |
note: type in trait
   --> C:\Users\User\.cargo\registry\src\index.crates.io-6f17d22bba15001f\specta-2.0.0-rc.5\src\type\mod.rs:63:30
    |
63  |     fn named_data_type(opts: DefOpts, generics: &[DataType]) -> NamedDataType;
    |                              ^^^^^^^
    = note: expected signature `fn(datatype::DefOpts<'_>, &[datatype::DataType]) -> named::NamedDataType`
               found signature `fn(&mut std::collections::BTreeMap<specta_id::SpectaID, Option<named::NamedDataType>>, &[datatype::DataType]) -> named::NamedDataType`
    = note: this error originates in the derive macro `Type` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0053]: method `definition_named_data_type` has an incompatible type for trait
   --> C:\Users\User\.cargo\registry\src\index.crates.io-6f17d22bba15001f\specta-2.0.0-rc.5\src\type\impls.rs:169:10
    |
169 | #[derive(Type)]
    |          ^^^^
    |          |
    |          expected `datatype::DefOpts<'_>`, found `&mut BTreeMap<SpectaID, ...>`
    |          help: change the parameter type to match the trait: `datatype::DefOpts<'_>`
    |
note: type in trait

Here are my code
cargo.toml

specta = "=2.0.0-rc.5"
tauri-specta = { version = "=2.0.0-rc.2", features = ["javascript", "typescript"] }

response.rs

use specta::Type;
use serde::{Deserialize, Serialize};
use super::auth::AccessToken;
// save token response 
#[derive(Serialize, Type)]
pub struct SaveTokenResponse {
    pub message: String,
    pub token: String,
    pub success: bool,
}


#[derive(Serialize, Type)]
pub struct SaveAccessTokenResponse {
    pub message: String,
    pub success: bool,
    pub token: AccessToken,
}


#[derive(Serialize, Type)]
pub struct GreetResponse {
    pub message: String,
}

argument.rs

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


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

tauri_command.rs

use std::env;

use tauri;

// super mod imports;
use super::auth::{AccessToken, Auth};
use super::filehelper::{ENV_FILE, ACCESS_TOKEN_FILE};



#[tauri::command]
#[specta::specta]
pub fn test_command() -> String {
    println!("access token path: {}", &*ACCESS_TOKEN_FILE);
    println!("ENV_FILE: {}", ENV_FILE);
    return env::var("DB_PASSWORD").unwrap();
}

#[tauri::command]
#[specta::specta]
pub fn greet(name: String) -> GreetResponse {
    GreetResponse {
        message: format!("Hello, {}! You've been greeted from Rust!", name),
    };
}

// remember to call `.manage(MyState::default())`
#[tauri::command]
#[specta::specta]
pub fn save_access_token(token: String) -> SaveAccessTokenResponse {
    dotenv::from_filename(ENV_FILE).ok();
    return Auth::save_access_token(token);
}

// a command to load the access token from the file
#[tauri::command]
#[specta::specta]
pub fn load_access_token() -> AccessToken {
    dotenv::from_filename(ENV_FILE).ok();
    return Auth::load_access_token();
}


#[tauri::command]
#[specta::specta]
pub fn save_code(code: String) -> SaveTokenResponse {
    dotenv::from_filename(ENV_FILE).ok();
    return Auth::save_auth_code(code);
}

#[tauri::command]
#[specta::specta]
pub fn load_code() -> String {
    dotenv::from_filename(ENV_FILE).ok();
    return Auth::load_auth_code();
}

main.rs

// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

mod libs;

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


use libs::tauri_actions::{save_access_token,load_access_token, greet, test_command, save_code, load_code};
use libs::filehelper::{ENV_FILE, initialize_user_files};


fn specta_builder() {
    let specta_builder = tauri_specta::ts::builder()
        .commands(tauri_specta::collect_commands![
            save_access_token,
            load_access_token,
            greet,
            test_command,
            save_code,
            load_code,
        ]);

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

    specta_builder.into_plugin()
}

fn main() {
    dotenv::from_filename(ENV_FILE).ok();
    initialize_user_files();


    let specta_builder = specta_builder();

    tauri::Builder::default()
    .plugin(tauri_plugin_single_instance::init(|app, argv, cwd| {
        println!("{}, {argv:?}, {cwd}", app.package_info().name);
        // app.emit_all("single-instance", Payload { args: argv, cwd }).unwrap();
    }))
        .plugin(specta_builder)
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

feat: add comments to disable linters

Comments to disable eslint, tslint, and prettier should be added at the top of the generated bindings. Otherwise, every time the bindings are regenerated they will likely violate the style guide. Alternatively, there should be a recommendation in the README to add the generated files to the ignore list for linters.

Future Stuff

Just dumping this here to get it outta the README.

  • Tauri event support #34
  • Reexport all the deps that the macro stuff needs.
  • Would be nice for it to be a single macro.
  • Stable OpenAPI support - Currently will crash if your types have generics.
  • Write exports for many different languages. Maybe for support with something like tauri-sys.
  • Clean up code
  • Proper unit tests

Should object/array function arguments be marked `readonly`?

I'm not amazing at typescript, but I'm running into an issue where I have a list of strings that are const so I can generate container types for them:

  const FILTER_KEYS = ["a","b","c"] as const;

If a specta-generated invoke needs a string[] argument, this needs a cast because Typescript can't guarantee it isn't mutated. But because invoke just serialises, I think it should be fine to add readonly to any list arguments (and potentially object arguments too).

Enums as HashMap keys generate invalid TypeScript definitions

This is the same issue as here: oscartbeaumont/specta#65
Which was already fixed in specta 1.0.3.

For some reason, the ts file exported by tauri-specta doesn't have this fix. I even tried running just specta directly from the same project (so using the exact same specta that tauri-specta is using), and the bug doesn't happen when using specta::ts::inline, but is still there when going through tauri-specta.

`mut` keyword included in command argument

This command:

#[command]
#[specta::specta]
pub async fn new_group(mut group: Group) {}

gets this binding:

export function newGroup(mutGroup: Group) {
    return invoke<Group[]>("new_group", { mutGroup })
}

bigint types?

Some types, like u64 and i64, bind to BigInt. Does Tauri ever actually serialize to BigInt? My OS webview version doesn't support it

[question] Optional Types?

Hi,

Thanks for this great plugin, is there a way to generate Option<T> types as ? optionals in TS?

Thanks

Infer plugin name from Tauri plugin

Right now it's taken in a string provided by the user but can we maybe PR to Tauri to expose an API for us to capture it? Idk, worth a try but not a major issue.

tauri with nextjs requires importing invoke from tauri-apps/api/tauri

Currently tauri-specta generates a binding.ts file with the following line:

import { invoke as TAURI_INVOKE } from "@tauri-apps/api";

When building the app cargo tauri dev using the NextJS framework, prerendering the app pages gives the following error:

ReferenceError: window is not defined

If the import is manually changed to

import { invoke as TAURI_INVOKE } from "@tauri-apps/api/tauri";

Then building the app works fine.

window is not defined

I'm getting the following error with this generated code
declare global {
interface Window {
__TAURI_INVOKE__<T>(cmd: string, args?: Record<string, unknown>): Promise<T>;
}
}

const invoke = window.__TAURI_INVOKE__;

If I change it to this
import { invoke } from "@tauri-apps/api";
then everything works. I'm using sveltekit for fronted

Input casting/type

The invoke function casts falsy values to undefined. This means values like 0 and '' turn into undefined.

api.invoke(key, input || undefined);

tauri-specta doesn't export any errors as interfaces in TS

For example if I take this code
#[tauri::command]
#[specta::specta]
pub fn license_check(
window: tauri::Window, machine_id: tauri::State<Option<states::StateMachineId>>,
) -> std::result::Result<states::StateLicense, errors::XmlDosTracedError> {
let info = dto::states::StateLicense::check_license(&machine_id)?;
window.manage(info.clone());
Ok(info)
}

It's OK about states::StateLicense but I cannot get my XmlDosTracedError along with the other functions and interfaces in TS

My XmlDosTracedError is a struct:
#[derive(Debug, Serialize, Deserialize, Type)]
pub struct XmlDosTracedError {
pub file: String,
pub line: u32,
pub column: u32,
pub version: String,
pub error: String,
}

Inconsistent Order of Exported Types

Bet this is the same bug as rspc had to do with each OS exporting in a different order.

Just fix it in Specta's TypeID system too as a fallback.

Move into the Tauri org

There was interest early on in moving this repo into the Tauri org. It's a thin wrapper to tie Specta and Tauri so I think honestly that would be great. No pressure on the Tauri side if they have changed their mind.

Launching the next major version of tauri-specta is currently blocked on Specta v2 and Tauri v2 coming out. I am estimating Feb next year for Specta v2 to launch but we will see. I assume this would be a good time to move the repo as telling people to use beta releases isn't great for stability.

Using tauri-specta with tauri v2

tauri-specta v2 has dependency on tauri v1.

Is it possible to use tauri-specta with tauri v2 ?
Otherwise I have error:

error: failed to select a version for `tauri-plugin-window`.
    ... required by package `ergo-wallet v0.2.0 (/home/desktop/src-tauri)`
versions that meet the requirements `^2.0.0-alpha` are: 2.0.0-alpha.2, 2.0.0-alpha.1, 2.0.0-alpha.0

the package `tauri-plugin-window` links to the native library `gdk-3`, but it conflicts with a previous package which links to `gdk-3` as well:
package `tauri v1.5.1`
    ... which satisfies dependency `tauri = "^1.5.1"` of package `tauri-specta v2.0.0-rc.2`
    ... which satisfies dependency `tauri-specta = "=2.0.0-rc.2"` of package `ergo-wallet v0.2.0 (/home/desktop/src-tauri)`
Only one package in the dependency graph may specify the same links value. This helps ensure that only one copy of a native library is linked in the final binary. Try to adjust your dependencies so that only one package uses the links ='tauri-plugin-window' value. For more information, see https://doc.rust-lang.org/cargo/reference/resolver.html#links.

Best regards,

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.