HomePlaygroundExpressionsDocsDriversBlogStatusRoadmapChangelog GitHub

Protocol-First Data Layer

Typed query AST from app to database.

QAIL builds queries as typed objects and encodes them as protocol bytes. No hand-built SQL interpolation on the AST path, with RLS and validation still enforced where they belong.

1 AST for API, driver, and database
449us on the N+1 benchmark path
RLS Native policy context remains in PostgreSQL
Rust cargo install qail
Zig zig fetch --save ...qail-zig/v0.2.0.tar.gz

First-party drivers and SDKs: Rust, Zig, TypeScript, Swift, Kotlin · Node.js native binding deferred · Latest stable: v0.26.0 (March 20, 2026)

Live Query Shape Protocol path overview
QAIL AST
Qail::get("users")
    .select_all()
    .filter("active", Eq, true)
encode + bind
POSTGRESQL SELECT * FROM users WHERE active = $1
QDRANT { "must": [{ "key": "active", "match": true }] }
RustRust ZigZig

Real Benchmark: N+1 Battle Test

Same query. Same database. Same 50-row result set with 3 JOINs.
100 iterations, release build, buffer cache warmed.
Snapshot run: February 2026.

QAIL AST
449µs
1 query
GQL + DataLoader
1.52ms
~3 queries
GQL Naive
18.2ms · 40× slower
~151 queries
REST Naive
22.5ms · 50× slower
~151 queries

QAIL executes one planned query on this path. Naive resolver/endpoint patterns can explode to 151 round-trips without join batching.

View all benchmarks →

The QAIL Stack

Four layers. One AST flows through all of them.

🧬

qail-core

THE AST

Parser, transpiler, type system. Queries are typed data structures, not hand-built SQL strings in app code. Schema validation at compile time.

get users fields id, email
  where active = true
  order by created_at desc
  limit 10
🔌

qail-pg / qdrant

NATIVE DRIVERS

Protocol-level PostgreSQL driver with typed values and built-in RLS context per connection.

let driver = PgDriver::connect_env().await?;
driver.set_rls_context(
    RlsContext::tenant("tenant-456")
).await?;
driver.fetch_all(&cmd).await?
🚀

qail-gateway

AUTO-REST + BINARY PROTOCOL

Zero-code API from your schema. Auto-REST with FK expansion, policy engine, WebSocket subscriptions, binary AST wire format.

GET /api/bookings
    ?status.eq=confirmed
    &sort=created_at:desc
    &expand=routes,payments
    &limit=20
⚙️

qail-workflow

STATE MACHINES

Declarative multi-step orchestration. Database queries, notifications, and events in typed state machines.

Workflow::new("booking")
    .step("validate", validate_fn)
    .step("charge", payment_fn)
    .step("confirm", confirm_fn)
    .on_error(rollback_fn)

Every Entry Point → One Pipeline

REST params, text queries, or binary AST all converge to the same AST. Policy injection and schema validation happen once. SQL text output is optional for tooling/debug views.

REST
GET /api/users?active=true
Text
get users where active = true
Binary
postcard::to_allocvec(&cmd)
QAIL AST
Qail { action: Get, table: "users", cages: [Filter(active = true)] }
POLICY ENGINE
+ injects AND tenant_id = 'tenant-456'
PROTOCOL ENCODER
Encode AST → Parse / Bind / Execute + typed bind values
POSTGRESQL (with PG-native RLS)
1 query. 1 connection. Done.

Supported Databases

Two production databases, one typed AST, plus built-in cache for hot paths.

PostgreSQL

PostgreSQL

Relational • Transactions • ACID

Production
Qdrant

Qdrant

Vector • AI/ML • Semantic Search

Production

Native Cache

Moka • TTL • Zero Latency

Built-in

Language Drivers

Production drivers: Zig and Rust. Direct SDKs: TypeScript (@qail/client), Swift, and Kotlin. Node.js native binding is deferred.

Zig

Zig

Pure Zig • TLS • Pool v0.2.0
Rust

Rust

cargo add qail-core Cargo

View all drivers →

🛡️ The "Wait, Stop!" Feature

Migration pipeline with policy checks, receipt verification, and explicit unsafe guards.

Terminal
$ qail migrate down --force --wait-for-lock

BLOCKED: Unsafe rollback context

Reason: non-TTY session requires explicit confirmation
Reason: receipt signature mismatch for target version

Action: verify receipt integrity and rerun with explicit maintainer confirmation.
🔍

Scans Before Running

Searches .rs, .ts, .js, .py for queries that reference changing columns

📍

Points to Line Numbers

Shows exactly where your code needs updating before migration

🎯

You're the Boss

Use --force to override when you know better

The data layer you don't write.

One AST. Protocol bytes. Compile-time safety. Built-in RLS.