Skip to content

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}Resource

Class 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

ElementPatternExample
Filelowercaseworkflows.ts, cache.ts
Class{Name}ResourceWorkflowsResource
Options type{Name}OptionsChatOptions
Result type{Name}ResultEmbeddingResult

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 Events

Adding 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 build

Auth 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% expiry

Backend Auth Middleware

Validates in order (first match wins):

  1. Backflow API Key → Full system access
  2. Tenant API Key → Tenant-scoped
  3. Firebase ID Token → RS256 user auth
  4. 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 MethodTenant Source
Tenant API Keytenant_api_keys table
Firebase/JWTtenant_id claim

SDK never sends x-tenant-id header—tenant comes from auth.

Analytics Integration

Tracker Types

TrackerPurposeInit Location
createAnalyticsMiddlewareAPI callssrc/index.ts
AIUsageTrackerLLM billingworkflow-executor.ts
IntegrationUsageTrackerExternal APIsexecutor.ts
UnifiedTrackerMCP/entity auditVarious

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 + R2

Key Files

PurposeFile
SDK clientapps/sdk/src/client.ts
SDK resourcesapps/sdk/src/resources/*.ts
Auth middlewaresrc/middleware/global-auth.ts
API key validationsrc/middleware/api-key.ts
AI usagesrc/lib/billing/ai-usage-tracker.ts
Integration trackingsrc/lib/integrations/integration-usage-tracker.ts
Entity/MCP trackingsrc/lib/analytics/unified-tracker.ts

Released under the ISC License.