TypeScript Support - atdts¶
This documentation is incomplete. Your help would be appreciated! In particular, some how-to guides would be great.
Tutorials¶
Hello World¶
Install atdts with opam:
opam install atdts
Create a file hello.atd containing this:
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:
...
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:
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:
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:
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:
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:
type message = {
subject: string;
~body: string;
~signature <ts default="'anonymous'">: string;
}
Finally, we’ll add an optional url field that doesn’t take a default value
at all:
type message = {
subject: string;
~body: string;
~signature <ts default="'anonymous'">: 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:
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¶
Defining default field values¶
[missing]
Renaming field names¶
[missing]
Deep dives¶
[missing]
Reference¶
Type mapping¶
ATD type |
TypeScript type |
JSON example |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
anything |
|
|
|
|
|
|
|
|
*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
<ts default="VALUE"> where VALUE evaluates to a TypeScript
expression e.g.
type foo = {
~answer <ts default="42">: 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 <json name="NAME"> 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:
type foo = {
id <json name="ID">: string
}
Similarly, the constructor names of sum types can also be given alternate names in the JSON representation. Here’s an example:
type bar = [
| Alpha <json name="alpha">
| Beta <json name="beta"> of int
]
Import declarations¶
ATD import statements allow types defined in other ATD files to be referenced from a given ATD file. For example:
from ext_types import tag, status
from long.module.path as ext import tag
For an import of the form from ext_types import tag, atdts generates a
TypeScript import statement:
import * as ext_types from "./ext_types"
Types from that module are then referenced as ext_types.Tag,
and the accompanying read/write functions as ext_types.readTag
and ext_types.writeTag.
The <ts name="NAME"> annotation on the module path overrides the
local TypeScript module name used in the import and in type references.
For example:
from ext_types <ts name="etypes"> import tag
generates:
import * as etypes from "./ext_types"
When an alias is given, the alias name is used as the local TypeScript module
name. The <ts name="NAME"> annotation on the path still controls the
generated import name if present. For example:
from long.module.path as ext import tag
generates:
import * as ext from "./long/module/path"
Note that dotted module paths are mapped to file paths using / as the
separator (e.g. long.module.path becomes "./long/module/path").
Note: The <ts from="..."> annotation on individual type definitions
is an older mechanism for referencing types from other modules.
The from ... import statement is the preferred approach for multi-file
ATD projects.
Field from¶
Position: left-hand side of a type definition, after the type name
Values: .ts file with exported types. This can be also seen as the
name of the original ATD file, without the .atd extension and
capitalized like an ATD module name.
Semantics: specifies the base name of the ATD modules where the type and values coming with that type are defined.
Example: First input file part1.atd:
type point = { x : int; y : int }
Second input file part2.atd depending on the first one:
type point <ts from="Part1"> = abstract
type points = point list
To use a different type name than defined in the Part1 module, add a
t field declaration to the annotation which refers to the original
type name:
type point_xy <ts from="Part1" t="point"> = abstract
type points = point_xy list
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 <json repr="object">will use JSON objects to represent a list of pairs of TypeScript type[string, Bar][]. Using the annotation<json repr="array">is equivalent to the default.(foo * bar) list <ts repr="map">will use a TypeScript map of typeMap<Foo, Bar>to represent the association list. Using the annotation<ts repr="array">is equivalent to the default.
Caveats¶
Generated typescript contains a flag telling the compiler not to run checks on the file. (Read)