TypeScript types

Requires the typescript Cargo feature.

With our TypeScriptRenderer, you can generate TypeScript types for all types that implement Schematic. To utilize, instantiate a generator, add types to render, and generate the output file.

#![allow(unused)]
fn main() {
use schematic::schema::{SchemaGenerator, TypeScriptRenderer};

let mut generator = SchemaGenerator::default();
generator.add::<CustomType>();
generator.generate(output_dir.join("types.ts"), TypeScriptRenderer::default())?;
}

For a reference implementation, check out moonrepo/moon.

Options

Custom options can be passed to the renderer using TypeScriptOptions.

#![allow(unused)]
fn main() {
use schematic::schema::TypeScriptOptions;

TypeScriptRenderer::new(TypeScriptOptions {
	// ...
	..TypeScriptOptions::default()
});
}

Indentation

The indentation of the generated TypeScript code can be customized using the indent_char option. By default this is a tab (\t).

#![allow(unused)]
fn main() {
TypeScriptOptions {
	// ...
	indent_char: "  ".into(),
}
}

Enum types

Enum types can be rendered in a format of your choice using the enum_format option and the EnumFormat enum. By default enums are rendered as TypeScript string unions, but can be rendered as TypeScript enums instead.

#![allow(unused)]
fn main() {
TypeScriptOptions {
	// ...
	enum_format: EnumFormat::Enum,
}
}
// Default
export type LogLevel = "debug" | "info" | "error";

// As enum
export enum LogLevel {
	Debug,
	Info,
	Error,
}

Furthermore, the const_enum option can be enabled to render const enum types instead of enum types. This does not apply when EnumFormat::Union is used.

#![allow(unused)]
fn main() {
TypeScriptOptions {
	// ...
	const_enum: true,
}
}
// Enabled
export const enum LogLevel {}

// Disabled
export enum LogLevel {}

Object types

Struct types can be rendered as either TypeScript interfaces or type aliases using the object_format option and the ObjectFormat enum. By default structs are rendered as TypeScript interfaces.

#![allow(unused)]
fn main() {
TypeScriptOptions {
	// ...
	object_format: ObjectFormat::Type,
}
}
// Default
export interface User {
	name: string;
}

// As alias
export type User = {
	name: string;
};

Properties format

Properties within a struct can be rendered as either optional or required in TypeScript, depending on usage. The default format for all properties can be customized with the property_format option and the PropertyFormat enum. By default all properties are required.

#![allow(unused)]
fn main() {
TypeScriptOptions {
	// ...
	property_format: PropertyFormat::Required,
}
}
// Default / required
export interface User {
	name: string;
}

// Optional
export interface User {
	name?: string;
}

// Optional with undefined union
export interface User {
	name?: string | undefined;
}

Type references

In the context of this renderer, a type reference is simply a reference to another type by its name, and is used by other types of another name. For example, the fields of a struct type may reference another type by name.

export type UserStatus = "active" | "inactive";

export interface User {
	status: UserStatus;
}

Depending on your use case, this may not be desirable. If so, you can enable the disable_references option, which disables references entirely, and inlines all type information. So the example above would become:

#![allow(unused)]
fn main() {
TypeScriptOptions {
	// ...
	disable_references: true,
}
}
export type UserStatus = "active" | "inactive";

export interface User {
	status: "active" | "inactive";
}

Additionally, the exclude_references option can be used to exclude a type reference by name entirely from the output, as demonstrated below.

#![allow(unused)]
fn main() {
TypeScriptOptions {
	// ...
	exclude_references: vec!["UserStatus".into()],
}
}
export interface User {
	status: "active" | "inactive";
}

Importing external types

For better interoperability, you can import external types from other TypeScript modules using the external_types option, which is a map of file paths (relative from the output location) to a list of types to import from that file. This is useful if:

  • You have existing types that aren’t generated and want to reference.
  • You want to reference types from other generated files, and don’t want to duplicate them.
#![allow(unused)]
fn main() {
TypeScriptOptions {
	// ...
	external_types: HashMap::from_iter([
		("./states".into(), vec!["UserStatus".into()]),
	]),
}
}
import type { UserStatus } from "./states";

export interface User {
	status: UserStatus;
}