Skip to content

Atypical-Consulting/Koine

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

784 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Koine

Koine

Write your domain's ubiquitous language once, in .koi files. Koine compiles it to idiomatic, self-contained C# — value objects, entities, aggregates, invariants, the whole Domain-Driven Design toolkit.

Try it in your browser Documentation NuGet .NET Tests Target License

The problem

Domain-Driven Design gives you a precise vocabulary — value objects, entities, aggregates, invariants, domain events, state machines — but in C# every one of those is a pile of mechanical boilerplate: validating constructors, value equality, identity equality, defensive copies, guard clauses, repository contracts. You write it by hand for every type. Then the model drifts from the glossary on the wiki, the "ubiquitous language" stops being ubiquitous, and the rules you cared about get buried in plumbing.

The solution

Koine is a small, readable DSL for DDD. You describe a bounded context using the same words your domain experts use, and the compiler emits the tactical code for you — correct, idiomatic, and with no runtime to reference. The model is the ubiquitous language: there is no second copy to keep in sync, and the rules stay front and centre instead of drowning in boilerplate.

The name evokes Koine Greek, the common language that became a lingua franca. The goal is to compile one domain model to many targets. C# is the primary, most complete target; a TypeScript emitter ships (--target typescript), a Python emitter ships (--target python → dependency-free Python 3.11+, mypy --strict-clean; the tactical core and the strategic/CQRS layer — read models, queries, policies, state machines, context maps/ACL), a PHP 8.1 emitter ships (--target php → dependency-free PHP 8.1, typed properties, readonly promoted properties; the tactical core and the strategic/CQRS layer — read models, query handler seams, application services/use cases/operations, specifications, policies, context-map ACL translators and integration-event subscriber seams), a Rust emitter ships (--target rust → an idiomatic crate: value objects as structs with smart constructors returning Result<_, DomainError>, smart enums as Rust enums matched exhaustively (with Match/Switch/from_name/from_value lookups), entities and aggregates with invariant-checked behaviors, factories that mint identities, domain events raised into a Vec-friendly DomainEvent collection, query DTOs and read-model projections, and repositories as traits; multi-context models compile end-to-end via crate::<module> qualification — a type owned by two contexts and referenced from a third resolves to a deterministic canonical owner (shared with the Java target) and raises a KOI1419 warning naming the chosen owner; depends only on rust_decimal for money and regex for matches, plus uuid when a model uses a factory), a Java 17 emitter ships (--target java → dependency-free, stdlib-only Java 17: value objects and domain events as validating records with compact constructors, smart enums as Java enums (with per-constant associated data), entities and aggregates as classes with identity equals/hashCode and invariant-guarded behaviors, generated identities as branded records minting java.util.UUID, domain events grouped under a per-context sealed interface DomainEvent, and repositories as interfaces — one public type per .java file, koine.runtime.DomainException for invariant failures), a Kotlin 2.x emitter ships (--target kotlin → idiomatic, dependency-free Kotlin/JVM: value objects and domain events as data classes (invariants in an init block), generated identities as @JvmInline value classes minting java.util.UUID, smart enums as enum classes (constructor val accessors + neutral-key fromKey/tryFromKey), entities and aggregates as classes with var … private set state, identity equals/hashCode, and invariant-guarded behaviors, events under a per-context sealed interface DomainEvent, and repositories as interfaces — optionality lives in the type system as T? (never Optional), koine.runtime.DomainException for invariant failures), a docs target emits living documentation (--target docs → Markdown + Mermaid diagrams) straight from the model, an AsyncAPI 3.0 target emits a single event-API document (--target asyncapi → channels, messages, JSON-Schema payloads, and send/receive operations derived from the integration-event + context-map graph), an OpenAPI target emits an API contract (--target openapi → a deterministic OpenAPI 3.1 YAML document per bounded context: value objects / read models / enums become components/schemas, commands become POST operations and queries become GET operations, and static value-object invariants lower to JSON-Schema validation keywords), and the parser and semantic model are kept strictly target-agnostic so further emitters can be added without touching them.

See it run — in your browser

The Koine compiler is itself compiled to WebAssembly, so you can write a model and watch it become C# without installing anything.

Koine Studio: a .koi domain model on the left, the C# it compiles to on the right, with no diagnostics — running entirely in the browser.

Koine Studio — your .koi model (left) and the C# it compiles to (right), live in the browser.

A tour of the IDE

The hero above is the editor split — but Studio is a full IDE, and a single shot can't show it. Each surface below is a click away in the live Studio:

Koine Studio with the emit target set to TypeScript — a .koi model on the left, the generated TypeScript on the right, and 'Emit TypeScript' in the toolbar.
Emit-target switcher — flip the same model between C#, TypeScript, Python, PHP, Rust, Java, and Kotlin output without leaving the page.
An interactive context-map graph — five bounded contexts (Billing, PaymentGateway, Entitlements, Subscription, Tenant) linked by labelled relationships such as AntiCorruptionLayer, CustomerSupplier and OpenHost.
Context-map graph — an interactive graph of the bounded contexts and the relationships between them.
The Subscription aggregate rendered as a diagram — its fields and methods, value objects and enums, and the Trialing → Active → Suspended → Cancelled state machine, straight from the model.
Diagram views — aggregates and state machines rendered as diagrams straight from the model.
The ubiquitous-language glossary — each domain term with its kind (context, enum, value) and description, tracked by a 'documented' progress bar.
Ubiquitous-language glossary — the generated glossary of every term in the domain, kept in lock-step with the model.
The model outline in the left rail, the .koi source in the centre, and a bottom Events panel listing the domain events with their bounded context and 'when' descriptions.
Model outline & panels — a structural outline of the model plus a bottom Events & Relationships panel.
Editor & live diagnostics — your .koi model on the left, the emitted code on the right, with error squiggles and quick info as you type (the hero split above) — the same parser and validator the CLI runs.

Syntax tree — the raw parse tree of the active file in the right rail: every node's kind, name, and source span, with error-recovery markers made visible. Click a node to jump the editor to it; move the caret to highlight the matching node.
  • Koine Studio — the full web IDE (every view in the tour above), running the real compiler in your browser. (Also ships as a native Tauri desktop app — same UI, see tooling/koine-studio.)
  • Playground — a lightweight, zero-install editor that recompiles to C#/TypeScript the moment you stop typing. Great for a quick taste or for following along with the tutorial.

Both run the same parser, validator, and emitters as the koine CLI — what you see in the browser is exactly what the build produces.

Concept Colorsone DDD concept, one color, everywhere. An aggregate is the same indigo, a value object the same blue, an enum the same amber in the explorer, on the canvas, in the code editor, in the playground, and in VS Code — driven by one palette and the language server's concept-kind signal, so the association carries from the tree straight into the source. See the Concept Colors guide.

📖 Full docs → https://atypical-consulting.github.io/Koine/ — getting started, a six-part tutorial, the complete language reference, the feature catalogue, and the CLI. (Source in website/; run locally with cd website && npm install && npm run dev.)

A taste of the language

A .koi file declares one or more bounded contexts. Inside a context you declare value objects, entities, aggregates, and enums:

context Billing {

  value Money {
    amount: Decimal
    currency: Currency
    invariant amount >= 0        "a monetary amount cannot be negative"
  }

  enum Currency { EUR, USD, GBP }

  value Email {
    raw: String
    invariant raw matches /^[^@]+@[^@]+$/   "invalid email address"
  }

  entity Customer identified by CustomerId {
    name: String
    email: Email
  }

  aggregate Order root Order {

    enum OrderStatus { Draft, Placed, Shipped, Cancelled }

    value OrderLine {
      product:   ProductId
      quantity:  Int
      unitPrice: Money
      subtotal:  Money = unitPrice * quantity     // derived (computed) field
    }

    entity Order identified by OrderId {
      customer: CustomerId
      lines:    List<OrderLine>
      status:   OrderStatus = Draft               // default value
      invariant status == Draft when lines.isEmpty
    }
  }
}

That compiles to plain C# records and classes with validating constructors, value/identity equality, a generated OrderId/CustomerId, an IOrderRepository contract, and the Money * int operator needed for subtotal — nothing for you to write, and nothing external to reference.

Why Koine?

  • One source of truth. The model is the ubiquitous language — no drift between the glossary and the code.
  • Idiomatic, dependency-free output. Generated C# is plain, readable, and self-contained; the Koine.Runtime markers are emitted alongside it, so there's nothing to install.
  • The whole tactical and strategic toolkit. Value objects, entities, aggregates, smart enums, invariants, commands, domain events, state machines, factories, specifications, services, policies, repositories, optimistic concurrency, the application layer (UoW, read models, CQRS), multi-file modules, context maps, integration events, and model versioning — all shipped.
  • Enforced DDD reference discipline. The compiler keeps your building blocks honest: a value object can't embed an entity or aggregate, commands and domain events carry data and identities rather than live references, one aggregate references another only by its id, and an entity holds domain state — never an event, read model, or query — violations are hard errors (KOI1601KOI1605), not lint.
  • A green build proves the domain. Every construct is snapshot-tested and compiled and executed through an in-memory Roslyn meta-test, so a passing build means the generated C# is correct and usable — not just that it parses.

Quick start (CLI)

Requires .NET 10.

Install the CLI as a .NET global tool from NuGet, then invoke it as koine:

dotnet tool install --global Koine.Cli
koine --version
koine build templates/starters/billing/billing.koi --target csharp --out ./generated

Or run it without installing via dnx (bundled with the .NET 10 SDK — it fetches the tool from NuGet and runs it in one shot, like npx):

dnx Koine.Cli build templates/starters/billing/billing.koi --target csharp --out ./generated

Or run it straight from a clone of this repo with dotnet run --project src/Koine.Cli -- …:

# Build everything and run the tests
./scripts/build/build.sh         # or: dotnet build && dotnet test

# Compile a domain model to C#
dotnet run --project src/Koine.Cli -- build templates/starters/billing/billing.koi --target csharp --out ./generated

# Add a runnable EF Core infrastructure layer (DbContext, repositories, unit of work, outbox, DI)
dotnet run --project src/Koine.Cli -- build templates/starters/billing/billing.koi --target csharp --out ./generated --layers domain,infrastructure

# Emit to TypeScript instead
dotnet run --project src/Koine.Cli -- build templates/starters/billing/billing.koi --target typescript --out ./generated

# Add the runnable, dependency-light TypeScript infrastructure layer (in-memory repositories, unit of
# work, transactional outbox, validation/transaction behaviors, a composition-root factory)
dotnet run --project src/Koine.Cli -- build templates/starters/billing/billing.koi --target typescript --out ./generated --layers domain,infrastructure

# Or to Python (tactical core + strategic/CQRS: read models, queries, policies, state machines, ACL)
dotnet run --project src/Koine.Cli -- build templates/starters/billing/billing.koi --target python --out ./generated_py

# Add the runnable, dependency-free Python infrastructure layer (in-memory repositories, unit of work,
# transactional outbox, validation/transaction behaviors, a provider/composition helper)
dotnet run --project src/Koine.Cli -- build templates/starters/billing/billing.koi --target python --out ./generated_py --layers domain,infrastructure

# Or to PHP 8.1 (tactical core + strategic/CQRS: read models, queries, services, policies, ACL)
dotnet run --project src/Koine.Cli -- build templates/starters/billing/billing.koi --target php --out ./generated_php

# Or to Rust (multi-context + CQRS — an idiomatic crate; `cargo build` proves it compiles)
dotnet run --project src/Koine.Cli -- build templates/starters/billing/billing.koi --target rust --out ./generated_rs

# Or to Java (dependency-free Java 17 — records, sealed events, invariant-guarded classes; `javac --release 17` proves it compiles)
dotnet run --project src/Koine.Cli -- build templates/starters/billing/billing.koi --target java --out ./generated_java

# Or to Kotlin (idiomatic Kotlin 2.x/JVM — data classes, @JvmInline value-class IDs, sealed events, T? optionality; `kotlinc` proves it compiles)
dotnet run --project src/Koine.Cli -- build templates/starters/billing/billing.koi --target kotlin --out ./generated_kt

# Emit the opt-in C# Application layer alongside the domain (handlers, validators, query handlers, DI)
dotnet run --project src/Koine.Cli -- build templates/starters/billing/billing.koi --target csharp --layers domain,application --out ./generated

# Generate living documentation (Markdown + Mermaid state/class/context-map diagrams)
dotnet run --project src/Koine.Cli -- build templates/starters/billing/billing.koi --target docs --out ./docs

# Emit an AsyncAPI 3.0 document from the integration-event + context-map graph
dotnet run --project src/Koine.Cli -- build templates/pizzeria --target asyncapi --out ./events

# Emit an OpenAPI 3.1 spec (one <Context>/openapi.yaml per bounded context: schemas, paths, parameters)
dotnet run --project src/Koine.Cli -- build templates/starters/billing/billing.koi --target openapi --out ./api

# Just check a model parses & validates (no output)
dotnet run --project src/Koine.Cli -- build templates/starters/billing/billing.koi

# Version
dotnet run --project src/Koine.Cli -- --version

The generated C# in ./generated is self-contained and compiles on its own. A path argument may be a single .koi file or a directory — directory mode compiles every .koi underneath as one model, so cross-file imports, context maps, and integration events resolve.

C# layers (--layers)

The C# target emits in composable layers, selected with --layers (or targets.csharp.layers in koine.config):

Layer What it emits
domain (default) The Domain model + the application/CQRS contracts — value objects, entities, aggregates, invariants, smart enums, events, the persistence-ignorant IRepository/IUnitOfWork interfaces, etc. Byte-identical to the historical output.
infrastructure A runnable EF Core realization of those contracts, per bounded context: a DbContext with a DbSet per aggregate root, IEntityTypeConfiguration mappings (value objects → owned types, the versioned token → IsRowVersion, smart enums → HasConversion, strongly-typed IDs → key converters), a concrete Repository + UnitOfWork, a transactional OutboxMessage + IntegrationEventDispatcher (for a publishing context), and an Add<Context>Infrastructure(this IServiceCollection, Action<DbContextOptionsBuilder>) DI extension. Implies domain.
# Domain contracts only (default — omit --layers for the same result)
koine build ./Models --target csharp --out ./generated --layers domain

# Domain + a regenerated EF Core infrastructure layer
koine build ./Models --target csharp --out ./generated --layers domain,infrastructure

The infrastructure is regenerated from the model on every build, so it can never silently drift from the ubiquitous language. The provider (SQL Server, Postgres, …) is supplied by the caller through the Action<DbContextOptionsBuilder>, so the emitter stays provider-agnostic. EF Core only in v1.

TypeScript & Python infrastructure (--layers infrastructure)

The same --layers infrastructure selector now applies to the TypeScript and Python targets (issue #241), keeping the "write the ubiquitous language once, get a runnable stack" promise across all three primary targets. Rather than a bundled ORM, each emits a dependency-light realization of the domain contracts, per bounded context with at least one entity-rooted aggregate:

  • a concrete repository over an injectable AggregateStore with a zero-dependency in-memory default (runnable in tests out of the box; swap in a persistent store to productionize) — declarative finders compile to concrete lookups;
  • a concrete unit of work realizing the per-context contract;
  • a transactional outbox (OutboxMessage + a drainable IntegrationEventDispatcher) for a publishing context, so the publisher stays decoupled from its subscribers;
  • validation + transaction pipeline behaviors (the idiomatic analogue of the C# MediatR decorators); and
  • a composition-root factory (TypeScript) / provider helper (Python) — the analogue of C#'s Add<Context>Infrastructure.

The shared primitives live once in an emitted infrastructure-runtime.ts / koine_infrastructure.py. The layer is off by default, so an unconfigured emit is byte-identical to the historical output; the generated TypeScript is tsc --strict-clean and the Python is mypy --strict-clean.

Every persisted aggregate round-trips — not just collection owners. A scalar-only root, a root that owns a scalar value object (OwnsOne), a versioned aggregate, and nested value objects all insert and re-query correctly: each persisted root (and any value object that owns a value object) gets a private parameterless persistence constructor EF Core materializes through, and plain scalar properties are mapped explicitly so EF persists the read-only auto-property via its backing field. A value-object collection (list of <ValueObject>) round-trips too: it is mapped with EF Core OwnsMany and backed by a mutable private List<T> (exposed read-only as IReadOnlyList<T>), with PropertyAccessMode.Field so EF can materialize owned children into it and a single-column surrogate key so the rows persist on every provider. Scalar (String/Int/…) collections are left to EF Core's primitive-collection convention.

Other CLI commands: check (model-versioning compatibility against a --baseline), coverage (proves declared == emitted and doubles as a CI gate), fmt (canonical formatter), init (scaffold a project), watch (rebuild on change), lsp (language server over stdio), and mcp (the MCP server — stdio by default, or --http to serve it over HTTP). See the CLI reference.

The C# Application layer (opt-in)

By default --target csharp stops at the application boundary: it emits the contractsIUnitOfWork, the I<Service> use-case interfaces, read-model projections, query objects and the IQueryHandler<,> runtime type — but no implementations. Pass --layers domain,application to also emit the Application layer that fills those in:

Construct Emitted application code
aggregate command a <Entity><Command>Request record + a handler that loads the aggregate via its IUnitOfWork repository, invokes the behavior, and SaveChangesAsync.
aggregate factory a <Entity><Factory>Request record + a handler that creates the aggregate, adds it via the repository, and commits.
value-object / command invariant a FluentValidation AbstractValidator<TRequest> rule (RuleFor(...).Must(...).WithMessage(...)) rendered from the same invariant the domain enforces — not re-derived by hand.
query a concrete IQueryHandler<,>; a single result keyed by the root's identity loads + projects via the To<ReadModel> mapper, other shapes throw until wired to your read store.
DI an Add<Context>Application(this IServiceCollection) extension registering every handler, validator and query handler.

Plain handlers (no third-party runtime dependency beyond FluentValidation) are the default. Two opt-in sub-options, also settable via koine.config (targets.csharp.application.mediatr, targets.csharp.application.mapping):

  • --app-mediatr — emit the MediatR shape instead: IRequest/IRequest<T> requests, IRequestHandler<,> handlers, and validation + transaction IPipelineBehavior<,>s.
  • --app-mapping plain|mapperly — DTO/read-model mapping strategy (plain hand-rolled mappers by default; mapperly is reserved for source-generated mapping).

With the layer off (the default), the emitted C# is byte-identical to before. Koine usecase declarations carry no binding to a specific aggregate behavior, so the generated I<Service> implementation throws NotImplementedException until wired — the generated command/factory handlers are the real entry points. MediatR/FluentValidation/Mapperly are C#-emitter concerns and never leak into the target-agnostic model.

The language

Constructs

Construct Emits
value X { … } sealed record with get-only properties, a validating constructor, value equality
entity X identified by XId { … } sealed class with identity-only equality + a generated XId value object (Guid by default; as natural(String|Int) or as sequence selects the strategy). A create factory either mints the id (Guid: XId.New() / XId::generate()) or, for a natural/sequence key, takes it as an explicit identity-typed parameter (create register(id: XId, …)) — KOI0808 only when a non-Guid factory provides neither.
aggregate A root R { … } nested types in the <Context> namespace; the root R implements IAggregateRoot, and an I<R>Repository contract is emitted for it
aggregate A root R versioned { … } the root additionally gains a get-only Version token; ConcurrencyConflictException is emitted into Koine.Runtime
repository { operations: … ; find name(p): List<R>|R } tunes the root's repository — its mutating method set plus intention-revealing async finders
service S { usecase U(p: T): R } an application-service interface IS with one async method per use case (Task/Task<R>); a context with aggregates also gets an IUnitOfWork
readmodel M from Src { id; total: Int = … } a flat, value-equal DTO record + a static ToM(this Src src) projection mapper
query Q(criteria): List<M>|M a query DTO record handled via the shared generic IQueryHandler<TQuery,TResult>
enum E { … } a self-contained smart enum (sealed class: static instances, Name/Value, All, FromName/FromValue, value equality, ==/!=)
name: Type a typed property + constructor parameter
name: Type = const a constructor parameter with a default value
name: Type = expr (refs siblings) a derived, get-only computed property (not in the constructor)
invariant <expr> "msg" a constructor guard that throws DomainInvariantViolationException
invariant <expr> matches /re/ … a regex guard (Regex.IsMatch)
invariant <body> when <cond> a conditional guard (if (cond && !body) throw)

The full construct set (commands, domain events, state machines, factories, specs, services, policies, context maps, integration events, model versioning) is mapped construct-by-construct to the C# it emits in the feature catalogue.

Expression sublanguage

Small and pure (no statements, no I/O): comparisons (== != < <= > >=), arithmetic (+ - * /), logical (&& || !), member access (lines.isEmpty), regex matches /…/, a when guard, identifiers, and literals.

Primitive type mapping (Koine → C#)

Koine C# Notes
String string
Int int
Decimal decimal money / quantities
Bool bool
Instant DateTimeOffset
List<T> IReadOnlyList<T> defensively copied in the constructor
<XId> generated ID value object a record wrapping a Guid

Current limitations

  • Soft keywords. Most Koine keywords (context, value, entity, aggregate, enum, command, service, policy, repository, readmodel, query, import, module, …) may be used as field names, and declaration keywords additionally as type names and in expressions. Only matches and invariant remain reserved; keywords are not usable in the few hard-Identifier positions (a type/command/state/enum-member name). Because <- and -> are atomic operators, a comparison against a negative operand needs a space (x < -1, not x<-1).
  • Reserved type names. List, Set, Map, and Range are built-in generics; a user type may not take one of these names.

Architecture

The pipeline is strictly layered so backends are pluggable:

.koi source
  → Lexer/Parser (ANTLR, generated from Grammar/KoineLexer.g4 + KoineParser.g4)
  → KoineModelBuilderVisitor → semantic model (Ast/, target-agnostic)
  → SemanticValidator (Semantics/) → diagnostics with line/column
  → IEmitter (Koine.Emit.CSharp, .TypeScript, .Python, .Php, .Rust, …) → source files
Koine.slnx
├── src/
│   ├── Koine.Compiler/     # parser, Ast/, semantics + emit CONTRACTS (IEmitter, EmitterRegistry, …)
│   │   ├── Grammar/        # KoineLexer.g4, KoineParser.g4
│   │   ├── Ast/            # semantic model + ModelIndex (NO target-specific concepts)
│   │   ├── Parsing/        # KoineModelBuilderVisitor, SyntaxErrorListener
│   │   ├── Semantics/      # SemanticValidator (+ focused validators)
│   │   ├── Emit/           # IEmitter, IEmitterProvider, EmitterOptions, EmitterRegistry, EmitterLoader
│   │   ├── Diagnostics/    # Diagnostic
│   │   └── Services/       # KoineCompiler (orchestrator) + LSP/tooling backend
│   ├── Koine.Emit.Common/  # shared emitter helpers (FactoryIdBinding, MarkdownDoc, OperatorNeedsAnalyzer)
│   ├── Koine.Emit.CSharp/      # CSharpEmitter (primary target)  ── each emitter its own assembly (#861)
│   ├── Koine.Emit.TypeScript/  # TypeScriptEmitter
│   ├── Koine.Emit.Python/      # PythonEmitter (tactical core + strategic/CQRS layer)
│   ├── Koine.Emit.Php/         # PhpEmitter (tactical core + strategic/CQRS layer, PHP 8.1)
│   ├── Koine.Emit.Rust/        # RustEmitter (multi-context + CQRS read side)
│   ├── Koine.Emit.Glossary/    # ubiquitous-language glossary
│   ├── Koine.Emit.Docs/        # living documentation (Markdown + Mermaid diagrams)
│   ├── Koine.Emit.AsyncApi/    # AsyncApiEmitter (AsyncAPI 3.0 doc from integration events)
│   ├── Koine.Emit.OpenApi/     # OpenApiEmitter (OpenAPI 3.1 spec per bounded context)
│   ├── Koine.Emit.All/     # aggregator: BuiltInEmitterProviders.All — one reference, all targets
│   ├── Koine.Cli/          # `koine` command-line tool
│   ├── Koine.Wasm/         # the compiler as a WebAssembly module (Playground + Studio web)
│   └── Koine.Mcp/          # MCP server for AI agents
└── tests/
    └── Koine.Compiler.Tests/   # parsing, semantic, snapshot (Verify), Roslyn compile meta-tests

The grammar is split into a separate lexer grammar so that matches /regex/ can use a lexer mode — this lets a regex literal be read as a single token without colliding with the / division operator. The single most important invariant: no C#-specific concept lives in Ast/ — that is what keeps multiple emitters possible.

The browser bundle (AOT)

Koine.Wasm compiles the whole compiler to WebAssembly for the Playground and Studio. The deployed bundle is AOT-compiled (opt-in KoineWasmAot MSBuild property, switched on by the docs-deploy job); a bare dotnet build/publish and the dev inner loop stay on the fast interpreter build, so only the deployed/CI path pays the slower AOT publish. Measured trade-off (pizzeria template via node src/Koine.Wasm/smoke-test.mjs --bench, best/median of 10 warm runs):

Bundle pizzeria compile (best / median) _framework raw / gzip publish (warm)
Interpreter (dev default) 30 / 39 ms 6.6 MB / 2.3 MB ~4–10 s
AOT (deployed) 7 / 8 ms 20.3 MB / ~5.9 MB ~24–32 s

≈5× faster per-keystroke compile for a ~3× larger (browser-cached, +3.6 MB gzipped) one-time download — worth it for the in-browser compiler, and the AOT-vs-interpreter lever #219 needs to pick a mobile fallback. WasmStripILAfterAOT is a no-op here (the compiler + ANTLR are rooted whole and reflect, so their IL metadata is retained either way). Full rationale: the comment block in src/Koine.Wasm/Koine.Wasm.csproj (issue #327).

Koine as a platform

Koine.Compiler ships as a NuGet library with a frozen, contract-gated public API (guarded by Microsoft.CodeAnalysis.PublicApiAnalyzers, so unintended public surface can never ship silently). You can embed the compiler, write your own analyzers, and ship your own emitters.

Embed the compiler. Koine.Compiler carries the parser, semantic model, and emit contracts; the built-in emitters ship in Koine.Emit.All (the BuiltInEmitterProviders.All aggregator). Add both to resolve a target like csharp, then compile a model in process:

dotnet add package Koine.Compiler
dotnet add package Koine.Emit.All
using Koine.Compiler;          // BuiltInEmitterProviders (from Koine.Emit.All)
using Koine.Compiler.Emit;     // EmitterRegistry, EmitterOptions, EmittedFile
using Koine.Compiler.Services; // KoineCompiler

var registry = new EmitterRegistry(BuiltInEmitterProviders.All);   // built-in providers (csharp, typescript, …)
registry.TryCreate("csharp", EmitterOptions.Empty, out var emitter);

var result = new KoineCompiler().Compile(source, emitter);  // string source or IReadOnlyList<SourceFile>
if (result.Success)
    foreach (EmittedFile file in result.Files)
        Console.WriteLine($"{file.RelativePath}\n{file.Contents}");
else
    foreach (var d in result.Diagnostics)
        Console.Error.WriteLine(d);

Write an analyzer. Implement IModelAnalyzer — a target-agnostic check over the resolved semantic model that reports Diagnostics:

using Koine.Compiler.Diagnostics;
using Koine.Compiler.Semantics;

public sealed class NoLowercaseTypeNames : IModelAnalyzer
{
    public string Id => "acme.no-lowercase-type-names";

    public void Analyze(AnalyzerContext context)
    {
        foreach (var ctx in context.Model.Contexts)
            foreach (var type in ctx.Types)
                if (char.IsLower(type.Name[0]))
                    context.Report(Diagnostic.Warning("ACME001",
                        $"type '{type.Name}' should be PascalCase", type.Span));
    }
}

Wire it in code via new KoineCompiler([new NoLowercaseTypeNames()]), or let the CLI discover it: point the analyzers key in koine.config at the assembly that contains it (a comma-separated list of paths) — any public parameterless-constructible IModelAnalyzer is loaded and run after the built-ins.

# koine.config
analyzers = ./build/Acme.KoineAnalyzers.dll

Ship an emitter. Implement IEmitterProvider (returning an IEmitter for your target) to add a brand-new backend without forking the compiler:

using Koine.Compiler.Ast;
using Koine.Compiler.Emit;

public sealed class GoEmitterProvider : IEmitterProvider
{
    public string Target => "go";
    public IEmitter Create(EmitterOptions options) => new GoEmitter(options);
}

public sealed class GoEmitter : IEmitter
{
    public IReadOnlyList<EmittedFile> Emit(KoineModel model) => /* … */;
}

The CLI loads external emitters from the emitters key in koine.config (same discovery rules as analyzers), so koine build Models/ --target go resolves your provider through the same EmitterRegistry the built-in targets use.

# koine.config
emitters = ./build/Acme.GoEmitter.dll

Tooling

  • Web IDE. Koine Studio and the Playground run the compiler in the browser (WebAssembly) — see See it run above. Studio also ships as a native desktop app (tooling/koine-studio). Its built-in AI copilot makes the .koi it generates valid by construction: a grammar-capable local model is constrained to Koine's grammar (GBNF token masking), hosted APIs fall back to bounded parse-and-repair against the real parser, and Apply to editor stays disabled until the model parses. In a folder workspace it can also edit across files in one turn — the assistant reads the workspace and stages full-file changes (new files land under the folder root) that you review as a per-file diff and apply together; nothing touches disk until you accept. See the Assistant guide.

  • Editor support. tooling/koine-textmate is a TextMate grammar for .koi that works in JetBrains Rider and VS Code. For live error squiggles, completion, hover docs, and go-to-definition, run the bundled language server (koine lsp) — it reuses the compiler's own parser + validator, so editor diagnostics match koine build, and hover/navigation resolve across every .koi file in the workspace. Setup in tooling/README.md.

  • AI agents (MCP server). src/Koine.Mcp is an MCP server (koine-mcp) that lets an AI agent author a complete domain in .koi: tools to koine_validate, koine_compile (csharp/typescript/python/php/glossary/docs), and koine_format, plus koine_reference and koine_examples so the agent learns the language. Install with dotnet tool install -g Koine.Mcp, then register it over stdio (the default, for editor-spawned clients like Claude Desktop):

    { "mcpServers": { "koine": { "command": "koine-mcp" } } }

    Or serve it over HTTP (Streamable HTTP/SSE) so any client connects by URL — no DLL paths. koine mcp --http (or koine-mcp --http) binds loopback on an OS-assigned port and prints the endpoint; an LM Studio mcp.json then collapses to one line (use a tool-capable model):

    { "mcpServers": { "koine": { "url": "http://127.0.0.1:PORT/mcp" } } }

    On the desktop, Koine Studio launches that HTTP server for you and shows the ready-to-paste mcp.json under Settings → Assistant (a "Copy mcp.json" button). From a checkout, ./scripts/install-mcp/install-mcp.sh (or .ps1 / .cmd) packs, installs, and registers the stdio server with Claude Desktop in one step. Full tool list + the HTTP recipe in the MCP guide.

Koine in the AI workflow

In spec-driven development, Koine is the deterministic implementation step. An AI agent authors a small .koi model (over the MCP server above) and a human reviews that model — not thousands of lines of generated code. From there, koine build compiles the reviewed spec to idiomatic C# with no AI in the loop, so the implementation is a milliseconds-long, zero-token compile instead of a generate-test-fix loop. Completeness isn't hoped-for: the compiler emits every declared type, and koine coverage proves declared == emitted (exiting non-zero if anything is missing, so it doubles as a CI gate). See the Model-as-spec guide.

Tech stack

  • .NET 10, C#
  • ANTLR 4 via Antlr4BuildTasks + Antlr4.Runtime.Standard (visitor, not listener)
  • Tests: xUnit, Verify snapshots, and an in-memory Roslyn meta-test that compiles and executes the emitted C#.

Status & roadmap

Koine ships the full tactical and strategic DDD toolkit and nine emitter targets alongside C# — six languages (TypeScript, Python, PHP 8.1, Rust, Java 17, Kotlin 2.x) plus three spec/doc targets (docs — Markdown + Mermaid, AsyncAPI 3.0, and OpenAPI 3.1) — the editor tooling (TextMate grammar, koine lsp language server, and the fmt / init / watch commands), and model-as-spec coverage (koine coverage proves declared == emitted and doubles as a CI gate, also exposed as the koine_coverage MCP tool). The feature catalogue maps every construct to the C# it emits. The current version is shown by the NuGet badge above.

Recently landed: Java 17 and Kotlin 2.x emitters (dependency-free, compile-verified via javac/kotlinc); DDD-kind metadata surfaced across the TypeScript/Python/PHP emitters (#1170); and the CLI is now published to NuGet.org via Trusted Publishing — dotnet tool install --global Koine.Cli (#1179). The full roadmap lives in USER-STORIES.md.

Templates

templates/ is the single, CI-validated source of truth for Koine's example domains. A template is a folder holding one or more .koi files plus a template.json manifest describing it. Every template is compiled green and every manifest is schema-validated on each build (TemplatesValidationTests), and the same set powers three places: the demo (below), Koine Studio's template gallery, and the Playground sample picker.

Templates come in four difficulty levels — starter, beginner, intermediate, advanced (the schema reserves all four; beginner is unused so far):

Template Difficulty What it models
starters/billing starter Money, orders, and invariants — the canonical Koine starter
starters/ordering starter An aggregate with a state machine — renders as a diagram
starters/contextmap starter Two bounded contexts and the relationship between them
starters/values starter Smart enums with data, quantities, ranges, and derived fields
ticketing intermediate A help-desk workflow with a ticket lifecycle and a cross-context SLA policy
pizzeria intermediate A six-context pizza shop (menu, ordering, kitchen, delivery, payment, promotions) + an external Gateway
library intermediate A lending library across five contexts — Book vs BookCopy, loans, reservations, fines
saas-subscription advanced Multi-tenant subscriptions with trials, metered quotas, dunning, and a payment-provider ACL

The template.json manifest

Each template folder carries a template.json validated against templates/template.schema.json:

Field Meaning
id Stable identifier — must equal the folder name
name Human-readable display name
tagline One-line summary shown in listings
description A paragraph describing what the template models
difficulty starter · beginner · intermediate · advanced — used to order and badge templates
tags Free-form keywords for search and filtering
contexts The bounded contexts the template defines
coreAggregate The headline aggregate that anchors the template
entryFile The primary .koi file to open first — must name a file in the folder
teaches The Koine concepts / DDD patterns a learner picks up
icon An icon identifier (emoji or icon name) for the template card

Demo

demo/ consumes the generated C# from a real .NET project. It compiles straight from the templates/pizzeria template — a pizzeria domain across six bounded contexts (plus an external card Gateway) tied together by a context map — so dotnet build demo/Pizzeria.Domain regenerates and compiles it, which makes building the demo the end-to-end proof that the pizzeria template emits compiling, runnable C#. Between the .koi template and Samples.cs it exercises the full shipped feature set — see demo/README.md for the feature-to-location map.

Contributing

Contributions are welcome! See CONTRIBUTING.md for how to build, test, and submit a change, and please follow our Code of Conduct. Security issues should be reported privately — see SECURITY.md. Notable changes are tracked in CHANGELOG.md.

License

Koine is licensed under the Apache License 2.0 — see LICENSE. Copyright © 2026 Atypical Consulting / Philippe Matray.

About

A domain-specific language for Domain-Driven Design: write a bounded context's ubiquitous language once in .koi files and the compiler emits idiomatic, self-contained C# (and TypeScript) — value objects, entities, aggregates, invariants, commands, events, state machines, repositories, CQRS, context maps.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

13 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors