# 🖊️ typewriter
> **Cross-Language Type Synchronization SDK for Rust**
> Define your types once in Rust. Get perfectly matching types in TypeScript, Python, Go, Swift, Kotlin, GraphQL, JSON Schema, Ruby, PHP, and Dart — automatically, forever.
[](https://crates.io/crates/typebridge)
[](https://docs.rs/typebridge)
[](LICENSE)
[](https://github.com/aarambh-darshan/typewriter/actions)
[](https://github.com/aarambh-darshan/typewriter)
<div align="center">
| proc macro powered | 10+ languages | forever in sync | zero boilerplate |
</div>
---
## ⚠️ The Problem
Every full-stack team using Rust on the backend faces the same invisible tax: **keeping types in sync across languages.**
```
Backend dev changes: Frontend/Mobile dev sees:
───────────────────── ─────────────────────────
pub struct User { interface User {
pub id: Uuid, ──────► id: string;
pub email: String, email: string;
pub name: String, ← ADDED // ❌ missing: name
pub role: Role, role: Role;
} }
// Runtime: undefined is not a string
```
OpenAPI codegen? Only works for HTTP APIs. Protobuf? Heavy toolchain. Manual sync? Humans forget.
**Root cause:** Every existing solution uses an *intermediary format* as source of truth instead of the Rust source itself.
---
## ✅ The Solution
**`typewriter`** makes your Rust structs and enums the **single, permanent source of truth** for all your type definitions.
Annotate once → generate everywhere. When the Rust type changes, every generated file updates automatically. No hand-maintained schema file. No intermediary format. No drift. Ever.
```rust
use typebridge::TypeWriter;
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, TypeWriter)]
#[sync_to(typescript, python)]
pub struct UserProfile {
pub id: Uuid,
pub email: String,
pub age: Option<u32>,
pub role: UserRole,
pub created_at: DateTime<Utc>,
}
// On cargo build, auto-generates:
// ✅ ./generated/typescript/user-profile.ts
// ✅ ./generated/typescript/user-profile.schema.ts
// ✅ ./generated/python/user_profile.py
// ✅ ./generated/go/user_profile.go
// ✅ ./generated/graphql/user_profile.graphql
// ✅ ./generated/json-schema/user_profile.schema.json
```
---
## 🚀 Quick Start
### 1. Add to Cargo.toml
```toml
[dependencies]
typebridge = "0.5.2"
serde = { version = "1", features = ["derive"] }
```
### 2. Annotate your types
```rust
use typebridge::TypeWriter;
use serde::{Serialize, Deserialize};
/// A user in the system.
#[derive(Serialize, Deserialize, TypeWriter)]
#[sync_to(typescript, python, go)]
pub struct User {
pub id: String,
pub email: String,
pub name: String,
pub age: Option<u32>,
pub is_active: bool,
pub tags: Vec<String>,
}
```
### 3. Build
```bash
cargo build
```
That's it. Check `./generated/typescript/`, `./generated/python/`, and `./generated/go/` for your generated files.
---
## 📦 Generated Output
### TypeScript → `user.ts`
```typescript
// Auto-generated by typewriter v0.4.2. DO NOT EDIT.
/**
* A user in the system.
*/
export interface User {
id: string;
email: string;
name: string;
age?: number | undefined;
is_active: boolean;
tags: string[];
}
```
### TypeScript Zod Schema → `user.schema.ts`
```typescript
import { z } from 'zod';
export const UserSchema = z.object({
"id": z.string(),
"email": z.string(),
"name": z.string(),
"age": z.number().optional(),
"is_active": z.boolean(),
"tags": z.array(z.string()),
});
```
To consume generated schemas at runtime, install `zod` in your TS app. Set `[typescript].zod = false` to disable global schema output, and use `#[tw(zod)]`/`#[tw(zod = false)]` for per-type overrides:
```bash
npm install zod
```
### Python → `user.py`
```python
# Auto-generated by typewriter v0.4.2. DO NOT EDIT.
from pydantic import BaseModel
from typing import Optional
class User(BaseModel):
"""A user in the system."""
id: str
email: str
name: str
age: Optional[int] = None
is_active: bool
tags: list[str]
```
### Go → `user.go`
```go
// Code generated by typewriter v0.4.2. DO NOT EDIT.
// Source: User
package types
// A user in the system.
type User struct {
Id string `json:"id"`
Email string `json:"email"`
Name string `json:"name"`
Age *uint32 `json:"age,omitempty"`
Is_active bool `json:"is_active"`
Tags []string `json:"tags"`
}
```
---
## 🎯 Features
### ✅ Core Generation Platform (v0.4.2)
- **TypeScript emitter** — `export interface`, `export type` unions, optional fields
- **TypeScript Zod schemas** — sibling `<type>.schema.ts` files with `export const <Type>Schema = ...` (enabled by default, configurable via `[typescript].zod` and `#[tw(zod)]`; runtime `zod` dependency)
- **Python emitter** — Pydantic v2 `BaseModel`, `Enum`, `Union` with `Literal` discriminators
- **Go emitter** — native `struct` parsing, `interface` data-carrying enums, and `omitempty` optional pointers
- **Swift emitter** — `Codable` structs and enums with `CodingKeys`
- **Kotlin emitter** — `data class` and `sealed class` with `kotlinx.serialization`
- **GraphQL SDL emitter** — `type`, `enum`, `union` definitions with custom scalars (`DateTime`, `JSON`)
- **JSON Schema emitter** — Draft 2020-12 `object` schemas, `string` enums, `oneOf` composition with format annotations (`uuid`, `date-time`, `date`)
- **Generic types** — `Pagination<T>` → `export interface Pagination<T>` (TS) / `class Pagination(BaseModel, Generic[T])` (Python)
- **Cross-file imports** — auto `import type { X } from './file'` (TS) / `from .file import X` (Python)
- **Serde compatibility** — auto-reads `#[serde(rename, skip, tag, flatten)]`
- **Custom attributes** — `#[tw(skip)]`, `#[tw(rename)]`, `#[tw(optional)]`, `#[tw(zod)]`
- **Doc comments** — Rust `///` flows to JSDoc in TS, docstrings in Python, `"""` in GraphQL
- **Smart type unwrapping** — `Box<T>`, `Arc<T>`, `Rc<T>` transparently unwrapped
- **Feature-gated emitters** — compile only what you need
- **TOML config** — `typewriter.toml` for output directories, file naming styles, readonly mode
### ✅ Phase 3 CLI (v0.3.1)
- `typewriter generate <file>` and `typewriter generate --all`
- `typewriter check --ci` with drift detection gate
- `typewriter check --json` / `--json-out` structured report output
- `typewriter watch [path]` auto-regeneration on Rust file save
- `cargo typewriter ...` subcommand support via `cargo-typewriter`
- `typebridge-cli` package published as version `0.2.2`.
See [CLI Guide](docs/cli.md) for full command reference and JSON schema.
### 🔮 Coming Soon
- VSCode / Neovim extensions
- Plugin API for custom language backends
See the full [Roadmap](ROADMAP.md) for details.
---
## ⚙️ Configuration
Create a `typewriter.toml` at your project root (optional — sensible defaults are used):
```toml
[typescript]
output_dir = "../frontend/src/types"
file_style = "kebab-case"
readonly = false
zod = true
[python]
output_dir = "../api/schemas"
pydantic_v2 = true
[graphql]
output_dir = "../schema/types"
file_style = "snake_case"
[json_schema]
output_dir = "../schemas"
file_style = "snake_case"
```
See [Configuration Guide](docs/configuration.md) for all options.
---
## 🏷️ Attributes
### Serde Compatibility
typewriter **automatically reads `#[serde(...)]` attributes** — no need to repeat them:
```rust
#[derive(Serialize, Deserialize, TypeWriter)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum PaymentStatus {
Pending,
Completed { transaction_id: String },
Failed { reason: String, code: u32 },
}
```
### Custom Attributes
```rust
pub struct User {
#[tw(skip)] // exclude from generated output
pub password_hash: String,
#[tw(rename = "displayName")] // override field name
pub username: String,
#[tw(optional)] // force optional even if not Option<T>
pub legacy_field: String,
}
```
See the full [Attributes Guide](docs/custom-attributes.md).
---
## 📊 Type Mapping Reference
| `String` | `string` | `str` | `string` | `String` | `String` | `String` | `string` |
| `u8`–`u32`, `i8`–`i32`, `f32`, `f64` | `number` | `int` / `float` | `uint*` / `int*` / `float*` | `UInt*` / `Int*` / `Float`/`Double` | `UInt` / `Int` / `Float`/`Double` | `Int` / `Float` | `integer` / `number` |
| `u64`, `i64` | `bigint` | `int` | `uint64` / `int64` | `UInt64` / `Int64` | `ULong` / `Long` | `String` | `integer` |
| `bool` | `boolean` | `bool` | `bool` | `Bool` | `Boolean` | `Boolean` | `boolean` |
| `Option<T>` | `T \| undefined` | `Optional[T]` | `*T` with `omitempty` | `T?` | `T? = null` | nullable (no `!`) | not in `required` |
| `Vec<T>` | `T[]` | `list[T]` | `[]T` | `[T]` | `List<T>` | `[T!]` | `array` |
| `HashMap<K,V>` | `Record<K, V>` | `dict[K, V]` | `map[K]V` | `[K: V]` | `Map<K, V>` | `JSON` | `object` |
| `Uuid` | `string` | `UUID` | `string` | `UUID` | `String` | `ID` | `string` + `uuid` format |
| `DateTime<Utc>` | `string` | `datetime` | `time.Time` | `Date` | `kotlinx.datetime.Instant` | `DateTime` | `string` + `date-time` format |
See [full type mapping reference](docs/type-mappings.md) for all supported types.
---
## 🏗️ Architecture
typewriter is a **Cargo workspace** with focused, independently publishable crates:
```
typewriter/
├── typewriter-core/ ← IR types, TypeMapper trait, config
├── typewriter-engine/ ← Shared scan/parse/emit + drift orchestration
├── typewriter-macros/ ← #[derive(TypeWriter)] proc macro
├── typewriter-typescript/ ← TypeScript emitter
├── typewriter-python/ ← Python emitter
├── typewriter-go/ ← Go emitter
├── typewriter-swift/ ← Swift emitter
├── typewriter-kotlin/ ← Kotlin emitter
├── typewriter-graphql/ ← GraphQL SDL emitter
├── typewriter-json-schema/ ← JSON Schema emitter
├── typewriter-cli/ ← `typebridge-cli` package (`typewriter` + `cargo-typewriter` binaries)
├── typewriter/ ← Main user-facing crate (re-exports)
├── typewriter-test/ ← Snapshot tests
└── example/ ← Working usage examples
```
See [ARCHITECTURE.md](ARCHITECTURE.md) for the full technical deep-dive.
---
## 🤝 Contributing
We welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) for:
- Development setup
- Code style guidelines
- How to add a new language emitter
- PR and review process
---
## 📄 License
```
Apache License 2.0
Copyright 2026 Aarambh Dev Hub
```
See [LICENSE](LICENSE) for the full text.
---
<div align="center">
**Built with ❤️ and 🦀 by [Aarambh Dev Hub](https://github.com/aarambh-darshan)**
*Define once. Generate everywhere. Never drift again.*
⭐ Star this repo if you find it useful!
</div>