Skip to Content
Core ConceptsArchitecture Overview

Architecture Overview

Understand how the SDK, backend, and database work together.

System Overview

┌─────────────┐ │ Your App │ │ (Frontend │ │ or Server) │ └──────┬──────┘ │ Import @synap/sdk ┌─────────────┐ │ Synap SDK │ ← High-level API │ │ │ • entities │ │ • relations │ │ • events │ └──────┬──────┘ │ Uses @synap/client (tRPC) ┌─────────────┐ │ API Layer │ ← tRPC Routers │ (Backend) │ │ │ │ • 20 routers│ │ • Auth │ │ • Validation│ └──────┬──────┘ ├─────────────┐ │ │ ▼ ▼ ┌──────────┐ ┌──────────┐ │ Database │ │ Inngest │ │(Postgres)│ │ Workers │ │ │ │ │ │ • Read │ │ • Write │ │ • Query │ │ • Process│ └──────────┘ └──────────┘

Data Flow

Writes (Event-Sourced)

// 1. Your app calls SDK await synap.entities.create({ type: 'task', title: 'Review PR' })

// 2. SDK publishes event via tRPC client.events.log.mutate({ eventType: 'entities.create.requested', data: { type: 'task', title: 'Review PR' } })

// 3. API validates & logs event POST /trpc/events.log → Insert into events table → Return success

// 4. Inngest picks up event entitiesWorker.handle(event) → Validate data → Insert into entities table → Upload file (if content exists) → Emit validation event

// 5. Broadcasts update SSE to connected clients → Webhooks to configured URLs

Timeline: ~50-100ms from SDK call to database

###Reads (Direct Query)

// 1. Your app calls SDK const tasks = await synap.entities.list({ type: 'task' })

// 2. SDK queries via tRPC client.entities.list.query({ type: 'task' })

// 3. API queries database SELECT * FROM entities WHERE type = 'task' AND userId = ?

// 4. Returns results immediately → No event processing needed → Fast, synchronous

Timeline: ~10-50ms

Technology Stack

SDK Layer

@synap/sdk ├── Built on @synap/client (tRPC client) ├── TypeScript for type safety └── Exports: • SynapSDK (main class) • EntitiesAPI • RelationsAPI • EventsAPI

API Layer

Backend (apps/api) ├── Next.js API routes ├── tRPC for type-safe APIs ├── 20 routers: │ ├── entities (CRUD) │ ├── relations (relationships) │ ├── events (logging) │ ├── search (full-text + semantic) │ └── ... 16 more └── Authentication (JWT/API keys)

Database Layer

PostgreSQL ├── Tables: │ ├── entities (materialized view) │ ├── events (event log) │ ├── relations (connections) │ ├── documents (content) │ └── ... 20 more tables └── Indexes for performance

Worker Layer

Inngest Workers ├── entities Walker (create/update/delete) ├── relationsWorker (link management) ├── documentsWorker (content processing) ├── enrichmentProjector (AI features) └── ... 8 workers total

Event Sourcing Architecture

###Event Log (Source of Truth)

events table: ├── id (UUID) ├── type (event type: 'entities.create.requested') ├── data (event payload) ├── userId (who) ├── timestamp (when) └── metadata (additional context)

Materialized View (Fast Queries)

entities table: ├── id (UUID) ├── userId (owner) ├── type (entity type) ├── title (display name) ├── metadata (type-specific data) └── timestamps

Key Insight:

  • Events = immutable history (what happened)
  • Entities = current state (what exists now)

Rebuild from Events

You can rebuild entities table from events:

// Get all events for entity const events = await synap.events.getHistory(entityId) // Replay to rebuild state let state = {} for (const event of events) { if (event.type === 'entities.create.validated') { state = event.data } if (event.type === 'entities.update.validated') { state = { ...state, ...event.data } } }

Multi-User Isolation

All operations are scoped to the authenticated user:

// Every query filtered by userId SELECT * FROM entities WHERE userId = 'current_user' // Every write includes userId INSERT INTO events (userId, type, data) VALUES ('current_user', ...) // Workers verify ownership if (entity.userId !== event.userId) throw new Error('Unauthorized')

Result: Complete data isolation between users

Real-Time Updates

Server-Sent Events (SSE)

// Workers broadcast after processing await broadcastNotification({ userId: 'user_123', message: { type: 'entity.created', entityId: 'task_456' } })

Webhooks

// Configure webhooks in API POST /webhooks { url: 'https://yourapp.com/synap-webhook', events: ['entities.create.validated', 'entities.update.validated'] } // Receive notifications webhook.send({ event: 'entities.create.validated', data: { entityId: '...' } })

Deployment Options

1. Synap Cloud (Managed)

const synap = new SynapSDK({ url: 'https://api.synap.app', apiKey: process.env.SYNAP_API_KEY })

Pros: Zero ops, auto-scaling, global CDN
Use case: Production apps

2. Self-Hosted

# Clone repo git clone https://github.com/synap-labs/synap # Run with Docker docker-compose up # SDK points to your instance const synap = new SynapSDK({ url: 'http://localhost:3000', apiKey: 'your-key' })

Pros: Full control, on-premise, custom integrations
Use case: Enterprise, compliance requirements

Security

Authentication

// API Key (recommended) const synap = new SynapSDK({ apiKey: process.env.SYNAP_API_KEY }) // Custom headers (OAuth, etc.) const synap = new SynapSDK({ headers: () => ({ Authorization: `Bearer ${getToken()}` }) })

Data Isolation

  • ✅ All queries filtered by userId
  • ✅ Workers verify entity ownership
  • ✅ Cross-user access blocked
  • ✅ Events logged per user

Event Immutability

  • ✅ Events never modified
  • ✅ Only new events created
  • ✅ Complete audit trail
  • ✅ Tamper-proof history

Performance Characteristics

OperationLatencyHow
Read10-50msDirect DB query
Write50-100msEvent → Worker → DB
Search20-100msDepends on index
History30-80msQuery events table

Optimizations:

  • Database indexes on common queries
  • Worker parallelization
  • Event batching
  • Connection pooling

Scalability

Horizontal Scaling

┌─────────┐ ┌─────────┐ ┌─────────┐ │ API #1 │ │ API #2 │ │ API #3 │ └────┬────┘ └────┬────┘ └────┬────┘ │ │ │ └────────────┼────────────┘ ┌──────▼──────┐ │ Database │ └──────┬──────┘ ┌────────────┼────────────┐ │ │ │ ┌────▼────┐ ┌───▼─────┐ ┌───▼─────┐ │Worker#1 │ │Worker#2 │ │Worker#3 │ └─────────┘ └─────────┘ └─────────┘
  • API servers can scale horizontally
  • Workers can scale horizontally
  • Database: read replicas for queries
  • Event log: partitioned by userId

Advanced: Direct tRPC Access

For operations not in SDK:

// Access underlying tRPC client const result = await synap.client.search.query({ query: 'advanced search', types: ['task', 'note'], limit: 100 }) // Call any of 20 routers await synap.client.webhooks.create.mutate({ url: 'https://...', events: ['entity.created'] })

Next Steps

Last updated on