DBDelve
Dive deep into your data. A fast, native, multi-engine database client written in Rust. Free and open source, no telemetry.
翻译准备中
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证A configuration language with schemas, assertions and capability-gated I/O. Compiles to JSON, YAML and TOML.
翻译准备中
正在加载完整 README。
查看完整 README ↗Write configuration as a small program - and get plain JSON, YAML or TOML.
Describe your services once, and Aura turns them into the configuration for every environment. A schema catches a typo before the deploy, not after it - including in data read from JSON and YAML. A manifest cannot read a file or an environment variable until you allow it, and a package you import never can.
It runs as a CLI, in CI, in the browser, and inside your Rust application.
One binary, no runtime to install, 16 keywords, under 2 MB to download.
Try it in your browser · Documentation · Русская версия
[!NOTE] Aura is a working preview (
0.x). All six specification phases are implemented, but nothing has run in production anywhere yet. Until 1.0 a minor release may still change the language, and the syntax is frozen only at 1.0. The changelog lists every change that needs an edit.
Nothing at all
Open the playground. The real compiler, compiled to WebAssembly. Nothing is installed and nothing leaves the page.
A binary
cargo install aura-lang
Or take a build from Releases:
Linux (gnu and static musl), macOS (Intel and Apple silicon), Windows — each
with a .sha256.
In CI
- uses: aura-config/setup-aura@v1
- run: aura check deploy.aura --strict
Resolves the version, verifies the checksum, puts aura on PATH.
In a Rust program
[dependencies]
aura-lang = { version = "0.4", default-features = false }
Without the default cli feature the library needs less than half the
packages, and no C toolchain at all.
[!TIP] On x86_64 Linux prefer the musl build — it is what
setup-aurainstalls. The gnu build requiresGLIBC_2.34and will not start on Ubuntu 20.04, Debian 11, CentOS 8 or Amazon Linux 2. The static build has no such floor, and starts measurably faster.
# deploy.aura
type Service
name: String
port: Int
replicas: Int = 2
end
# `=` binds a value for use below; it never reaches the output
services = [
new Service name: "api", port: 8080, replicas: 3 end
new Service name: "web", port: 3000 end
]
def deploy(stage, scale)
services.map (s, i) ->
name: "#{s.name}-#{stage}"
port: s.port
replicas: s.replicas * scale
end
end
# `:` outputs - these two keys are the whole result
staging: deploy("staging", 1)
production: deploy("production", 3)
$ aura eval deploy.aura --format yaml
staging:
- name: api-staging
port: 8080
replicas: 3
- name: web-staging
port: 3000
replicas: 2
production:
- name: api-production
port: 8080
replicas: 9
- name: web-production
port: 3000
replicas: 6
One function made both environments: replicas is computed, and web got the
schema's default of 2. services and deploy are not in the output - :
exports, = and def do not. A typo in a field, or a port written as a string,
stops the run before anything is written.
At scale: five services into two environments - 10 Deployments and 6 Services, 359 lines of Kubernetes YAML.
The rules live in Aura and the service calls them with its own types. Editing
the rule needs no rebuild, and the script can read nothing it was not granted -
here, nothing at all, which Script proves when it loads.
use aura_lang::Script;
use serde::Serialize;
#[derive(Serialize)]
struct Order {
plan: String,
seats: i64,
}
// The rule lives in Aura: change the price without rebuilding the service.
const PRICING: &str = r#"
pub def price(order)
base = order.plan == "pro" ? 20.0 : 8.0
base * order.seats.to_float()
end
"#;
pub fn run() -> Result<f64, aura_lang::Error> {
// Loaded once; it can read no file and no variable - none was granted.
let rules = Script::builder().source("pricing.aura", PRICING).load()?;
let order = Order {
plan: "pro".into(),
seats: 3,
};
rules.call("price", (order,))
}
run() returns 60.0. Calls are bounded in steps, memory and depth, so a rule
that loops by mistake ends the call with an error instead of stalling the
service. More in Embedding.
| Problem | Aura's answer |
|---|---|
| YAML breaks from one stray space | No significant indentation: structure comes from line breaks and an explicit end |
| Configs "work on my machine" | Deterministic by construction: without explicit flags a manifest has no access to files or environment variables |
A module pulled from the internet reads /etc/passwd | Imported modules are isolated from I/O, and cannot borrow the caller's rights |
| "Why is prod on a different port?" | Values are immutable; shadowing requires the explicit shadow keyword |
| Dependency version drift in CI | Versioned imports and aura.lock with a token-stream integrity hash, plus --frozen |
| Dead config fragments live for years | Static analysis finds unused variables and imports; --strict makes them fail CI |
A generated config.json nobody can trace | The manifest is the source, and --dry-run reports what it read and would write |
Aura's central rule — the same idea as locals versus outputs in Terraform:
tmp = base * 2 # = a private variable: does NOT end up in JSON
port: tmp + 1 # : a property: ends up in JSON
This is what makes dead-code analysis precise: an unused = variable is always
genuine cruft (W0501), never "maybe someone needs this output".
path = "/etc/global.config"
domain "prod"
path = "/var/log" # E0302: shadowing requires the keyword
shadow path = "/var/log" # OK - the intent is explicit
end
Reassigning a name within the same scope is always an error (E0301).
enum Tier
"frontend"
"backend"
"cache"
end
type Service
name: String
tier: Tier
port: Int = 8080 # optional: omit it and the default applies
end
api: new Service
name: "api"
tier: "backand" # E0514: did you mean "backend"? members: ...
end
A missing field is E0511, a type mismatch E0512, an extra field E0513 under
--strict. Int and Float are separate types, so byte limits and 64-bit IDs
never lose precision. An enum member stays an ordinary string — the JSON output
does not change, only what is accepted.
Optionality never introduces a null: a default is a value, not an absence.
The schema that validates the manifest can also type the program reading its JSON, so there are no hand-written structs to keep in sync:
aura types config.aura --lang rust # or ts | go
export type Scheme = "https" | "http";
export interface Endpoint {
host: string;
port: number;
scheme: Scheme;
}
Rust gets a serde struct plus an enum with #[serde(rename)]; Go gets a struct
with json: tags and typed constants. Parsing only — the manifest is never
evaluated, so no capabilities are involved, and the output is already canonical
for rustfmt, gofmt and prettier.
def labels(app) # a def body is an object
app: app
end
up = (s) -> s.upper() end # a lambda
xs.compact().uniq().map (item, index) ->
"#{index}: #{item}"
end
Sixty methods across String, Int, Float, Bool, List and Object — the
full list is in the method reference.
range(n) produces [0 … n-1], which is how you generate N shards or replicas
without listing them by hand.
cond takes the first true arm, and the else is mandatory — no branch yields
nothing:
tier = cond
region == "eu" -> "frankfurt"
region == "us" -> "virginia"
else -> "singapore"
end
A text … end block is an ordinary multi-line string. Interpolation works;
braces inside do not, which is what makes generating nginx configs practical:
entrypoint: text
#!/bin/sh
echo "starting #{app_name}"
exec ./server --port #{port}
end
now() does not exist and never will — an unreproducible config cannot be
written. Durations and dates are first-class:
ttl = "1h30m".parse_duration() # -> 5400 seconds
refresh: (ttl / 3).format_duration() # -> "30m"
If a build timestamp is needed the host supplies it as an input:
env("BUILD_TIME", …).
Dot for fields, brackets only for list indices — one operator per operation:
loaded = read_file("./data.json").parse_json()
version: loaded.package.version # ordinary keys
port: loaded.servers."eu west".port # a key with a space
first: loaded.apps[0].name # list index (out of bounds is E0317)
optional: loaded.get("maybe", "fallback") # safe access
A typo in a key is E0308 with a position, not a silent null.
import github/actions/[email protected] as rust # a version is mandatory
import "templates/k8s_defaults.aura" as defaults
Cyclic imports are reported with the full chain. Every module is loaded, parsed
and evaluated exactly once. Exact versions and integrity hashes live in
aura.lock, and --frozen makes CI refuse anything else.
aura add is the only place Aura touches the network. eval always runs
offline, so a result never depends on what a registry served today.
The language reads TOML, JSON and YAML and writes all three, so conversion is one line:
config: read_file("./legacy.toml").parse_toml()
aura eval convert.aura --allow-read=. --format yaml
Unlike yq or jq you can validate against a schema, merge several sources and
add assert checks along the way — conversion with guarantees.
By default a manifest can do nothing: no files, no environment variables. Rights are granted per run, and they do not propagate.
| Flag | What it allows |
|---|---|
--allow-read=<dir> | read_file() inside that directory (repeatable; paths are canonicalised, .. cannot escape). A relative path in read_file() is read from the manifest's folder, as an import is (D37) |
--allow-env[=A,B] | env() for the listed variables — with no list, all of them |
--allow-imports-io | extends the root's rights to imported modules |
--hermetic | the opposite: no I/O at all, E0505 everywhere; excludes the --allow-* flags |
[!IMPORTANT] Grants belong to the root manifest. A module you import cannot call
env()orread_file()even when the root holds those rights, and an exported function runs with the capabilities of the module it came from — so isolation cannot be borrowed by persuading you to call something innocent-looking.
A call without a grant is E0310; a grant that does not cover the path is
E0311. An effectful call inside an imported module is caught statically as
W0512, before anything runs.
--hermetic inverts the question. Rather than granting rights it requires that
none are needed — and because E0505 is an analysis error, this is settled
without evaluating anything, including for branches a given run would not take:
$ aura check --hermetic deploy.aura
[E0505] Error: env() is not allowed in hermetic mode
aura eval <file.aura> [--strict] [--dry-run] [--frozen] [--hermetic]
[--allow-read=<dir>] [--allow-env[=A,B]] [--allow-imports-io]
[--max-steps=N] [--max-memory=MB] [--stats]
[--format json|json-flat|yaml|toml] [-o out.json] [--registry-dir=<dir>]
aura check <file.aura> [--strict] [--hermetic]
aura fmt <files...> [--check]
aura types <file.aura> --lang rust|ts|go [--out <file>]
aura docs --agent [-o <file>]
aura add <path>@vX.Y.Z [--from <file>] [--registry-dir=<dir>]
| Mode | Behaviour |
|---|---|
--strict | analysis warnings become errors; extra schema fields are forbidden |
--dry-run | a full evaluation, but nothing is written — you get a report of what it read and would write |
--frozen | dependencies resolve strictly via aura.lock; a mismatch is an error and the lock is never rewritten |
--hermetic | settles, statically, that the manifest performs no I/O |
Exit codes: 0 success, 1 diagnostics, 2 an I/O or argument error.
aura docs --agent prints the complete language reference — syntax, standard
library, every diagnostic code — assembled from the compiler's own definitions,
at roughly four thousand tokens. Small enough to hand over whole, and it always
describes the binary that produced it. One line in your repository's AGENTS.md
is enough:
Before writing or editing .aura files, run `aura docs --agent` for the complete
language reference.
The same text is published at /llms.txt.
The CLI contract is the API, in the tradition of terraform, jq and
pandoc: JSON on stdout, stable E0xxx codes on stderr, exit codes 0/1/2.
import json, subprocess
r = subprocess.run(["aura", "eval", "app.aura", "--frozen"], capture_output=True, text=True)
if r.returncode != 0:
raise RuntimeError(r.stderr)
config = json.loads(r.stdout)
const { execFileSync } = require("node:child_process");
const config = JSON.parse(execFileSync("aura", ["eval", "app.aura", "--frozen"]));
out, err := exec.Command("aura", "eval", "app.aura", "--frozen").Output()
if err != nil { log.Fatal(err) }
var config map[string]any
json.Unmarshal(out, &config)
For production: --frozen with a committed lock file, capabilities only
explicit, and --format yaml|toml if the consumer prefers another shape.
use aura_lang::Script;
let cfg: MyConfig = Script::builder()
.file("config/app.aura")
.allow_read("config") // the files it reads, beside it
.env_values([("APP_ENV", "production")]) // exactly these, and no others
.strict()
.load()?
.output()?; // what `aura eval` prints, typed
There is no intermediate file: the application reads the manifest itself, and
aura types --lang rust writes the structs for its schemas.
A manifest's pub def is also a callable value, so rules can live in a .aura
file and the application can call them per request, changing behaviour without a
rebuild:
use aura_lang::Script;
let rules = Script::load("rules.aura")?; // hermetic: proves it reads nothing
let route: Route = rules.call("route", (path, method))?;
Every call has a budget in steps and bytes, memory per call is constant, and one
Script serves every thread. A script can call functions the application gives
it - import "host:geo" as geo - and be reloaded when its file changes, held to
every check the first load passed. See the
embedding guide;
cargo run --example checkout shows all of it in one service.
Mobile applications consume the result: a server or CI evaluates the manifest, the client reads the JSON. A WebAssembly build exists and is CI-tested — it is what the playground runs on — and npm, PyO3 and a C ABI are on demand.
Errors carry a file, line and column, highlight the code, and suggest a fix:
[E0302] Error: 'global_file_path' shadows an outer variable
╭─[ production_deploy.aura:24:3 ]
24 │ global_file_path = "/var/log/aura.log"
│ ─────────┬────────
│ ╰── add `shadow`
│
│ Help: write `shadow global_file_path = ...` to make the shadowing explicit
────╯
Every code is stable and documented in the catalogue — a test keeps that list and the compiler in step, in both directions.
flowchart LR
S["source<br/>&'a str"] --> L["lexer<br/>Vec<Token>"]
L --> P["parser<br/>AST"]
P --> A["analysis<br/>Vec<Diagnostic>"]
A --> E["evaluation<br/>Value"]
E --> O["JSON · YAML · TOML"]
Tokens and the AST borrow the source's memory — nothing is copied along the way.
crates/
├── aura-lang # the library and the `aura` CLI, one publishable crate
│ ├── lexer/ # zero-copy DFA, newline normalisation
│ ├── parser/ # recursive descent + Pratt expressions
│ ├── analysis/ # dead code, undefined names, shadow rules
│ ├── eval/ # tree-walking interpreter, environments, method registry
│ ├── vfs/ # resolvers, cycle detection, aura.lock
│ └── serialize/ # Value -> JSON/YAML/TOML, Int without precision loss
└── aura-lsp # the language server, built on aura-lang
Invariants. Zero-copy: neither lexer nor parser copies a string.
Determinism: JSON key order is declaration order, and two runs are
byte-identical. Immutability: containers live in Arc, so cloning a value is
O(1).
cargo bench -p aura-lang, on the reference manifest:
| Stage | Result |
|---|---|
| Lexer | 258 MiB/s |
| Lexer + parser | 178 MiB/s |
| Lexer + parser + resolver | 71 MiB/s |
| Full pipeline (lex + parse + evaluate) | 33 µs per manifest |
Measured 2026-07-29 on x86_64 Linux. Numbers from one machine are worth what they cost — treat them as an order of magnitude, and re-run them on yours.
| Tests | the whole suite, on Linux, macOS and Windows |
| Conformance | every example in examples/ driven through the real binary, output diffed against a pinned expectation |
| End to end | packaging/e2e.sh asserts the documented claims – exit codes, capability refusals, hermetic mode, output formats, and byte-identical output across two runs |
| Containers | on a tag, those artefacts run on debian:12, ubuntu:22.04, alpine, ubuntu:20.04, and aarch64 under emulation |
| Miri | the source arena, under both Stacked Borrows and Tree Borrows |
| Fuzzing | a cargo-fuzz target per stage: lexer, parser, pipeline, formatter, codegen, resolver, and the JSON/YAML/TOML readers. ClusterFuzzLite runs them nightly with a corpus kept between runs, and on every pull request that touches the compiler |
| Cross-platform | cargo check for freebsd, aarch64-linux, musl and wasm32 |
| Documentation | snippets must be canonical aura fmt and name nothing the language lacks; the diagnostic catalogue, the method reference and the decision log must match the compiler, in both languages |
The recursive-descent parser is DoS-hardened: deeply nested input yields E0208
rather than a stack overflow, and that is checked in release builds, where there
is no thread with a large stack to hide behind.
cargo test --workspace # units, conformance, property tests, snapshots
cargo bench -p aura-lang # lexer, parser, resolver, full pipeline
The formal specification, including the numbered design decisions the code refers to by name, is SPEC.md.
My name is Ilya Brin. I came up with Aura and I develop it, working on the language together with Claude, an AI model by Anthropic.
The idea is an old one. It goes back to when I was writing my first language, in Go. That was when I came across Starlark and thought that computed configuration is a genuinely useful thing. For Go, though, it was overkill: everything compiles fast anyway, and a YAML file or a few constants in the code are enough. With Rust it became clear that I would have to write such a language myself. I have several growing projects and production services that need it. I trusted Claude with writing the code, and I read everything it writes. That way I am also learning Rust from real code rather than from textbook examples.
We have different responsibilities. I am responsible for what the Aura language is: why it exists, what must never be in it, and which rules it keeps. There are only a few:
Claude:
The decisions are mine, and no change reaches the project without my agreement.
Along the way we settled into habits that have proven themselves:
Commits written by Claude carry a Co-Authored-By trailer, starting with the one
that added this section. Earlier ones don't: back then Aura was an internal tool for
my own projects.
All six specification phases are implemented. The syntax is still allowed to change, and is frozen at 1.0.
The language and its tooling
enumaura.lock with a token-stream hash--strict, --dry-run, --hermeticaura fmt — canonical formatting with a token-stream guaranteeaura types — Rust, TypeScript and Go from a manifest's schemasaura docs --agent — the whole language for a coding assistantScript with a budget per call (D33, D34), functions
the host provides (D35), reload while the service runs (D36), a whole
manifest read into your own typesnow() forbidden by constructionEcosystem and distribution
aura-config/setup-aura@v1
for GitHub ActionsTowards 1.0. Three criteria remain: a frozen syntax, a promise of backward compatibility, and real users whose configs must not break.
Issues and pull requests are welcome. Before opening one:
cargo test --workspace
cargo clippy --workspace --all-targets -- -D warnings
cargo fmt --all --check
cargo build -p aura-lang && find examples -name '*.aura' -print0 | xargs -0 ./target/debug/aura fmt --check
CI runs exactly those four. The last is the language formatting its own examples. Code comments and commit messages are in English. AGENTS.md records where each fact has its single home — worth reading before adding a second copy of one.
MIT (LICENSE-MIT) or Apache 2.0 (LICENSE-APACHE), at your option.
Unless you state otherwise, any contribution you intentionally submit for inclusion shall be licensed as above, with no additional terms.