TypeScript Examples

TypeScript in, TJS out: each example is real TypeScript, converted and run.

Every example in this section is ordinary TypeScript. The page does to it what you would do to your own code, in two steps. fromTS (the converter, exported from tjs-lang/lang/from-ts) turns the TypeScript into TJS; the TJS compiler then turns that into JavaScript, which is what runs. So the pipeline is TS → TJS → JS. For what TJS itself is, start at TJS for TypeScript Developers.

The examples are live. Open one's code with its view/edit code button, change it, and choose Refresh from the example menu (or press ⌘R in the editor) to run it again.

Faithful or checked

One line decides what you are looking at: /* @tjs TjsStrict */ at the top of the file.

Without it, converted TypeScript keeps JavaScript's behaviour, and the file runs exactly as tsc output would. That is the default so that converting a project cannot change what it does: you can convert everything first and turn checking on one file at a time. The types are not thrown away. Each converted function gets a __tjs property describing its parameters and return type, which documentation and schema tools can read; nothing checks calls against it.

With the line, the file becomes full TJS. Every parameter type is a check that runs on every call, and TJS's other rules apply too: == stops coercing, private becomes real privacy, and a class declared in the file is called without new. TJS for TypeScript Developers lists the differences.

This page calls the first kind faithful and the second checked. Every example that demonstrates a rejection is checked. Delete the TjsStrict line from one and run it again to see the same bad call go through unchecked; add the line to a faithful one to see what it would have caught.

When a check fails

Take a two-line function and a bad call. tsc rejects the call, so the example hides it from tsc with as any, which is what a value arriving from JSON or an untyped library looks like at runtime:

/* @tjs TjsStrict */
function greet(name: string): string {
  return `Hello, ${name}!`
}

console.log(String(greet(42 as any)))

The function does not throw. It returns a MonadicError, a value that says which parameter was wrong, what type it expected and what it got:

MonadicError: Expected string for 'greet.ts:2:greet.name', got number

greet.ts is the file, 2 the line and greet.name the parameter. Code converted without a file name, as in the live examples, shows <source> in its place.

Errors are values in TJS so that a bad argument cannot unwind the stack through code that was never written to catch anything. The cost, when you migrate, is that a caller which ignores the return value now carries on with an error object where it expected a string or a number. So check it: MonadicError extends Error, so result instanceof Error works anywhere, and code that imports the runtime can use import { isMonadicError } from 'tjs-lang/runtime'. While debugging, call installRuntime() and then configure({ throwTypeErrors: true }), both from the same module, and a failed check throws instead. (Without installRuntime() the converted code uses its own built-in copy of the runtime and ignores configure.)

A checked function that receives an error as an argument returns that error untouched instead of running, so one bad value does not set off a chain of new ones:

const r = greet(42 as any)
shout(r) === r // true, if shout is a checked function too

A faithful function does not do this. It receives the error as an ordinary value and runs with it.

The examples

The examples are grouped into four sections, from the basics of conversion to the parts of TypeScript that have no runtime meaning. In the tables, both means the example shows the same code twice: first faithful, then with the TjsStrict line.

Introduction

What conversion does to a function, and how TypeScript's type names become runtime types.

Example Kind What it shows
Hello TypeScript checked one function, one rejected call
Multiple Functions faithful what conversion does to a file, and where the line would go
Type Transformation faithful how TypeScript's type names become TJS examples

Validation

The payoff: types that are checked when the program runs, and tests written in /*test '…' { … }*/ comments, which tsc ignores and TJS runs.

Example Kind What it shows
Runtime Validation checked primitive parameters checked on every call
Object Validation checked an interface checked as a whole shape
Optional Parameters checked ? and default values
Embedded Tests both /*test comments run either way; the last one tests a rejected call

Patterns

Arrays, nested objects and nullable values, the shapes most real code is made of.

Example Kind What it shows
Array Operations both the same functions without checks, then with them
Nested Objects checked interfaces that refer to interfaces
Union Types checked T | null and unions of string literals

Advanced

Generics, async functions and classes, where TypeScript's types and the runtime part ways most.

Example Kind What it shows
Generic Functions both what survives of a type parameter
Async Functions checked a failed check resolves to a MonadicError; the promise does not reject
Classes both the faithful class, then the checked one, which is called without new
The Full Picture checked everything above in one file

Seeing the converted code

Open an example's code panel (the view/edit code button) and pick its JS tab to see the JavaScript that ran. The panel has no tab for the TJS in between; where it matters, the example's own sections ("What to notice", "What the converter writes") quote the TJS that fromTS produced. To see it for your own file, run the converter that ships with the tjs-lang package (see Converting a Project):

bunx tjs convert --emit-tjs greet.ts

The real output also carries a /* tjs <- greet.ts */ header and /* line N */ markers (Converting a Project explains them), so the TJS quoted in the examples is trimmed.