Skip to main content
The application uses Cloudflare D1 (SQLite) for data persistence. The schema is defined through migrations in migrations/*.sql.

Tables

races

Stores F1 race schedule and circuit information for each season.
CREATE TABLE IF NOT EXISTS races (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  season INTEGER NOT NULL,
  round INTEGER NOT NULL,
  name TEXT NOT NULL,
  circuit_name TEXT NOT NULL,
  circuit_id TEXT NOT NULL,
  locality TEXT,
  country TEXT,
  date TEXT NOT NULL,
  time TEXT,
  wikipedia_url TEXT,
  UNIQUE(season, round)
);
id
INTEGER
Auto-incrementing primary key
season
INTEGER
required
Year of the season (e.g., 2024)
round
INTEGER
required
Round number within the season (1-24)
name
TEXT
required
Official race name (e.g., “Australian Grand Prix”)
circuit_name
TEXT
required
Full circuit name (e.g., “Albert Park Circuit”)
circuit_id
TEXT
required
Jolpica API circuit identifier (e.g., “albert_park”)
locality
TEXT
City or locality (e.g., “Melbourne”)
country
TEXT
Country name (e.g., “Australia”)
date
TEXT
required
Race date in ISO format (YYYY-MM-DD)
time
TEXT
Race start time in UTC (HH:MM:SSZ), or NULL if unknown
wikipedia_url
TEXT
Wikipedia article URL for the race
Constraints:
  • UNIQUE(season, round) — only one race per round per season
  • Index: idx_races_season_round on (season, round)

race_entries

Stores driver results for each race, including grid position, finish position, and points.
CREATE TABLE IF NOT EXISTS race_entries (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  race_id INTEGER NOT NULL REFERENCES races(id),
  jolpica_driver_id TEXT NOT NULL,
  driver_code TEXT NOT NULL,
  driver_name TEXT NOT NULL,
  constructor TEXT NOT NULL,
  grid_position INTEGER,
  finish_position INTEGER,
  status TEXT NOT NULL,
  points REAL NOT NULL DEFAULT 0,
  fastest_lap INTEGER NOT NULL DEFAULT 0
);
id
INTEGER
Auto-incrementing primary key
race_id
INTEGER
required
Foreign key to races.id
jolpica_driver_id
TEXT
required
Jolpica API driver identifier (e.g., “max_verstappen”)
driver_code
TEXT
required
Three-letter driver code (e.g., “VER”, “HAM”)
driver_name
TEXT
required
Full driver name: “givenName familyName” (e.g., “Max Verstappen”)
constructor
TEXT
required
Team/constructor name (e.g., “Red Bull”)
grid_position
INTEGER
Starting grid position (1-20), or NULL if pit lane start
finish_position
INTEGER
Final classification position, or NULL if DNF/DSQ/DNS
status
TEXT
required
Finish status (e.g., “Finished”, “+1 Lap”, “Collision”, “Engine”)
points
REAL
required
Championship points earned (0.0-26.0 with fastest lap)
fastest_lap
INTEGER
required
1 if driver set fastest lap, 0 otherwise
Constraints:
  • Foreign key: race_id REFERENCES races(id)
  • Index: idx_race_entries_race_id on race_id

standings_snapshots

Stores championship standings snapshots before and after each race for both drivers and constructors.
CREATE TABLE IF NOT EXISTS standings_snapshots (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  race_id INTEGER NOT NULL REFERENCES races(id),
  snapshot_type TEXT NOT NULL CHECK(snapshot_type IN ('before', 'after')),
  entity_type TEXT NOT NULL CHECK(entity_type IN ('driver', 'constructor')),
  position INTEGER NOT NULL,
  entity_id TEXT NOT NULL,
  entity_name TEXT NOT NULL,
  points REAL NOT NULL,
  wins INTEGER NOT NULL DEFAULT 0
);
id
INTEGER
Auto-incrementing primary key
race_id
INTEGER
required
Foreign key to races.id
snapshot_type
TEXT
required
When the snapshot was taken: 'before' or 'after'
entity_type
TEXT
required
Type of entity: 'driver' or 'constructor'
position
INTEGER
required
Championship position (1-20 for drivers, 1-10 for constructors)
entity_id
TEXT
required
Jolpica identifier (driver_id or constructor_id)
entity_name
TEXT
required
Display name for the driver or constructor
points
REAL
required
Total championship points at this snapshot
wins
INTEGER
required
Total race wins at this snapshot
Constraints:
  • Foreign key: race_id REFERENCES races(id)
  • Check: snapshot_type IN ('before', 'after')
  • Check: entity_type IN ('driver', 'constructor')
  • Index: idx_standings_snapshots_race_id on race_id

TypeScript interfaces

The database row types are defined in src/types.ts:
export interface Race {
  id: number;
  season: number;
  round: number;
  name: string;
  circuit_name: string;
  circuit_id: string;
  locality: string | null;
  country: string | null;
  date: string;       // "YYYY-MM-DD"
  time: string | null; // "HH:MM:SSZ" UTC, or null
  wikipedia_url: string | null;
}

export interface RaceEntry {
  id: number;
  race_id: number;
  jolpica_driver_id: string;
  driver_code: string;
  driver_name: string;
  constructor: string;
  grid_position: number | null;
  finish_position: number | null;
  status: string;
  points: number;
  fastest_lap: number; // 0 | 1
}

export interface StandingsSnapshot {
  id: number;
  race_id: number;
  snapshot_type: 'before' | 'after';
  entity_type: 'driver' | 'constructor';
  position: number;
  entity_id: string;
  entity_name: string;
  points: number;
  wins: number;
}

Migrations

The schema is versioned through numbered migration files:
  • 0000_baseline.sql — Creates initial tables and indexes
  • 0001_rename_columns.sql — Renames races.urlwikipedia_url and race_entries.driver_idjolpica_driver_id
ALTER TABLE races RENAME COLUMN url TO wikipedia_url;
ALTER TABLE race_entries RENAME COLUMN driver_id TO jolpica_driver_id;
Migrations are applied using wrangler d1 migrations apply during deployment.

Relationships

races (1) ──< (N) race_entries
  └───< (N) standings_snapshots
  • Each race can have multiple race_entries (one per driver)
  • Each race can have multiple standings_snapshots (before/after × driver/constructor)
  • All relationships use race_id as the foreign key

Build docs developers (and LLMs) love