first
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
# 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 })`
|
||||
Reference in New Issue
Block a user