.carrier → lexer → parser → AST → semantic → IR → rust → cargo → binary.
Carrier keeps a real compiler pipeline. Rust is the backend, not the language you write. Each stage lives in its own crate with a single responsibility.
Stages and responsibilities
Eight crates. One job each.
The compiler workspace is a plain Cargo workspace. Reading the code is the fastest way to understand how Carrier behaves.
carrier-lexerTokenizerConverts .carrier source into tokens with spans. Handles comments, doc comments, string literals, identifiers, and attribute annotations.
carrier-parserParserProduces the AST for declarations, statements, and expressions. Preserves spans for diagnostic-quality error messages.
carrier-astAST nodesSpan-carrying syntax tree. The canonical shape every later stage consumes.
carrier-semanticSemantic analysisName resolution, type checking, CRUD and auth validation, policy checks, query defaults, idempotency scope rules, built-in call signatures.
carrier-irIntermediate formLowers the semantic model into a codegen-oriented program. Decouples language surface from generation.
carrier-codegen-rustCode generationGenerates a standalone Rust service project, plus OpenAPI, manifest, and migration artifacts.
carrier-runtimeRuntime helpersThe internal runtime depended on by generated services: auth, Postgres, cache, jobs, schedules, events, idempotency, outbound HTTP.
carriercCompiler CLIProject discovery, generation, build orchestration, dev watch, OpenAPI export, and migration commands.
What the generated service gets for free.
Generated Rust services link against carrier-runtime. You never write against it directly — it powers the constructs you declare in .carrier source.
/health · /ready · /openapi.json · /docs.auth.register, auth.login, auth.current_user.carrier.current_roles and carrier.current_tenant per request, before any DB work.Idempotency-Key. Scope: {METHOD} {PATH}:{user.id|public}.REDIS_URL is healthy, otherwise an in-memory fallback. Keys are tenant-scoped by the runtime.client declarations.Every build emits the same shape
Deterministic, machine-readable outputs — not debug artifacts. Tools can treat these as the ground truth of a Carrier project.
.carrier/generated/generated Rust service project.carrier/generated/migrations/SQL migrations & schema snapshots.carrier/manifest.jsondeclarations · routes · models · policies · jobs.carrier/build/<binary>native executable
/openapi.jsonserved by the binary/docsAPI explorer UI/health · /readyliveness · readiness- JSON logs to stdoutstructured metadata for downstream collectors
Escape hatches are intentional, not afterthoughts
Carrier defines a clear sequence for how you reach for Postgres. Most code lives at the top. Escape hatches are explicit and typed.
- 1Generated CRUDstandard list · get · create · update · delete · restore
- 2Model helpersModel.get, Model.search, count_by_*, exists_by_*, get_by_*, list_by_*
- 3Actions + transactionsauth context, row locks via get_for_update, cache, jobs, audit
- 4Raw SQLsql.list_as, sql.one_as, sql.scalar_as, sql.exec — typed results
- 5DB functionsdb.call_as, db.fn_one_as, db.fn_scalar_as — stored functions & builtins
Current limits
Carrier ships what the compiler actually implements. These are the places where it deliberately stops short today.
src/ still compiles together — imports are not yet selective loaders..carrier/generated/migrations/.Json payload types so the job body handles an empty input.return from inside a transaction { } block is not supported yet. Do work in the block; return after.