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 URLsTimeline: ~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, synchronousTimeline: ~10-50ms
Technology Stack
SDK Layer
@synap/sdk
├── Built on @synap/client (tRPC client)
├── TypeScript for type safety
└── Exports:
• SynapSDK (main class)
• EntitiesAPI
• RelationsAPI
• EventsAPIAPI 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 performanceWorker Layer
Inngest Workers
├── entities Walker (create/update/delete)
├── relationsWorker (link management)
├── documentsWorker (content processing)
├── enrichmentProjector (AI features)
└── ... 8 workers totalEvent 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)
└── timestampsKey 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
| Operation | Latency | How |
|---|---|---|
| Read | 10-50ms | Direct DB query |
| Write | 50-100ms | Event → Worker → DB |
| Search | 20-100ms | Depends on index |
| History | 30-80ms | Query 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
- Event Sourcing → - Deep dive into events
- Entities & Types → - Entity model
- Relationships → - Relationship system
- API Reference → - Complete SDK API
Last updated on