Edges
Edge types define directed relationships between node types, with optional properties and cardinality constraints.
An edge declaration creates a directed relationship between two node types. Each edge type gets its own storage table with three built-in columns — id, src, and dst — plus one column per declared property.
Basic syntax
edge WorksAt: Person -> CompanyThe identifier after edge is the type name. The source and destination are existing node type names separated by ->.
Edge properties
Edges can carry properties just like nodes. Declare them inside braces:
edge Knows: Person -> Person {
since: Date?
confidence: F64?
}Property rules are the same as for nodes: each property has a name, a type, and optional annotations. Nullable properties use the ? suffix.
Self-referencing edges
An edge can connect a node type to itself:
edge Knows: Person -> Person
edge DependsOn: Task -> TaskThis is how you model recursive structures like social graphs, dependency chains, or org hierarchies.
Cardinality constraints
Use the @card body constraint to limit how many edges of a given type can originate from or point to a single node.
edge ManagedBy: Employee -> Manager {
@card(1..1)
}The range syntax:
| Pattern | Meaning |
|---|---|
@card(1..1) | Exactly one edge per source node. |
@card(0..1) | At most one edge per source node. |
@card(1..) | At least one edge per source node. |
@card(0..) | No constraint (default). |
Cardinality is checked when edges are inserted or deleted. A mutation that would violate the constraint is rejected.
Preventing duplicate edges
Use @unique(src, dst) to ensure that at most one edge of a given type exists between any pair of nodes:
edge WorksAt: Person -> Company {
role: String?
@unique(src, dst)
}Without this constraint, multiple WorksAt edges between the same Person and Company are allowed (which may be useful for modeling separate employment periods).
Edge storage
Every edge row contains:
| Column | Type | Description |
|---|---|---|
id | U64 | Auto-generated unique identifier. |
src | U64 | Row id of the source node. |
dst | U64 | Row id of the destination node. |
Plus one column per declared property. The src and dst columns are indexed automatically for efficient traversal in both directions.
Full example
A decision-tracking graph with multiple edge types:
node Actor {
name: String @key
role: String
}
node Decision {
slug: String @key
title: String @index
status: enum(proposed, accepted, rejected, superseded)
}
node Signal {
slug: String @key
title: String @index
strength: enum(strong, moderate, weak)
}
edge Authored: Actor -> Decision {
date: Date
@description("Records who authored a decision and when.")
}
edge Informed: Signal -> Decision {
weight: F64?
@description("Links a signal to the decision it informed.")
}
edge Supersedes: Decision -> Decision {
@card(0..1)
@description("A newer decision that replaces an older one.")
}
edge Supports: Signal -> Signal {
@unique(src, dst)
@description("One signal corroborating another.")
}