@coasys/ad4m / Exports / RelationOptions
Interface: RelationOptions
Options for relation decorators (@HasMany, @HasOne, @BelongsToOne, @BelongsToMany).
Table of contents
Properties
- datatype
- filter
- getter
- instantiateAs
- interpretationHint
- local
- ordering
- polymorphic
- readOnly
- target
- through
- where
Properties
datatype
• Optional datatype: string
SHACL sh:datatype for the relation's target values. Set when the
relation holds encoded literal values (e.g. "xsd:string" for a
HasMany<string> that stores plain strings). The executor uses
this to decode literal:<type>:<value> wire form on hydration.
Omit for URI relations that point at other model instances.
Mutually exclusive with target: a relation holds either literals or
model instances. Combines with through or getter.
Example
@HasMany({ through: "run://source", datatype: "xsd:string" })
sources: string[] = [];Defined in
core/src/model/decorators.ts:916 (opens in a new tab)
filter
• Optional filter: boolean
Whether to auto-generate a DB-level conformance filter when target is set.
Defaults to true when target is present — the query will only return linked
nodes whose shape matches the target model (required properties, flags, etc.).
Set to false to opt out of filtering while keeping hydration capability.
Defined in
core/src/model/decorators.ts:877 (opens in a new tab)
getter
• Optional getter: string
Custom getter to resolve the relation values. Use this for custom graph traversals.
The expression can reference 'Base' which will be replaced with the instance's base expression.
Example: "SELECT ?target WHERE { ?target flux://has_reply
Mutually exclusive with through — a getter replaces link-based resolution,
so there is no predicate to add to or remove from. When getter is provided
the relation is read-only (no adder/remover actions are generated). The
exception is readOnly: true, where through names the predicate the
getter reads and becomes the shape's path.
Combines with target, which names the class the traversal's values
hydrate into — without it include has no shape to resolve and the relation
can only return bare URIs. Also combines with where, applied as a
post-getter filter against the target class.
Example
Traversing a reified edge — a connection stored as a record rather
than a predicate, so no through can express it:
@HasMany({
getter: `SELECT ?target WHERE {
?citation <paper://cites_from> <Base> .
?citation <paper://cites_to> ?target .
}`,
target: () => Paper,
})
cited: Paper[] = [];Defined in
core/src/model/decorators.ts:860 (opens in a new tab)
instantiateAs
• Optional instantiateAs: () => Ad4mModelLike[]
Type declaration
▸ (): Ad4mModelLike[]
The model classes a polymorphic result may be constructed as.
Classification happens in the executor, structurally — there is no
rdf:type triple to read, so a target's class is derived from the flags
and required properties it carries. What cannot cross the wire is the
TypeScript constructor, so this names the classes this side can build.
@Model already records each class's name on the class itself, so the
name → class mapping is derived from the list rather than restated
alongside it.
This is advisory, not a constraint. It never narrows what the query returns and never excludes a target: a class missing from the list arrives as plain JSON carrying its class name, so partial knowledge degrades instead of failing. Declaring what this call site can construct is a different statement from declaring what the relation may contain — a heterogeneous relation is open by definition, and a list that silently dropped unrecognised members would defeat the point of reading it polymorphically at all. Constraining a read to particular classes belongs in the query, not here.
A thunk rather than an array, for the same reason target is one: it is
evaluated at query time, so a class defined later in a circular import
graph still resolves.
Returns
Defined in
core/src/model/decorators.ts:970 (opens in a new tab)
interpretationHint
• Optional interpretationHint: string
Natural-language hint describing what this relation MEANS semantically —
the sentence-level rationale for when to use it, not just its structural
shape. Emitted as an ad4m://interpretation_hint link on the SHACL
property node; read back by the Rust harness (ClassProposeShape. relations[N].hint) and rendered into the _propose_link_child tool's
predicate field description so the LLM knows which relation applies
to which situation instead of guessing from the predicate name alone.
Example
@HasMany(() => Belief, {
through: "ns://basedOn",
interpretationHint: "The prior beliefs this intention derives from.",
})
basedOn: Belief[] = [];Defined in
core/src/model/decorators.ts:1020 (opens in a new tab)
local
• Optional local: boolean
Whether the link is stored locally (not shared on the network)
Defined in
core/src/model/decorators.ts:870 (opens in a new tab)
ordering
• Optional ordering: Object
Give this collection a user-controlled order that survives concurrent edits.
A @HasMany is a set of links, and hydration sorts them by link timestamp.
That is right for an append-only collection — a transcript, a message
thread — where timestamp order is the order. It cannot express a sequence
somebody chose: a kanban column, a playlist, the blocks of a post.
Declaration only, for now. Setting this emits ad4m://ordering on the
property shape, where the executor reads it back — but no read or write
path acts on it yet, so a relation declaring it still hydrates by link
timestamp exactly as one that does not. Wiring save and hydration to the
declaration is the next change; nothing about how you write the relation
will change when it lands — assign the array in the order you want and
save.
Ordering is declared in the type system, rather than passed per query, so that every writer gets it once it is wired: the ORM, MCP agents, raw WS-RPC callers, another app sharing the neighbourhood. Implemented client-side it would order only what this client wrote.
Example
@HasMany(() => Task, {
through: "kanban://has_task",
ordering: { strategy: "linkedList" },
})
tasks: Task[] = [];Type declaration
| Name | Type |
|---|---|
strategy | "linkedList" |
Defined in
core/src/model/decorators.ts:1001 (opens in a new tab)
polymorphic
• Optional polymorphic: boolean
Hydrate each target as the class it actually is, rather than as the class this relation declares.
A heterogeneous relation has no single right target. Declaring the base
class loses every subclass property — the executor hydrates against the
shape it is given, so an ImagePost read as a Post arrives with its own
fields simply absent, not merely mislabelled. Declaring nothing leaves
include with no shape to resolve at all.
With this set, the targets are classified, grouped by concrete class, and hydrated against their own shapes — one query per distinct class present, not per instance.
Pair with instantiateAs so the results become instances of the right
model class rather than plain objects.
Example
@HasMany({
through: "we://children",
polymorphic: true,
instantiateAs: () => [TextBlock, ImageBlock],
})
children: Post[] = [];Defined in
core/src/model/decorators.ts:944 (opens in a new tab)
readOnly
• Optional readOnly: boolean
A relation read through through that this model must not write: no
add*/remove*/set* methods and no adder or remover actions. Pair
it with getter when the stored links need filtering on the way out
(only those signed by their target, say) but the predicate should
still be the shape's path, so a subscription re-runs when one lands.
Defined in
core/src/model/decorators.ts:868 (opens in a new tab)
target
• Optional target: () => Ad4mModelLike
Type declaration
▸ (): Ad4mModelLike
The target model class (use a thunk to avoid circular-dependency issues). Optional for untyped string relations.
Combines with getter, where it names the class the traversal's values hydrate into.
Returns
Defined in
core/src/model/decorators.ts:830 (opens in a new tab)
through
• Optional through: string
The predicate URI used to link the two models.
Defaults to 'ad4m://has_child' when omitted.
Cannot be combined with getter, except on a readOnly relation.
Defined in
core/src/model/decorators.ts:827 (opens in a new tab)
where
• Optional where: Where
Filter constraints on the linked target nodes, using the same query DSL
as Model.query().where(...). Property names reference the target
model's properties (resolved via target metadata).
When target is set and where is omitted, conformance conditions are
auto-derived from the target shape (flags + required properties).
Providing where overrides this auto-derivation.
Mutually exclusive with filter: false. Combines with getter, where it
is applied as a post-getter filter against the target class.
Example
@HasMany(() => Message, {
through: "flux://entry_type",
where: { status: "active", priority: { gte: 3 } }
})
activeMessages: Message[] = []