SDK Architecture
Internal patterns for extending the Backflow SDK with new resources, auth, and analytics.
Resource Organization
File Structure
apps/sdk/src/
├── client.ts # BackflowClient with HTTP methods
├── index.ts # Main export, creates client + resources
└── resources/
├── index.ts # Re-exports all resources
├── entities.ts # EntitiesResource
├── workflows.ts # WorkflowsResource
└── {name}.ts # {Name}ResourceClass Pattern
typescript
// apps/sdk/src/resources/example.ts
import { BackflowClient } from '../client'
export interface ExampleOptions { limit?: number }
export interface ExampleResult { id: string; name: string }
export class ExampleResource {
constructor(private client: BackflowClient) {}
async list(options?: ExampleOptions): Promise<{ data: ExampleResult[] }> {
return this.client.get('/example', options)
}
async get(id: string): Promise<ExampleResult> {
return this.client.get(`/example/${id}`)
}
async create(data: Partial<ExampleResult>): Promise<ExampleResult> {
return this.client.post('/example', data)
}
async update(id: string, data: Partial<ExampleResult>): Promise<ExampleResult> {
return this.client.put(`/example/${id}`, data)
}
async delete(id: string): Promise<{ success: boolean }> {
return this.client.delete(`/example/${id}`)
}
}Naming Conventions
| Element | Pattern | Example |
|---|---|---|
| File | lowercase | workflows.ts, cache.ts |
| Class | {Name}Resource | WorkflowsResource |
| Options type | {Name}Options | ChatOptions |
| Result type | {Name}Result | EmbeddingResult |
BackflowClient Methods
typescript
client.get<T>(path, params?) // GET with query params
client.post<T>(path, body?) // POST with JSON body
client.put<T>(path, body?) // PUT
client.delete<T>(path) // DELETE
client.upload<T>(path, file) // FormData upload
client.subscribeSSE(path, handlers) // Server-Sent EventsAdding a New Resource
Step 1: Create Resource File
typescript
// apps/sdk/src/resources/notifications.ts
import { BackflowClient } from '../client'
export interface Notification {
id: string
message: string
read: boolean
}
export class NotificationsResource {
constructor(private client: BackflowClient) {}
async list(): Promise<{ data: Notification[] }> {
return this.client.get('/notifications')
}
async markRead(id: string): Promise<{ success: boolean }> {
return this.client.post(`/notifications/${id}/read`)
}
}Step 2: Export from Index
typescript
// apps/sdk/src/resources/index.ts
export { NotificationsResource, type Notification } from './notifications'Step 3: Wire into Client
typescript
// apps/sdk/src/index.ts
import { NotificationsResource } from './resources'
export function createBackflow(config: BackflowConfig) {
const client = new BackflowClient(config)
return {
client,
notifications: new NotificationsResource(client),
}
}Step 4: Create Backend Route
typescript
// src/routes/notifications.ts
import { Hono } from 'hono'
export function createNotificationRoutes(app: Hono) {
app.get('/notifications', async (c) => {
const tenantId = c.get('tenantId')
const userId = c.get('user')?.uid
// implementation
})
}Step 5: Build
bash
cd apps/sdk && npm run buildAuth Integration
SDK Auth Flow
createBackflow({ clientKey })
│
▼
POST /auth/token (x-api-key header)
│
▼
Exchange for JWT → Cache locally
│
▼
All requests: Authorization: Bearer <jwt>
│
▼
Auto-refresh at 80% expiryBackend Auth Middleware
Validates in order (first match wins):
- Backflow API Key → Full system access
- Tenant API Key → Tenant-scoped
- Firebase ID Token → RS256 user auth
- JWT Token → HS256 user auth
Auth Context
typescript
// Available in route handlers
c.get('tenantId') // string
c.get('user') // { uid, email, role, tenant_id }
c.get('authProvider') // 'firebase' | 'jwt' | 'tenant-api-key'Tenant ID Resolution
| Auth Method | Tenant Source |
|---|---|
| Tenant API Key | tenant_api_keys table |
| Firebase/JWT | tenant_id claim |
SDK never sends x-tenant-id header—tenant comes from auth.
Analytics Integration
Tracker Types
| Tracker | Purpose | Init Location |
|---|---|---|
createAnalyticsMiddleware | API calls | src/index.ts |
AIUsageTracker | LLM billing | workflow-executor.ts |
IntegrationUsageTracker | External APIs | executor.ts |
UnifiedTracker | MCP/entity audit | Various |
Adding AI Usage Tracking
typescript
import { AIUsageTracker } from '../lib/billing/ai-usage-tracker'
const tracker = new AIUsageTracker(dbRouter, config)
await tracker.trackLLMExecution({
tenantId,
userId,
executionType: 'llm',
provider: 'openai',
model: 'gpt-4',
promptTokens: 100,
completionTokens: 50,
durationMs: 1200,
status: 'success',
})Adding Integration Tracking
typescript
import { IntegrationUsageTracker } from '../lib/integrations/integration-usage-tracker'
await tracker.trackIntegrationCall({
tenantId,
integrationType: 'github',
action: 'list_repos',
durationMs: 340,
status: 'success',
})Adding Entity Change Tracking
typescript
import { getUnifiedTracker } from '../lib/analytics/unified-tracker'
await getUnifiedTracker().trackEntityFieldChanges({
tenantId,
entityType: 'users',
entityId: 'user-123',
changeType: 'update',
changes: { email: { old: 'a@x.com', new: 'b@x.com' } },
})Storage Flow
Event → Redis counter (48h TTL) → Buffer → Firestore + R2Key Files
| Purpose | File |
|---|---|
| SDK client | apps/sdk/src/client.ts |
| SDK resources | apps/sdk/src/resources/*.ts |
| Auth middleware | src/middleware/global-auth.ts |
| API key validation | src/middleware/api-key.ts |
| AI usage | src/lib/billing/ai-usage-tracker.ts |
| Integration tracking | src/lib/integrations/integration-usage-tracker.ts |
| Entity/MCP tracking | src/lib/analytics/unified-tracker.ts |