Skip to content

Annotations Reference ​

Use this page when you need the full annotation syntax, inheritance rules, merge behavior, and the built-in annotation catalog.

Annotations are metadata declarations that provide additional information about types, interfaces, and properties.

Purpose ​

Annotations serve multiple purposes:

  • UI metadata - Labels, placeholders, descriptions for schema-driven UI tools
  • Validation constraints - Min/max values, patterns, length restrictions
  • Database metadata - Collection names, indexes, field strategies
  • Documentation - Descriptions and multi-line documentation
  • Custom metadata - Any domain-specific information

Where to Apply ​

Annotations can be applied anywhere:

atscript
@meta.description 'User entity'    // Interface annotation
export interface User {
    @meta.id                     // Property annotation
    id: string
}

@expect.minLength 3                // Type annotation
export type Username = string

Annotation Inheritance ​

Type to Property ​

When a property uses a type with annotations, annotations merge with property having priority:

atscript
@expect.minLength 3
@expect.maxLength 20
export type Username = string

export interface User {
    @expect.maxLength 15    // Overrides type's maxLength
    username: Username       // Inherits minLength: 3, gets maxLength: 15
}

Property References ​

When a property references another property, annotations merge in order:

  1. Final type annotations
  2. Referenced property annotations
  3. Current property annotations (highest priority)

Intersections and extends ​

A property several intersection members (or several parents of extends A, B) declare reads the annotations of each, the later member winning; a member written in place wins over the others. See How a merge is built.

Merge Strategies ​

When annotations are merged (from type inheritance, property references, or ad-hoc annotations), the merge strategy determines how same-named annotations combine:

Replace (default) — Higher-priority annotations replace lower-priority ones entirely:

atscript
@expect.min 3
export type PositiveInt = number

export interface Config {
    @expect.min 10          // Replaces type's @expect.min 3
    threshold: PositiveInt   // Result: @expect.min is 10
}

Append — Both values are kept, accumulating into an array:

atscript
@expect.pattern '^[a-z]+$'
export type SafeString = string

export interface Form {
    @expect.pattern '^\S+$', 'i', 'No spaces'
    code: SafeString
    // Result: both patterns are validated
}

The strategy is configured per annotation via AnnotationSpec. Most annotations use replace. The built-in @expect.pattern uses append.

Repeatable Annotations ​

Annotations marked with multiple: true can appear more than once on the same node. Their values are stored as arrays:

atscript
export interface User {
    @expect.pattern '^[A-Z]'
    @expect.pattern '.{3,}'
    name: string
}

When merged, how repeated annotations combine depends on the merge strategy:

  • multiple: true + replace (default) — The higher-priority set replaces the entire array
  • multiple: true + append — Values from both sides are concatenated into a single array

Annotation Syntax ​

atscript
@meta.label 'User Name'           // With argument
@meta.sensitive                   // Flag (no argument)
@expect.pattern "^[A-Z]", "i"     // Multiple arguments
@meta.documentation 'Line 1'      // Can be repeated
@meta.documentation 'Line 2'

Arguments can be optional. Annotations without arguments are flag annotations.

String Literals ​

String arguments (single or double quoted) are raw: Atscript does not decode escape sequences, so the text between the quotes reaches the metadata exactly as written, backslashes included. This is why regex patterns need no double escaping:

atscript
@expect.pattern '^\S+$'           // pattern is ^\S+$ (one backslash)
  • A backslash before the quote (\" or \') lets the quote appear inside the string. The backslash is kept, so "say \"hi\"" produces say \"hi\".
  • A string may end with an escaped backslash: "C:\\" closes at its last quote and produces C:\\. An even run of backslashes before a quote ends the string; an odd run escapes the quote.
  • \n, \t and similar sequences are not turned into control characters; they stay as two characters.
atscript
@meta.label "a\\"                 // value: a\\
@expect.pattern "^[^\"]+$", "u"   // pattern: ^[^\"]+$

Comments ​

Line (//) and block (/* */) comments can go anywhere in an annotation line: after the annotation or its last argument, between arguments, and on their own lines between annotations and the property. They never change the annotation's value.

atscript
@meta.id                              // primary key
@expect.minLength 3 /* min */, "Too short"
@meta.description 'See https://x.io' // `//` inside quotes is part of the string
id: string

A block comment that spans several lines ends the annotation like a line break, so what follows it is not an argument of that annotation.

Core Annotations ​

Atscript provides common-purpose annotations:

Meta Annotations (@meta.*) ​

  • @meta.label 'text' - Human-readable label
  • @meta.id - Marks identifier field (multiple fields form composite PK)
  • @meta.description 'text' - Field description
  • @meta.documentation 'text' - Multi-line docs (repeatable)
  • @meta.sensitive - Marks sensitive data
  • @meta.readonly - Read-only field
  • @meta.default 'value' - Default value (string as-is, other types parsed as JSON)
  • @meta.example 'value' - Example value (string as-is, other types parsed as JSON)

UI Annotations (@ui.*) ​

@ui.* annotations (placeholders, form layout hints, component overrides) are provided by a separate UI plugin, not by @atscript/typescript itself. See the UI plugin docs for the full reference. The annotation shape shown in this guide (@ui.placeholder 'text', @ui.component 'name', etc.) follows the same syntax rules as the other namespaces above.

Validation Annotations (@expect.*) ​

  • @expect.minLength 5, "Custom error message" - Minimum string/array length (optional message)
  • @expect.maxLength 100, "Custom error message" - Maximum string/array length (optional message)
  • @expect.min 0, "Custom error message" - Minimum number value (optional message)
  • @expect.max 100, "Custom error message" - Maximum number value (optional message)
  • @expect.exclusiveMin 0, "Custom error message" - Number must be strictly greater than the value (optional message, since 0.1.109)
  • @expect.exclusiveMax 1, "Custom error message" - Number must be strictly less than the value (optional message, since 0.1.109)
  • @expect.int "Custom error message" - Must be integer (optional message)
  • @expect.pattern "regex", "flags", "message" - Pattern validation (repeatable, optional message). Every check of a value gives the same result: the g flag has no effect, and y matches at the start of the value only (like ^); the compiler warns about both
  • @expect.minKeys 1, "Custom error message" - Object must have at least this many keys (records { [*]: T }, objects with optional props; counts the keys the result keeps, see Key counts; optional message)
  • @expect.maxKeys 20, "Custom error message" - Object must have at most this many keys (optional message)
  • @expect.message "Custom error message" - Message for a type mismatch: wrong type, not the literal, no union branch, unknown discriminator, wrong tuple length (on a prop or a type; constraint failures keep their own messages). See Error Codes
  • @expect.array.uniqueItems "Custom error message" - Enforce unique items in an array (by key fields if defined, otherwise by deep equality; optional message)
  • @expect.array.key "Custom error message" - Mark a field as a key inside an array of objects (used for uniqueness checks, lookups, and patch operations; does not enforce uniqueness by itself; optional message)

@expect.min / @expect.max include the bound itself; @expect.exclusiveMin / @expect.exclusiveMax reject it:

atscript
interface Pricing {
    @expect.exclusiveMin 0
    price: number               // > 0

    @expect.exclusiveMin 0
    @expect.exclusiveMax 1, "Rate must be below 1"
    rate: number | null         // null, or 0 < rate < 1
}

@expect.array.key has compile-time constraints: the field must be string or number, cannot be optional, and multiple key fields form a composite key.

All validation annotations accept an optional custom error message as the last argument. When validation fails, the custom message is used instead of the default error message.

Nullable Fields ​

Validation annotations also work on a nullable field — a union of the target type with null / undefined, written inline or through a type alias. null passes; any other value must satisfy the constraint:

atscript
type Percent = number | null

interface Settings {
    @expect.min 0
    @expect.max 100
    discount: number | null      // null, 0..100

    @expect.maxLength 64
    @expect.pattern "^[a-z-]+$"
    slug?: string | null         // omitted, null, or a matching string ≤ 64 chars

    @expect.maxLength 5
    @expect.array.uniqueItems
    tags: string[] | null

    @expect.int
    share: Percent
}

A union of different non-null types (number | string) is still rejected with Expected type is (number), got union (number | string).

Array Annotations Example ​

@expect.array.uniqueItems and @expect.array.key work together to enforce unique array elements by identity fields:

atscript
interface Order {
    @meta.id
    id: number

    @expect.array.uniqueItems "Duplicate line items"
    items: OrderItem[]
}

interface OrderItem {
    @expect.array.key
    productId: number

    quantity: number
    price: number
}

For primitive arrays, @expect.array.uniqueItems checks by deep equality — no @expect.array.key needed:

atscript
interface Product {
    @expect.array.uniqueItems "Tags must be unique"
    tags: string[]
}

Form Validation (@meta.required) ​

  • @meta.required or @meta.required "Custom error message" - For strings: must contain at least one non-whitespace character. For booleans: must be true (optional message)
  • On a non-optional field, a missing value (or null where the type does not accept it) fails with the @meta.required error and message too (code REQUIRED, REQUIRED_CHECKED for booleans), not a type error.
  • On an optional field (name?: string) the field may be omitted, but null is rejected with the @meta.required message.
  • On a nullable field (name: string | null) null is accepted, and a string must still not be empty.

Form Validation

Use string.required or @meta.required to ensure required string fields are not empty or whitespace-only. A plain string type accepts '' as valid — @meta.required catches this common form validation gap. For checkboxes, @meta.required on a boolean field ensures the value is true (e.g., "accept terms").

Normalize Annotations (@normalize.*) ​

Normalizers change string input before it is validated:

  • @normalize.trim - Removes leading and trailing whitespace (also the string.trimmed primitive)
  • @normalize.lowercase - Converts to lower case
  • @normalize.uppercase - Converts to upper case

They apply in that order (trim, then lowercase, then uppercase) and only to strings: a string field, a nullable string (string | null), a union of string literals, or a type alias of one, whose fields and array items inherit them. Any other type is a compile error.

atscript
@normalize.trim
@normalize.lowercase
export type Email = string.email

export interface Signup {
    email: Email              // '  Ann@Example.COM ' → 'ann@example.com'
    backupEmails: Email[]     // each item normalized

    @normalize.trim
    @expect.minLength 2
    name: string              // checked after trimming: ' A ' fails

    @normalize.lowercase
    plan: 'free' | 'pro'      // 'PRO' → 'pro'
}

The validator never applies them. They run where input is prepared for validation: normalizeForType() (see Input Normalization) and the Moost validation pipe, which normalizes before it validates.

Emit Annotations (@emit.*) ​

  • @emit.jsonSchema — Pre-compute and embed the JSON Schema for an interface at build time, regardless of the global jsonSchema plugin setting
  • @emit.compiledValidator — Emit a build-time compiled validator check for this type even when the global compileValidators option is off. See Compiled Validators

Annotate Block Directives (@annotate.*) ​

  • @annotate.reset 'name' — Only inside annotate blocks: removes annotation name (full name, no @) from the entry's field or, at the top of the block, from the type: its own value, the one it inherits from the type it references, from an as copy's target or from lower-ranked blocks. The block's own value of that annotation applies over the reset. See Removing an Annotation.

Database Annotations (@db.*) ​

Database annotations (tables, columns, indexes, relations, views, schema sync) are provided by a separate package, @atscript/db/plugin, and are documented at https://db.atscript.dev. Install the DB plugin from that ecosystem to register @db.* annotations in your project.

Special Annotation Argument Types ​

Some annotations accept special argument types beyond strings and numbers:

  • Ref arguments — Type references using dot-notation chains (e.g., User.id). Custom annotations can declare type: 'ref' arguments.
  • Query arguments — SQL-like expressions in backticks (e.g., `Task.status != 'done'`). Custom annotations can declare type: 'query' arguments. Comparison operators (=, !=, >, >=, <, <=) work at any nesting level, including inside parentheses — e.g. `A.at >= B.start and (B.end = null or A.at <= B.end)` (since 0.1.90).
  • Literal value arguments — custom annotations can declare type: 'value': a string, number or boolean literal stored as written (@x.default 0 is the number 0); a number or boolean must fit the field's type. See Literal Values.
  • Expression and order arguments (since 0.1.99) — custom annotations can declare type: 'expr' (arithmetic: `openCount * 10 + coalesce(rank, 0)`) or type: 'order' (`raisedAt desc, id`) backtick arguments. Inside backticks / is division; a regexp literal is recognized only after matches. See Expression and Order Arguments.

Released under the MIT License.