tauri-ts-generator 1.8.1

CLI tool to generate TypeScript bindings from Tauri commands
Documentation
# 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.

| Key | Description | Default |
|-----|-------------|---------|
| `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.

| Key | Description | Default |
|-----|-------------|---------|
| `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.

| Key | Description | Default |
|-----|-------------|---------|
| `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:

| Rust Type | TypeScript Type |
|-----------|-----------------|
| `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
export type Status = "ACTIVE" | "INACTIVE";
```

**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 {
  name: string | null;      // Default behavior
  volume?: number; // With #[ts(optional)]
}
```

### 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