Relations API
The Relations API provides methods for creating, querying, and managing relationships between entities.
Overview
All mutations are event-sourced - relationship changes create immutable event records.
All queries are direct reads - optimized for fast relationship traversal.
import { SynapSDK } from '@synap/sdk'
const synap = new SynapSDK({
url: 'https://api.synap.app',
apiKey: 'your-api-key'
})
// Access the Relations API
synap.relationsMethods
create()
Create a relationship between two entities (event-sourced).
Signature:
create(
sourceEntityId: string,
targetEntityId: string,
type: RelationType
): Promise<{ success: boolean }>Relationship Types:
type RelationType =
| 'assigned_to' // Task → Person
| 'mentions' // Note → Entity
| 'links_to' // Note → Note
| 'parent_of' // Project → Task
| 'relates_to' // Generic relationship
| 'tagged_with' // Entity → Tag
| 'created_by' // Entity → Person
| 'attended_by' // Event → Person
| 'depends_on' // Task → Task
| 'blocks' // Task → TaskExample:
// Assign task to person
await synap.relations.create(taskId, personId, 'assigned_to')
// Link notes together
await synap.relations.create(note1Id, note2Id, 'links_to')
// Set task dependency
await synap.relations.create(task1Id, task2Id, 'depends_on')
// Tag an entity
await synap.relations.create(entityId, tagId, 'tagged_with')Directional Relationships:
// Task → Person (task IS assigned_to person)
await synap.relations.create(taskId, personId, 'assigned_to')
// Task A → Task B (A depends_on B)
await synap.relations.create(taskAId, taskBId, 'depends_on')What Happens:
- SDK validates ownership of both entities
- Publishes
relations.create.requestedevent - Worker verifies and creates relationship
relations.create.validatedevent emitted- Real-time update broadcast
delete()
Delete a relationship (event-sourced).
Signature:
delete(relationId: string): Promise<{ success: boolean }>Example:
// Get relation ID first
const relations = await synap.relations.get(taskId, {
type: 'assigned_to'
})
// Delete specific relationship
await synap.relations.delete(relations[0].id)Alternative Pattern:
// Delete all relations of a type
const relations = await synap.relations.get(entityId, {
type: 'tagged_with',
direction: 'source'
})
await Promise.all(
relations.map(r => synap.relations.delete(r.id))
)get()
Get relationship records for an entity (direct read).
Signature:
get(entityId: string, options: RelationFilter): Promise<Relation[]>Parameters:
interface RelationFilter {
type?: RelationType // Filter by type
direction: 'source' | 'target' | 'both'
limit?: number // Max results (default: 50)
}Returns:
interface Relation {
id: string
sourceEntityId: string
targetEntityId: string
type: RelationType
userId: string
createdAt: Date
}Example:
// Get all outgoing relations
const outgoing = await synap.relations.get(entityId, {
direction: 'source'
})
// Get specific type
const assignments = await synap.relations.get(taskId, {
type: 'assigned_to',
direction: 'source'
})
// Get all relations (both directions)
const allRelations = await synap.relations.get(entityId, {
direction: 'both',
limit: 100
})Direction Examples:
// Task → Person (assigned_to)
const task = 'task-123'
const person = 'person-456'
// Get from task perspective (outgoing)
const assigned = await synap.relations.get(task, {
type: 'assigned_to',
direction: 'source' // task IS SOURCE
})
// Returns: [{ sourceEntityId: task, targetEntityId: person }]
// Get from person perspective (incoming)
const tasks = await synap.relations.get(person, {
type: 'assigned_to',
direction: 'target' // person IS TARGET
})
// Returns: [{ sourceEntityId: task, targetEntityId: person }]getRelated()
Get related entities (direct read).
Signature:
getRelated(entityId: string, options: RelationFilter): Promise<Entity[]>Example:
// Get people assigned to task
const assignees = await synap.relations.getRelated(taskId, {
type: 'assigned_to',
direction: 'source'
})
// Get all notes linked from current note
const linkedNotes = await synap.relations.getRelated(noteId, {
type: 'links_to',
direction: 'source'
})
// Get tasks this task depends on
const dependencies = await synap.relations.getRelated(taskId, {
type: 'depends_on',
direction: 'source'
})
// Get all related entities
const related = await synap.relations.getRelated(entityId, {
direction: 'both'
})Returns Full Entities:
const people = await synap.relations.getRelated(taskId, {
type: 'assigned_to',
direction: 'source'
})
people.forEach(person => {
console.log(person.title) // Full entity data
console.log(person.metadata) // Including metadata
})getStats()
Get relationship statistics for an entity (direct read).
Signature:
getStats(entityId: string): Promise<RelationStats>Returns:
interface RelationStats {
total: number // Total relationships
outgoing: number // As source
incoming: number // As target
byType: Record<RelationType, { // Breakdown by type
total: number
outgoing: number
incoming: number
}>
}Example:
const stats = await synap.relations.getStats(taskId)
console.log(`Total: ${stats.total}`)
console.log(`Assigned to: ${stats.byType.assigned_to?.total || 0}`)
console.log(`Dependencies: ${stats.byType.depends_on?.outgoing || 0}`)Use Cases:
// Check if task has assignees
const stats = await synap.relations.getStats(taskId)
if (stats.byType.assigned_to?.total > 0) {
console.log('Task is assigned')
}
// Count task dependencies
const depCount = stats.byType.depends_on?.outgoing || 0
console.log(`This task depends on ${depCount} other tasks`)Common Patterns
One-to-Many Relationships
// Assign task to multiple people
const taskId = 'task-123'
const teamMembers = ['person-1', 'person-2', 'person-3']
await Promise.all(
teamMembers.map(personId =>
synap.relations.create(taskId, personId, 'assigned_to')
)
)
// Query all assignees
const assignees = await synap.relations.getRelated(taskId, {
type: 'assigned_to',
direction: 'source'
})Bi-Directional Querying
// Task dependencies (Task A depends on Task B)
await synap.relations.create(taskA, taskB, 'depends_on')
// From Task A: what does A depend on?
const dependencies = await synap.relations.getRelated(taskA, {
type: 'depends_on',
direction: 'source'
})
// From Task B: what depends on B?
const dependents = await synap.relations.getRelated(taskB, {
type: 'depends_on',
direction: 'target'
})Tagged Entities
// Create tags
const { entityId: tagHighPriority } = await synap.entities.create({
type: 'tag',
title: 'high-priority'
})
// Tag entities
await synap.relations.create(task1, tagHighPriority, 'tagged_with')
await synap.relations.create(task2, tagHighPriority, 'tagged_with')
await synap.relations.create(note1, tagHighPriority, 'tagged_with')
// Find all entities with tag
const tagged = await synap.relations.getRelated(tagHighPriority, {
type: 'tagged_with',
direction: 'target'
})Relationship Graph
// Build complete graph for an entity
async function getEntityGraph(entityId: string) {
const stats = await synap.relations.getStats(entityId)
const related = await synap.relations.getRelated(entityId, {
direction: 'both'
})
return {
entity: await synap.entities.get(entityId),
stats,
related
}
}
const graph = await getEntityGraph('task-123')Relationship Types Explained
Task Relationships
// assigned_to: Task → Person
await synap.relations.create(taskId, personId, 'assigned_to')
// depends_on: Task → Task
await synap.relations.create(task1, task2, 'depends_on')
// blocks: Task → Task
await synap.relations.create(task1, task2, 'blocks')
// parent_of: Project → Task
await synap.relations.create(projectId, taskId, 'parent_of')Note Relationships
// mentions: Note → Entity
await synap.relations.create(noteId, entityId, 'mentions')
// links_to: Note → Note
await synap.relations.create(note1, note2, 'links_to')
// tagged_with: Note → Tag
await synap.relations.create(noteId, tagId, 'tagged_with')Generic Relationships
// relates_to: Any → Any
await synap.relations.create(entityA, entityB, 'relates_to')
// created_by: Any → Person
await synap.relations.create(entityId, userId, 'created_by')Best Practices
✅ Do’s
// Check entities exist before creating relation
const source = await synap.entities.get(sourceId)
const target = await synap.entities.get(targetId)
await synap.relations.create(sourceId, targetId, 'links_to')
// Use appropriate relationship types
await synap.relations.create(taskId, personId, 'assigned_to') // Good
// Query efficiently
const people = await synap.relations.getRelated(taskId, {
type: 'assigned_to', // Specific type
direction: 'source',
limit: 10
})❌ Don’ts
// Don't create circular depends_on
await synap.relations.create(taskA, taskB, 'depends_on')
await synap.relations.create(taskB, taskA, 'depends_on') // Bad!
// Don't use wrong direction
await synap.relations.create(personId, taskId, 'assigned_to') // Wrong!
// Should be: task → person
// Don't create duplicate relationships
// Check first or handle errorsType Safety
Full TypeScript support:
import { RelationType, Relation } from '@synap/sdk'
// Type-safe relationship types
const type: RelationType = 'assigned_to' // Autocomplete!
// Typed relations
const relations: Relation[] = await synap.relations.get(entityId, {
type: 'assigned_to',
direction: 'source'
})Next Steps
- Events API → - Track relationship changes
- Entities API → - Manage entities
- Examples → - See relationships in action