Skip to content

Repository files navigation

Tsify

Tsify is a library for generating TypeScript definitions from Rust code.

Using this with wasm-bindgen will automatically output the types to .d.ts.

Inspired by typescript-definitions and ts-rs.

Example

Click to show Cargo.toml.
[dependencies]
tsify = "0.5.7"
serde = { version = "1.0", features = ["derive"] }
wasm-bindgen = { version = "0.2" }
use serde::{Deserialize, Serialize};
use tsify::Tsify;
use tsify::Ts;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsError;

#[derive(Tsify, Serialize, Deserialize)]
pub struct Point {
    x: i32,
    y: i32,
}

#[wasm_bindgen]
pub fn into_js() -> Result<Ts<Point>, JsError> {
    let point = Point { x: 0, y: 0 };
    Ok(point.into_ts()?)
}

#[wasm_bindgen]
pub fn from_js(point: Ts<Point>) -> Result<(), JsError> {
    let point: Point = point.to_rust()?;
    Ok(())
}

Will generate the following .d.ts file:

/* tslint:disable */
/* eslint-disable */
export interface Point {
    x: number;
    y: number;
}


export function from_js(point: Point): void;

export function into_js(): Point;

This is the behavior due to typescript_custom_section and Rust Type conversions.

Crate Features

  • json (default) enables serialization through serde_json.
  • js enables serialization through serde-wasm-bindgen and generates the appropriate types for it. This will be the default in future versions.

Attributes

Tsify container attributes

  • into_wasm_abi (deprecated) implements IntoWasmAbi and OptionIntoWasmAbi. This can be converted directly from Rust to JS via serde_json or serde-wasm-bindgen. Deprecated in favour of using Ts<T> as on function parameters and return type (why).
  • from_wasm_abi (deprecated) implements FromWasmAbi and OptionFromWasmAbi. This is the opposite operation of the above. Deprecated in favour of using Ts<T> as on function parameters and return type (why).
  • namespace generates a namespace for the enum variants.
  • type overrides at the container level.
  • type_params overrides params at the container level.

Serializer configuration options

  • missing_as_null
  • hashmap_as_object
  • large_number_types_as_bigints

Tsify field attributes

  • type
  • type_params
  • optional

Serde attributes

  • rename
  • rename-all
  • tag
  • content
  • untagged
  • skip
  • skip_serializing
  • skip_deserializing
  • skip_serializing_if = "Option::is_none"
  • flatten
  • default
  • transparent

Why are the wasm_abi attributes deprecated?

#[tsify(into_wasm_abi, from_wasm_abi)] moves (de)serialization into the wasm-bindgen ABI boundary, and that boundary cannot report failure.

wasm_bindgen::convert::FromWasmAbi::from_abi returns Self, not Result<Self, _>, and there is no fallible variant of it or of RefFromWasmAbi / LongRefFromWasmAbi / VectorFromWasmAbi. So when serde fails to deserialize what JavaScript passed in, the generated impl has only one way out: wasm_bindgen::throw_str, which raises a JavaScript exception that unwinds straight past the wasm frames. As wasm-bindgen's own documentation warns:

Note that it is very easy to leak memory with this function because this function, unlike panic! on other platforms, will not run destructors.

Everything alive at that moment leaks: the serde error, the partially deserialized value, and — because arguments are converted one after another — every argument already converted before the failing one. From JavaScript this looks like an ordinary, catchable exception, so an application can appear to handle bad input correctly while its wasm heap grows on every failure, until the instance dies with RuntimeError: memory access out of bounds (see #65 and #86).

Ts<T> keeps the boundary infallible: it is a #[repr(transparent)] wrapper whose FromWasmAbi impl only forwards the underlying JsValue. Deserialization then happens inside your function, where it is an ordinary Result — the from_js example at the top of this page shows the shape. Because the function returns normally, destructors run and nothing leaks. The generated TypeScript is unchanged, so .d.ts consumers are unaffected.

Ts<T> needs only #[derive(Tsify)] — do not add #[tsify(from_wasm_abi)] alongside it. Note also that Ts<Vec<T>> is not supported, only Vec<Ts<T>>; to convert a whole vector, use items.into_iter().map(|x| x.to_rust()).collect::<Result<Vec<_>, _>>()?.

Type Override

use tsify::Tsify;

#[derive(Tsify)]
pub struct Foo {
    #[tsify(type = "0 | 1 | 2")]
    x: i32,
}

Generated type:

export interface Foo {
    x: 0 | 1 | 2;
}

Optional Properties

use tsify::Tsify;

#[derive(Tsify)]
struct Optional {
    #[tsify(optional)]
    a: Option<i32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    b: Option<String>,
    #[serde(default)]
    c: i32,
}

Generated type:

export interface Optional {
    a?: number;
    b?: string;
    c?: number;
}

Enum

use tsify::Tsify;

#[derive(Tsify)]
enum Color {
    Red,
    Blue,
    Green,
    Rgb(u8, u8, u8),
    Hsv {
        hue: f64,
        saturation: f64,
        value: f64,
    },
}

Generated type:

export type Color =
    | "Red"
    | "Blue"
    | "Green"
    | { Rgb: [number, number, number] }
    | { Hsv: { hue: number; saturation: number; value: number } };

Enum with namespace

use tsify::Tsify;

#[derive(Tsify)]
#[tsify(namespace)]
enum Color {
    Red,
    Blue,
    Green,
    Rgb(u8, u8, u8),
    Hsv {
        hue: f64,
        saturation: f64,
        value: f64,
    },
}

Generated type:

declare namespace Color {
    export type Red = "Red";
    export type Blue = "Blue";
    export type Green = "Green";
    export type Rgb = { Rgb: [number, number, number] };
    export type Hsv = {
        Hsv: { hue: number; saturation: number; value: number };
    };
}

export type Color = Color.Red | Color.Blue | Color.Green | Color.Rgb | Color.Hsv;

Type Aliases

use tsify::{declare, Tsify};

#[derive(Tsify)]
struct Foo<T>(T);

#[declare]
type Bar = Foo<i32>;

Generated type:

export type Foo<T> = T;
export type Bar = Foo<number>;

About

A library for generating TypeScript definitions from rust code.

Topics

Resources

Contributing

Stars

500 stars

Watchers

4 watching

Forks

Used by

Contributors

Languages