TypeScript · LLVM · clang -O2

TypeScript,
compiled
to native.

You write .ts. It writes LLVM IR, hands it to clang, and out comes a real executable. Your operating system is none the wiser. No runtime. No V8. No Electron.

One self-contained binary — for CLI tools, Docker microservices, and desktop apps.

Because native is Gold
shell
$ git clone https://github.com/memphisx/KlainMainLang
$ cd KlainMainLang && make build   # → ./klainmain
$ ./klainmain app.ts   # → native binary
$ ./app
hello, native world
Targeted feature areas
~99%TypeScript core language369 / 374 targeted features
~96%Web Platform APIs78 / 81 targeted features
~97%Node.js APIs146 / 150 targeted features

These are curated feature-area checklists, “does the core case work?”, not external conformance. They mirror the project's own status matrix, caveats and all.

External conformance · full public suites
19.1%Test2628,213 / 42,942 in-scope · 15.7% of the full corpus
1.6%Web Platform Tests31 / 1,966 runnable documents · 392 / 644 subtests where a document executed
62.4%TypeScript accept/reject7,021 / 11,247 cases agree with tsc
11.7%Node.js test/parallel303 / 2,586 runnable files pass

Shown for the default -compat=strict lane. Low on purpose, and shown on purpose: this is a typed subset — most of Test262 is eval-based untyped JS, and Node's suite is dynamic JavaScript against the full platform, both largely out of scope by design, not silent failures. A second lane, -compat=js, trades strict's guarantees for vanilla-JS reach and scores higher on the untyped corpora. All four suites, both lanes, per OS →

01

Why it exists

No runtime, no VM

The output is a native executable from clang -O2, not a bundled interpreter. Zero startup warmup, small binaries, plain libc by default.

Real servers

http.createServer — the real Node shape — speaks HTTP/1.1 and HTTP/2 (h2c) on one port, with WebSocket (ws/wss) upgrades. fs, worker_threads and cluster give you real OS threads and processes. TLS on both ends.

Browser-shaped APIs, off-browser

fetch, URL, WebSocket, Web Crypto, Streams, AbortController, timers, the web APIs that make sense without a DOM, and none of the ones that don't.

Desktop apps, one file

klain:webview opens a real window over the system browser engine and calls straight into typed native code. Any SPA (React/Vue/Svelte/Quasar) embeds into the binary with new Webview({ serve }) — a single-file app, packaged to a .app or .desktop.

You pick the trade-offs

number is a JS-faithful 64-bit float by default, opt into sized machine ints (int8…uint64) with a JSDoc width when you want them. Memory is manual by default (never frees, perfect for short-lived CLIs); pass -mm=gc for a real Boehm collector. Crypto / bigint / regex backends chosen per compile.

Ships in a scratch image

One statically-linkable binary, no interpreter and no node_modules to copy in. A Docker microservice fits in a FROM scratch image measured in megabytes, boots instantly, and has nothing extra to patch or CVE-scan.

02

Real TypeScript in,
real binaries out

Every snippet below is an actual file under examples/. It compiles and runs, verified on every build. No slideware.

import { go, Channel, select, defaultCase } from 'klain:sync'

const primes = new Channel<number>(1024)  // primes stream to the collector
const done = new Channel<number>(0)        // a worker signals when finished

// Fan out: 8 goroutines test interleaved slices, in parallel on every core.
for (let id = 0; id < 8; id++) {
  go(() => {
    for (let n = 2 + id; n <= 500000; n += 8) {
      if (isPrime(n)) primes.send(n)
    }
    done.send(id)
  })
}

// Fan in: select takes whichever channel is ready — count primes as they
// arrive, tally completions, then drain the last buffered results.
let count = 0, finished = 0
while (finished < 8) {
  select(
    primes.recvCase((p: number) => { count += 1 }),
    done.recvCase((id: number) => { finished += 1 }),
  )
}
let draining = true
while (draining) {
  select(
    primes.recvCase((p: number) => { count += 1 }),
    defaultCase(() => { draining = false }),
  )
}
console.log(`primes below 500000: ${count}`)  // 41538
03

The pipeline,
in one breath

sourceapp.ts
lexer

Lexer

Source text becomes a flat token stream.

emits tokens
Pratt descent

Parser

Recursive descent with precedence climbing builds the tree.

emits AST
ResolveProgram

Resolver

Every transitive import is parsed and merged into one program.

emits one AST
~60 emit_*.go

LLVM emitter

Small domain files write LLVM IR text, one main().

emits .ll
clang

clang -O2

The real backend turns IR into a host-arch executable.

emits binary
runs./app

→ one .ll, one clang call, one generated main(), one binary that runs unsupervised.

The name is the mission statement

KlainMain

Not German. Not a clothing brand. Klain Main isn't “klein Main” (a cute little main()), and it isn't the Augsburg streetwear label. It's Greek.

Κλάιν Μάιν is slang for “I don't care”, the good kind: do the thing anyway, even when everyone tells you it's futile, for no better reason than because you can. That's the whole reason this compiler exists, because “how would I even build one” turned out to be a far better rabbit hole than whatever the day's actual plan was.

Read the docs
Macedonia is Greek

Write fresh TypeScript.
Ship a small, predictable binary.