typewriter-plugin 0.1.0

Plugin API for typewriter custom language emitters
Documentation

🖊️ 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.

Crates.io Docs.rs License CI GitHub

🦀 Rust Core 🌐 Polyglot ⚡ Zero Drift 🔧 Proc Macro
proc macro powered 10+ languages forever in sync zero boilerplate

⚠️ 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.

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

[dependencies]
typebridge = "0.5.2"
serde = { version = "1", features = ["derive"] }

2. Annotate your types

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

cargo build

That's it. Check ./generated/typescript/, ./generated/python/, and ./generated/go/ for your generated files.


📦 Generated Output

TypeScript → user.ts

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

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

Python → user.py

# 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

// 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 emitterexport 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 emitterCodable structs and enums with CodingKeys
  • Kotlin emitterdata class and sealed class with kotlinx.serialization
  • GraphQL SDL emittertype, 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 typesPagination<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 unwrappingBox<T>, Arc<T>, Rc<T> transparently unwrapped
  • Feature-gated emitters — compile only what you need
  • TOML configtypewriter.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 for full command reference and JSON schema.

🔮 Coming Soon

  • VSCode / Neovim extensions
  • Plugin API for custom language backends

See the full Roadmap for details.


⚙️ Configuration

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.


🏷️ Attributes

Serde Compatibility

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 },
}

Custom Attributes

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.


📊 Type Mapping Reference

Rust Type TypeScript Python Go Swift Kotlin GraphQL JSON Schema
String string str string String String String string
u8u32, i8i32, 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.


🏗️ 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 for the full technical deep-dive.


🤝 Contributing

We welcome contributions! See 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 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!