Files
platform/src/databases/CLAUDE.md
T
2026-02-16 19:34:35 +00:00

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