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:
BetterAuthAdapter-- base implementation using the better-auth libraryFirebaseAuthAdapter-- extends BetterAuthAdapter with Firebase-specific operationsfirebaseIdTokenplugin -- 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:
- Create a new file in the appropriate
adapter/subdirectory - Implement the adapter interface
- Add a configuration option to select the adapter (typically an environment variable)
- Register the adapter in the initialization code
See the MongoDB Adapter documentation for a concrete example.