Skip to main content

Overview

Hyperscape uses Drizzle ORM for database access:
  • Development: SQLite (zero config)
  • Production: PostgreSQL with pgvector extension
PostgreSQL 16+ with pgvector: The production database requires PostgreSQL 16 or higher with the pgvector extension. Use the pgvector/pgvector:pg16 Docker image for local development (updated in commit a9e9003, Feb 2026).

Schema Location

Database schema is defined in:

Tables

Characters Table Schema

The characters table stores all character data including skills:
Prayer Columns:
  • prayerLevel / prayerXp — Prayer skill progression
  • prayerPoints — Current prayer points (0 to prayerLevel)
  • prayerMaxPoints — Maximum prayer points (equals prayerLevel)
  • activePrayers — JSON array of active prayer IDs (e.g., '["thick_skin"]')
Prayer points are stored as integers in the database but tracked with fractional precision in-memory for accurate drain calculations.

SKIP_MIGRATIONS Environment Variable

When SKIP_MIGRATIONS=true, the server skips:
  • Built-in migration execution
  • hasRequiredPublicTables validation check
  • Migration recovery loop
Use Cases:
  • CI/testing environments using drizzle-kit push for declarative schema creation
  • External schema management (avoids FK ordering issues in migration files)
  • Integration tests that create schema before server startup
Important: When using SKIP_MIGRATIONS=true, you MUST create the database schema externally (e.g., via drizzle-kit push) before starting the server. The server will not create tables or run migrations. Example CI Workflow (commits eb8652a, 6a5f4ee):
Why This Exists:
  • Server’s built-in migrations have FK ordering issues (migration 0050 references arena_rounds from older migrations)
  • drizzle-kit push creates schema declaratively without these problems
  • Prevents “relation already exists” errors on fresh databases
  • Allows CI to use declarative schema creation instead of sequential migrations
  • Fixed in commits: eb8652a (CI integration), 6a5f4ee (table validation skip)
Behavior Changes (commit 6a5f4ee): Previously, SKIP_MIGRATIONS=true only skipped migration execution but still ran:
  • hasRequiredPublicTables check
  • Migration recovery loop
This caused failures when schema was created externally via drizzle-kit push because the migration journal wasn’t populated. Now, SKIP_MIGRATIONS=true skips ALL migration-related checks:
Implementation Details: The SKIP_MIGRATIONS check is placed at the beginning of the migration function, before any database validation or migration execution:
This ensures that when SKIP_MIGRATIONS=true, the server assumes the schema is already created and valid, and proceeds directly to application startup.
Do NOT run drizzle-kit push then start the server without SKIP_MIGRATIONS=true. This creates tables without populating the migration journal, causing the server’s migration code to fail on re-creation attempts.

Drizzle Commands

Run from packages/server/:

Push Schema

Apply schema changes directly (development):

Generate Migrations

Create migration files for changes:

Run Migrations

Apply pending migrations:

Studio

Open Drizzle Studio to browse data:

Configuration

packages/server/drizzle.config.ts:

Development vs Production

Development (SQLite)

No configuration needed—uses local SQLite file:

Production (PostgreSQL)

Set DATABASE_URL in environment:
pgvector Extension Required: Production deployments must use PostgreSQL 16+ with the pgvector extension. For Docker deployments, use pgvector/pgvector:pg16 instead of postgres:16-alpine (updated in commit a9e9003).

Connection Pooling (Supabase)

When using Supabase with the Supavisor connection pooler, prepared statements must be disabled (fixed in commits 8aaaf28, f7ab9f7):
Why This Is Needed:
  • Supavisor operates in transaction mode for connection pooling
  • Transaction mode doesn’t support prepared statements (PostgreSQL limitation)
  • Attempting to use prepared statements causes XX000 errors: “prepared statement does not exist”
  • Disabling prepared statements trades minor performance for compatibility
When to Disable:
  • Using Supabase with Supavisor pooler
  • Getting XX000 errors on database queries
  • Connection string contains pooler.supabase.com
When to Keep Enabled:
  • Local PostgreSQL development
  • Direct PostgreSQL connections (no pooler)
  • PgBouncer in statement mode
  • Neon, Railway, or other providers without transaction pooling
If you see XX000 errors, check if your connection string uses a pooler and set prepare: false.

Migration Workflow

Automatic Migrations (Default)

By default, the server automatically runs migrations on startup:
  1. Checks for required public tables
  2. Runs pending migrations from src/database/migrations/
  3. Populates migration journal table
  4. Validates schema integrity

Skip Migrations (CI/Testing)

For CI environments where schema is created externally via drizzle-kit push, set:
Behavior when enabled (commit 6a5f4ee):
  • Skips migration execution
  • Skips hasRequiredPublicTables check
  • Skips migration recovery loop
  • Assumes schema is already created and valid
Use cases:
  • CI integration tests using drizzle-kit push for declarative schema creation
  • Avoids foreign key ordering issues in migration files
  • Prevents migration journal conflicts when schema created externally
Why this is needed:
  • Server’s built-in migration has FK ordering issues (migration 0050 references arena_rounds from older migrations)
  • drizzle-kit push creates schema declaratively without these problems
  • Allows CI to use push for clean schema creation, then skip server migration
Only use SKIP_MIGRATIONS=true in CI/testing environments. Production should always run migrations normally.

Migrations

Migrations are stored in packages/server/src/database/migrations/:

Recent Migrations

Migration Format

Migrations use IF NOT EXISTS to be idempotent and safe to re-run.

Migration 0050 Fix (commit e4b6489)

Migration 0050 was fixed to add IF NOT EXISTS guards to prevent errors on fresh databases: Problem:
  • Migration 0050 duplicated CREATE TABLE statements from earlier migrations
  • agent_duel_stats table was created in both migration 0039 and 0050
  • On fresh databases, running all migrations sequentially caused 42P07 errors
  • Same table created twice without IF NOT EXISTS protection
Solution:
Affected Tables:
  • agent_duel_stats
  • arena_fee_shares
  • arena_staking_points
  • wallet_links
Impact:
  • Fresh database installations now complete all migrations successfully
  • Prevents “relation already exists” errors (42P07)
  • Allows clean database setup from scratch
  • Fixes CI integration test failures
All migrations should use IF NOT EXISTS guards to be idempotent and safe to re-run.
The activePrayers column stores a JSON array of prayer IDs. Format: '["thick_skin", "burst_of_strength"]'. Empty array when no prayers are active: '[]'.

Schema Changes

1

Edit schema

Modify packages/server/src/database/schema.ts
2

Generate migration

3

Apply migration

4

Verify

Check that migration file was created in src/database/migrations/

Reset Database

Development Reset

Delete the SQLite file:

Docker PostgreSQL Reset

This permanently deletes all player data.
Docker Image Update: The local PostgreSQL container now uses pgvector/pgvector:pg16 instead of postgres:16-alpine to support vector similarity search features (commit a9e9003, Feb 2026).

Backup and Restore

PostgreSQL Backup

PostgreSQL Restore

Troubleshooting

Schema Out of Sync

If you see schema errors after pulling updates:

Connection Refused

Ensure PostgreSQL is running:
If not running: