# tauri-ts-generator
A powerful CLI tool to automatically generate TypeScript bindings from your Rust Tauri commands, structs, and enums.
`tauri-ts-generator` scans your Rust source code, parses `#[tauri::command]` macros and data structures, and generates type-safe TypeScript interfaces and invocation functions. This eliminates manual typing boilerplate and ensures your frontend and backend are always in sync.
## Features
- **Automated Scanning**: Recursively scans your `src-tauri` directory for commands and types.
- **Type Safety**: Generates exact TypeScript definitions for Rust structs, enums, and type aliases.
- **Serde Support**:
- Respects `#[serde(rename = "...")]` attributes, preserving the exact name and overriding camelCase conversion.
- Handles `#[serde(rename_all = "...")]` for enums and structs.
- Supports `#[serde(tag = "...")]`, `#[serde(content = "...")]`, and `#[serde(untagged)]` enum representations.
- Supports `#[serde(flatten)]` to generate TypeScript intersection types.
- Fields with `#[serde(skip)]` are excluded from TypeScript output.
- Support for `#[ts(optional)]` attribute on `Option` fields to generate `prop?: T` instead of `T | null`.
- Provides `#[derive(tauri_ts_generator::TS)]` to register the `ts` attribute namespace.
- **Smart Type Mapping**:
- Maps common Rust types (`String`, `Vec`, `Option`, `Result`) to TypeScript equivalents.
- Handles external crate types like `chrono::DateTime`, `uuid::Uuid`, `url::Url`, and `rust_decimal::Decimal`.
- **Async Handling**: Correctly generates `Promise<T>` for async commands.
- **Tauri Integration**:
- Automatically imports `invoke` from `@tauri-apps/api/core`.
- Supports `#[tauri::command(rename_all = "...")]` to control argument casing (e.g. `snake_case`).
- **Macro Support**: Optional integration with `cargo-expand` to resolve types generated by macros (e.g., `progenitor`).
- **Conflict Resolution**: Detects and handles naming conflicts or ambiguous imports.
## Installation
```bash
cargo install tauri-ts-generator
```
Or run directly from source:
```bash
cargo run --release -- generate
```
## Quick Start
1. **Initialize Configuration** (Run in your Tauri project root):
```bash
tauri-ts-generator init
```
This creates a `tauri-codegen.toml` file.
2. **Generate Bindings**:
```bash
tauri-ts-generator generate
```
## Configuration (`tauri-codegen.toml`)
Customize the generator behavior using the TOML configuration file.
### `[input]` Section
Defines where the generator looks for code.
| `source_dir` | Root directory of your Rust source code. | `"src-tauri/src"` |
| `exclude` | List of directories or files to ignore. | `["tests", "target"]` |
| `use_cargo_expand` | Enable if you use macro-generated types (requires `cargo-expand`). | `false` |
| `cargo_manifest` | Path to `Cargo.toml` for `cargo-expand`. Auto-detected if empty. | `None` |
### `[output]` Section
Defines where the generated TypeScript files are saved.
| `types_file` | Path for generated interfaces/types. | `"src/generated/types.ts"` |
| `commands_file` | Path for generated invoke functions. | `"src/generated/commands.ts"` |
### `[naming]` Section
Customize naming conventions for generated types and functions.
| `type_prefix` | Prefix added to all generated interface names (e.g., "I"). | `""` |
| `type_suffix` | Suffix added to all generated interface names (e.g., "DTO"). | `""` |
| `function_prefix` | Prefix for generated command functions. | `""` |
| `function_suffix` | Suffix for generated command functions. | `""` |
## Type Mappings
The generator maps Rust types to TypeScript as follows:
| `String`, `&str`, `char` | `string` |
| `i8`...`i64`, `u8`...`u64`, `f32`, `f64` | `number` |
| `bool` | `boolean` |
| `Option<T>` | `T \| null` (default), or optional field `?: T` (with `#[ts(optional)]`) |
| `Vec<T>` | `T[]` |
| `HashMap<K, V>` | `Record<K, V>` (if K is string/number) |
| `Result<T, E>` | `Promise<T>` (in return types) |
| `()` / `Unit` | `void` |
| `bytes::Bytes` | `number[]` |
| `serde_json::Value` | `unknown` |
### Supported External Types
Common types from popular crates are mapped automatically:
- **Chrono**: `DateTime`, `NaiveDate`, `NaiveTime` → `string`
- **Time**: `OffsetDateTime`, `Date` → `string`
- **Uuid**: `Uuid` → `string`
- **Url**: `Url` → `string`
- **Rust Decimal**: `Decimal` → `string`
- **Std**: `Path`, `PathBuf`, `IpAddr` → `string`; `Duration` → `number`
## Examples
### 1. Basic Command & Struct
**Rust:**
```rust
#[derive(Serialize)]
pub struct User {
pub id: i32,
pub name: String,
}
#[tauri::command]
pub async fn get_user(id: i32) -> Result<User, String> { /* ... */ }
```
**TypeScript Output:**
```typescript
export interface User {
id: number;
name: string;
}
export async function getUser(id: number): Promise<User> {
return invoke<User>("get_user", { id });
}
```
### 2. Serde Rename (Exact Casing)
When `#[serde(rename = "...")]` is used, the generator preserves the exact casing, skipping the default camelCase conversion.
**Rust:**
```rust
#[derive(Serialize)]
pub struct Config {
#[serde(rename = "API_KEY")]
pub api_key: String,
pub retries: i32,
}
```
**TypeScript Output:**
```typescript
export interface Config {
API_KEY: string; // Exactly as renamed in Rust
retries: number; // Default camelCase
}
```
### 3. Enums
Supports various serde representations.
**Rust:**
```rust
#[derive(Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum Status {
Active,
Inactive,
}
```
**TypeScript Output:**
```typescript
**Supported `rename_all` values:**
- `lowercase`, `UPPERCASE`
- `camelCase`, `PascalCase`
- `snake_case`, `SCREAMING_SNAKE_CASE`
- `kebab-case`, `SCREAMING-KEBAB-CASE`
### 4. Command Arguments Rename
Use `rename_all` on commands to control argument keys in the `invoke` payload.
**Rust:**
```rust
#[tauri::command(rename_all = "snake_case")]
pub fn update_user(user_id: i32, new_email: String) { /* ... */ }
```
**TypeScript Output:**
```typescript
export async function updateUser(userId: number, newEmail: string): Promise<void> {
// Arguments are mapped to snake_case in the payload
return invoke<void>("update_user", { user_id: userId, new_email: newEmail });
}
```
### 5. Option with Undefined
By default, `Option<T>` maps to `T | null`. You can use the `#[ts(optional)]` attribute to map it to `prop?: T` instead.
> **Note:** You must add `#[derive(tauri_ts_generator::TS)]` to enable the `#[ts(...)]` attribute on your structs.
**Rust:**
```rust
use tauri_ts_generator::TS;
#[derive(Serialize, TS)]
pub struct Config {
pub name: Option<String>,
#[ts(optional)]
pub volume: Option<f32>,
}
```
**TypeScript Output:**
```typescript
export interface Config {
}
```
### 6. Skipping Fields
Fields with `#[serde(skip)]` are excluded from the TypeScript output. Note that `skip_serializing` and `skip_deserializing` are **not** excluded, as they only affect one direction of serialization.
**Rust:**
```rust
#[derive(Serialize)]
pub struct User {
pub id: i32,
pub name: String,
#[serde(skip)]
pub internal_cache: Vec<u8>, // Excluded from TypeScript
#[serde(skip_serializing)]
pub password_hash: String, // Kept in TypeScript (needed for input)
}
```
**TypeScript Output:**
```typescript
export interface User {
id: number;
name: string;
passwordHash: string; // skip_serializing fields are kept
// internal_cache is excluded due to #[serde(skip)]
}
```
### 7. Serde Flatten (Intersection Types)
Use `#[serde(flatten)]` to embed one struct's fields into another. The generator produces TypeScript intersection types.
**Rust:**
```rust
#[derive(Serialize)]
pub struct Address {
pub city: String,
pub country: String,
}
#[derive(Serialize)]
pub struct User {
pub name: String,
#[serde(flatten)]
pub address: Address,
}
```
**TypeScript Output:**
```typescript
export interface Address {
city: string;
country: string;
}
export type User = {
name: string;
} & Address;
```
This works correctly for both command arguments (input) and return types (output).
## CLI Reference
```bash
tauri-ts-generator <COMMAND> [OPTIONS]
Commands:
generate Generate TypeScript bindings
init Create a default configuration file
help Print help information
Options:
-v, --verbose Enable verbose logging (useful for debugging scanning/parsing)
-c, --config Path to config file (default: tauri-codegen.toml)
```
## License
MIT