Relationships
Connect any entity to any entity with Synap’s universal relationship API.
What are Relationships?
Relationships link two entities together with a typed connection:
interface Relation {
id: string
userId: string
sourceEntityId: string // "From" entity
targetEntityId: string // "To" entity
type: RelationType // Relationship type
createdAt: Date
}Relationship Types
Synap provides 10 built-in relationship types:
| Type | Description | Example |
|---|---|---|
assigned_to | Task → Person | ”Review PR” assigned to Marie |
mentions | Note → Entity | ”Meeting notes” mentions Marie |
links_to | Note → Note | ”Project plan” links to “Budget” |
parent_of | Project → Task | ”Q4 Goals” parent of “Revenue task” |
relates_to | Generic | Any entity → Any entity |
tagged_with | Entity → Tag | ”Urgent task” tagged with #priority |
created_by | Entity → Person | ”Report” created by John |
attended_by | Event → Person | ”Meeting” attended by team |
depends_on | Task → Task | ”Deploy” depends on “Testing” |
blocks | Task → Task | ”Bug fix” blocks “Release” |
Plus: You can use custom types for domain-specific needs!
Creating Relationships
Basic Syntax
await synap.relations.create(
sourceEntityId, // From this entity
targetEntityId, // To this entity
'relationship_type' // With this type
)Examples
Assign task to person
const { entityId: taskId } = await synap.entities.create({
type: 'task',
title: 'Review budget'
})
const { entityId: personId } = await synap.entities.create({
type: 'person',
title: 'Marie Johnson'
})
// Create assignment relationship
await synap.relations.create(taskId, personId, 'assigned_to')Link notes together
// Bi-directional knowledge graph
await synap.relations.create(note1Id, note2Id, 'links_to')
await synap.relations.create(note2Id, note1Id, 'links_to')Create task dependencies
// Task B depends on Task A
await synap.relations.create(taskBId, taskAId, 'depends_on')Tag entities
await synap.relations.create(taskId, tagId, 'tagged_with')Querying Relationships
Get all relations for an entity
const relations = await synap.relations.get(entityId, {
direction: 'both' // 'source' | 'target' | 'both'
})
console.log(`Entity has ${relations.length} relationships`)Filter by type
// Get only task assignments
const assignments = await synap.relations.get(taskId, {
type: 'assigned_to',
direction: 'source'
})Get related entities (with join)
Instead of just relationship records, get the actual entities:
// Get all people assigned to this task
const assignees = await synap.relations.getRelated(taskId, {
type: 'assigned_to',
direction: 'source'
})
assignees.forEach(person => {
console.log(`Assigned to: ${person.title}`)
})Get relationship statistics
const stats = await synap.relations.getStats(entityId)
console.log({
total: stats.total, // Total relationships
outgoing: stats.outgoing, // Where entity is source
incoming: stats.incoming, // Where entity is target
byType: stats.byType // Count per relationship type
})Direction Explained
Relationships are directional (source → target):
// Task assigned_to Person
await synap.relations.create(taskId, personId, 'assigned_to')When querying, you can filter by direction:
// Get where entity is SOURCE (outgoing)
const outgoing = await synap.relations.get(taskId, {
direction: 'source'
})
// Returns: assignments FROM this task
// Get where entity is TARGET (incoming)
const incoming = await synap.relations.get(personId, {
direction: 'target'
})
// Returns: tasks assigned TO this person
// Get BOTH directions
const all = await synap.relations.get(entityId, {
direction: 'both'
})Common Patterns
Task Assignment System
// Create task
const { entityId: taskId } = await synap.entities.create({
type: 'task',
title: 'Review code'
})
// Assign to person
await synap.relations.create(taskId, personId, 'assigned_to')
// Get assignee
const [assignee] = await synap.relations.getRelated(taskId, {
type: 'assigned_to',
direction: 'source'
})
// Get all tasks for person
const tasks = await synap.relations.getRelated(personId, {
type: 'assigned_to',
direction: 'target'
})Knowledge Graph (Bi-directional Links)
// Link notes together (both ways)
async function linkNotes(note1Id, note2Id) {
await synap.relations.create(note1Id, note2Id, 'links_to')
await synap.relations.create(note2Id, note1Id, 'links_to')
}
// Get all linked notes
const linkedNotes = await synap.relations.getRelated(noteId, {
type: 'links_to',
direction: 'both' // Get links in both directions
})Task Dependencies
// Task B depends on Task A
await synap.relations.create(taskBId, taskAId, 'depends_on')
// Get what a task depends on
const dependencies = await synap.relations.getRelated(taskId, {
type: 'depends_on',
direction: 'source'
})
// Get what depends on this task
const dependents = await synap.relations.getRelated(taskId, {
type: 'depends_on',
direction: 'target'
})Tagging System
// Create tags
const { entityId: urgentTagId } = await synap.entities.create({
type: 'note', // Use note type for tags
title: '#urgent'
})
// Tag entity
await synap.relations.create(taskId, urgentTagId, 'tagged_with')
// Get all entities with tag
const tagged = await synap.relations.getRelated(urgentTagId, {
type: 'tagged_with',
direction: 'target'
})
// Get tags for entity
const entityTags = await synap.relations.getRelated(taskId, {
type: 'tagged_with',
direction: 'source'
})Deleting Relationships
// Get relationship ID first
const relations = await synap.relations.get(taskId, {
type: 'assigned_to'
})
// Delete specific relationship
await synap.relations.delete(relations[0].id)Custom Relationship Types
Use custom types for domain-specific needs:
// Custom types (as strings)
await synap.relations.create(
companyId,
personId,
'employs' // Custom type
)
await synap.relations.create(
bookId,
authorId,
'written_by' // Custom type
)
await synap.relations.create(
projectId,
milestoneId,
'includes' // Custom type
)Note: Custom types work but won’t have TypeScript autocomplete.
Event Sourcing
All relationship operations are event-sourced:
// Create relationship
await synap.relations.create(taskId, personId, 'assigned_to')
↓
// Creates event
{
eventType: 'relations.create.requested',
data: {
sourceEntityId: taskId,
targetEntityId: personId,
type: 'assigned_to'
}
}
↓
// Worker processes → Database updated
↓
// Validation event emitted
{
eventType: 'relations.create.validated',
data: { relationId: '...' }
}Benefits:
- Complete audit trail of all relationship changes
- Can replay to see historical connections
- Automatic retries on failures
Best Practices
✅ Do’s
// Use semantic relationship types
await synap.relations.create(taskId, personId, 'assigned_to') // ✅ Clear
// Create bi-directional links when needed
await synap.relations.create(note1, note2, 'links_to')
await synap.relations.create(note2, note1, 'links_to')
// Use getRelated() for better DX
const assignees = await synap.relations.getRelated(taskId, {
type: 'assigned_to'
}) // ✅ Returns entities directly
// Clean up relationships when deleting entities
const relations = await synap.relations.get(entityId, { direction: 'both' })
for (const rel of relations) {
await synap.relations.delete(rel.id)
}
await synap.entities.delete(entityId)❌ Don’ts
// Don't create duplicate relationships
// Check first or use unique constraints
// Don't use relationships for data storage
await synap.relations.create(taskId, personId, 'assigned_to', {
metadata: { assignedAt: '...' } // ❌ Not supported
})
// Use entity metadata instead
// Don't create circular dependencies
await synap.relations.create(taskA, taskB, 'depends_on')
await synap.relations.create(taskB, taskA, 'depends_on') // ❌ Circular!Performance Tips
- Use
getRelated()instead ofget()+ separate entity queries - Limit results with
limitparameter - Use specific
directionfilters to reduce results - Cache relationship data when possible
Next Steps
- Entities & Types → - Understand entity model
- Relations API Reference → - Complete API docs
- Task Manager Example → - See relationships in action
Last updated on