typewire 0.0.3

Derive-based cross-language type bridging with compile-time schema embedding and multi-target codegen
Documentation

typewire

CI docs.rs crates.io license

Work in progress -- not production ready. API and schema format may change.

Derive-based cross-language type bridging for Rust.

#[derive(Typewire)] generates bidirectional conversion methods and compile-time schema records from your Rust types. Define types once in Rust, get type-safe foreign-language bindings and declarations automatically.

Currently supported targets:

  • WebAssembly (wasm32) -- generates to_js, from_js, patch_js via wasm-bindgen, with TypeScript .d.ts generation
  • Kotlin and Swift -- planned

Relationship with wasm-bindgen

Typewire does not replace wasm-bindgen -- it builds on top of it. Where wasm-bindgen handles the low-level ABI boundary (function exports, memory management, JS glue code), typewire adds support for richer type shapes that wasm-bindgen alone cannot express: enums with data, tagged unions, nested structs, optional fields, generic types, HashMap/BTreeMap, and more. The generated code uses wasm-bindgen's JsValue and js-sys primitives under the hood.

Quick look

use typewire::Typewire;

#[derive(Clone, Typewire)]
#[typewire(rename_all = "camelCase")]
pub struct Todo {
  pub id: u32,
  pub title: String,
  pub completed: bool,
  pub description: Option<String>,
  pub priority: Priority,
  pub tags: Vec<String>,
}

#[derive(Clone, Typewire)]
#[typewire(rename_all = "lowercase")]
pub enum Priority {
  Low,
  Medium,
  High,
}

#[derive(Clone, Typewire)]
#[typewire(tag = "type", content = "data")]
pub enum Command {
  Add(Todo),
  Toggle { id: u32 },
  Remove { id: u32 },
  SetPriority { id: u32, priority: Priority },
}

Build to wasm, run the CLI, and get TypeScript declarations:

export type Command =
  | { type: "Add"; data: Todo }
  | { type: "Toggle"; data: { id: number } }
  | { type: "Remove"; data: { id: number } }
  | { type: "SetPriority"; data: { id: number; priority: Priority } };

export type Priority = "low" | "medium" | "high";

export interface Todo {
  id: number;
  title: string;
  completed: boolean;
  description: string | null;
  priority: Priority;
  tags: string[];
}

How it works

#[derive(Typewire)]  -->  encode (link section)  -->  decode  -->  TypeScript .d.ts
     (derive)              (typewire-schema)         (CLI)        (codegen)
  1. Derive -- #[derive(Typewire)] analyzes your types and generates target-specific conversion methods. With the schemas feature enabled, it also embeds schema records in a binary link section.

  2. Extract -- The typewire CLI reads schema records from compiled binaries and generates typed declarations for the target language.

  3. Strip -- The CLI strips the schema section from the binary so it doesn't ship to production.

Features

Feature What it enables
derive (default) Re-exports #[derive(Typewire)]
schemas Embeds schema records in link sections for codegen
cli Binary target for schema extraction and declaration generation
uuid Typewire impl for uuid::Uuid
chrono Typewire impl for chrono::DateTime, NaiveDate, etc.
url Typewire impl for url::Url
indexmap Typewire impl for IndexMap and IndexSet
bytes Typewire impl for bytes::Bytes
base64 Base64 encoding for Vec<u8> fields via #[typewire(base64)]
serde_json Typewire impl for serde_json::Value

Serde compatibility

#[typewire(...)] supports the same attributes as serde:

  • Container: rename_all, tag, content, untagged, transparent, default, deny_unknown_fields, from, try_from, into
  • Variant: rename, alias, skip, other, untagged
  • Field: rename, alias, skip, default, flatten, skip_serializing_if, with = "serde_bytes", base64, display, lenient

When a type also derives Serialize/Deserialize, typewire reads #[serde(...)] attributes too, so you don't need to duplicate them. But when only deriving Typewire, prefer #[typewire(...)].

Efficient patching

patch_js performs structural diffing -- it only touches the JS properties that actually changed. For collections, it uses LCS-based diffing to emit minimal splice operations instead of replacing the entire array.

CLI usage

# Install
cargo install typewire --features cli

# Generate TypeScript from a wasm binary
typewire target/wasm32-unknown-unknown/release/my_app.wasm -o types.d.ts

# The schema section is stripped automatically (use --no-strip to keep it)

Workspace

Crate Role
typewire Main library: trait, primitive/compound impls, CLI binary
typewire-derive Proc-macro: #[derive(Typewire)]
typewire-schema Schema metadata: binary format, encode/decode, emitters

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.