# Database Patterns ## Overview Three PostgreSQL databases managed with Drizzle ORM: | Database | Purpose | Package | |----------|---------|---------| | `officer_db` | Main app data | `officerdb` | | `ephemeral_db` | Cache & temporary data | `ephemeraldb` | ## Type System ### File Structure Each database package has: ``` db_name/ ├── src/ │ ├── index.ts # DB connection, exports schema + drizzle helpers │ ├── types.ts # All type exports (Select, Insert, extended) │ └── schema/ │ ├── index.ts # Re-exports all schema files │ └── *.ts # Table definitions └── package.json # Exports: "." and "./types" ``` ### Type Naming Convention ```ts // Pattern 1: Simple table (no relations needed in API) export type Screenshot = typeof Schema.Screenshots.$inferSelect; export type ScreenshotInsert = typeof Schema.Screenshots.$inferInsert; // Pattern 2: Table with relations (for hydrated API responses) export type UserSelect = typeof Schema.Users.$inferSelect; export type UserInsert = typeof Schema.Users.$inferInsert; export type User = UserSelect & { company: Company; passkeys: Passkey[]; // computed fields passkeyCount: number; }; ``` ### Type Organization Organize types by domain with comments: ```ts // officerdb/types.ts // Auth export type PasskeySelect = ... export type Passkey = PasskeySelect & { user: User }; // Companies & Websites export type CompanySelect = ... export type Company = CompanySelect & { ... }; // Experiments export type ExperimentSelect = ... export type Experiment = ExperimentSelect & { ... }; ``` ## Importing Types ```ts // ✅ Good - import from types subpath import type { User } from 'officerdb/types'; // ✅ Good - import schema/connection from main import { officerdb, eq, Users } from 'officerdb'; // ❌ Bad - don't define manual types in schema files // ❌ Bad - don't import types from schema directly ``` ## Null Safety Drizzle-inferred types correctly reflect nullable columns. Add guards when needed: ```ts export async function handleOauthAccount(account: GanOauth) { // Guard for nullable fields if (!account.refreshToken || !account.companyId) { return; } // Now TypeScript knows these are non-null const token = await refreshAccessToken(account.refreshToken); } ``` ## Schema Best Practices - Use `bigserial` with `mode: 'number'` for IDs - Use `bigint` with `mode: 'number'` for foreign keys - Always add indexes for frequently queried columns - Use `varchar` with explicit length limits - Timestamps: `timestamp('...', { withTimezone: true })`