Skip to main content

TypeScript

The goal of this backend is not only to generate TypeScript type declarations compatible with how we represent data in JSON but also to provide a fully type-safe and sound approach to serialization and interaction with other languages.

Let us start with a simple observation. Consider the Sidex type i8. How do we map this type to TypeScript? A naive approach would simply map i8 to the TypeScript number type, however, while 3.1415 is a number it is certainly not i8. Hence, if we were to map i8 to number, we could, for example, pass 3.1415 to a method which expects i8 without TypeScript complaining — but, this is not type-safe and also not what we want. We want a mapping where we can use i8 as a number (e.g., when it is returned by a method) while not being able to use just any number as i8. The language mapping provided here, enables precisely that.

More general, the language mapping is such that for any Sidex type T and JSON representation "..." of data of type T

JSON.parse("...")

has type ts(T), i.e., the TypeScript language mapping of type T. Furthermore, for any data ... of type ts(T)

JSON.stringify(...)

yields a JSON representation of type T. This ensures that we can use the standard functions for JSON.

Runtime Library

TypeScript's type system is structural, while Sidex's type system is nominal.

A central part of the runtime library is the Nominal TS type definition:

declare const SIDEX_PATH_SYMBOL: unique symbol

/**
* A nominal type based on `T` and identified by it's Sidex path `P`.
*/
export type Nominal<T, P extends string> = T & { [SIDEX_PATH_SYMBOL]: P }

With that, we define types:

type I8 = Nominal<number, "i8">

Any value of type I8 is a number, however, not any number is also of type i8.

We can use I8 in computations and it will just behave like number.

Type Mapping

The type attribute can be used on any type definition to explicitly define a TS type:

#[typescript(type = "<TYPE-EXPR>")]

Whether a type is nominal or not can be controlled with the following attributes:

#[typescript(nominal)]
#[typescript(non_nominal)]

Opaque types are automatically made nominal if they have no type = ... attribute.

JSON Types

If no type attribute is present, the JSON type attribute is used.

For instance,

#[json(type = "string")]
opaque Uuid

will result in:

type Uuid = Nominal<string, "::bundle_name::schema_name::Uuid">

Helper functions for converting between types will also be generated, e.g.:

function asUuid(x: string): Uuid {
return x as Uuid
}

Services

🚧 TODO: Think about how services will be translated.

interface Api {
(arg1: I8): Promise<string>
}

Supported Extensions

Extension: Diff and Path

The TypeScript backend has support for the Diff and Patch extension. It supports applying patches to objects and generating patches with the help of Immer.