Directives

Every @ directive in .forge — which are enforced at write time, which are semantic-only markers, and their argument syntax.

Directives are @-prefixed annotations on a field or a model. Some are enforced at write time — a violation is rejected with an HTTP status. The rest are semantic-only markers: parsed and carried through, but not yet enforced by the generated code.

Argument syntax#

Directive arguments accept numbers, bare identifiers, and quoted string literals:

age: u32 @min(13) @max(150)
status: string @default(pending)
status: string @default("pending")     // equivalent — both stored as a string
phone: string @pattern("^[0-9]+$")

Quoted strings support the escapes \", \\, \n, \t, \r. An unterminated or multiline string is a lex error.

Enforced directives#

The generated code checks these and rejects a bad write.

DirectiveArgsApplies toEffect
@min(number)numericValue must be ≥ the minimum, else 422
@max(number)numericValue must be ≤ the maximum, else 422. Not a string-length check — use @length for strings
@length(count) or (min, max)stringString length must be in range, else 422
@emailnonestringMust be a valid email, else 422
@urlnonestringMust be a valid URL, else 422
@pattern / @regex(regex)stringValue must match the regex, else 422
@on_delete(restrict|cascade|set_null)FK fieldOn-delete referential policy

Field constraints — @min @max @length @email @url#

The generated validate_<model> enforces these at the top of insert/update; a violation is a field-constraint error (HTTP 422). A nullable field is validated only when Some.

User {
  age: u32 @min(13) @max(150)
  name: string @length(1, 100)
  email: string @email
  website: string @url
}

@pattern / @regex#

The generated validate_<model> compiles a per-field regex and rejects a non-matching string with a field-constraint error (HTTP 422). A nullable field is validated only when Some.

User {
  handle: string @pattern("^[a-z0-9_]{3,20}$")
}

@on_delete#

Declares what happens to a child when its referenced parent is deleted. Enforced in the generated Database::delete_<parent> wrapper (which the REST DELETE route goes through):

  • restrict (the default when @on_delete is absent) — refuses to delete a parent that still has a live child referencing it → 409.
  • cascade — recursively deletes every referencing child; each child's own @on_delete rules fire, and a cycle is bounded by a max depth.
  • set_null — nulls each referencing child's FK. Valid only on an optional FK (?Target); set_null on a required *Target is a hard codegen error.
Post {
  author: *User @on_delete(cascade)      // deleting a user deletes their posts
  category: ?Category @on_delete(set_null)
}

Semantic-only markers#

These parse and are carried through, but the generated code does not enforce them. Use them to document intent; do not rely on them for integrity.

DirectiveArgsApplies toIntended meaning
@default(value)anyDefault on insert (marker only — a caller still supplies the value)
@computednoneanyRead-only computed field
@fulltextnonestringFull-text index intent
@materializednoneanyMaterialized field
@relations(*) or (fields)component refComponent relation inclusion

Markers are documentation, not behavior

@default, @computed, @fulltext, and @materialized are parsed and preserved but have no runtime effect yet — for example @default(0) does not populate a value; the caller must still supply one. The enforced set is the table above (@min/@max/@length/ @email/@url, @pattern/@regex, and @on_delete).

Model-level directives#

These appear on their own line inside a model block, not attached to a field.

DirectiveArgsMeaning
@index(field1, field2, ...)Composite index over ≥ 2 fields
@projection(name: col, col, ...)A named partial-read projection
@soft_deletenoneEnable soft delete for the model
Order {
  id: +uuid
  user_id: uuid
  created_at: timestamp
  @index(user_id, created_at)
  @projection(card: id, created_at)
  @soft_delete
}

See indexes & projections for @index and @projection in depth.

`@relations` is component-only

@relations(*) / @relations(a, b) is valid only on a component-reference field (tsx://, jsx://, api://). Using it on a scalar field is a parse error.

Search documentation

Find pages across the ForgeDB docs