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.

Single-developer, experimental, AGPL-3.0. Built for CLI tools & Docker microservices.

Vergina Sun
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
~95%TypeScript core language295 / 309 targeted features
100%Web Platform APIs55 / 55 targeted features
~95%Node.js APIs81 / 85 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
14.8%Test262 (in-scope subset)5,067 / 34,334 · 10.9% over the full corpus
51.4%TypeScript accept/reject4,755 / 9,256 cases agree with tsc
1.8%Node.js test/parallel21 / 1,195 runnable files pass

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. The honest breakdown.

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.listen speaks HTTP/1.1 and HTTP/2 (h2c) on one port. 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.

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.

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 http from 'http'

interface Res {
  status: number
  body: string
  headers: Map<string, string>
}

http.listen(8080, (req: HttpRequest): Res => {
  let respHeaders: Map<string, string> = new Map<string, string>()
  respHeaders.set('Content-Type', 'text/plain')

  if (req.path === '/hello') {
    let name: string = req.query.has('name') ? req.query.get('name') : 'stranger'
    return { status: 200, body: 'hello, ' + name, headers: respHeaders }
  }

  return { status: 404, body: 'not found: ' + req.path, headers: respHeaders }
})
03

The pipeline,
in one breath

  1. 01

    Lexer

    Source text becomes a flat token stream.

  2. 02

    Parser

    Recursive descent with Pratt precedence climbing builds the AST.

  3. 03

    Resolver

    Transitive imports are parsed and merged into one AST.

  4. 04

    LLVM emitter

    ~60 small domain files write LLVM IR text.

  5. 05

    clang -O2

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

→ 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
Greek flag — Vergina Sun canton

Write fresh TypeScript.
Ship a small, predictable binary.