Skip to Content
Core ConceptsRelationships

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:

TypeDescriptionExample
assigned_toTask → Person”Review PR” assigned to Marie
mentionsNote → Entity”Meeting notes” mentions Marie
links_toNote → Note”Project plan” links to “Budget”
parent_ofProject → Task”Q4 Goals” parent of “Revenue task”
relates_toGenericAny entity → Any entity
tagged_withEntity → Tag”Urgent task” tagged with #priority
created_byEntity → Person”Report” created by John
attended_byEvent → Person”Meeting” attended by team
depends_onTask → Task”Deploy” depends on “Testing”
blocksTask → 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')
// 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' })

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' })
// 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 of get() + separate entity queries
  • Limit results with limit parameter
  • Use specific direction filters to reduce results
  • Cache relationship data when possible

Next Steps

Last updated on