A library for generating TypeScript definitions from rust code.

Overview

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.1"
serde = { version = "1.0", features = ["derive"] }
wasm-bindgen = { version = "0.2", features = ["serde-serialize"] }
use serde::{Deserialize, Serialize};
use tsify::Tsify;
use wasm_bindgen::prelude::*;

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

#[wasm_bindgen]
pub fn into_js() -> Point {
    Point { x: 0, y: 0 }
}

#[wasm_bindgen]
pub fn from_js(point: Point) {}

Will generate the following .d.ts file:

/* tslint:disable */
/* eslint-disable */
/**
 * @returns {Point}
 */
export function into_js(): Point;
/**
 * @param {Point} point
 */
export function from_js(point: Point): void;
export type Point = { x: number; y: number };

Attributes

Tsify container attributes

  • into_wasm_abi implements IntoWasmAbi and OptionIntoWasmAbi. This can be converted directly from Rust to JS via JSON.
  • from_wasm_abi implements FromWasmAbi and OptionFromWasmAbi. This is the opposite operation of the above.

Tsify field attributes

  • type
  • optional

Serde attributes

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

Type Override

use tsify::Tsify;

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

Generated type:

export type Foo = { x: 0 | 1 | 2 };

Optional Properties

, #[serde(default)] c: i32, }">
#[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 type Optional = { a?: number; b?: string; c?: number };

Crate Features

Comments
  • Use of `null` rather than `undefined`

    Use of `null` rather than `undefined`

    I was running into an issue yesterday with adjacently tagged enums. If you have something like:

    #[derive(Debug, Serialize, Deserialize, Tsify)]
    #[serde(tag = "reason", content = "inner")]
    pub enum ParseBaseUriError {
        MissingTrailingSlash,
        ...
    }
    

    Then the generated types have the following structure

    declare namespace ParseBaseUriError {
        export type MissingTrailingSlash = { reason: "MissingTrailingSlash"; inner: null };
        ...
    

    The issue here is that the type suggests inner must be null, where in my testing, serializing these objects just leaves the field as undefined.

    I've got a branch where I've written a potential fix, but I have a feeling it might be quite hacky and I may not understand the reasoning behind picking null originally. Is there a reason why this library chooses null for unit types rather than undefined? I tried thinking about this and I'm not sure which one I'd pick, as I suspect neither map perfectly.

    That branch makes inner optional, while keeping the value of null. If that seems sensible I'm happy to open a PR.

    bug 
    opened by Alfred-Mountfield 5
  • Enum as Namespace

    Enum as Namespace

    In the mission of creating a cleaner typescript api for my own library, i updated the code of this lib to emit enums as namespaces of types. Therefore each enum option now has its own type declaration and the union type is created from those types. For example:

    declare namespace GenericEnum {
        export type GenericEnumUnit = "Unit";
        export type GenericEnumNewType<T> = { NewType: T };
        export type GenericEnumSeq<T, U> = { Seq: [T, U] };
        export type GenericEnumMap<T, U> = { Map: { x: T; y: U } };
    }
    
    export type GenericEnum<T, U> = GenericEnum.GenericEnumUnit | GenericEnum.GenericEnumNewType<T> | GenericEnum.GenericEnumSeq<T, U> | GenericEnum.GenericEnumMap<T, U>;
    

    I also introduced the enum_reimport_module option to further cleanup the namespace using a little "hack", since you cannot import inside the module. With this option the internal enum looks like this:

    #[derive(Tsify)]
    #[tsify(enum_reimport_module)]
    enum Internal {
        Struct { x: String, y: i32 },
        EmptyStruct {},
        Tuple(i32, String),
        EmptyTuple(),
        Newtype(Foo),
        Unit,
    }
    

    without enum_reimport_module:

    declare namespace Internal {
        export type InternalStruct = { Struct: { x: string; y: number } };
        export type InternalEmptyStruct = { EmptyStruct: {} };
        export type InternalTuple = { Tuple: [number, string] };
        export type InternalEmptyTuple = { EmptyTuple: [] };
        export type InternalNewtype = { Newtype: Foo };
        export type InternalUnit = "Unit";
    }
    
    export type Internal = Internal.InternalStruct | Internal.InternalEmptyStruct | Internal.InternalTuple | Internal.InternalEmptyTuple | Internal.InternalNewtype | Internal.InternalUnit;
    

    with enum_reimport_module:

    import type * as Internal_Module from "./tsify";
    declare namespace Internal {
        export type Struct = { Struct: { x: string; y: number } };
        export type EmptyStruct = { EmptyStruct: {} };
        export type Tuple = { Tuple: [number, string] };
        export type EmptyTuple = { EmptyTuple: [] };
        export type Newtype = { Newtype: Internal_Module.Foo };
        export type Unit = "Unit";
    }
    
    export type Internal = Internal.Struct | Internal.EmptyStruct | Internal.Tuple | Internal.EmptyTuple | Internal.Newtype | Internal.Unit;
    
    opened by 28Smiles 4
  • Namespaced Recursive Enums generate broken Typescript

    Namespaced Recursive Enums generate broken Typescript

    When you have a recursive enum such as Foo here:

    use tsify::Tsify;
    use serde::{Serialize, Deserialize};
    
    #[derive(Tsify)]
    #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
    #[serde(rename_all = "camelCase", deny_unknown_fields)]
    pub struct OneOf<T> {
        one_of: Vec<T>,
    }
    
    #[derive(Tsify)]
    #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
    pub struct Bar {}
    
    #[derive(Tsify)]
    #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
    #[serde(untagged)]
    pub enum Foo {
        OneOfFoo(OneOf<Foo>),
        Bar(Bar),
    }
    

    then the generated Typescript bindings fail to compile

    type __FooBar = Bar;
    type __FooFoo = Foo;
    type __FooOneOf<A> = OneOf<A>;
    declare namespace Foo {
        export type OneOfFoo = __FooOneOf<__FooFoo>;
        export type Bar = __FooBar;
    }
    
    export type Foo = Foo.OneOfFoo | Foo.Bar;
    
    export interface Bar {}
    
    export interface OneOf<T> {
        oneOf: T[];
    }
    

    Due to TS2456: Type alias 'Foo' circularly references itself.

    This does not happen if the type was just

    export type Foo = OneOf<Foo> | Bar
    

    I assumed #[serde(untagged)] would provide that but it seems not?

    opened by Alfred-Mountfield 2
  • Doesn't work for return types if the function is async?

    Doesn't work for return types if the function is async?

    Hi! thanks for creating this library. As the title says, it seems not to work for return types if the exported bindgen function is async. This error shows:

    the trait bound `wasm_bindgen::JsValue: From<MyStruct>` is not satisfied
    

    If I make the function non async, it works. For parameters it always works.

    opened by ivanschuetz 1
  • Flatten and Option results in broken types

    Flatten and Option results in broken types

    Discovered another strange edge-case today, see the following:

    #[test]
    fn test_flatten_optional() {
        #[derive(Tsify)]
        struct A {
            a: i32,
            b: String,
        }
    
        #[derive(Tsify)]
        struct B {
            #[tsify(optional)]
            #[serde(flatten)]
            extra: Option<A>,
            c: i32,
        }
    
        assert_eq!(
            B::DECL,
            indoc! {""}
        );
    }
    

    You get the generated type:

    export interface B extends A | null {
        c: number;
    }
    

    which is invalid.

    I suspect the way to solve this would be for B to become a type and to use & instead of extends so you could do something like:

    type B = {
    
    } & (A | {})
    

    I'm not sure of a way to express this using interfaces, so I suspect this would be a pretty substantial change.

    opened by Alfred-Mountfield 1
  • Support for Generics when using `type = `

    Support for Generics when using `type = `

    If you have a type like this:

    #[derive(Tsify)]
    #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
    pub struct Foo<T> {
        #[tsify(type = "[T, ...T[]]")]
        bar: Vec<T>,
    }
    

    then the generated binding looks like this:

    export interface Foo {
        bar: [T, ...T[]];
    }
    

    when I would have expected it to be

    export interface Foo<T> {
        bar: [T, ...T[]];
    }
    

    (and just for clarity, if you don't use the type = macro then it looks like):

    export interface Foo<T> {
        bar: T[];
    }
    
    bug 
    opened by Alfred-Mountfield 0
  • Tsify without wasm_bindgen

    Tsify without wasm_bindgen

    Hello, I was just wondering if it's possible to decouple this from wasm_bindgen.

    I understand that the original purpose of the library was to focus on wasm_bindgen but I was wondering if this could become a more generalised library for creating cross-language type definitions, similar to https://github.com/1Password/typeshare

    opened by Alfred-Mountfield 0
  • Discriminated unions?

    Discriminated unions?

    I was trying to switch ("match style") over enum cases with fields, but this seems not to be possible. Example (src):

    type AppEvent =
      | { kind: "click"; x: number; y: number }
      | { kind: "keypress"; key: string; code: number }
      | { kind: "focus"; element: HTMLElement };
    
    function handleEvent(event: AppEvent) {
      switch (event.kind) {
        case "click":
          // We know it is a mouse click, so we can access `x` and `y` now
          console.log(`Mouse clicked at (${event.x}, ${event.y})`);
          break;
        case "keypress":
          // We know it is a key press, so we can access `key` and `code` now
          console.log(`Key pressed: (key=${event.key}, code=${event.code})`);
          break;
        case "focus":
          // We know it is a focus event, so we can access `element`
          console.log(`Focused element: ${event.element.tagName}`);
          break;
      }
    }
    

    Potential feature request, or maybe it's already possible and I'm missing something?

    opened by ivanschuetz 1
  • Internally tagged enums can generate invalid TS when using non-object types

    Internally tagged enums can generate invalid TS when using non-object types

    If you have an enum such as this:

    #[derive(Debug, Serialize, Deserialize, Tsify)]
    #[serde(tag = "reason")]
    pub enum ParseBaseUriError {
        MissingTrailingSlash,
        UrlParseError(String),
        CannotBeABase,
    }
    

    then the generated types look as follows:

    declare namespace ParseBaseUriError {
        export type MissingTrailingSlash = { reason: "MissingTrailingSlash" };
        export type UrlParseError = { reason: "UrlParseError" } & string;
        export type CannotBeABase = { reason: "CannotBeABase" };
    }
    

    And I believe UrlParseError is unsatisfiable.

    I don't really have a good suggestion for how to resolve this, perhaps disallowing new-type like variants, or possibly only ones that contain types that can be mapped to a non-object JS type. For now I have resolved this by picking a different tagging mechanism, or turning the variants into structs, but I thought I'd flag in case there are any other ideas or to allow adding a warning.

    enhancement 
    opened by Alfred-Mountfield 1
  • Support for `RefFromWasmAbi` and `RefMutFromWasmAbi`

    Support for `RefFromWasmAbi` and `RefMutFromWasmAbi`

    I've just tried out this crate and the typescript interfaces it's creating are awesome. Unfortunately I don't seem to be able to use it in methods like

    #[wasm_bindgen]
    modify_some_object(my_wasm_obj: &mut MyWasmObj); 
    

    as tsify(into_wasm_abi, from_wasm_abi) doesn't implement support for RefFromWasmAbi and RefMutFromWasmAbi

    Is there any plan to support deriving impl's for these?

    opened by Alfred-Mountfield 6
Owner
Madono Haru
Madono Haru
Interface definitions for the Compute@Edge platform in witx.

?? compute-at-edge-abi This repository contains witx definitions for the Compute@Edge platform ABI. About witx The witx file format is an experimental

Fastly 14 Apr 5, 2022
AVR device definitions

avrd AVR device definitons in Rust. Documentation This crate exposes information about different AVR microcontrollers so it can be used pragmatically.

The AVR-Rust project 32 Dec 28, 2022
tiny_id is a Rust library for generating non-sequential, tightly-packed short IDs.

tiny_id tiny_id is a Rust library for generating non-sequential, tightly-packed short IDs. Most other short ID generators just string together random

Paul Butler 33 Aug 6, 2022
Blazing fast linter for JavaScript and TypeScript written in Rust

deno_lint A Rust crate for writing fast JavaScript and TypeScript linters. This crate powers deno lint, but is not Deno specific and can be used to wr

Deno Land 1.3k Dec 29, 2022
A bundler (mainly for TypeScript projects) made in Rust

TSAR A bundler (mainly for TypeScript projects) made in Rust. Also my first Rust Project! What does TSAR stand for Like phar (PHP Archive) or JAR (Jav

null 2 Mar 19, 2022
Generating Permutations with Rust

rust_permutations Generating Permutations with Rust Permutation Input: a string of unknown length that can contain only three characters: 0, 1, or . F

Flavio Rasseli 1 Oct 25, 2021
A tool of generating and viewing dice roll success distributions.

AZDice A GUI tool for generating and visualising dice roll probability distributions. Aims Intended to help people trying to get game balance just rig

null 13 Mar 2, 2021
Ever wanted to torture your CPU by generating every image possible?

dumpsterfire Ever wanted to torture your CPU by generating every image possible? Well, now you can! This thing is worse than mining Bitcoin, since the

null 2 Jun 14, 2022
Easy to use Rust i18n library based on code generation

rosetta-i18n rosetta-i18n is an easy-to-use and opinionated Rust internationalization (i18n) library powered by code generation. rosetta_i18n::include

null 38 Dec 18, 2022
Rust library to scan files and expand multi-file crates source code as a single tree

syn-file-expand This library allows you to load full source code of multi-file crates into a single syn::File. Features: Based on syn crate. Handling

Vitaly Shukela 11 Jul 27, 2022
A library to compile USDT probes into a Rust library

sonde sonde is a library to compile USDT probes into a Rust library, and to generate a friendly Rust idiomatic API around it. Userland Statically Defi

Ivan Enderlin 40 Jan 7, 2023
Migrate C code to Rust

C2Rust helps you migrate C99-compliant code to Rust. The translator (or transpiler) produces unsafe Rust code that closely mirrors the input C code. T

Immunant 3k Jan 8, 2023
Minimal, flexible framework for implementing solutions to Advent of Code in Rust

This is advent_of_code_traits, a minimal, flexible framework for implementing solutions to Advent of Code in Rust.

David 8 Apr 17, 2022
Simplified glue code generation for Deno FFI libraries written in Rust.

deno_bindgen This tool aims to simplify glue code generation for Deno FFI libraries written in Rust. Quickstart # install CLI deno install -Afq -n den

Divy Srivastava 173 Dec 17, 2022
A simple code boilerplate generator written in Rust.

?? Cgen What is Cgen? A modern, cross-platform, multi-language boilerplate generator aimed to make your code generation less hectic! If you wish to su

Rithul Kamesh 1 Dec 25, 2021
A code coverage tool for Rust projects

Tarpaulin Tarpaulin is a code coverage reporting tool for the Cargo build system, named for a waterproof cloth used to cover cargo on a ship. Currentl

null 1.8k Jan 2, 2023
Rust macro that uses GPT3 codex to generate code at compiletime

gpt3_macro Rust macro that uses GPT3 codex to generate code at compiletime. Just describe what you want the function to do and (optionally) define a f

Maximilian von Gaisberg 59 Dec 18, 2022
Generate bindings to use Rust code in Qt and QML

Rust Qt Binding Generator This code generator gets you started quickly to use Rust code from Qt and QML. In other words, it helps to create a Qt based

KDE GitHub Mirror 768 Dec 24, 2022
A rollup plugin that compile Rust code into WebAssembly modules

rollup-plugin-rust tl;dr -- see examples This is a rollup plugin that loads Rust code so it can be interop with Javascript base project. Currently, th

Fahmi Akbar Wildana 37 Aug 1, 2022