Tsonic compiles a strict, deterministic subset of TypeScript into C#, then into native binaries or .NET libraries.
The current V1 model is:
- one compiler-owned noLib core
- one active ambient surface per workspace
- explicit package-based CLR and module interop
- strict-AOT rejection for anything that cannot be lowered deterministically
- TypeScript authoring with explicit numeric/value semantics when needed
- NativeAOT and regular .NET outputs from the same compiler
- Direct CLR interop through
@tsonic/dotnetand generated bindings - JS-surface authoring through
@tsonic/js - Node module support through
@tsonic/nodejs - First-party source-package consumption for Tsonic-authored npm packages
Tsonic separates the language prelude from the ambient runtime personality.
- compiler core: always-on noLib baseline (
Promise, iterators, utility types, array shape) clrsurface: default ambient CLR-first world@tsonic/jssurface: JS-style globals and receiver methods@tsonic/nodejs: normal package, not a surface
That means:
- CLR workspace:
"abc"exposes CLR-shaped ambient members- import CLR APIs explicitly from
@tsonic/dotnet/...
- JS workspace:
"abc".trim(),[1, 2, 3].map(...),console.log(...),JSON,Datework as ambient JS APIs
- Node usage:
- keep JS surface active
- add
@tsonic/nodejs - import
node:*modules normally
npm install -g tsonicRequirements:
- Node.js 22+
- .NET 10 SDK
mkdir hello-clr
cd hello-clr
tsonic init
tsonic runGenerated sample:
import { Console } from "@tsonic/dotnet/System.js";
export function main(): void {
Console.WriteLine("Hello from Tsonic!");
}mkdir hello-js
cd hello-js
tsonic init --surface @tsonic/js
tsonic runGenerated sample:
export function main(): void {
const message = " Hello from Tsonic JS surface! ".trim();
console.log(message);
}mkdir hello-node
cd hello-node
tsonic init --surface @tsonic/js
tsonic add npm @tsonic/nodejsThen author normal Node-style imports:
import * as path from "node:path";
import * as fs from "node:fs";
export function main(): void {
const file = path.join("src", "App.ts");
console.log(file, fs.existsSync(file));
}Run it:
tsonic runUse @tsonic/core/types.js for CLR-specific numeric/value intent:
import type { int, long, bool } from "@tsonic/core/types.js";Use @tsonic/core/lang.js for language intrinsics:
import {
defaultof,
nameof,
sizeof,
stackalloc,
out,
} from "@tsonic/core/lang.js";Import CLR APIs explicitly:
import { Console } from "@tsonic/dotnet/System.js";
import { Enumerable } from "@tsonic/dotnet/System.Linq.js";
export function main(): void {
const xs = [1, 2, 3];
const filtered = Enumerable.Where(xs, (x: number): boolean => x > 1);
Console.WriteLine(filtered.Count().ToString());
}For external CLR dependencies:
tsonic add nuget Microsoft.Extensions.Logging 10.0.0
tsonic add package ./libs/MyCompany.MyLib.dll
tsonic restoretsonic init now creates npm-publish-ready source packages by default. Each project gets a source manifest at:
packages/<project>/tsonic/package-manifest.json
Example:
{
"schemaVersion": 1,
"kind": "tsonic-source-package",
"surfaces": ["@tsonic/js"],
"source": {
"exports": {
".": "./src/App.ts"
}
}
}Installed source packages with that manifest are compiled transitively as part of the same Tsonic program.
tsonic generate
tsonic build
tsonic run
tsonic test
tsonic packSupported output shapes include:
- NativeAOT executable
- managed executable
- managed library
- NativeAOT shared/static library
- AST-only emitter pipeline
- Promise constructor +
then/catch/finallylowering - deterministic closed-world
import()support - supported
import.metasubset:url,filename,dirname, and bareimport.meta - broader object-literal support:
- accessors
- computed constant keys
- shorthand methods
- supported
arguments.length/arguments[index]cases
nameof(...)andsizeof<T>()- deterministic generic function values in supported monomorphic contexts
- User guide:
docs/README.md - Site:
https://tsonic.org/tsonic/ - Architecture:
docs/architecture/README.md
MIT