Tanstack Start

Schema Syntax

Aureline schema syntax, supported declarations, attributes, and SurrealQL equivalents.

Aureline schema files describe the SurrealDB schema that Aureline can parse, validate, and emit today. This page documents the current language surface only; CLI workflows and migration behavior are intentionally out of scope.

The examples use aureline.schema as the file name, but the syntax is not tied to a specific file name.

Source File

An Aureline schema is a sequence of top-level items:

/// Optional documentation comments.
table User schemafull {
  email string @unique
}

analyzer text_search {
  tokenizers blank, class
  filters lowercase, snowball(english)
}

#surql {
  DEFINE EVENT custom_event ON TABLE user WHEN true THEN RETURN $after;
}

Supported top-level items:

ItemPurpose
tableDefines a SurrealDB table and its fields.
analyzerDefines a SurrealDB analyzer for full-text indexes.
#surql { ... }Passes raw SurrealQL through unchanged, after trimming outer whitespace.
/// commentsCaptured as documentation comments in the AST.

Line comments use //. Triple-slash comments use /// and are preserved as documentation comments.

Identifiers must start with an ASCII letter and can contain ASCII letters, numbers, and underscores.

Tables

Define a table with table, a name, an optional modifier, and a block body.

table User schemafull {
  id record<User>
  email string @unique
  profile object @flexible
}

Supported table modifiers:

ModifierSurrealQL equivalent
noneDEFINE TABLE user;
schemafullDEFINE TABLE user SCHEMAFULL;
schemalessDEFINE TABLE user SCHEMALESS;
dropDEFINE TABLE user DROP;

Table names are emitted in snake case. For example, UserProfile emits as user_profile.

Table bodies can contain fields and block attributes. Table views are not supported by the Aureline grammar today. Use a top-level #surql { ... } block if you need raw SurrealQL that Aureline does not model yet.

Fields

A field is a name, a type, optional inline attributes, and optionally an attribute block.

table Article {
  title string @index
  body string {
    @fulltext(analyzer: text_search, bm25: (1.2, 0.75), highlights: true)
  }
}

SurrealQL equivalent:

DEFINE FIELD title ON article TYPE string;
DEFINE INDEX article_title_idx ON article FIELDS title;
DEFINE FIELD body ON article TYPE string;
DEFINE INDEX article_body_fts ON article FIELDS body FULLTEXT ANALYZER text_search BM25(1.2, 0.75) HIGHLIGHTS;

Field attribute blocks are useful when a field has multiple rules or longer SurQL arguments:

table User {
  email string {
    @assert(#surql { $value != NONE AND string::is::email($value) })
    @allow(op: "SELECT", #surql { WHERE $auth.id = id })
  }
}

Types

Primitive types:

Type
any
bool
bytes
datetime
decimal
duration
float
int
number
object
range
regex
string
uuid

Compound types:

SyntaxMeaning
option<T>Optional value. At field top level, this is equivalent to T?.
array<T>Array of T.
array<T, N>Array of T with length N.
set<T>Set of T.
set<T, N>Set of T with length N.
recordAny record reference.
record<table>Record reference constrained to a table.
geometry<feature>Geometry constrained to one feature.
geometry<feature | feature>Geometry constrained to multiple features.

Optional field syntax:

table User {
  age int?
  nickname option<string>
}

Both fields emit as option<...> types:

DEFINE FIELD age ON user TYPE option<int>;
DEFINE FIELD nickname ON user TYPE option<string>;

Nested option<T> stays nested:

table Example {
  values array<option<string>>
}

Attributes

Attributes attach schema rules to fields or tables. The parser accepts a generic argument shape, then validation decides which attributes and argument names are valid.

Field attributes use @:

table User {
  email string @unique(name: "user_email_lookup")
}

Block attributes use @@ inside a table body:

table Membership {
  account record<Account>
  user record<User>

  @@unique(fields: [account, user])
}

Attribute arguments can be positional or keyword arguments:

@hnsw(dimension: 1536, dist: cosine)
@assert(#surql { $value != NONE })

Supported attribute value forms:

FormExample
number1536, 1.2
booleantrue, false
identifiercosine, simple_analyzer
string"custom_name"
array[account, user]
tuple(1.2, 0.75)
SurQL block#surql { $value != NONE }
inline SurQL#s`$value != NONE`

There must be no whitespace between #s and the opening backtick.

Field Attributes

AttributeValid onPurpose
@uniqueany fieldCreates a unique index on that field.
@indexany fieldCreates a standard index on that field.
@flexibleobject fields onlyEmits TYPE object FLEXIBLE.
@hnswarray<float> fields onlyCreates a vector HNSW index.
@fulltextstring fields onlyCreates a full-text index.
@assertany fieldValidates a SurQL expression for field assertions.
@allowany fieldValidates a SurQL permission clause for one operation.

@unique and @index

table User {
  email string @unique
  status string @index(name: "idx_user_status")
}

Accepted arguments:

ArgumentTypeRequired
nameidentifier or stringno

@flexible

table Document {
  metadata object @flexible
}

@flexible takes no arguments and is only valid on object fields.

@hnsw

table Document {
  embedding array<float> @hnsw(dimension: 1536, dist: cosine, type: f32, efc: 200, m: 16)
}

Accepted arguments:

ArgumentTypeRequired
dimensionnon-negative integeryes
distidentifierno
typeidentifierno
efcnon-negative integerno
mnon-negative integerno
nameidentifier or stringno

SurrealQL equivalent:

DEFINE INDEX document_embedding_hnsw ON document FIELDS embedding HNSW DIMENSION 1536 TYPE F32 DIST COSINE EFC 200 M 16;

@fulltext

analyzer text_search {
  tokenizers blank, class
  filters lowercase, snowball(english)
}

table Article {
  body string @fulltext(analyzer: text_search, bm25: (1.2, 0.75), highlights: true)
}

Accepted arguments:

ArgumentTypeRequired
analyzeridentifieryes
bm25tuple of two numbersno
highlightsbooleanno
nameidentifier or stringno

@assert

table User {
  email string @assert(#surql {
    $value != NONE AND string::is::email($value)
  })
}

@assert expects exactly one positional #surql { ... } block or inline #s`...` value. The body is validated as a SurrealDB expression.

@allow

table User {
  id record<User> @allow(op: "SELECT", #surql { WHERE $auth.id = id })
}

Accepted arguments:

ArgumentTypeRequired
opstring: "SELECT", "CREATE", "UPDATE", or "DELETE"yes
permissionpositional #surql { ... } or #s`...` yes

The permission body is validated as a SurrealDB field permission clause.

Block Attributes

Block attributes are written inside a table body and start with @@.

AttributePurpose
@@indexCreates a composite standard index.
@@uniqueCreates a composite unique index.
@@countCreates a count index.

@@index and @@unique

table Membership {
  account record<Account>
  user record<User>

  @@index(fields: [account, user], name: "membership_lookup")
  @@unique(fields: [account, user])
}

Accepted arguments:

ArgumentTypeRequired
fieldsarray of field identifiersyes
nameidentifier or stringno

Every field named in fields must exist in the same table.

@@count

table User {
  email string

  @@count
}

@@count takes no arguments.

Analyzers

Analyzers are top-level declarations used by @fulltext.

analyzer text_search {
  tokenizers blank, class
  filters lowercase, snowball(english), edgengram(1, 3)
}

SurrealQL equivalent:

DEFINE ANALYZER text_search TOKENIZERS blank,class FILTERS lowercase,snowball(english),edgengram(1,3);

Supported analyzer clauses:

ClauseSyntax
tokenizerstokenizers name, name
filtersfilters name, name(arg, arg)

Filter arguments are alphanumeric tokens.

Raw SurrealQL

Use a top-level #surql { ... } block for schema-level escape hatches that Aureline does not model yet:

#surql {
  DEFINE EVENT audit_user ON TABLE user WHEN true THEN RETURN $after;
}

The outer #surql { and } delimiters are removed. The body is emitted after trimming outer whitespace.

Use #surql { ... } or inline #s`...` inside attributes when an attribute expects SurQL:

table User {
  email string @assert(#s`$value != NONE`)
  owner record<User> @allow(op: "UPDATE", #surql {
    WHERE $auth.id = owner
  })
}

SurQL blocks can contain nested braces, strings, and comments. Aureline owns only the outer delimiters; the inner body is validated by SurrealDB parsing where the attribute rule requires validation.

Current Unsupported Syntax

The current grammar does not model:

Not supported as Aureline syntax
table views
relations as first-class declarations
events as first-class declarations
functions as first-class declarations
permissions as table-level declarations
arbitrary SurrealDB DEFINE statements, except through #surql { ... }

Use #surql { ... } for unsupported SurrealQL schema constructs until Aureline grows first-class syntax for them.

On this page