Aurora

The fullstack Varian platform — Zenith backend + Lumen frontend, one language, one binary.

download Download page

All vn_modules/ files — including zenith.vn, lumen.vn, lumen_ui.vn, db.vn, and http.vn — are automatically concatenated as a prelude when you run an Aurora project. new_app(), lumen_mount(), sqlite.connect(), and the entire Lumen UI registry are always in scope. No import, no require, no npm install. The toolchain auto-detects Aurora, Lumen, or Zenith project type and compiles accordingly.

Why Aurora?

Aurora's pitch is not "yet another fullstack framework." It's one language, one toolchain, one binary — from database schema to DOM morph. No separate frontend build, no JavaScript framework, no node_modules, no dependency tree.

language

One language, the entire stack

Backend API routes, database queries, session management, HTML templates, and UI components — all Varian. Share types, validation, and business logic between server and client with no serialization boundary.

flash_on

Zero-config scaffolding

vn new myapp creates a complete fullstack project: main.vn, pages/, lib/, public/, constellation.toml. vn dev starts a live-reload dev server. No config files to write.

autorenew

Server-driven UI

Lumen components render on the server. A 5 KB client forwards events over WebSocket; the server re-renders and morphs only the changed DOM nodes. Zero hydration bugs, zero client framework, zero Varian in the browser.

neurology

Comptime ORM + DB drivers

SQLite and Postgres drivers built in. The comptime ORM checks SQL strings at compile time and generates bound queries. bind() throws on parameter count mismatch — not at runtime.

inventory_2

Batteries included

JWT auth, signed sessions, password hashing, CORS, rate limiting, Redis driver, HTML template engine, request validation, Lumen UI component registry — all built in through vn_modules/. Zero external dependencies.

package_2

Deploys as one binary

vn build --release produces a single native executable with Zenith, Lumen, the ORM, and the database schema all compiled in. No runtime to install, no node_modules on the server, no "works on my machine."

description

OpenAPI for free

Every route's summary string feeds a generated OpenAPI spec at /docs. Request and response schemas are declared inline with the handler — no separate spec file, no decorator soup.

track_changes

Multi-core by default

Zenith clusters across threads with independent VMs, SIMD request parsing, writev single-syscall responses, and per-request arena allocation. Lumen's WebSocket event loop handles concurrent connections without threads.

rocket_launch Scaffolding an Aurora project

A single command creates a complete fullstack project:

sh
$ vn new myapp
cd myapp
vn dev                # live-reload dev server on :8090

This creates a project tree with every file you need:

project tree
myapp/
  main.vn               # Zenith entry point — app config, routes, middleware
  pages/
    index.lumen          # Lumen landing page (live reactive component)
    components/          # shared UI components (ProductCard, Nav, etc.)
  lib/
    config.vn            # env-based configuration
    db.vn                # SQLite schema, migration, seed
    middleware.vn         # logging, CORS, rate limiter
    api_products.vn       # product listing REST endpoints
    api_cart.vn           # session-backed shopping cart
    api_orders.vn         # checkout + order creation
    auth.vn               # registration, login, logout
    jobs.vn               # email worker pool + cron
    pages.vn              # Lumen page mounting with SSR data
  public/                # static assets (images, favicons)
  constellation.toml     # package manifest (kind = "aurora")

vn dev detects the Aurora project type automatically, starts the Zenith server, mounts all Lumen pages, and watches for changes with live reload. vn build --release compiles everything into a single native binary.

code Full-stack examples

Every Aurora project follows the same architecture. Here is the complete walkthrough of a production fullstack app — the Aurora storefront reference implementation.

input Entry point — main.vn

The main.vn wires together every module: config, database, middleware, API routes, auth, background jobs, and Lumen page mounting. It is the single entry point for the entire application.

main.vn
use "lib/config.vn"
use "lib/db.vn"
use "lib/middleware.vn"
use "lib/api_products.vn"
use "lib/api_cart.vn"
use "lib/api_orders.vn"
use "lib/auth.vn"
use "lib/jobs.vn"
use "lib/pages.vn"
use ".gen/pages.vn"

let c = cfg()
let conn = db_init(c.db_path)
let app = new_app()
app.title = "Aurora"

install_middleware(app, c)
register_product_api(app, conn, c)
register_cart_api(app, conn, c)
let email_pool = start_jobs(conn, c)
register_order_api(app, conn, c, email_pool)
register_auth(app, conn, c)
mount_pages(app, conn, c)

app.enable_docs("/docs")
app.serve_static("/public", "public")
app.on_error(|err, req| {
    return http.create_struct(
        ["status", "body", "content_type"],
        [500, "<!DOCTYPE html>...", "text/html"]
    )
})
app.listen(c.port)

Notice: no import for new_app(), http.create_struct, sqlite — all auto-in-scope through the vn_modules/ prelude.

settings Configuration — lib/config.vn

Centralized env-based configuration. Every env.get() call lives in one place — no magic numbers or hardcoded secrets scattered across modules.

lib/config.vn
fn cfg() {
    let port = 8080
    let port_str = env.get("PORT", "8080")
    try { port = json_decode(port_str) } catch e {}

    let secret = env.get("SESSION_SECRET", "")
    if secret == "" {
        secret = auth.sha1_base64("" + time.now_ms())
        print("[config] WARNING: SESSION_SECRET not set")
    }

    return http.create_struct(
        ["port", "session_secret", "db_path", "per_page"],
        [port, secret, env.get("DB_PATH", "aurora.db"), 12]
    )
}

storage Database — lib/db.vn

SQLite schema initialization, migration, and seed data. The comptime ORM (db.vn in vn_modules/) provides a builder API (select(), bind(), run_sqlite()) that validates queries at compile time.

lib/db.vn
fn db_init(db_path) {
    let conn = sqlite.connect(db_path)

    sqlite.query(conn, "CREATE TABLE IF NOT EXISTS products (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL, price_cents INTEGER NOT NULL,
        stock INTEGER NOT NULL DEFAULT 0,
        image TEXT NOT NULL DEFAULT '',
        description TEXT NOT NULL DEFAULT '',
        featured INTEGER NOT NULL DEFAULT 0)")

    sqlite.query(conn, "CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        email TEXT NOT NULL UNIQUE, pass_hash TEXT NOT NULL,
        name TEXT NOT NULL DEFAULT '',
        created TEXT NOT NULL DEFAULT (datetime('now')))")

    sqlite.query(conn, "CREATE TABLE IF NOT EXISTS orders (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        user_id INTEGER, total_cents INTEGER NOT NULL,
        status TEXT NOT NULL DEFAULT 'pending',
        created TEXT NOT NULL DEFAULT (datetime('now')))")

    let existing = sqlite.query(conn, "SELECT COUNT(*) AS c FROM products")
    if existing[0].c == 0 { seed_data(conn) }
    return conn
}

fn format_price(cents) {
    let dollars = cents / 100
    let c = cents % 100
    if c < 10 { return "$" + dollars + ".0" + c }
    return "$" + dollars + "." + c
}

link Middleware — lib/middleware.vn

Middleware is applied as a chain with app.add_middleware(). Each middleware receives the request and a next callback.

lib/middleware.vn
fn install_middleware(app, c) {
    app.add_middleware(|req, next| {
        let start = time.now_ms()
        let resp = next(req)
        let ms = time.now_ms() - start
        print("[Aurora] " + req.method + " " + req.path
              + " -> " + resp.status + " (" + ms + "ms)")
        return resp
    })
    app.add_middleware(cors(["*"], ["GET", "POST", "PUT", "DELETE"], ["*"]))
    app.add_middleware(rate_limit(c.rate_max, c.rate_window))
}

api REST API — lib/api_products.vn

Product listing and detail endpoints. Routes are registered with a summary string for auto-generated OpenAPI docs. Query parameters parsed with query(), database queries use bound parameters.

lib/api_products.vn
fn register_product_api(app, conn, c) {
    app.get("/api/products", |req| {
        let q = query(req, "q", "")
        let page = 0
        let pstr = query(req, "page", "1")
        if pstr != null and pstr != "" {
            try { page = json_decode(pstr) - 1 } catch e {}
        }
        let offset = page * c.per_page
        let rows
        if q != "" {
            rows = sqlite.query(conn,
                "SELECT * FROM products WHERE name LIKE ? ORDER BY id LIMIT ? OFFSET ?",
                ["%" + q + "%", c.per_page, offset])
        } else {
            rows = sqlite.query(conn,
                "SELECT * FROM products ORDER BY id LIMIT ? OFFSET ?",
                [c.per_page, offset])
        }
        return json_res(rows, 200)
    }, "List products", http.create_struct(
        ["query_params", "response_schema", "response_status"],
        [http.create_struct(["q", "page"], ["string", "integer"]), "array", 200]
    ))

    app.get("/api/products/:id", |req| {
        let id = _validate.get_field(req.params, "id")
        if id == null { return err_res("Missing id", 400) }
        let rows = sqlite.query(conn, "SELECT * FROM products WHERE id = ?", [id])
        if rows.len() == 0 { return err_res("Not found", 404) }
        return json_res(rows[0], 200)
    }, "Get product by id", http.create_struct(
        ["response_schema", "response_status", "error_status"],
        ["object", 200, 404]
    ))
}

shopping_cart Session-backed Cart — lib/api_cart.vn

The cart is stored in the session cookie. session_get and session_set handle JWT-signed cookie encryption automatically.

lib/api_cart.vn
fn register_cart_api(app, conn, c) {
    app.get("/api/cart", |req| {
        return json_res(get_cart(req, c.session_secret), 200)
    }, "Get cart", http.create_struct(
        ["response_schema", "response_status"], ["array", 200]
    ))

    app.post("/api/cart/:id", |req| {
        let id = _validate.get_field(req.params, "id")
        if id == null { return err_res("Missing product id", 400) }
        let qty = req.json.qty
        if qty == null { qty = 1 }
        let cart = get_cart(req, c.session_secret)
        let found = false
        for i in 0..cart.len() {
            if cart[i].product_id == id {
                cart[i].qty = cart[i].qty + qty
                found = true; break
            }
        }
        if !found {
            cart = cart.push(http.create_struct(
                ["product_id", "qty"], [id, qty]))
        }
        return save_cart(json_res(cart, 200), cart, req, c.session_secret)
    }, "Add to cart", http.create_struct(
        ["request_schema", "response_schema", "response_status"],
        ["object", "array", 200]
    ))

    app.delete("/api/cart/:id", |req| {
        let id = _validate.get_field(req.params, "id")
        if id == null { return err_res("Missing product id", 400) }
        let cart = get_cart(req, c.session_secret)
        let new_cart = []
        for i in 0..cart.len() {
            if cart[i].product_id != id {
                new_cart = new_cart.push(cart[i])
            }
        }
        return save_cart(json_res(new_cart, 200), new_cart, req, c.session_secret)
    }, "Remove from cart", null)
}

lock Authentication — lib/auth.vn

Registration, login, and logout with password hashing and JWT-signed session cookies. Routes include OpenAPI schema annotations for auto-generated docs.

lib/auth.vn
fn register_auth(app, conn, c) {
    app.post("/api/register", |req| {
        let json = req.json
        let email = _validate.get_field(json, "email")
        let pass = _validate.get_field(json, "password")
        if email == null or pass == null {
            return err_res("Email and password required", 400)
        }
        let existing = sqlite.query(conn,
            "SELECT id FROM users WHERE email = ?", [email])
        if existing.len() > 0 {
            return err_res("Email already registered", 409)
        }
        let hash = auth.sha1_base64(pass)
        sqlite.query(conn, "INSERT INTO users (email, pass_hash, name) VALUES (?, ?, ?)",
            [email, hash, _validate.get_field(json, "name")])
        let rows = sqlite.query(conn, "SELECT last_insert_rowid() AS id")
        let session_data = http.create_struct(
            ["uid", "email"], [rows[0].id, email])
        return session_set(json_res(http.create_struct(
            ["uid", "email"], [rows[0].id, email]), 201),
            session_data, c.session_secret, null)
    }, "Register user", http.create_struct(
        ["request_schema", "response_schema", "response_status"],
        ["object", "object", 201]
    ))

    app.post("/api/login", |req| {
        let json = req.json
        let email = _validate.get_field(json, "email")
        let pass = _validate.get_field(json, "password")
        let rows = sqlite.query(conn,
            "SELECT id, name FROM users WHERE email = ? AND pass_hash = ?",
            [email, auth.sha1_base64(pass)])
        if rows.len() == 0 {
            return err_res("Invalid credentials", 401)
        }
        let u = rows[0]
        return session_set(json_res(http.create_struct(
            ["uid", "email"], [u.id, email]), 200),
            http.create_struct(["uid", "email", "name"], [u.id, email, u.name]),
            c.session_secret, null)
    }, "Login user", http.create_struct(
        ["request_schema", "response_schema", "error_status"],
        ["object", "object", 401]
    ))
}

publish Mounting Lumen Pages with SSR Data — lib/pages.vn

Lumen pages mount onto Zenith routes with the lumen_mount_data API. The callback runs on every request, fetches data from the database, and passes it to the Lumen component as initial state — full server-side rendering with zero client data fetching.

lib/pages.vn
fn mount_pages(app, conn, c) {
    // Shop page — DB-driven products with search
    let shop_comp = _lumen.get_component("Shop")
    if shop_comp != null {
        lumen_mount_data(app, "/shop", shop_comp, |req| {
            let q = query(req, "q", "")
            let items = []
            let rows
            if q != "" {
                rows = sqlite.query(conn,
                    "SELECT * FROM products WHERE name LIKE ? ORDER BY id",
                    ["%" + q + "%"])
            } else {
                rows = sqlite.query(conn,
                    "SELECT * FROM products ORDER BY id", [])
            }
            for i in 0..rows.len() {
                let p = rows[i]
                items = items.push(http.create_struct(
                    ["id","name","price","stock","image"],
                    [p.id, p.name, format_price(p.price_cents), p.stock, p.image]))
            }
            return http.create_struct(
                ["hasProducts", "products", "query"],
                [items.len() > 0, items, q])
        })
    }

    // Product detail — DB-backed SSR with :id param
    let detail_comp = _lumen.get_component("ProductDetail")
    if detail_comp != null {
        lumen_mount_data(app, "/product/:id", detail_comp, |req| {
            let id = _validate.get_field(req.params, "id")
            if id == null { return http.create_struct(["id"], [0]) }
            let rows = sqlite.query(conn,
                "SELECT * FROM products WHERE id = ?", [id])
            if rows.len() == 0 { return ... } // 404 fallback
            let p = rows[0]
            return http.create_struct(
                ["id","name","price","stock",
                 "stockText","stockBadgeTone","description","image"],
                [p.id, p.name, format_price(p.price_cents), p.stock,
                 p.stock > 0 ? "In Stock" : "Out of Stock",
                 p.stock > 0 ? "success" : "danger",
                 p.description, p.image])
        })
    }

    // Cart — session-driven SSR
    let cart_comp = _lumen.get_component("Cart")
    if cart_comp != null {
        lumen_mount_data(app, "/cart", cart_comp, |req| {
            let cart_items = get_cart(req, c.session_secret)
            let items = []; let total_cents = 0
            for i in 0..cart_items.len() {
                let ci = cart_items[i]
                let rows = sqlite.query(conn,
                    "SELECT * FROM products WHERE id = ?", [ci.product_id])
                if rows.len() > 0 {
                    total_cents = total_cents + rows[0].price_cents * ci.qty
                    items = items.push(...)
                }
            }
            return http.create_struct(
                ["hasItems", "items", "total"],
                [items.len() > 0, items, format_price(total_cents)])
        })
    }
}

widgets Lumen UI Component — ProductCard.lumen

A Lumen component lives in pages/components/ — it is automatically compiled as a component struct that gets imported by other pages. This one renders a product card that fetches cart data from the /api endpoints.

pages/components/ProductCard.lumen
<template>
  <div class="product-card">
    <div class="card-img-wrap">
      <img src=""
           alt=""
           loading="lazy" />
      <div class="card-stock"
           data-tone="danger">
        <span class="stock-dot"></span>
        Out of Stock
      </div>
    </div>
    <div class="card-body">
      <h3 class="card-title"></h3>
      <p class="card-price"></p>
      <div class="card-footer">
        <button @click="addToCart">+ Add</button>
      </div>
    </div>
  </div>
</template>

<style scoped>
.product-card {
  background: var(--lumen-surface);
  border: 1px solid var(--lumen-border);
  border-radius: 12px;
  overflow: hidden;
}
.product-card:hover {
  transform: translateY(-4px);
}
</style>

<script>
fn state() {
    return { id: 0, name: "Product",
             price: "$0.00", stock: 0, image: "" }
}
fn addToCart(s, v) {
    let resp = post("/api/cart/" + s.id, "{\"qty\":1}")
    return s
}
</script>

compare_arrows Aurora vs Next.js

Aurora is to Varian what Next.js is to JavaScript — but the setup story is fundamentally different because Aurora ships everything built in.

timer Time to "Hello World"

check_circle Aurora

$ vn new myapp
$ cd myapp && vn dev
→ 2 commands, 0 seconds install, 0 dependencies

schedule Next.js

$ npx create-next-app@latest myapp
(2 min interactive prompts)
$ cd myapp && npm install
(30-60s, 200MB+ node_modules)
$ npm run dev
→ 3 commands, 2+ min install, 1000+ dependencies

table Feature comparison

ConcernAurora (Varian)Next.js (JavaScript)
Scaffolding vn new myapp — 1 command npx create-next-app — interactive prompts, 200MB+ download
Language parity One language — Varian for backend API, database, templates, and UI components Two languages — JavaScript/TypeScript for frontend, Node/JS for API routes (or Python/Go/Rust for a separate backend)
Dependencies Zero — everything built in via vn_modules/ prelude package.json with 1000+ transitive deps; separate DB driver, ORM, auth library, validation library, testing library
Database Built-in — SQLite and Postgres drivers with comptime ORM Choose Prisma, Drizzle, Kysely, or raw drivers — all npm install with their own deps
Auth Built-in — JWT signing, session cookies (HttpOnly, SameSite=Lax), password hashing, CSRF protection NextAuth.js, Lucia, Clerk, or self-built — each with its own setup and dependency tree
UI model Server-driven — HTML-over-WebSocket, 5 KB client, zero hydration bugs React Server Components + client components — 100KB+ React runtime, hydration mismatch bugs
Build output Single native binaryvn build --release, no runtime to install Node.js project + node_modules + build artifacts — deploy requires Node runtime
API docs Auto-generated OpenAPI spec from route summaries — app.enable_docs("/docs") Swagger/OpenAPI setup requires additional libraries and decorator configuration
Live reload Built-invn dev hot-pushes updated components over WebSocket Turbopack or webpack HMR — fast but needs configuration
Form validation Built-in_validate.get_field(), _validate.get_keys(), schema annotations on routes Zod, Yup, or custom validation — all npm install
CORS / rate limiting Built-incors() and rate_limit() middleware, one-liner setup cors + express-rate-limit or similar — npm install each
Supply chain risk Near zero — one C codebase, no transitive dependency tree 1000+ packages from npm, each a potential vector (event-stream, colors.js, etc.)

flash_on What setup looks like in practice

Next.js setup checklist (real-world ecommerce app):

  1. Install Node.js (nvm install)
  2. Create project (npx create-next-app)
  3. Install database driver (npm install @prisma/client + npm install prisma --dev)
  4. Set up Prisma schema + migrations (npx prisma init, npx prisma migrate)
  5. Install auth (npm install next-auth + providers)
  6. Configure NextAuth route handler, session provider, callbacks
  7. Install form validation (npm install zod + @hookform/resolvers)
  8. Set up CORS middleware for API routes
  9. Configure next.config.js, tailwind.config.js, tsconfig.json
  10. Deploy: install Node on server, copy node_modules, run npm run build + npm start

Aurora setup checklist (same ecommerce app):

  1. vn new myapp — creates main.vn, pages/, lib/, public/, schema, config, middleware, auth scaffold
  2. vn dev — live-reload dev server with everything mounted
  3. Deploy: vn build --release → copy one binary to server, run it

category When to use Aurora vs Lumen vs Zenith

The toolchain auto-detects the project type, but here is how to think about which one fits your project:

Project shapeUseScaffold
Fullstack web app (backend + UI with reactive components)Auroravn new
Frontend-only (static or interactive site, no server API)Lumenvn lumen new
API server or microservice (no frontend)Zenithmanual
CLI tool or scriptVarianvn init

vn build prints the detected type: "Aurora" when both pages/ and a Zenith entry exist, "Lumen" for frontend-only, "Zenith" for server-only.