Cross-Language Type Synchronization SDK for Rust Define your types once in Rust. Get perfectly matching types in TypeScript, Python, Go, Swift, Kotlin, GraphQL, and JSON Schema — automatically, forever.
| 🦀 Rust Core | 🌐 Polyglot | ⚡ Zero Drift | 🔧 Proc Macro |
|---|---|---|---|
| proc macro powered | 7+ languages | forever in sync | zero boilerplate |
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.
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.
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[dependencies]
typebridge = "0.5.0"
serde = { version = "1", features = ["derive"] }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>,
}cargo buildThat's it. Check ./generated/typescript/, ./generated/python/, and ./generated/go/ for your generated files.
// 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[];
}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:
npm install zod# 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]// 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"`
}- TypeScript emitter —
export interface,export typeunions, optional fields - TypeScript Zod schemas — sibling
<type>.schema.tsfiles withexport const <Type>Schema = ...(enabled by default, configurable via[typescript].zodand#[tw(zod)]; runtimezoddependency) - Python emitter — Pydantic v2
BaseModel,Enum,UnionwithLiteraldiscriminators - Go emitter — native
structparsing,interfacedata-carrying enums, andomitemptyoptional pointers - Swift emitter —
Codablestructs and enums withCodingKeys - Kotlin emitter —
data classandsealed classwithkotlinx.serialization - GraphQL SDL emitter —
type,enum,uniondefinitions with custom scalars (DateTime,JSON) - JSON Schema emitter — Draft 2020-12
objectschemas,stringenums,oneOfcomposition 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.tomlfor output directories, file naming styles, readonly mode
typewriter generate <file>andtypewriter generate --alltypewriter check --ciwith drift detection gatetypewriter check --json/--json-outstructured report outputtypewriter watch [path]auto-regeneration on Rust file savecargo typewriter ...subcommand support viacargo-typewritertypebridge-clipackage published as version0.2.2.
See CLI Guide for full command reference and JSON schema.
- VSCode / Neovim extensions
- Plugin API for custom language backends
See the full Roadmap for details.
Create a typewriter.toml at your project root (optional — sensible defaults are used):
[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 for all options.
typewriter automatically reads #[serde(...)] attributes — no need to repeat them:
#[derive(Serialize, Deserialize, TypeWriter)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum PaymentStatus {
Pending,
Completed { transaction_id: String },
Failed { reason: String, code: u32 },
}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.
| Rust Type | TypeScript | Python | Go | Swift | Kotlin | GraphQL | JSON Schema |
|---|---|---|---|---|---|---|---|
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 for all supported types.
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 for the full technical deep-dive.
We welcome contributions! See CONTRIBUTING.md for:
- Development setup
- Code style guidelines
- How to add a new language emitter
- PR and review process
Apache License 2.0
Copyright 2026 Aarambh Dev Hub
See LICENSE for the full text.
Built with ❤️ and 🦀 by Aarambh Dev Hub
Define once. Generate everywhere. Never drift again.
⭐ Star this repo if you find it useful!