Skip to content

Adapter Pattern

PSI uses an adapter pattern extensively to decouple the core application logic from specific service implementations. This enables database portability, provider switching, and easier testing.

There are 5 server-side adapter categories (Auth, Database, Email, LLM, Translation) and 2 client-side adapters (Auth, Database).

Adapter Overview

graph LR
    subgraph "Core Application"
        API[API Logic / Modules]
        API --> AuthI[Auth Adapter Interface]
        API --> DBI[Database Adapter Interface]
        API --> EmailI[Email Adapter Interface]
        API --> LLMI[LLM Adapter Interface]
    end

    subgraph "Auth Implementations"
        AuthI --> FBAuth[Firebase Auth Adapter]
        AuthI --> BetterAuth[Better Auth Adapter]
    end

    subgraph "Database Implementations"
        DBI --> FBDB[Firebase RTDB Adapter]
        DBI --> MongoDB[MongoDB Adapter]
    end

    subgraph "Email Implementations"
        EmailI --> PM[Postmark Adapter]
        EmailI --> SG[SendGrid Adapter]
        EmailI --> SMTP[SMTP Adapter]
    end

    subgraph "LLM Implementations"
        LLMI --> OAI[OpenAI Adapter]
        LLMI --> Azure[Azure OpenAI Adapter]
        LLMI --> Persp[Perspective API Adapter]
    end

    subgraph "Translation Implementations"
        API --> TransI[Translation Adapter Interface]
        TransI --> TLLM[LLM Adapter / OpenAI]
        TransI --> DeepL[DeepL Adapter]
        TransI --> TS[TextShuttle Adapter]
        TransI --> SRG[SRG SSR Adapter]
    end

Server-Side Adapters

Auth Adapter

Located in server/adapter/auth/. Manages user authentication, session management, and SSO token conversion.

interface AuthAdapter {
    getSession(request): Promise<Session>;
    getUser(userId): Promise<User>;
    getUserByEmail(email): Promise<User>;
    createUser(data): Promise<User>;
    deleteUser(userId): Promise<void>;
    createCustomToken(userId): Promise<string>;
    listUsers(): Promise<User[]>;
    importUsers(users): Promise<void>;
}

Implementation stack:

  1. BetterAuthAdapter -- base implementation using the better-auth library
  2. FirebaseAuthAdapter -- extends BetterAuthAdapter with Firebase-specific operations
  3. firebaseIdToken plugin -- better-auth plugin verifying Firebase ID tokens from Authorization headers

Selection: AUTH_ADAPTER environment variable (default: firebase)

Database Adapter

Located in server/adapter/database/. Abstracts all data storage operations.

Implementation Module Configuration
Firebase RTDB firebase-adapter.ts Default (when using Firebase)
MongoDB mongodb-adapter.ts DATABASE_ADAPTER=mongodb + MONGODB_URL

Both adapters implement the same interface for reading/writing instances, collections, module data, and silo configurations. The MongoDB adapter uses compound-prefixed document IDs for data locality.

Email Adapter

Located in server/adapter/email/. Sends transactional emails (notifications, verification).

Implementation Configuration
Postmark EMAIL_PROVIDER=postmark + POSTMARK_API_KEY
SendGrid EMAIL_PROVIDER=sendgrid + SENDGRID_API_KEY
SMTP EMAIL_PROVIDER=smtp + SMTP_MAIL_SERVER, SMTP_USER, SMTP_PASSWORD

LLM Adapter

Located in server/adapter/llm/. Provides AI capabilities for moderation and content analysis.

Implementation Configuration
OpenAI OPENAI_PROVIDER=openai + OPENAI_KEY
Azure OpenAI OPENAI_PROVIDER=azure_openai + AZURE_OPENAI_KEY
Perspective API PERSPECTIVE_API_KEY (always available alongside OpenAI)

Translation Adapter

Located in server/adapter/translation/. Translates user-generated content between languages for multi-language silos.

Implementation Configuration
LLM (OpenAI) TEXT_TRANSLATION_PROVIDER=llm (default)
DeepL TEXT_TRANSLATION_PROVIDER=deepl + DEEPL_API_KEY
TextShuttle TEXT_TRANSLATION_PROVIDER=textshuttle + TEXT_SHUTTLE_API_KEY
SRG SSR TEXT_TRANSLATION_PROVIDER=srgssr-crknews-translation-api + SRG_TRANSLATION_API_URL

The translation adapter is selected per-silo. For example, SRG uses their enterprise translation API while other silos default to LLM-based translation via OpenAI.

Client-Side Adapters

Client Auth Adapter

Located in client/adapter/auth/.

interface AuthAdapter {
    getCurrentUser(): User | null;
    getIdTokenAsync(): Promise<string>;
    signOutAsync(): Promise<void>;
    signInWithGoogleAsync(): Promise<void>;
    signInWithTokenAsync(token: string): Promise<void>;
    onAuthStateChanged(callback): Unsubscribe;
}

Default implementation: FirebaseAuthAdapter using the Firebase Auth SDK.

Client Database Adapter

Located in client/adapter/database/.

Adapter When Used
FirebaseDatabaseAdapter Default -- direct Firebase RTDB connection via SDK
ServerDatabaseAdapter When silo is configured with databaseAdapter: 'server'
DemoDatabaseAdapter Tests and demos -- in-memory, no external dependency

The ServerDatabaseAdapter proxies all reads and writes through the PSI backend API, which is useful for deployments where clients shouldn't have direct database access.

Benefits

Benefit How Adapters Help
Database portability Switch from Firebase RTDB to MongoDB without changing application code
Provider flexibility Partners can use their preferred email or AI service
Testability In-memory adapters (DemoDatabaseAdapter, mongo mock) enable fast tests
Gradual migration New adapters can be introduced alongside existing ones
Security ServerDatabaseAdapter prevents direct client-to-database access

Adding a New Adapter

To add a new implementation:

  1. Create a new file in the appropriate adapter/ subdirectory
  2. Implement the adapter interface
  3. Add a configuration option to select the adapter (typically an environment variable)
  4. Register the adapter in the initialization code

See the MongoDB Adapter documentation for a concrete example.