daml2ts - TypeScript code generator for DAML

daml2ts : TypeScript of DAML

Introduction

daml2ts generates TypeScript for interacting with DAML via the json-api .

daml2ts inputs are compiled DAML modules. daml2ts outputs are TypeScript source files containing definitions modeling the DAML entities found.

The code daml2ts generates uses the library @daml/types.

Usage

daml2ts is invoked via the DAML SDK assistant.

In outline, the command to generate TypeScript from DAML is daml codegen ts DAR -o OUTDIR –main-package-name=PACKAGE where DAR is the path to a DAR file (generated via daml build), OUTDIR is a directory where you want the TypeScript to be written and PACKAGE is a desired TypeScript package name.

Here’s a complete example that generates TypeScript from a project produced from the standard “skeleton” template.

1
2
3
4
daml new my-proj skeleton # Create a new project based off the skeleton template
cd my-proj # Enter the newly created project directory
daml build  # Compile the project's DAML files into a DAR
daml codegen ts .daml/dist/my-proj-0.0.1.dar -o generated/ts --main-package-name=my-proj # Generate Typescript from the DAR
  • On execution of these commands:
    • The directory my-proj/generated/ts contains the generated TypeScript source files;
    • The files are arranged into directories;
    • One of those directories will be named as per the PACKAGE argument and will contain the TypeScript definitions corresponding to the DAML files in the project;
    • For example, generated/ts/my-proj/Main.ts contains the TypeScript definitions for daml/Main.daml;
    • The remaining directories contain supporting TypeScript corresponding to modules of the DAML standard library;
    • Those directories have numeric names (the names are hashes of the DAML-LF package they are derived from).

To get a quickstart idea of how to use what has been generated, you may wish to jump to the Templates and choices section and return to the reference material that follows as needed.

Primitive DAML types: @daml/types

To understand the TypeScript code generated by daml2ts, it is helpful to keep in mind this quick review of the TypeScript equivalents of the primitive DAML types provided by @daml/types.

Interfaces:

  • Template<T extends object, K = unknown>
  • Choice<T extends object, C, R, K = unknown>

Types:

DAML TypeScript TypeScript definition
() Unit {}
Bool Bool boolean
Int Int string
Decimal Decimal string
Numeric ν Numeric string
Text Text string
Time Time string
Party Party string
[τ] List<τ> τ[]
Date Date string
ContractId τ ContractId<τ> string
Optional τ Optional<τ> null | (null extends τ ? [] | [Exclude<τ, null>] : τ)
TextMap τ TextMap<τ> { [key: string]: τ }
(τ₁, τ₂) Tuple₂<τ₁, τ₂> {_1: τ₁; _2: τ₂}

Note

The types given in the “TypeScript” column are defined in @daml/types.

Note

For n-tuples where n ≥ 3, representation is analogous with the pair case (the last line of the table).

Note

The TypeScript types Time, Decimal, Numeric and Int all alias to string. These choices relate to the avoidance of precision loss under serialization over the json-api.

Note

The Typescript definition of type Optional<τ> in the above table might look complicated. It accounts for differences in the encoding of optional values when nested versus when they are not (i.e. “top-level”). For example, null and "foo" are two possible values of Optional<Text> whereas, [] and ["foo"] are two possible values of type Optional<Optional<Text>> (null is another possible value, [null] is not).

DAML to TypeScript mappings

The mappings from DAML to TypeScript are best explained by example.

Records

In DAML, we might model a person like this.

1
2
3
4
5
data Person =
  Person with
    name: Text
    party: Party
    age: Int

Given the above definition, the generated TypeScript code will be as follows.

1
2
3
4
5
type Person = {
  name: string;
  party: daml.Party;
  age: daml.Int;
}

Variants

This is a DAML type for a language of additive expressions.

1
2
3
4
data Expr a =
    Lit a
  | Var Text
  | Add (Expr a, Expr a)

In TypeScript, it is represented as a discriminated union.

1
2
3
4
type Expr<a> =
  |  { tag: 'Lit'; value: a }
  |  { tag: 'Var'; value: string }
  |  { tag: 'Add'; value: {_1: Expr<a>, _2: Expr<a>} }

Sum-of-products

Let’s slightly modify the Expr a type of the last section into the following.

1
2
3
4
data Expr a =
    Lit a
  | Var Text
  | Add {lhs: Expr a, rhs: Expr a}

Compared to the earlier definition, the Add case is now in terms of a record with fields lhs and rhs. This renders in TypeScript like so.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
type Expr<a> =
  |  { tag: 'Lit2'; value: a }
  |  { tag: 'Var2'; value: string }
  |  { tag: 'Add'; value: Expr.Add<a> }

namespace Expr {
  type Add<a> = {
    lhs: Expr<a>;
    rhs: Expr<a>;
  }
}

The thing to note is how the definition of the Add case has given rise to a record type definition Expr.Add.

Enums

DAML enumerations map naturally to TypeScript.

1
data Color = Red | Blue | Yellow

The companion TypeScript type is the following.

1
2
3
4
5
enum Color {
  Red = 'Red',
  Blue = 'Blue',
  Yellow = 'Yellow',
}

Templates and choices

Here is a DAML template of a basic ‘IOU’ contract.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
template Iou
  with
    issuer: Party
    owner: Party
    currency: Text
    amount: Decimal
  where
    signatory issuer
    choice Transfer: ContractId Iou
      with
        newOwner: Party
      controller owner
      do
        create this with owner = newOwner

daml2ts generates types for each of the choices defined on the template as well as the template itself.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
type Transfer = {
  newOwner: daml.Party;
}

type Iou = {
  issuer: daml.Party;
  owner: daml.Party;
  currency: string;
  amount: daml.Numeric;
}

Each template results in the generation of a companion object. Here, is a schematic of the one generated from the Iou template [2].

1
2
3
4
5
6
const Iou: daml.Template<Iou, undefined> & {
  Archive: daml.Choice<Iou, DA_Internal_Template.Archive, {}, undefined>;
  Transfer: daml.Choice<Iou, Transfer, daml.ContractId<Iou>, undefined>;
} = {
  /* ... */
}
[2]The undefined type parameter captures the fact that Iou has no contract key.

The exact details of these companion objects are not important - think of them as representing “metadata”.

What is important is the use of the companion objects when creating contracts and exercising choices using the @daml/ledger package. The following code snippet demonstrates their usage.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import Ledger from  '@daml/ledger';
import {Iou, Transfer} from /* ... */;

const ledger = new Ledger(/* ... */);

// Contract creation; Bank issues Alice a USD $1MM IOU.

const iouDetails: Iou = {
  issuer: 'Chase',
  owner: 'Alice',
  currency: 'USD',
  amount: 1000000.0,
};
const aliceIouCreateEvent = await ledger.create(Iou, iouDetails);
const aliceIouContractId = aliceIouCreateEvent.contractId;

// Choice execution; Alice transfers ownership of the IOU to Bob.

const transferDetails: Transfer = {
  newOwner: 'Bob',
}
const [bobIouContractId, _] = await ledger.exercise(Transfer, aliceIouContractId, transferDetails);

Observe on line 14, the first argument to create is the Iou companion object and on line 22, the first argument to exercise is the Transfer companion object.