Carrier
Preview · compiled language · v0.1

A compiled language
for APIs and services.

Carrier is a standalone compiled language for API services and Postgres-backed business logic. Routes, auth, CRUD, actions, transactions, policies, and jobs as first-class language constructs — lowered through a real compiler pipeline into a native service binary.

Source.carrier
CodegenRust → Cargo
RuntimeTokio · Axum
StorePostgres
CacheRedis · in-mem
OutputNative binary
api/src/main.carrier
Doctor Directory · api/src
service DoctorDirectory {
openapi { title: "Doctor Directory API" version: "0.2.0" }
server { host: env("HOST", "0.0.0.0") port: env_int("PORT", 4001) }
}
auth jwt AdminAuth {
issuer: env("JWT_ISSUER", "carrier")
audience: env("JWT_AUDIENCE", "carrier-users")
secret: env("JWT_SECRET", "local-dev-secret")
roles_claim: "roles"
}
model Doctor {
id: UUID
slug: String @length(min: 3, max: 80)
full_name: String @length(min: 3, max: 120)
specialty: String
rating: Float @range(min: 0, max: 5)
status: DoctorStatus = active
deleted_at: Time?
created_at: Time
updated_at: Time
}
crud Doctor at "/doctors" {
create: protect AdminAuth roles [admin]
list: public
get: public
update: protect AdminAuth roles [admin]
delete: soft using deleted_at protect AdminAuth roles [admin]
list_defaults { page_size: 20 sort: "rating" direction: desc scope: active }
filters { specialty: exact city: exact language: contains }
searchable { full_name specialty city bio }
}
service
one per project
auth jwt
roles-aware
crud
filters · sort · soft delete
compiled · typed · compiler-backed·narrow on purpose·optimized for Postgres-backed business services·explicit about auth, policies, jobs, and clients·structured for humans and AI code generators
Why Carrier

Built narrow, so the backend gets out of the way.

Most backend frameworks give you a generic language plus conventions. Carrier inverts that. The backend patterns that actually matter — routes, auth, CRUD, policies, transactions, jobs — are language-level constructs the compiler understands.

Compiled, not configured
One .carrier source tree compiles through lexer, parser, semantic analysis, IR, Rust codegen, and Cargo into a native service binary. Rust is an implementation detail.
Opinionated CRUD
crud generates list, get, create, update, delete, and restore — with filters, sort, pagination, search, soft delete, and explicit scopes.
Actions with real transactions
action blocks open Postgres transactions, lock rows with get_for_update, and call auth, cache, jobs, audit, and typed external clients.
Policies that compile to RLS
policy declarations produce PostgreSQL RLS SQL and runtime session context for carrier.current_roles and carrier.current_tenant.
Idempotency as a keyword
Mark a route idempotent and Carrier stores the JSON response in Postgres, short-circuiting retries with the same Idempotency-Key.
Workflow edges, typed
job, event, and schedule give you durable async work, cron-driven fan-out, and audit-backed event records.
Compiler pipeline

A real compiler, not a macro wrapper over a web framework.

Each stage has a clean responsibility and lives in its own crate. Carrier separates language surface from runtime, so the generated Rust service stays boring and reviewable.

01
.carrier
source
02
lexer
tokens
03
parser
AST
04
semantic
types · validation
05
IR
lowering
06
codegen
rust
07
cargo / rustc
build
08
service
native binary
crates/carrier-*.carrier/build/<binary>
Source
.carrier files under src/. Every file compiles together, sorted by path.
Generated
.carrier/generated/ holds the Rust service, migrations, and the machine-readable manifest.
Binary
.carrier/build/<binary> is a native service. Tokio async server, /health, /ready, /openapi.json, /docs.
Language primitives

Fifteen top-level constructs. That’s it.

Carrier’s entire declaration surface fits on one line. Every one of these is implemented today. Scope is the feature.

service
API surface & server
auth jwt
JWT issuer · roles
import
validated metadata
enum
named variants
type
records & projections
model
persisted records
crud
generated resource endpoints
fn
pure helpers
action
business logic · transactions
policy
role · tenant · RLS SQL
route
HTTP surface
client
typed outbound JSON
event
durable events
job
async work
schedule
cron-driven jobs
action · transaction · idempotent
api/src/main.carrier
row locking
/// Transaction-safe admin action with row locking
action audit_locked_doctor(id: UUID) -> Doctor {
let actor = auth.current_user()
transaction {
let locked = Doctor.get_for_update(id: id, scope: "all")
logs.info("doctor lock audit", {
action_name: "audit_locked_doctor"
doctor_id: locked.id
actor_email: actor.email
})
}
return Doctor.get(id: id, scope: "all")
}
/// Idempotent admin route; retries return the stored response
route POST "/admin/doctors/{id}/lock"
protect AdminAuth roles [admin] idempotent -> Doctor
{
params { id: UUID }
handler { return audit_locked_doctor(params.id) }
}
policy · tenant RLS · @version
src/00_models/slots.carrier
booking-service
policy AppointmentSlot {
tenant_field: org_id
read: roles [viewer, scheduler]
write: roles [scheduler]
deleted: roles [scheduler]
}
model AppointmentSlot {
id: UUID
org_id: String @index
clinician_name: String @length(min: 3, max: 120)
starts_at: Time
status: SlotStatus = open
version: Int @version
deleted_at: Time?
created_at: Time
updated_at: Time
}
Runtime · platform

Postgres-first. Jobs, cache, idempotency, RLS — built in.

Generated services depend on the Carrier runtime for the boring, load-bearing parts you’d otherwise rebuild per project.

Postgres models & migrations
Generated migrations, schema snapshots, row locking via get_for_update, optimistic @version, and explicit scopes for soft-deleted records.
Policies & RLS SQL
policy compiles to PostgreSQL ENABLE ROW LEVEL SECURITY. Per request, the runtime sets carrier.current_roles and carrier.current_tenant.
Jobs · schedules · events
Durable jobs backed by Postgres. Cron-style schedules polled by the runtime. Emitted events recorded in a first-class events table.
Cache with graceful fallback
cache.get_as, cache.set, redis.publish. Uses Redis when REDIS_URL is healthy, otherwise an in-memory fallback.
Idempotent routes
Mark routes idempotent. Replays with the same Idempotency-Key return the stored response body without re-running the handler.
OpenAPI & manifest
Every build emits /openapi.json, /docs, and a first-class .carrier/manifest.json of declarations — AI-friendly, tooling-friendly.
search · cache · emitpublic search with 120s cache and event emission
api/src/main.carrier
GET /search/doctors
route GET "/search/doctors" public -> DoctorListResponse {
query {
q: String? specialty: String? city: String? country: String?
page: Int = 1 per_page: Int = 20
sort: String = "rating" direction: String = "desc"
scope: String = "active"
}
handler {
let key = "doctor-search:" + json.stringify({
q: query.q, city: query.city, page: query.page
})
if cache.exists(key) {
return cache.get_as("DoctorListResponse", key)
}
let result = Doctor.search(
q: query.q, city: query.city,
page: query.page, per_page: query.per_page,
sort: query.sort, direction: query.direction,
scope: query.scope
)
cache.set(key, result, ttl_seconds: 120)
emit DoctorSearchCached { cache_key: key, scope: query.scope }
return result
}
}
job · schedule · idempotent
src/15_async/jobs.carrier
booking-service
event SlotReminderQueued {
slot_id: UUID
trigger: String
}
job send_slot_reminder(payload: Json) -> Void {
logs.info("slot reminder job", { job_name: "send_slot_reminder", payload: payload })
audit.record("send_slot_reminder", "AppointmentSlot", "scheduled", payload)
}
schedule "*/15 * * * *" run send_slot_reminder
route POST "/slots/{id}/notify"
protect StaffAuth roles [scheduler] idempotent -> Json
{
params { id: UUID }
handler {
let job_id = jobs.enqueue("send_slot_reminder", { slot_id: params.id }, delay_seconds: 5)
emit SlotReminderQueued { slot_id: params.id, trigger: "manual" }
return { job_id: job_id }
}
}
Declarations
15
top-level constructs
Built-ins
string · array · json · cache · sql · db · logs · audit · jobs · redis
typed namespaces
Artifacts
OpenAPI · manifest · migrations · binary
Examples
4
from minimal to doctor-directory
For AI generation

Precise enough for humans. Constrained enough for agents.

Carrier’s advantage isn’t maximal language power. It’s that the backend surface is small, explicit, and compiler-checked — exactly the shape that lets code generators stay consistent across a project.

Narrow syntax. Fifteen declarations cover services, auth, routes, models, CRUD, actions, policies, jobs, and clients. Fewer ways to be wrong.
Authoring tiers. The docs grade constructs: Tier 1 for first-pass-safe, Tier 2 for most services, Tier 3 for advanced runtime work. Start at the smallest tier that fits.
First-class manifest. Every project emits .carrier/manifest.json with models, routes, policies, jobs, schedules, and clients. Tools read it instead of scraping source.
Compiler feedback loop. carrier check runs the full semantic pass: type errors, CRUD misuse, auth/policy validation, invalid idempotency scope, and more.
Predictable layout. The multi-file convention (00_models, 10_types, 20_actions, 30_routes) keeps generation order stable across agents.
docs/ai-authoring.md
Generation recipe
// docs/ai-authoring.md — fast generation recipe
1. write carrier.toml
2. declare one service
3. add auth jwt if any route is protected
4. create enum used by models or query defaults
5. create model declarations
6. create type declarations for I/O and pagination
7. add crud for resource-like models
8. add policy for role or tenant visibility
9. add action for real business writes
10. add custom route
11. add job / event / schedule only when needed
12. add client for outbound integrations
13. add pure fn helpers last
Examples

Four checked-in examples. Ladder from minimal to platform.

Every example in the repository compiles today. They’re ordered so you can learn the language by walking up the ladder.

sql.list_as · typed rows
api/src/main.carrier
GET /reports/doctors/by-city
/// Admin reporting endpoint using raw SQL mapped into a typed result list
route GET "/reports/doctors/by-city" protect AdminAuth roles [admin]
-> DoctorCityReportRow[]
{
handler {
return sql.list_as(
"DoctorCityReportRow",
"select city, count(*)::bigint as total from doctors where deleted_at is null group by city order by total desc, city asc"
)
}
}
client · typed JSON
api/src/main.carrier
WeatherApi
client WeatherApi {
base_url: env("WEATHER_API_URL", "https://api.example.com")
header "x-api-key": env("WEATHER_API_KEY", "")
timeout_ms: 3000
}
type WeatherResponse {
city: String
temperature_c: Float
condition: String
}
route GET "/weather/{city}" public -> WeatherResponse {
handler {
return WeatherApi.get("/forecast", query: { city: params.city })
}
}
Quickstart

From zero to a native binary in three commands.

The compiler workspace is a Rust project; Carrier services compile through it.

compiler workspace
$ cargo test
$ cargo run -p carrierc -- --help

Build and test the compiler. The carrierc CLI scaffolds projects, type-checks .carrier sources, generates Rust + OpenAPI + migrations, and drives build, run, and dev.

a service
# new project, then in the project root:
$ carrier check
$ carrier build
$ carrier run # or ./.carrier/build/<binary>
$ carrier openapi > openapi.json
$ carrier migrate generate

Works identically across every example in the repo — hello-carrier, inventory-control, booking-service, and the flagship api/.

Current limits
Imports are validated metadata, not selective loaders. Migration diffing is structural and does not infer renames automatically.
Observability
JSON logs to stdout with structured metadata. Audit records, events, jobs, and schedules are durable. Full tracing/metrics backends are external integrations.
Transactions
transaction { } opens a real DB transaction. return inside the block is not supported yet — return after the block closes.

Backend work as a language problem,
not a framework problem.

Read the language reference, walk the examples, and ship a compiled service.