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 @card(...) on the edge header to limit how many edges of a given type can
originate from a single source 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.
Edge-level annotations
Annotations that describe the edge type itself appear on the edge header, after
any @card(...) annotation and before the optional property block:
edge Authored: Actor -> Decision @description("Records who authored a decision and when.") {
date: Date
}Edge types can use @description("..."), @instruction("..."), and
@rename_from("OldEdgeName") on the header.
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 @description("Records who authored a decision and when.") {
date: Date
}
edge Informed: Signal -> Decision @description("Links a signal to the decision it informed.") {
weight: F64?
}
edge Supersedes: Decision -> Decision @card(0..1) @description("A newer decision that replaces an older one.")
edge Supports: Signal -> Signal @description("One signal corroborating another.") {
@unique(src, dst)
}