Models & fields
Model and field syntax in .forge — the field form, modifier positions, and the parser-enforced declaration rules.
A model is an entity that becomes a stored table. Declare it by name — no keyword — followed by a block of fields.
Model syntax#
ModelName {
field: type
field: type
}Rules the parser enforces (fatal on violation):
- The model name must be PascalCase —
User,OrderItem.userororder_itemfail to parse. - A model must contain at least one field. An empty
User { }is rejected. - Model names must be unique within the schema.
User {
id: +uuid
email: &string @email
created_at: +timestamp
}Field syntax#
Each field has the form:
name: [MODIFIER]type [@directive ...]name— required, snake_case (created_at, notcreatedAt). Non-snake_case names are a fatal parse error.type— required, immediately after the colon. One of the scalar types, an enum, a struct, or a relation.- modifiers — optional
+,&,^prefixes (before the type). - directives — optional
@...directives after the type.
Field names must be unique within a model — a duplicate id is rejected.
Modifier positions#
The prefix modifiers + (auto-generate), & (unique), and ^ (indexed) appear
between the colon and the type name. Any combination is allowed, in any order:
field: +type // auto-generate
field: &type // unique
field: ^type // indexed
field: +&^type // all three (any combination)The nullable modifier ? is different: it appears after the type (postfix), though a
prefix form also parses. See modifiers for the full rules.
field: string? // nullable string (postfix — the idiomatic form)
field: ?string // prefix nullable also parses
field: +uuid? // auto-generate + nullableRead the sigils left to right
username: &^string @length(3, 50) reads as: unique, indexed string, with a length
constraint. Prefix sigils modify the type; postfix ? and @-directives follow it. (+
would need an auto-gen-eligible type — u32/u64/uuid/timestamp — so it can't go on a
string.)
Worked field examples#
Post {
id: +uuid // auto-generated UUID PK
title: string @length(1, 200) // required string, length constraint
slug: ^&string @length(1, 200) // indexed + unique string
view_count: u32 @default(0) // default marker (semantic)
published: bool @default(false)
published_at: timestamp? // nullable timestamp
author: *User // required foreign key
created_at: +timestamp // auto-generated timestamp
}Terminators & layout#
- No semicolons. Fields and models are delimited by newlines and the
{/}block braces — newlines are significant. - A field's
@directives must follow its type on the same logical line (before the next newline or the next constraint). - Definitions may span multiple lines; horizontal whitespace is insignificant.
See comments & whitespace for the details.