# 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 })`
