Write TypeScript.
Compile to a native binary.

Lumen type‑checks familiar TypeScript syntax and compiles it straight to a small, dependency‑free executable. No VM, no interpreter, no Node.

curl -fsSL https://lumen-lang.org/install.sh | sh
interface Point {
  x: int;
  y: int;
}

function distanceSq(a: Point, b: Point): int {
  let dx = a.x - b.x;
  let dy = a.y - b.y;
  return dx * dx + dy * dy;
}

let origin: Point = { x: 0, y: 0 };
let p: Point = { x: 3, y: 4 };
console.log(`distance² = ${distanceSq(origin, p)}`);  // 25

Call C directly

Declare the function, name the library, and the compiler links it, with no bindings to generate and no runtime in between. defer runs cleanup at scope exit. How the C FFI works →

// @link m
declare function sqrt(x: number): number;

function hypot(a: number, b: number): number {
  using _ = defer(() => console.log("computed"));   // runs at scope exit
  return sqrt(a * a + b * b);
}

console.log(hypot(3.0, 4.0));   // computed → 5

What's in the language

A predictable static subset of TypeScript: no prototypes, no eval, no dynamic shapes. Each card opens a stop on the language tour, with a sample you can run in the browser.

Static types, inferred

int, number, string, bool, records, tuples and enums. Locals and return types infer; nothing is any.

Null safety

T | null with ??, ?. and flow narrowing across if, guards and field paths. No undefined.

Classes and generics

extends, super, access modifiers, accessors, and generics monomorphized at compile time with no boxing.

Errors, defer and tests

try/catch/finally with real stack traces, scope-exit defer, and test blocks run by lumen test.

Async, threads and events

async/await on a native event loop, Worker.run on real OS threads, and a typed EventEmitter<T>.

URL imports and C

A package is a URL, compiled in at build time. declare function plus // @link calls C libraries directly.

Take the language tour →

Fast by default

Ahead-of-time native code with no interpreter to warm up. Whole-process wall clock on one machine against Node 22; the programs, the numbers and their caveats are in bench/.

~17×faster startup3 ms vs 53 ms for a trivial program
1.2–1.3×compute loopsarithmetic, method dispatch, short-lived objects
~3×regex checksanchored patterns compiled to native matchers
~3.5×event emit loopEventEmitter<T> vs Node's on 10M emits

Where V8 wins today: loops that allocate millions of throwaway objects, since Lumen has no generational garbage collector. Escape analysis keeps the common cases on the stack; see the benchmark notes for the shape of code that still favours V8.

Errors that explain themselves

Compile errors say what's wrong and how to fix it — expected/got types, did‑you‑mean suggestions, caret underlines, and color. Uncaught runtime errors print a real stack trace with your file and line, even across imports.

$ lumen check main.ts
main.ts:4:1: error: 'add' expects 2 arguments, got 1
main.ts:6:1: error: `string` has no method 'toUperCase' — did you mean 'toUpperCase'?
main.ts:8:1: error: 'r' (`string | null`) may be null — check `!= null`
  before reading '.length', or use optional chaining `?.length`
main.ts:9:1: error: `if` condition must be `boolean`, got `i32` —
  truthiness is not supported; write `x != 0`

$ lumen run app.ts
util.ts:2:14: Uncaught Error: negative input
  2 |   if (n < 0) throw new Error("negative input")
    |              ^
    at risky (util.ts:2:14)
    at process (app.ts:7:3)
    at <main> (app.ts:12:1)

$ lumen test app.ts
ok adds
FAIL rounds — expected 4, found 3
    at app.ts:21
1 passed, 1 failed

Install

One self-contained binary, no other toolchain required. The script installs on macOS and Linux (x86_64 and arm64); on Windows, download the .zip from the releases page.

curl -fsSL https://lumen-lang.org/install.sh | sh

Your first program

lumen init writes a starter main.ts plus the ambient declarations and tsconfig.json, so the project is editor- and type-checker-clean from the first keystroke.

lumen init my-app
cd my-app
lumen compile main.ts && ./main

Live edit loop

lumen watch rebuilds and re-runs the program whenever the entry file or any of its local imports changes:

lumen watch main.ts            # rebuild and re-run on change
lumen watch --no-run main.ts   # rebuild only

Cross-compile and static binaries

A binary built the plain way links against the host's libc, so it runs on machines at least as new as the one that built it. Name a target with --target and ask for --static to get a self-contained binary that runs anywhere:

lumen compile --target x86_64-linux-musl --static main.ts
lumen compile --library-path ./vendor --link gc main.ts

--library-path names a directory to search for a library that --link asks for, so a vendored C library links without a package manager on the machine that runs the program.