Skip to Content
API ReferenceRelations API

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.relations

Methods

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 → Task

Example:

// 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:

  1. SDK validates ownership of both entities
  2. Publishes relations.create.requested event
  3. Worker verifies and creates relationship
  4. relations.create.validated event emitted
  5. 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 errors

Type 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

Last updated on