zanith

.zn DSL Syntax

An alternative to the TypeScript builder — a clean, structured language for defining schemas. Both approaches compile to the exact same runtime graph.

When to use the DSL

The TypeScript builder is great for programmatic control — loops, conditionals, custom functions. The .zn DSL is better when you want readability above all else. Teams with non-TypeScript developers, or schemas that are mostly straightforward, benefit from the cleaner syntax.

TypeScript builder

  • ·Full programmatic control
  • ·IDE autocomplete built-in
  • ·Can use loops, conditions, functions
  • ·Best for complex/dynamic schemas

.zn DSL

  • ·Clean, readable syntax
  • ·Less noise, more signal
  • ·Structured blocks (fields, relations, indexes)
  • ·Best for straightforward schemas

Complete example

TSschema/user.zn
/// Application users
model User {
table "users"
comment "Application users"
 
meta {
module = "auth"
owner = "platform-team"
}
 
fields {
id uuid primary default(uuid())
createdAt datetime default(now())
updatedAt datetime autoUpdate
 
/// Login email address
email string unique
/// Display name
name string?
role Role default(USER)
settings json?
}
 
relations {
posts hasMany Post {
foreignKey authorId
}
}
 
indexes {
index(email)
}
 
constraints {
unique(email, name)
}
}

Model structure

Every model is a block with named sections. Each section has a clear purpose:

BlockPurposeExample
table "name"Custom PostgreSQL table nametable "users"
comment "text"Model documentationcomment "Application users"
meta { ... }Key-value metadatamodule = "auth"
fields { ... }Column definitionsemail string unique
relations { ... }Foreign key relationshipsauthor belongsTo User { ... }
indexes { ... }Database indexesindex(email, createdAt)
constraints { ... }Unique/check constraintsunique(email, name)

Field syntax

Each field is a single line: name type modifiers...

TS
id uuid primary default(uuid()) // PK with auto UUID
email string unique // unique string
name string? // nullable (? suffix)
age int default(0) // integer with default
score float // decimal number
active boolean default(true) // boolean
createdAt datetime default(now()) // timestamp with default
updatedAt datetime autoUpdate // auto-update on change
bio text? // long text, nullable
data json? // JSONB column

Relation syntax

Relations are explicit blocks with configuration:

TS
relations {
// belongsTo — this model has the FK
author belongsTo User {
field authorId
references id
onDelete cascade
}
 
// hasMany — other model has the FK
posts hasMany Post {
foreignKey authorId
}
 
// manyToMany — through junction
tags manyToMany Tag {
through PostTag
fromField postId
toField tagId
}
}

Keyword reference

KeywordMeaning
primaryPrimary key
uniqueUnique constraint on the field
default(value)Default value — now(), uuid(), 0, true, USER
autoUpdateAuto-updates when the record changes
string?Nullable field (the ? suffix)
///Doc comment — becomes metadata in the schema graph
table "name"Custom table name
meta { key = value }Arbitrary key-value metadata
index(f1, f2)Database index on one or more fields
unique(f1, f2)Composite unique constraint
import "path"Import another .zn file

Imports

Split your schema across files and import them:

TSschema/post.zn
import "../enums/role.zn"
import "../auth/user.zn"
 
model Post {
table "posts"
fields {
id uuid primary default(uuid())
title string
}
relations {
author belongsTo User {
field authorId
references id
}
}
}