Relations
August 6, 2026 ยท View on GitHub
Relations are generated from Split-YAML relations and plays facts. Each
generated role token preserves both its declaring relation and permitted player
types.
Declare roles and players
attributes:
person-id: {value: string}
company-id: {value: string}
since: {value: date}
entities:
person:
owns: {person-id: {key: true}}
company:
owns: {company-id: {key: true}}
relations:
employment:
owns:
since: {card: {min: 0, max: 1}}
relates:
employee: {card: 1}
employer: {card: 1}
plays:
person:
employment:
employee: {card: {min: 0, max: 1}}
company:
employment: [employer]
Relates-side cardinality constrains players on one relation instance. Plays-side cardinality constrains how often a player participates in that role.
Construct and write a generated relation
from app_models import Company, CompanyId, Employment, Person, PersonId, Since
from datetime import date
ada = Person.manager(db).put(Person(person_id=PersonId("ada")))
acme = Company.manager(db).put(Company(company_id=CompanyId("acme")))
employment = Employment(
employee=ada,
employer=acme,
since=Since(date(2026, 1, 1)),
)
Employment.manager(db).insert(employment)
Generated constructors reject a player from the wrong package, type, role, or projection. A generated reference can be used when an IID/key is already known and a complete hydrated model is unnecessary.
Query through roles
session = Employment.query(db)
person = session.exact(Person)
employment = session.exact(Employment)
employee = employment.role(Employment.employee).connects(person)
rows = session.query(person, employment).where(employee).rows(limit=100)
Role tokens are owner-aware: Employment.employee cannot be applied to a
binding of another relation, and the connected player binding must be allowed
by the canonical plays facts.
Relation inheritance and specialization
relations:
participation:
abstract: true
relates: [participant]
employment:
sub: participation
relates:
employee: {as: participant, card: 1}
as specializes a parent role. Generated constructors expose the effective
child role, while exact/subtype queries preserve the canonical declaring-role
identity required by TypeDB.
Ordered/list roles are declared with the Split-YAML ordering facts supported by the selected semantic profile. Provider capability checks remain authoritative; generation never claims an operation the provider runtime cannot execute.