With a guy on my team, we recently released a preliminary version of a side-project that could be promising. I wrote this article to present our work. And I start with: Why did we decide to create TypeOnly?

Because it’s not always possible to write DRY code with TypeScript…

TypeScript typing definitions are not available at runtime. Sometime this forces us to repeat ourselves, as in the following example:

type ColorName = "red" | "green" | "blue" function isColorName(name: string): name is ColorName {

return ["red", "green", "blue"].includes(name)

}

This kind of code is not ideal. There is an open discussion on Github related to this subject, and the TypeScript team is not ready to provide a solution.

A new language?

TypeOnly is a new language but not a new syntax. TypeOnly aims to be and remain a strict subset of TypeScript: any code that compiles with TypeOnly will also compile with TypeScript. It is the “pure typing” part of TypeScript: only interface and type definitions. To learn more, read a detailed description of the TypeOnly language.

The TypeOnly parser is implemented from scratch and does not require TypeScript as a dependency. It can be used outside a TypeScript project, such as in a JavaScript project, or to validate JSON data with a command line tool.

How to validate JSON data with TypeOnly (command line version)

Create a file “drawing.d.ts” with the following code:

// drawing.d.ts export interface Drawing {

color: ColorName

dashed?: boolean

shape: Rectangle | Circle

} export type ColorName = "red" | "green" | "blue" export interface Rectangle {

kind: "rectangle",

x: number

y: number

width: number

height: number

} export interface Circle {

kind: "circle",

x: number

y: number

radius: number

}

Then, create a JSON file “drawing.json”:

{

"color": "green",

"shape": {

"kind": "circle",

"x": 100,

"y": 100,

"radius": "wrong value"

}

}

We are ready to validate the JSON file:

$ npx @typeonly/validator-cli -s drawing.d.ts -t "Drawing" drawing.json

In property 'radius', value '"wrong value"' is not conform to number.

A mistake is detected in the JSON file. Fix it by replacing the value of the property "radius" with a valid number. For example: "radius": 50 . And run the command again:

$ npx @typeonly/validator-cli -s drawing.d.ts -t "Drawing" drawing.json

The JSON file is conform.

Good. The validator no longer complain.

The TypeOnly tool suite

In the previous section, the file containing types was parsed on the fly. It is a useful feature for a command line validator, but if you need to validate several JSONs from your Node.js program, you’ll want to parse the typing once, then reuse the parsed stuff several time. Or, even better: you’ll want to parse the typing definition at compile time, save the parsed result, and then you no longer need the parser at runtime. This is the default way to work with TypeOnly. As a result, using typing metadata is a fast and lightweight process.

TypeOnly currently comes with 4 npm packages:

typeonly: The parser, implemented using ANTLR, is quite performant and small. It takes .d.ts files and generates RTO files (RTO stands for Raw TypeOnly, the file extension is .rto.json ).

files and generates RTO files (RTO stands for Raw TypeOnly, the file extension is ). @typeonly/loader: A lightweight API that helps to load RTO files.

@typeonly/validator: A lightweight API that validates JSON or JavaScript data.

@typeonly/validator-cli: A CLI that uses the parser and the validator on the fly.

Tutorial, part I: Parse TypeOnly code

In this tutorial we’ll see how to use TypeOnly in a JavaScript or a TypeScript project. In a new directory, install typeonly as a dependency:

npm init

npm install typeonly --save-dev

Create a subdirectory src/ and copy into it our drawing.d.ts file given in the example above. Then, edit the file package.json and add an entry in the section "scripts" :

"scripts": {

"typeonly": "typeonly -o dist-rto/ -s src/"

},

Now we can execute the TypeOnly parser via our script:

npm run typeonly

This command creates a file drawing.rto.json in a new directory dist-rto/ . A RTO ( .rto.json ) file contains useful metadata extracted from a .d.ts typing file. In the next sections we'll see two ways to use this generated RTO file.

Tutorial, part II: Use typing metadata at runtime

In order to navigate through RTO ( .rto.json ) files at runtime, we need the @typeonly/loader package:

npm install @typeonly/loader

Create a file src/main.js with the following content:

// src/main.js

const { loadModules, literals } = require("@typeonly/loader") async function main() {

const modules = await loadModules({

modulePaths: ["./drawing"],

baseDir: `${__dirname}/../dist-rto`

}) const { ColorName } = modules["./drawing"].namedTypes

console.log("Color names:", literals(ColorName, "string"))

} main().catch(console.error)

If you write this code in a TypeScript source file, simply replace the require syntax with a standard import .

We can execute our program:

$ node src/main.js

Color names: [ 'red', 'green', 'blue' ]

Yes, it’s as easy as it seems: the list of color names is now available at runtime.

Notice that at runtime, our code doesn’t depend on the TypeOnly parser. We use @typeonly/loader which is a lightweight wrapper for .rto.json files.

Tutorial, part III: How to validate JSON data with TypeOnly (API version)

The package @typeonly/validator is built using @typeonly/loader . We need it:

npm install @typeonly/validator

Create a file src/validate-main.js with the following content:

// src/validate-main.js

const { createValidator } = require("@typeonly/validator") const data = {

"color": "green",

"shape": {

"kind": "circle",

"x": 100,

"y": 100,

"radius": 50

}

} async function main() {

const validator = await createValidator({

loadModules: {

modulePaths: ["./drawing"],

baseDir: `${__dirname}/../dist-rto`

}

})

const result = validator.validate("./drawing", "Drawing", data)

console.log(result)

} main().catch(console.error)

Execute this new program:

$ node src/validate-main.js

{ valid: true }

As in the previous section, our code doesn’t depend on the TypeOnly parser at runtime. The validator uses the loader which just loads .rto.json file(s).

Conclusion

What is not covered by this article?

The TypeOnly language is described here. In particular, it allows imports and exports, which means that you can split your typing in several source files.

The APIs of the packages typeonly , @typeonly/loader and @typeonly/validator are not yet well documented but they are well typed. You will need the help of a TypeScript IDE to see all the options and provided data structures.

What’s next?

The help of contributors will be greatly appreciated. For us, TypeOnly is a side project and we don’t have a lot of time for it. However, we plan to work on several subjects in a near future: