{"id":15154141,"url":"https://github.com/matsdk/taurpc","last_synced_at":"2025-04-12T19:49:20.976Z","repository":{"id":170346877,"uuid":"646272610","full_name":"MatsDK/TauRPC","owner":"MatsDK","description":"Typesafe IPC layer for Tauri applications","archived":false,"fork":false,"pushed_at":"2025-04-08T15:32:04.000Z","size":919,"stargazers_count":128,"open_issues_count":3,"forks_count":9,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-12T19:49:12.556Z","etag":null,"topics":["rust","tauri"],"latest_commit_sha":null,"homepage":"https://docs.rs/taurpc/","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MatsDK.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE_APACHE-2.0","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-05-27T21:00:02.000Z","updated_at":"2025-04-12T13:05:41.000Z","dependencies_parsed_at":null,"dependency_job_id":"6b027baa-3602-4b68-a344-ee2efa0605c5","html_url":"https://github.com/MatsDK/TauRPC","commit_stats":{"total_commits":90,"total_committers":4,"mean_commits":22.5,"dds":0.3222222222222222,"last_synced_commit":"dced3348cad6d85cd59801128ecc5ac91b0f7389"},"previous_names":["matsdk/taurpc"],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MatsDK%2FTauRPC","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MatsDK%2FTauRPC/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MatsDK%2FTauRPC/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MatsDK%2FTauRPC/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MatsDK","download_url":"https://codeload.github.com/MatsDK/TauRPC/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248625501,"owners_count":21135513,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["rust","tauri"],"created_at":"2024-09-26T17:03:08.809Z","updated_at":"2025-04-12T19:49:20.951Z","avatar_url":"https://github.com/MatsDK.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# TauRPC\n\n[![](https://img.shields.io/npm/v/taurpc)](https://www.npmjs.com/package/taurpc) [![](https://img.shields.io/crates/v/taurpc)](https://crates.io/crates/taurpc) [![](https://img.shields.io/docsrs/taurpc)](https://docs.rs/taurpc/) ![](https://img.shields.io/crates/l/taurpc)\n\nThis package is a Tauri extension to give you a fully-typed IPC layer for [Tauri commands](https://v2.tauri.app/develop/calling-rust/#commands) and [events](https://v2.tauri.app/develop/calling-rust/#event-system).\n\nThe TS types corresponding to your pre-defined Rust backend API are generated on runtime, after which they can be used to call the backend from your TypeScript frontend framework of choice. This crate provides typesafe bidirectional IPC communication between the Rust backend and TypeScript frontend.\n[Specta](https://github.com/oscartbeaumont/specta) is used under the hood for the type-generation. The trait-based API structure was inspired by [tarpc](https://github.com/google/tarpc).\n\n# Usage🔧\n\nFirst, add the following crates to your `Cargo.toml`:\n\n```toml\n# src-tauri/Cargo.toml\n\n[dependencies]\ntaurpc = \"0.4.1\"\n\nspecta = { version = \"=2.0.0-rc.22\", features = [\"derive\"] }\n# specta-typescript = \"0.0.9\"\ntokio = { version = \"1\", features = [\"full\"] }\n```\n\nThen, declare and implement your IPC methods and resolvers. If you want to use your API for Tauri's events, you don't have to implement the resolvers, go to [Calling the frontend](https://github.com/MatsDK/TauRPC/#calling-the-frontend)\n\n```rust\n// src-tauri/src/main.rs\n\n#[taurpc::procedures]\ntrait Api {\n    async fn hello_world();\n}\n\n#[derive(Clone)]\nstruct ApiImpl;\n\n#[taurpc::resolvers]\nimpl Api for ApiImpl {\n    async fn hello_world(self) {\n        println!(\"Hello world\");\n    }\n}\n\n#[tokio::main]\nasync fn main() {\n    tauri::Builder::default()\n        .plugin(tauri_plugin_shell::init())\n        .invoke_handler(taurpc::create_ipc_handler(ApiImpl.into_handler()))\n        .run(tauri::generate_context!())\n        .expect(\"error while running tauri application\");\n}\n```\n\nThe `#[taurpc::procedures]` trait will generate everything necessary for handling calls and the type-generation. Now, you should run `pnpm tauri dev` to generate and export the TS types.\nThe types will by default be exported to `bindings.ts` in the root of your project, but you can specify an export path by doing this `#[taurpc::procedures(export_to = \"../src/types.ts\")]`.\n\nThen on the frontend install the taurpc package.\n\n```bash\npnpm install taurpc\n```\n\nNow on the frontend you import the generated types, if you specified the `export_to` attribute on your procedures you should import your from there.\nWith these types a typesafe proxy is generated that you can use to invoke commands and listen for events.\n\n```typescript\nimport { createTauRPCProxy } from '../bindings.ts'\n\nconst taurpc = createTauRPCProxy()\nawait taurpc.hello_world()\n```\n\nThe types for taurpc are generated once you start your application, run `pnpm tauri dev`. If the types are not picked up by the LSP, you may have to restart typescript to reload the types.\n\nYou can find a complete example (using Svelte) [here](https://github.com/MatsDK/TauRPC/tree/main/example).\n\n# Using structs\n\nIf you want to use structs for the inputs/outputs of procedures, you should always add `#[taurpc::ipc_type]` to make sure the coresponding ts types are generated. This make will derive serde `Serialize` and `Deserialize`, `Clone` and `specta::Type`.\n\n```rust\n#[taurpc::ipc_type]\n// #[derive(serde::Serialize, serde::Deserialize, specta::Type, Clone)]\nstruct User {\n    user_id: u32,\n    first_name: String,\n    last_name: String,\n}\n\n#[taurpc::procedures]\ntrait Api {\n    async fn get_user() -\u003e User;\n}\n```\n\n# Accessing managed state\n\nTo share some state between procedures, you can add fields on the API implementation struct. If the state requires to be mutable, you need to use a container that enables interior mutability, like a [Mutex](https://doc.rust-lang.org/std/sync/struct.Mutex.html).\n\nYou can use the `window`, `app_handle` and `webview_window` arguments just like with Tauri's commands. [Tauri docs](https://v2.tauri.app/develop/calling-rust/#accessing-the-webviewwindow-in-commands)\n\n```rust\n// src-tauri/src/main.rs\n\nuse std::sync::Arc;\nuse tokio::sync::Mutex;\nuse tauri::{Manager, Runtime, State, Window};\n\ntype MyState = Arc\u003cMutex\u003cString\u003e\u003e;\n\n#[taurpc::procedures]\ntrait Api {\n    async fn with_state();\n\n    async fn with_window\u003cR: Runtime\u003e(window: Window\u003cR\u003e);\n}\n\n#[derive(Clone)]\nstruct ApiImpl {\n    state: MyState\n};\n\n#[taurpc::resolvers]\nimpl Api for ApiImpl {\n    async fn with_state(self) {\n        // ... \n        // let state = self.state.lock().await;\n        // ... \n    }\n\n    async fn with_window\u003cR: Runtime\u003e(self, window: Window\u003cR\u003e) {\n        // ...\n    }\n}\n\n#[tokio::main]\nasync fn main() {\n    tauri::Builder::default()\n        .invoke_handler(taurpc::create_ipc_handler(\n            ApiImpl {\n                state: Arc::new(Mutex::new(\"state\".to_string())),\n            }\n            .into_handler(),\n        ))\n        .run(tauri::generate_context!())\n        .expect(\"error while running tauri application\");\n}\n```\n\n# Custom error handling\n\nYou can return a `Result\u003cT, E\u003e` to return an error if the procedure fails. This is will reject the promise on the frontend and throw an error.\nIf you're working with error types from Rust's std library, they will probably not implement `serde::Serialize` which is required for anything that is returned in the procedure.\nIn simple scenarios you can use `map_err` to convert these errors to `String`s. For more complex scenarios, you can create your own error type that implements `serde::Serialize`.\nYou can find an example using [thiserror](https://github.com/dtolnay/thiserror) [here](https://github.com/MatsDK/TauRPC/blob/main/example/src-tauri/src/main.rs).\nYou can also find more information about this in the [Tauri guides](https://v2.tauri.app/develop/calling-rust/#error-handling).\n\n# Extra options for procedures\n\nInside your procedures trait you can add attributes to the defined methods. This can be used to ignore or rename a method. Renaming will change the name of the procedure on the frontend.\n\n```rust\n#[taurpc::procedures]\ntrait Api {\n    // #[taurpc(skip)]\n    #[taurpc(alias = \"_hello_world_\")]\n    async fn hello_world();\n}\n```\n\n# Routing\n\nIt is possible to define all your commands and events inside a single procedures trait, but this can quickly get cluttered. By using the `Router` struct you can create nested commands and events,\nthat you can call using a proxy TypeScript client.\n\nThe path of the procedures trait is set by using the `path` attribute on `#[taurpc::procedures(path = \"\")]`, then you can create an empty router and use the `merge` method to add handlers to the router.\nYou can only have 1 trait without a path specified, this will be the root. Finally instead of using `taurpc::create_ipc_handler()`, you should just call `into_handler()` on the router.\n\n```rust\n// Root procedures\n#[taurpc::procedures]\ntrait Api {\n    async fn hello_world();\n}\n\n#[derive(Clone)]\nstruct ApiImpl;\n\n#[taurpc::resolvers]\nimpl Api for ApiImpl {\n    async fn hello_world(self) {\n        println!(\"Hello world\");\n    }\n}\n\n// Nested procedures, you can also do this (path = \"api.events.users\")\n#[taurpc::procedures(path = \"events\")]\ntrait Events {\n    #[taurpc(event)]\n    async fn event();\n}\n\n#[derive(Clone)]\nstruct EventsImpl;\n\n#[taurpc::resolvers]\nimpl Events for EventsImpl {}\n\n#[tokio::main]\nasync fn main() {\n    let router = Router::new()\n        .merge(ApiImpl.into_handler())\n        .merge(EventsImpl.into_handler());\n\n    tauri::Builder::default()\n        .invoke_handler(router.into_handler())\n        .run(tauri::generate_context!())\n        .expect(\"error while running tauri application\");\n}\n```\n\nNow on the frontend you can use the proxy client.\n\n```typescript\n// Call `hello_world` on the root layer\nawait taurpc.hello_world()\n\n// Listen for `event` on the `events` layer\nconst unlisten = await taurpc.events.event.on(() =\u003e {\n  console.log('Hello World!')\n})\n```\n\n# Typescript export configuration\n\nYou can specify a `Specta` typescript export configuration on the `Router`. These options will overwrite `Specta`'s defaults. Make sure to install the latest version of `specta_typescript`.\nAll available options can be found in [specta_typescript's docs](https://docs.rs/specta-typescript/latest/specta_typescript/struct.Typescript.html).\n\n```rust\nlet router = Router::new()\n    .export_config(\n        specta_typescript::Typescript::default()\n            .header(\"// My header\\n\")\n            .bigint(specta_typescript::BigIntExportBehavior::String),\n            // Make sure you have the specified formatter installed on your system.\n            .formatter(specta_typescript::formatter::prettier)\n    )\n    .merge(ApiImpl.into_handler())\n    .merge(EventsImpl.into_handler());\n```\n\n# Calling the frontend\n\nTrigger [events](https://v2.tauri.app/develop/calling-rust/#event-system) on your TypeScript frontend from your Rust backend with a fully-typed experience.\nThe `#[taurpc::procedures]` macro also generates a struct that you can use to trigger the events, this means you can define the event types the same way you define the procedures.\n\nFirst start by declaring the API structure, by default the event trigger struct will be identified by `TauRpc{trait_ident}EventTrigger`. If you want to change this, you can add an attribute to do this, `#[taurpc::procedures(event_trigger = ApiEventTrigger)]`.\nFor more details you can look at the [example](https://github.com/MatsDK/TauRPC/blob/main/example/src-tauri/src/main.rs).\n\nYou should add the `#[taurpc(event)]` attribute to your events. If you do this, you will not have to implement the corresponding resolver.\n\n```rust\n// src-tauri/src/main.rs\n\n#[taurpc::procedures(event_trigger = ApiEventTrigger)]\ntrait Api {\n    #[taurpc(event)]\n    async fn hello_world();\n}\n\n#[derive(Clone)]\nstruct ApiImpl;\n\n#[taurpc::resolvers]\nimpl Api for ApiImpl {}\n\n#[tokio::main]\nasync fn main() {\n    tauri::Builder::default()\n        .invoke_handler(taurpc::create_ipc_handler(ApiImpl.into_handler()))\n        .setup(|app| {\n            let trigger = ApiEventTrigger::new(app.handle());\n            trigger.hello_world()?;\n\n            Ok(())\n        })\n        .run(tauri::generate_context!())\n        .expect(\"error while running tauri application\");\n}\n```\n\nThen, on the frontend you can listen for the events with types:\n\n```typescript\nconst unlisten = await taurpc.hello_world.on(() =\u003e {\n  console.log('Hello World!')\n})\n\n// Run this inside a cleanup function, for example within useEffect in React and onDestroy in Svelte\nunlisten()\n```\n\n## Sending an event to a specific window\n\nBy default, events are emitted to all windows. If you want to send an event to a specific window by label, you can do the following:\n\n```rust\nuse taurpc::Windows;\n\ntrigger.send_to(Windows::One(\"main\".to_string())).hello_world()?;\n// Options:\n//   - Windows::All (default)\n//   - Windows::One(String)\n//   - Windows::N(Vec\u003cString\u003e)\n```\n\n# Using channels\n\nTauRPC will also generate types if you are using [Tauri Channels](https://v2.tauri.app/develop/calling-frontend/#channels).\nOn the frontend you will be able to pass a typed callback function to your command.\n\n```rust\n#[taurpc::ipc_type]\nstruct Update {\n    progress: u8,\n}\n\n#[taurpc::procedures]\ntrait Api {\n    async fn update(on_event: Channel\u003cUpdate\u003e);\n}\n\n#[derive(Clone)]\nstruct ApiImpl;\n\n#[taurpc::resolvers]\nimpl Api for ApiImpl {\n    async fn update(self, on_event: Channel\u003cUpdate\u003e) {\n        for progress in [15, 20, 35, 50, 90] {\n            on_event.send(Update { progress }).unwrap();\n        }\n    }\n}\n```\n\nCalling the command:\n\n```typescript\nlet taurpc = createTauRPCProxy()\nawait taurpc.update((update) =\u003e {\n  console.log(update.progress)\n})\n```\n\n# Features\n\n- [x] Basic inputs\n- [x] Struct inputs\n- [x] Sharing state\n  - [ ] Use Tauri's managed state?\n- [x] Renaming methods\n- [x] Nested routes\n- [x] Merging routers\n- [x] Custom error handling\n- [x] Typed outputs\n- [x] Async methods - [async traits👀](https://blog.rust-lang.org/inside-rust/2023/05/03/stabilizing-async-fn-in-trait.html)\n  - [ ] Allow sync methods\n- [x] Calling the frontend\n- [x] Renaming event trigger struct\n- [x] Send event to specific window\n- [ ] React/Svelte handlers\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmatsdk%2Ftaurpc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmatsdk%2Ftaurpc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmatsdk%2Ftaurpc/lists"}