Skip to Content
Database Schema

Database Schema

MeetAI uses Neon DB (serverless Postgres) with Drizzle ORM for type-safe schema definitions and queries. The connection uses @neondatabase/serverless Pool driver with full transaction support.

Entity Relationship Diagram

MeetAI Database Schema — generated with Eraser.io

Enums

EnumValuesPurpose
meeting_statusupcoming, active, completed, processing, cancelled, abandonedTracks meeting lifecycle
participant_rolehost, co_host, attendee, viewerRole-based access within a meeting
session_statusactive, completed, abandoned, processingIndividual call session state
action_statusproposed, pending, confirmed, rejected, executing, completed, failedHuman-in-the-loop action lifecycle
action_typesend_email, create_calendar_event, create_jira_ticket, create_github_issue, post_slack_messageSupported AI-triggered actions

Key Design Decisions

Transcript stored as JSONB array

export interface TranscriptItem { role: "human" | "assistant"; speaker: AgentId | UserId | "unknownUser"; text: string; timestamp: number; // epoch ms }

Why JSONB, not a separate transcript_lines table? Transcripts are always read and written as a batch per meeting. A JSONB column avoids N+1 query problems, simplifies upserts (array concat via jsonb_set), and keeps the data co-located with the meeting row for fast reads. The trade-off is losing per-line indexing — acceptable because we never query individual transcript lines independently.

Composite Primary Key on meeting_participants

The (meetingId, userId) composite PK enforces that a user can only appear once per meeting — no duplicate joins. This eliminates an entire class of race conditions during invite acceptance.

nanoid() for Primary Keys

All application tables use nanoid() string IDs instead of auto-incrementing integers. This enables:

  • Client-side ID generation (optimistic UI)
  • URL-safe tokens (invite links use the row ID directly)
  • No sequential ID enumeration attacks

Indexes on meeting_actions

CREATE INDEX meeting_actions_meeting_idx ON meeting_actions(meeting_id); CREATE INDEX meeting_actions_status_idx ON meeting_actions(status);

These cover the two most frequent query patterns:

  1. “Show all actions for this meeting” (dashboard view)
  2. “Find all pending actions” (background executor)
Last updated on