2.6 KiB
2.6 KiB
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
// 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:
// 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
// ✅ 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:
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
bigserialwithmode: 'number'for IDs - Use
bigintwithmode: 'number'for foreign keys - Always add indexes for frequently queried columns
- Use
varcharwith explicit length limits - Timestamps:
timestamp('...', { withTimezone: true })