Tsonic Architecture
Technical documentation for contributors and advanced users.
Design Goals
Tsonic is designed around these principles:
- Exact Semantics: JavaScript behavior preserved exactly via runtime libraries
- Native Performance: NativeAOT compilation for fast startup and execution
- Zero Magic: Clear errors instead of guessing; explicit over implicit
- Functional Codebase: Immutable data, pure functions, explicit dependencies
Architecture Overview
Tsonic is a multi-phase compiler that transforms TypeScript to native executables:
┌─────────────────────────────────────────┐
│ TypeScript Source │
└──────────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ FRONTEND PACKAGE │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ Parse │ -> │ Resolve │ -> │ Validate │ -> │ Build IR │ │
│ │ TypeScript │ │ Imports │ │ Rules │ │ │ │
│ └────────────┘ └────────────┘ └────────────┘ └────────────┘ │
└─────────────────────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ IR (Intermediate Representation) │
│ Language-agnostic AST with semantics │
└──────────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ EMITTER PACKAGE │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ Types │ │ Statements │ │Expressions │ │ Modules │ │
│ │ Emitter │ │ Emitter │ │ Emitter │ │ Emitter │ │
│ └────────────┘ └────────────┘ └────────────┘ └────────────┘ │
└─────────────────────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ C# Source Code │
└──────────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ BACKEND PACKAGE │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ Generate │ -> │ Generate │ -> │ dotnet │ -> │ Copy │ │
│ │ .csproj │ │ Program.cs │ │ publish │ │ Binary │ │
│ └────────────┘ └────────────┘ └────────────┘ └────────────┘ │
└─────────────────────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Native Binary │
│ (via .NET NativeAOT compiler) │
└─────────────────────────────────────────┘
Package Structure
packages/
├── frontend/ # TypeScript parsing, validation, IR building
├── emitter/ # C# code generation from IR
├── backend/ # .NET build orchestration
└── cli/ # Command-line interface
Dependency Graph
cli
├── frontend
├── emitter
└── backend
frontend (standalone)
emitter
└── frontend (for IR types)
backend (standalone)
Core Data Structures
IR (Intermediate Representation)
The IR is the central data structure bridging TypeScript and C#:
- IrModule: Represents a compiled TypeScript file
- IrStatement: Function, class, variable declarations, control flow
- IrExpression: Literals, operators, calls, member access
- IrType: Primitives, references, arrays, functions, unions
See IR Documentation for complete type definitions.
Result Types
All operations return Result<T, E> instead of throwing exceptions:
type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };
Diagnostics
Errors are collected as Diagnostic objects with:
- Error code (TSN1xxx - TSN9xxx)
- Severity (error, warning)
- Message and hint
- Source location
See Diagnostics for error handling details.
Key Design Decisions
Why IR?
The IR provides:
- Separation of concerns: Frontend knows nothing about C#
- Optimization opportunities: Can transform IR before emission
- Testability: Test IR building and emission independently
- Extensibility: Could target other languages in future
Why Functional Programming?
The compiler uses strict FP principles:
- Immutable data: No unexpected mutations during compilation
- Pure functions: Same inputs always produce same outputs
- Explicit dependencies: No hidden global state
- Testability: Pure functions are trivial to test
Why NativeAOT?
NativeAOT provides:
- Fast startup (no JIT warmup)
- Small binaries (tree shaking)
- No runtime dependency
- Predictable performance
Table of Contents
Core Architecture
- Overview - Design principles and goals
- Pipeline - 7-stage compilation pipeline
- IR - Intermediate Representation types
- Diagnostics - Error handling and reporting
Packages
- Packages - Monorepo structure
- Frontend - TypeScript parsing and IR building
- Emitter - C# code generation
- Backend - .NET build orchestration
Reference
- Type Mappings - TypeScript to C# conversion
- Runtime - Tsonic.Runtime library
Development
# Build all packages
./scripts/build/all.sh
# Run unit/golden tests (all workspaces)
npm test
# Run a focused subset (Mocha)
npm run test:emitter -- --grep <pattern>
npm run test:frontend -- --grep <pattern>
npm run test:cli -- --grep <pattern>
# Run full E2E suite (final gate)
./test/scripts/run-all.sh
# Run a subset of fixtures (iteration only; skips unit/golden)
./test/scripts/run-e2e.sh --filter <pattern>
# Format code
./scripts/build/format.sh
# Lint code
./scripts/build/lint.sh
Contributing
- Read the coding standards
- Understand the pipeline flow
- Follow functional programming principles
- Add tests for new functionality
- Run format and lint before committing