Skip to content

TOML Schema DSL

Author TypeDB schemas in TOML instead of TypeQL.

Overview

TypeBridge accepts schemas in two formats: TypeQL (.tql) and a TOML DSL (.toml). Both formats feed the same parser and code generator — TOML is an alternative authoring surface, not a separate pipeline.

The TOML DSL is useful when you prefer a data-file syntax over TypeQL prose, want IDE tooling for schema keys, or are generating schema files programmatically.

Routing

When generate_models receives a path that ends in .toml, it automatically routes the file through the TOML transpiler before passing the result to the generator. No extra flags are required.

from type_bridge.generator import generate_models

# .toml suffix routes automatically through the transpiler
generate_models("schema.toml", "out/models/")

If you hold TOML text in memory (not in a file), pass format="toml" to force the TOML path:

toml_text = """
[attributes.name]
value = "string"

[entities.person]
owns = ["name"]
"""

generate_models(toml_text, "out/models/", format="toml")

A .tql file or raw TypeQL text continues to work exactly as before.

DSL Reference

Attributes

[attributes.NAME]
value = "string"      # value type (see table below); required unless sub is set
sub   = "parent"      # inherit from a parent attribute instead of declaring a value type
abstract = true       # mark as abstract
bindgen_case = "SnakeCase" # specify class name case explicitly for bindgen
annotations = ["dto_name(MyDTO)"] # specify code generator annotations for bindgen


# Annotation constraints (optional, combine as needed)
regex  = "^active|inactive$"
values = ["active", "inactive"]
range  = "0..150"

value and sub are mutually exclusive — an attribute either has a value type or inherits from a parent, not both.

Supported value types:

TOML value TypeDB type
"string" string
"long" long
"integer" integer
"int" integer
"double" double
"boolean" boolean
"bool" boolean
"datetime" datetime
"datetime-tz" datetime-tz
"date" date
"duration" duration
"decimal" decimal

Example — annotation constraints:

[attributes.status]
value  = "string"
values = ["active", "inactive", "archived"]

[attributes.age]
value = "integer"
range = "0..150"

[attributes.email]
value = "string"
regex = "^[^@]+@[^@]+\\.[^@]+$"

[attributes.person-id]
sub = "id"     # inherits from id; no value key

Entities

[entities.NAME]
sub      = "parent"    # optional; inherit from another entity
abstract = true        # optional; mark as abstract
bindgen_case = "PascalCase" # optional; explicitly override class case naming
annotations = ["dto_name(MyDTO)"] # optional; add custom generator annotations
owns     = [...]       # list of owned attributes (see below)
plays    = [...]       # list of roles the entity plays (see below)

owns entries accept either a plain string (attribute name) or a table with options:

owns = [
    "name",                                       # optional, no annotation
    { attribute = "email", key = true },          # @key
    { attribute = "tag",   unique = true },       # @unique
    { attribute = "score", card = "0..100" },     # @card(0..100)
]

plays entries are tables that name the relation and role:

plays = [
    { relation = "employment", role = "employee" },
    { relation = "friendship", role = "friend", card = "0..5" },
]

Set card on a plays entry to emit plays-side cardinality: plays relation:role @card(...).

Full entity example:

[attributes.username]
value = "string"

[attributes.score]
value = "integer"
range = "0..10000"

[attributes.tag]
value  = "string"
values = ["beginner", "intermediate", "expert"]

[entities.user]
bindgen_case = "PascalCase"
annotations = ["dto_name(UserDto)"]
owns = [
    { attribute = "username", key = true },
    "score",
    { attribute = "tag", card = "0..3" },
]
plays = [
    { relation = "membership", role = "member" }
]

Relations

[relations.NAME]
sub      = "parent"    # optional; inherit from another relation
abstract = true        # optional; mark as abstract
bindgen_case = "PascalCase" # optional; explicitly override class case naming
annotations = ["dto_name(MyDTO)"] # optional; add custom generator annotations
roles    = [...]       # list of role definitions (see below)
owns     = [...]       # list of owned attributes (same syntax as entities)
plays    = [...]       # roles this relation itself plays

roles entries define the roles a relation relates. Each role is a table:

roles = [
    { name = "employer" },                        # no cardinality constraint
    { name = "employee", card = "1..3" },         # @card(1..3)
    { name = "author",   as = "contributor" },    # role override
]

card sets the @card annotation on the role. as sets a role override (as contributor).

Relations can also play roles in other relations, using the same plays entry syntax as entities:

[relations.publication]
roles = [{ name = "publisher" }]
plays = [
    { relation = "contribution", role = "work" },
    { relation = "review", role = "reviewed", card = "0..5" },
]

Full relation example:

[relations.membership]
roles = [
    { name = "member", card = "1.." },
    { name = "group",  card = "1..1" },
]
owns = ["joined-at"]
plays = []

Functions

[functions.NAME]
signature = "fun NAME($param: type) -> return-type"
body      = """
  match
    ...;
  return ...;"""

The signature string is the full TypeQL function signature, without a trailing colon. The body string is verbatim TypeQL passed through to the transpiler unchanged.

Stream return (curly-brace form):

[functions.top-scorer]
signature = "fun top-scorer($g: game) -> { player }"
body = """  match
    ($g, $p) isa participation;
  return { $p };"""

Scalar return:

[functions.max-score]
signature = "fun max-score($g: game) -> double"
body = """  match
    $g has score $s;
  return max($s);"""

Structs

[structs.NAME]
fields = [
    { name = "field-name", type = "string" },
    { name = "optional-field", type = "integer", optional = true },
]

Each field entry must have name and type. Set optional = true for nullable fields (generates field: T | None = None in Python).

[structs.player-stats]
fields = [
    { name = "wins",     type = "integer" },
    { name = "losses",   type = "integer" },
    { name = "nickname", type = "string",  optional = true },
]

Complete Example

The following schema combines every TOML family:

# Attributes
[attributes.email]
value = "string"
regex = "^[^@]+@[^@]+\\.[^@]+$"

[attributes.username]
value = "string"

[attributes.score]
value    = "integer"
range    = "0..10000"

[attributes.tag]
value  = "string"
values = ["beginner", "intermediate", "expert"]

[attributes.created-at]
value = "datetime"

# Entities
[entities.user]
owns = [
    { attribute = "username",     key  = true },
    "email",
    "score",
    { attribute = "tag",          card = "0..5" },
]
plays = [{ relation = "membership", role = "member" }]

[entities.team]
owns = [
    { attribute = "username", key = true },
    "created-at",
]
plays = [{ relation = "membership", role = "group" }]

# Relations
[relations.membership]
roles = [
    { name = "member", card = "1.."  },
    { name = "group",  card = "1..1" },
]
owns = ["created-at"]

# Functions
[functions.team-members]
signature = "fun team-members($t: team) -> { user }"
body = """  match
    ($t, $u) isa membership;
  return { $u };"""

# Structs
[structs.user-profile]
fields = [
    { name = "display-name", type = "string" },
    { name = "bio",          type = "string", optional = true },
]

Generate models from this file:

from type_bridge.generator import generate_models

generate_models("schema.toml", "out/models/")

Or from TOML text held in memory:

generate_models(toml_text, "out/models/", format="toml")

Error Reporting

A malformed TOML schema raises ValueError with a message that identifies the offending field or type. Common errors:

Condition Example error
Attribute has both value and sub "Attribute 'id': cannot set both value and sub"
Unknown value type "Attribute 'score': unknown value type 'uint'"
Attribute sub references an unknown parent "Attribute 'child-id': sub parent 'unknown' not defined"
Role player references an unknown type "Relation 'review': role 'reviewer' player type not found"
Struct with no fields "Struct 'empty-struct': fields list is empty"
Malformed function body "Function 'my-fn': body does not contain a return statement"

Current Scope

The TOML DSL covers attributes, entities, relations, functions, and structs for the schema forms used by the generator. It supports abstract subtypes, relation-level plays, and per-plays cardinality. TOML is still a front-end to TypeQL: emitted TypeQL is parsed by the shared schema parser before model generation.

See Also

  • Code Generator — full generate_models API reference and CLI usage
  • Attributes — attribute types and constraints
  • Entities — entity inheritance and ownership
  • Relations — relations, roles, and role players