hrtyy.dev

Type in Runtime with TypeScript

Use TypeScript type in runtime.

TypeScript's type is so powerful in compile time, but it is not available in runtime. Because it is stripped out in the transpiling process.

For example, we often validate input data, received from API or user input, in runtime. I prepared a simple validation function as the following.

validateUser.ts
1type User = {
2 id: number;
3 name: string;
4}
5
6function validateUser(input: unknown): input is User {
7 // TODO: ImplementME
8 return true
9}

Transpiling this code, I can get the following JavaScript code.

validateUser.js
1"use strict";
2function validateUser(input) {
3 // TODO: ImplementME
4 return true;
5}

Type User is stripped. In build time, I could get the information of it. Something like, it has two properties. One is id. It has number type and the other is name, string type.

I need to implement this function carefully to run in runtime.

1function validateUser(input: unknown): input is User {
2 return (
3 typeof input === "object" &&
4 input !== null &&
5 "id" in input &&
6 typeof input.id === "number" &&
7 "name" in input &&
8 typeof input.name === "string"
9 );
10}

This is a simple example, so there are other better ways. But, if I can use type information in runtime, it will be better.

TypeScript compiler API

Load source code

TypeScript compiler API is useful for manipulating TypeScript type.

To get type information from source code, we need to load the source code.

t.ts
1import * as path from "node:path";
2import * as ts from "typescript";
3
4function t() {
5 const file = path.resolve(__dirname, "t-sample.ts");
6 const program = ts.createProgram([file], {
7 declaration: true,
8 emitDeclarationOnly: true,
9 });
10
11 const source = program.getSourceFile(file);
12 if (!source) {
13 throw new Error(`No source file: ${file}`);
14 }
15
16 console.log(source.getFullText());
17}
18
19t()
t-sample.ts
1type User = {
2 name: string;
3};

This code reads the source code, "t-sample.ts", in the same directory and prints the content.

1$ ts-node t.ts
2type User = {
3 name: string;
4};
5
6✨ Done in 1.27s.

Get type information

We get "source" object from "program". The type of it is ts.SourceFile. It is an interface extended from ts.Node. Source code is represented as a tree structure. ts.SourceFile is the root of the tree.

We can get type information traversing the tree.

1function showSyntaxTree(node: ts.Node, indent: string) {
2 console.log(`${indent}${ts.SyntaxKind[node.kind]}`);
3 ts.forEachChild(node, (child) => {
4 showSyntaxTree(child, indent + " ");
5 });
6}

This function prints the nodes of tree. I added some comments. "Identifier" of "TypeAliasDeclaration" is "User", corresponding to type User. "Identifier" of "PropertySignature" is "name", corresponding to name: string.

1SourceFile
2 TypeAliasDeclaration
3 Identifier # User
4 TypeLiteral
5 PropertySignature
6 Identifier # name
7 StringKeyword
8 EndOfFileToken