************************** TypeScript Support - atdts ************************** This documentation is incomplete. Your help would be appreciated! In particular, some how-to guides would be great. .. The atdts documentation is divided in four parts as advocated by Daniele Procida: https://documentation.divio.com/ I recommend watching the 30-min presentation. Tutorials ========= .. Tutorials are learning-oriented. The reader is taken through a series of actions that will directly show them what the tool is capable of. Explanations should not be necessary. documentation category: practical/exploring Hello World ----------- Install ``atdts`` with ``opam``:: opam install atdts Create a file ``hello.atd`` containing this: .. code-block:: ocaml type message = { subject: string; body: string; } Call ``atdts`` to produce ``hello.ts``:: $ atdts hello.atd There's now a file ``hello.ts`` that contains a class looking like this: .. code-block:: typescript ... export type Message = { subject: string; body: string; } export function writeMessage(x: Message, context: any = x): any { ... } export function readMessage(x: any, context: any = x): Message { ... } ... Let's write a TypeScript program ``say_hello.ts`` that uses this code: .. code-block:: typescript import * as hello from "./hello" const msg: hello.Message = { subject: "Hello", body: "Dear friend, I hope you are well." } console.log(JSON.stringify(hello.writeMessage(msg))) Running it will print the JSON message:: $ tsc --lib es2017,dom say_hello.ts {"subject":"Hello","body":"Dear friend, I hope you are well."} Such JSON data can be parsed. Let's write a program ``read_message.ts`` that consumes JSON data from standard input: .. code-block:: typescript import * as hello from "./hello" import * as readline from "readline" const rl = readline.createInterface({ input: process.stdin, output: process.stdout }) rl.question('', (data: string) => { const msg = hello.readMessage(JSON.parse(data)) console.log("subject: " + msg.subject) }) Output:: # Install dependencies $ npm install --save-dev @types/node $ npm install readline # Compile $ tsc --lib es2017,dom read_message.ts # Run $ echo '{"subject": "big news", "body": ""}' | js read_message.js subject: big news It works! But what happens if the JSON data lacks a ``"subject"`` field? Let's see:: $ echo '{"body": ""}' | js read_message.js {"body": ""} readline.js:1086 throw err; ^ Error: missing field 'subject' in JSON object of type 'Message' ... And what if our program also thought that the correct field name was ``subj`` rather than subject? Here's ``read_message_wrong.ts`` which tries to access a ``subj`` field: .. code-block:: typescript import * as hello from "./hello" import * as readline from "readline" const rl = readline.createInterface({ input: process.stdin, output: process.stdout }) rl.question('', (data: string) => { const msg = hello.readMessage(JSON.parse(data)) console.log("subject: " + msg.subj) }) Let's compile our program:: $ tsc --lib es2017,dom read_message_wrong.ts read_message_wrong.ts:11:33 - error TS2339: Property 'subj' does not exist on type 'Message'. 11 console.log("subject: " + msg.subj) ~~~~ Found 1 error in read_message_wrong.ts:11 The typechecker detected that our program makes incorrect assumptions about the message format without running it. ATD Records, JSON objects, TypeScript objects --------------------------------------------- An ATD file contains types that describe the structure of JSON data. JSON objects map to TypeScript types and objects. They're called records in the ATD language. Let's define a simple record type in the file ``hello_plus.atd``: .. code-block:: ocaml type message = { subject: string; ~body: string; } Note the `~` in front of the ``body`` field. It means that this field has a default value. Whenever the JSON field is missing from a JSON object, a default value is assumed. The implicit default value for a string is ``""``. Let's add a ``signature`` field whose default value isn't the empty string: .. code-block:: ocaml type message = { subject: string; ~body: string; ~signature : string; } Finally, we'll add an optional ``url`` field that doesn't take a default value at all: .. code-block:: ocaml type message = { subject: string; ~body: string; ~signature : string; ?url: string option; } Let's generate the TypeScript code for this. :: $ atdts hello_plus.atd Let's update our reader program ``read_message_plus.ts`` to this: .. code-block:: typescript import * as hello_plus from "./hello_plus" import * as readline from "readline" const rl = readline.createInterface({ input: process.stdin, output: process.stdout }) rl.question('', (data: string) => { const msg = hello_plus.readMessage(JSON.parse(data)) console.log(msg) }) We can test it, showing us the final value of each field:: $ tsc --lib es2017,dom read_message_plus.ts $ echo '{"subject":"hi"}' | js read_message_plus.js {"subject":"hi"} { subject: 'hi', body: '', signature: 'anonymous', url: undefined } How-to guides ============= .. How-to guides are goal-oriented. They're for solving specific problems once the reader is a user with a sense of what the tool can do for them. documentation category: practical/producing Defining default field values ----------------------------- [missing] Renaming field names -------------------- [missing] Deep dives ========== .. Deep dives are focused on understanding. They're discussions on a topic. documentation category: theoretical/exploring [missing] Reference ========= .. A reference is precise and complete. documentation category: theoretical/producing Type mapping ------------ +--------------------+-------------------------------+-------------------------+ | ATD type | TypeScript type | JSON example | +====================+===============================+=========================+ | ``unit`` | ``null`` | ``null`` | +--------------------+-------------------------------+-------------------------+ | ``bool`` | ``bool`` | ``True`` | +--------------------+-------------------------------+-------------------------+ | ``int`` | ``Int``\* | ``42`` or ``42.0`` | +--------------------+-------------------------------+-------------------------+ | ``float`` | ``number`` | ``6.28`` | +--------------------+-------------------------------+-------------------------+ | ``string`` | ``string`` | ``"Hello"`` | +--------------------+-------------------------------+-------------------------+ | ``string list`` | ``string[]`` | ``["a", "b", "c!"]`` | +--------------------+-------------------------------+-------------------------+ | ``(bool * float)`` | ``[boolean, number]`` | ``[-1, 1]`` | +--------------------+-------------------------------+-------------------------+ | ``int nullable`` | ``Int | null`` | ``42`` or ``null`` | +--------------------+-------------------------------+-------------------------+ | ``abstract`` | ``any`` | anything | +--------------------+-------------------------------+-------------------------+ | ``{ id: string }`` | ``{ id: string }`` | ``{"id": "3hj8d"}`` | +--------------------+-------------------------------+-------------------------+ | ``[A | B of int]`` | ``{kind: 'A'}`` | ``"A"`` or ``["B", 5]`` | | | ``| {kind: 'B', value: Int}`` | | +--------------------+-------------------------------+-------------------------+ | ``foo_bar`` | ``FooBar`` | | +--------------------+-------------------------------+-------------------------+ \*the ``Int`` type is an alias for ``number`` but additionally, the read and write functions generated by atdts check that the number is a whole number. Supported ATD annotations ------------------------- Default field values ^^^^^^^^^^^^^^^^^^^^ Record fields following a ``~`` assume a default value. The default value can be implicit as mandated by the ATD language specification (false for ``bool``, zero for ``int``, etc.) or it can be a user-provided value. A user-provided default uses an annotation of the form ```` where ``VALUE`` evaluates to a TypeScript expression e.g. .. code-block:: ocaml type foo = { ~answer : int; } For example, the JSON value ``{}`` will be read as ``{answer: 42}``. Field and constructor renaming ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Alternate JSON object field names can be specified using an annotation of the form ```` where ``NAME`` is the desired field name to be used in the JSON representation. For example, the following specifies the JSON name of the ``id`` field is ``ID``: .. code-block:: ocaml type foo = { id : string } Similarly, the constructor names of sum types can also be given alternate names in the JSON representation. Here's an example: .. code-block:: ocaml type bar = [ | Alpha | Beta of int ] Alternate representations for association lists ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ List of pairs can be represented by JSON objects or by TypeScript maps if the correct annotations are provided: * ``(string * bar) list `` will use JSON objects to represent a list of pairs of TypeScript type ``[string, Bar][]``. Using the annotation ```` is equivalent to the default. * ``(foo * bar) list `` will use a TypeScript map of type ``Map`` to represent the association list. Using the annotation ```` is equivalent to the default. Caveats ========= * Generated typescript contains a flag telling the compiler not to run checks on the file. `(Read) `_