ts-rs

repository·main·Indexed 23 days ago

https://github.com/aleph-alpha/ts-rs

A Rust library that generates TypeScript type declarations from Rust structs and enums to keep data structures in sync between Rust backends (or WebAssembly) and TypeScript frontends. It provides a TS derive macro, programmatic export methods, and extensive configuration via #[ts] attributes and environment variables. With the serde-compat feature, it respects many serde attributes to ensure TypeScript types match serialized data.

Tokens
4.8K
Snippets
5
Records
25
Agent score
82%

What's inside ts-rs

  1. Use Serde attributes for TypeScript compatibility

    main

    With the serde-compat feature (enabled by default), ts-rs respects many serde attributes to ensure your TypeScript types match your serialized data.

    Supported attributes:

    • rename, rename-all, rename-all-fields
    • tag, content, untagged (for enums)
    • skip, skip_serializing, skip_serializing_if, flatten, default

    Important Notes:

    • skip_serializing and skip_serializing_if only work correctly if used with #[serde(default)] to ensure the generated type is valid for both serialization and deserialization.
    • skip_deserializing is ignored.
    • If you want to exclude a field from the TypeScript type but cannot use #[serde(skip)], use #[ts(skip)] instead.
  2. Use struct field attributes to customize TypeScript types

    main

    You can fine-tune how individual fields in a struct are represented in TypeScript:

    • #[ts(type = "...")]: Overrides the TypeScript type used for this field.
    • #[ts(as = "...")]: Overrides the type used in TypeScript, often used with custom serializers (e.g., #[ts(as = "Option<_>")]).
    • #[ts(rename = "...")]: Renames the field in the generated TypeScript object.
    • #[ts(inline)]: Inlines the type definition instead of using a named type.
    • #[ts(skip)]: Omits this field from the generated TypeScript type.
    • #[ts(flatten)]: Flattens the field, inlining all its keys into the parent object.
    • #[ts(optional)]: Changes Option<T> from t: T | null to t?: T.
    • #[ts(optional = "nullable")]: Changes Option<T> to t?: T | null.
    • #[ts(optional = false)]: Overrides a struct-level #[ts(optional_fields)] setting for this specific field.
  3. Use Serde attributes with `ts-rs`

    main

    If the serde-compat feature is enabled, ts-rs will automatically merge certain #[serde] attributes into the TypeScript generation logic. This allows you to control TypeScript output using familiar Serde patterns.

    Supported #[serde] attributes that affect ts-rs:

    • #[serde(rename = "...")]: Affects field/type renaming.
    • #[serde(rename_all = "...")]: Affects field naming conventions.
    • #[serde(tag = "...")]: Affects how tags are handled.
    • #[serde(bound = "...")]: Affects trait bounds.

    Note: #[serde(default)], #[serde(deny_unknown_fields)], and #[serde(crate = "...")] are recognized to prevent warnings but do not change the TypeScript output directly.

  4. How standard library types are mapped to TypeScript

    main

    The ts-rs library provides automatic TypeScript type generation for common Rust types by implementing the TS trait. When you derive TS on your own structs, the library recursively resolves the TypeScript equivalents of their fields.

    Key mappings include:

    • Options: Option<T> becomes T | null.
    • Results: Result<T, E> becomes { Ok : T } | { Err : E }.
    • Vectors: Vec<T> becomes Array<T>.
    • Fixed-size Arrays: [T; N] becomes a TypeScript tuple [T, T, ...] (up to a configurable limit, otherwise it falls back to Array<T>).
    • HashMaps: HashMap<K, V> becomes a TypeScript index signature {[key in K]: V}. If the key is an enum or use_v11_hashmap is enabled, the key is optional (?).
    • Primitives:
      • Integers (u8, i32, etc.) and floats (f32, f64) map to number.
      • bool maps to boolean.
      • String, str, Path, and IP addresses map to string.
      • () maps to null.
  5. Configure ts-rs export settings via environment variables

    main
    VariableDescriptionDefault
    TS_RS_EXPORT_DIRBase directory into which bindings will be exported./bindings
    TS_RS_IMPORT_EXTENSIONFile extension used in import statementsnone
    TS_RS_LARGE_INTBinding used for large integer types (i64, u64, i128, u128)bigint
    # <project-root>/.cargo/config.toml
    [env]
    TS_RS_EXPORT_DIR = { value = "bindings", relative = true }
    TS_RS_LARGE_INT = "number"
  6. Use container attributes to customize TS generation

    main

    The #[ts(...)] attribute allows you to control how the TS trait is implemented for structs and enums.

    Common Container Attributes

    • #[ts(export)]: Generates a test to export the type to disk.
    • #[ts(export_to = "...")]: Specifies a custom path relative to the export directory.
    • #[ts(rename = "...")]: Sets the TypeScript name of the generated type.
    • #[ts(rename_all = "...")]: Renames all fields/variants (e.g., camelCase, snake_case, PascalCase).
    • #[ts(crate = "...")]: Overrides the default ::ts_rs reference.
    • #[ts(concrete(Param = Type))]: Disables generic parameters by specifying a concrete type. Useful for types with associated types that TypeScript cannot represent.
    • #[ts(bound = "...")]: Overrides the bounds generated on the TS implementation.

    Struct Attributes

    • #[ts(tag = "...")]: Includes the struct name as a field with the given key.
    • #[ts(optional_fields)]: Makes all Option<T> fields optional (t?: T).
    • #[ts(optional_fields = "nullable")]: Makes all Option<T> fields optional and nullable (t?: T | null).

    Enum Attributes

    • #[ts(tag = "...")], #[ts(content = "...")], #[ts(untagged)]: Changes the enum representation (see Serde enum representations).
    • #[ts(repr(enum))]: Exports the enum as a TypeScript enum instead of a type union.
    • #[ts(rename_all_fields = "...")]: Renames fields of all struct variants within the enum.
  7. Use enum variant attributes to customize TypeScript types

    main

    Control how specific variants of an enum are represented:

    • #[ts(rename = "...")]: Renames the variant.
    • #[ts(skip)]: Omits the variant from the generated TypeScript type.
    • #[ts(untagged)]: Treats the variant as untagged, regardless of the enum's global tag/content settings.
    • #[ts(rename_all = "...")]: Renames all fields within a struct variant.
  8. Configure large integer representation

    main

    By default, ts-rs maps large integer types (i64, u64, i128, u128) to the TypeScript bigint type. You can change this behavior using the TS_RS_LARGE_INT environment variable or the Config::with_large_int method.

    This is useful if your frontend environment does not support bigint and you prefer using number (though note that precision may be lost for values exceeding $2^{53}-1$).

  9. Generate TypeScript types from Rust types using the TS trait

    main

    The core of ts-rs is the TS trait. You can implement this trait for your Rust types using the #[derive(TS)] macro. This allows you to generate TypeScript type declarations that match your Rust data structures.

    Automatic Exporting via Tests

    Because Rust procedural macros run before compilation, ts-rs cannot export files during the normal build process. Instead, it generates a test that writes the bindings to disk when you run cargo test.

    To trigger an automatic export, add the #[ts(export)] attribute to your type:

    #[derive(ts_rs::TS)]
    #[ts(export)]
    struct User {
        user_id: i32,
        first_name: String,
        last_name: String,
    }

    When you run cargo test, the following TypeScript type will be exported to ./bindings/User.ts (by default):

    export type User = { user_id: number, first_name: string, last_name: string, };
    #[derive(ts_rs::TS)]
    #[ts(export)]
    struct User {
        user_id: i32,
        first_name: String,
        last_name: String,
    }
  10. Generate TypeScript bindings using the TS derive macro

    main

    To export a type, derive the ts_rs::TS trait and use the #[ts(export)] attribute. When you run cargo test or cargo test export_bindings, ts-rs will automatically generate the corresponding TypeScript file in your configured export directory.

    Example:

    #[derive(ts_rs::TS)]
    #[ts(export)]
    struct User {
        user_id: i32,
        first_name: String,
        last_name: String,
    }

    This will produce a file (e.g., bindings/User.ts) containing:

    export type User = { user_id: number, first_name: string, last_name: string, };
  11. Export TypeScript bindings programmatically

    main

    If you do not want to rely on running tests to generate your files, you can use the TS trait methods to export bindings manually in your code:

    • TS::export_all()
    • TS::export()
    • TS::export_to_string()