Skip to main content

bomboni_wasm_derive

Derive macro for generating WASM bindings and TypeScript declarations from Rust types.

Derive Macro

Wasm

Derive macro for generating WASM bindings.
#[derive(Wasm)]
This macro generates:
  • TypeScript declarations for your Rust types
  • Automatic serialization/deserialization using serde-wasm-bindgen
  • WASM ABI implementations for JavaScript interop
  • The Wasm trait implementation

Basic Usage

use bomboni_wasm::Wasm;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Wasm)]
pub struct User {
    name: String,
    age: u32,
    email: Option<String>,
}
Generated TypeScript:
export interface User {
  name: string;
  age: number;
  email?: string | null;
}

Attributes

The #[wasm] attribute can be used to customize the generated bindings.

Container Attributes

These attributes are applied to structs and enums.

wasm_bindgen_crate

Custom path to the wasm-bindgen crate.
#[derive(Wasm)]
#[wasm(wasm_bindgen_crate = "::wasm_bindgen")]
pub struct Config { }

js_sys_crate

Custom path to the js-sys crate.
#[derive(Wasm)]
#[wasm(js_sys_crate = "::js_sys")]
pub struct Config { }

bomboni_crate

Custom path to the bomboni crate.
#[derive(Wasm)]
#[wasm(bomboni_crate = "::bomboni")]
pub struct Config { }

bomboni_wasm_crate

Custom path to the bomboni_wasm crate.
#[derive(Wasm)]
#[wasm(bomboni_wasm_crate = "crate")]
pub struct Config { }

wasm_abi

Generate both IntoWasmAbi and FromWasmAbi implementations.
#[derive(Wasm)]
#[wasm(wasm_abi = true)]
pub struct Data { }

into_wasm_abi

Generate IntoWasmAbi implementation.
#[derive(Wasm)]
#[wasm(into_wasm_abi = true)]
pub struct Data { }

from_wasm_abi

Generate FromWasmAbi implementation.
#[derive(Wasm)]
#[wasm(from_wasm_abi = true)]
pub struct Data { }

enum_value

Generate enum value object for string enums.
#[derive(Serialize, Deserialize, Wasm)]
#[wasm(enum_value = true)]
pub enum Status {
    Active,
    Inactive,
    Pending,
}
Generated TypeScript:
export const Status = Object.freeze({
  ACTIVE: "Active",
  Active: "ACTIVE",
  INACTIVE: "Inactive",
  Inactive: "INACTIVE",
  PENDING: "Pending",
  Pending: "PENDING",
});

js_value

Custom JsValue conversion configuration.
#[derive(Wasm)]
#[wasm(js_value)]
pub struct CustomType { }
With custom conversion functions:
#[derive(Wasm)]
#[wasm(js_value(into = "custom_into", try_from = "custom_try_from"))]
pub struct CustomType { }
With string conversion:
#[derive(Wasm)]
#[wasm(js_value(convert_string))]
pub enum Color {
    Red,
    Green,
    Blue,
}

proxy

Use a proxy type for WASM bindings.
#[derive(Wasm)]
#[wasm(proxy = "ProxyType")]
pub struct ComplexType { }
With custom conversion:
#[derive(Wasm)]
#[wasm(proxy(source = "ProxyType", into = "into_proxy", try_from = "from_proxy"))]
pub struct ComplexType { }

rename

Custom name for the type in TypeScript.
#[derive(Wasm)]
#[wasm(rename = "UserProfile")]
pub struct User { }

rename_all

Rename rule for all fields and variants.
#[derive(Wasm)]
#[wasm(rename_all = "camelCase")]
pub struct Config {
    api_key: String,
    max_retries: u32,
}
Generated TypeScript:
export interface Config {
  apiKey: string;
  maxRetries: number;
}
Supported rename rules:
  • lowercase
  • UPPERCASE
  • PascalCase
  • camelCase
  • snake_case
  • SCREAMING_SNAKE_CASE
  • kebab-case
  • SCREAMING-KEBAB-CASE

rename_wrapper

Control wrapper type renaming for protobuf wrappers.
#[derive(Wasm)]
#[wasm(rename_wrapper = true)]
pub struct Data {
    value: DoubleValue,  // Becomes `number` in TypeScript
}

override_type

Override the generated TypeScript type.
#[derive(Wasm)]
#[wasm(override_type = "CustomJsType")]
pub struct SpecialType { }
Generated TypeScript:
export type SpecialType = CustomJsType;

change_ref / change_refs

Change reference type names.
#[derive(Wasm)]
#[wasm(change_ref = "CustomName")]
pub struct Container<T> {
    items: Vec<T>,
}
With multiple mappings:
#[derive(Wasm)]
#[wasm(change_refs = [("OldType", "NewType"), ("Another", "Mapped")])]
pub struct Data { }

Field Attributes

These attributes are applied to struct fields.

rename

Custom name for the field in TypeScript.
#[derive(Wasm)]
pub struct User {
    #[wasm(rename = "userId")]
    id: String,
}

override_type

Override the TypeScript type for this field.
#[derive(Wasm)]
pub struct Data {
    #[wasm(override_type = "Date")]
    timestamp: i64,
}

always_some

Force the field to always be present in TypeScript (removes Option).
#[derive(Wasm)]
pub struct Config {
    #[wasm(always_some = true)]
    #[serde(default)]
    enabled: Option<bool>,  // Becomes `boolean` instead of `boolean | null`
}

rename_wrapper

Control wrapper type renaming for this field.
#[derive(Wasm)]
pub struct Message {
    #[wasm(rename_wrapper = true)]
    count: Int32Value,  // Becomes `number`
}

change_ref / change_refs

Change reference type names for this field.
#[derive(Wasm)]
pub struct Container {
    #[wasm(change_ref = "Item")]
    items: Vec<T>,
}

Variant Attributes

These attributes are applied to enum variants.

rename

Custom name for the variant in TypeScript.
#[derive(Wasm)]
pub enum Status {
    #[wasm(rename = "active")]
    Active,
}

override_type

Override the TypeScript type for this variant.
#[derive(Wasm)]
pub enum Response {
    #[wasm(override_type = "SuccessData")]
    Success(Data),
}

Enum Tagging

The macro respects serde’s enum tagging attributes:

External Tagging (default)

#[derive(Wasm)]
pub enum Message {
    Text(String),
    Number(f64),
}
Generated TypeScript:
export type Message = {
  Text: string;
  Number?: null;
} | {
  Number: number;
  Text?: null;
};

Internal Tagging

#[derive(Wasm)]
#[serde(tag = "type")]
pub enum Message {
    Text { content: String },
    Number { value: f64 },
}
Generated TypeScript:
export type Message = {
  type: "Text";
  content: string;
} | {
  type: "Number";
  value: number;
};

Adjacent Tagging

#[derive(Wasm)]
#[serde(tag = "type", content = "data")]
pub enum Message {
    Text(String),
    Number(f64),
}
Generated TypeScript:
export type Message = {
  type: "Text";
  data: string;
} | {
  type: "Number";
  data: number;
};

Complete Example

use bomboni_wasm::Wasm;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Wasm)]
#[wasm(rename_all = "camelCase")]
pub struct BlogPost {
    post_id: String,
    title: String,
    content: String,
    
    #[wasm(rename = "createdAt")]
    created_timestamp: i64,
    
    #[serde(default)]
    tags: Vec<String>,
    
    author: Author,
    status: PostStatus,
}

#[derive(Serialize, Deserialize, Wasm)]
pub struct Author {
    id: String,
    name: String,
}

#[derive(Serialize, Deserialize, Wasm)]
#[serde(tag = "type")]
pub enum PostStatus {
    Draft,
    Published { published_at: i64 },
    Archived,
}
Generated TypeScript:
export interface BlogPost {
  postId: string;
  title: string;
  content: string;
  createdAt: number;
  tags?: string[];
  author: Author;
  status: PostStatus;
}

export interface Author {
  id: string;
  name: string;
}

export type PostStatus = {
  type: "Draft";
} | {
  type: "Published";
  published_at: number;
} | {
  type: "Archived";
};

Build docs developers (and LLMs) love