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](https://img.shields.io/crates/v/typebridge.svg)](https://crates.io/crates/typebridge)
[![Docs.rs](https://docs.rs/typebridge/badge.svg)](https://docs.rs/typebridge)
[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
[![CI](https://github.com/aarambh-darshan/typewriter/actions/workflows/ci.yml/badge.svg)](https://github.com/aarambh-darshan/typewriter/actions)
[![GitHub](https://img.shields.io/badge/github-aarambh--darshan-blue?logo=github)](https://github.com/aarambh-darshan/typewriter)

<div align="center">

| 🦀 Rust Core | 🌐 Polyglot | ⚡ Zero Drift | 🔧 Proc Macro |
|:---:|:---:|:---:|:---:|
| 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

| 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](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>