- Docs
- Concepts
- Compiler boundary
Compiler boundary
Why runtime packages never import the DSL compiler, how module layering enforces it, and the separate schema-module rule.
Runtime packages must never import dsl. The compiler turns a .zen schema into Go source once; a running service imports only generated output, the container, and adapters. Linking the frontend would bloat every binary and let request-time code special-case schema logic.
The compile-time pipeline
Section titled “The compile-time pipeline”source text -> lexer -> parser -> ast -> resolver -> ir.Schema -> compile -> backend outputsEverything on that line is compile-time only; runtime imports only its output (typed ORM, HTTP routes, gRPC stubs).
- Compile time
diag,token,lexer,ast,parser: bytes to a best-effort syntax tree (never panics, depth guard, recovery). - Compile time
naming,resolver,ir: multi-pass[]*ast.Fileto*ir.Schema, keep-going diagnostics. - Compile time
format,breaking,compile,backend: formatter,Changetaxonomy, filename-sorted build,Backendinterface. - Compile time
dsl/gengrammarviatools/gengrammar: renderseditors/nvimandeditors/vscode. - Runtime
container,config,orm, adapters: load, resolve, query, close. - Tooling
cmd/zever,tools/zever-lsp: CLI and editor support (LSP is its own module).
Keep compile input and runtime config apart
Section titled “Keep compile input and runtime config apart”The compiler reads schema files and backend flags, never runtime config (env only). Schema errors come from .zen files; service errors (adapters, DSNs, secrets) come from config.FromEnv.
Compile, then run against generated output
Section titled “Compile, then run against generated output”zever check schema/todo.zenzever fmt -l schema/todo.zenzever compile schema/todo.zen --backend=zenorm,openapi,atlas --out generatedzever db migrate --dry-run --adapter=sqlite schema/todo.zengo run ./examples/todo/cmd/server -addr :8080check validates; fmt -l lists unformatted files; compile emits ORM, OpenAPI, and DDL into generated/; migrate --dry-run previews SQL. The server imports generated/ and resolves through the container, never dsl (Container).
Regenerate with zever compile; never hand-edit generated/. Entry points are parser.New(file, src).ParseFile() then resolver.Resolve(files), or compile.Compile(files, backends...); compile/testdata/app.zen is the canonical fixture. After touching token.Keywords or resolver.ScalarTypeNames, run make generate (go run ./tools/gengrammar).
Do not confuse it with schema modules
Section titled “Do not confuse it with schema modules”zever check-boundaries checks a different rule: cross-schema-module references, where a relation, RPC return type, or permission resource reaches across .zen module directories. The resolver enforces it every compile; check-boundaries reports the Resolve() diagnostics (Zen syntax).
Design decisions and trade-offs
Section titled “Design decisions and trade-offs”dsl is a top-level module, so Go’s internal/ visibility does not apply; an accidental import fails review. The parser never panics, so failures surface as diagnostics.
Troubleshooting
Section titled “Troubleshooting”go build fails importing dsl
A runtime package reached the compiler. Drop the import; use generated/.
check-boundaries reports cross-module references
A relation or RPC type crosses .zen module directories. Move it.
compile prints diagnostics but writes no output
Resolve errors block backends. Fix the .zen position; run zever check.
The editor misses new keywords after a grammar change
token.Keywords or resolver.ScalarTypeNames changed. Run make generate and commit editors/.
Edits in generated/ vanish
Compile overwrites generated/. Edit the .zen source.