Skip to content

Split the database schema file into a folder of focused files - #1720

Merged
stefan-burke merged 6 commits into
mainfrom
split-schema
Jul 11, 2026
Merged

stefan-burke merged 6 commits into
mainfrom
split-schema

Conversation

@stefan-burke

@stefan-burke stefan-burke commented Jul 10, 2026

Copy link
Copy Markdown
Member

What changed

The database schema lived in one file, src/shared/db/migrations/schema.ts, that had grown to 1626 lines. Almost all of it was a single giant list of table definitions, with trigger definitions, types, and a hash function bolted on at the end. It was the only file in the project that had to be exempted from our own 1000-line-file limit.

This PR splits that one big file into 11 small files in a schema/ folder, each under 400 lines, grouped by what they hold:

  • types.ts — the column, index, table, and trigger types
  • version.ts — the latest-change label and the migrations bookkeeping table name
  • tables-core.ts — settings, auth, listings, images, rate limits
  • tables-attendees.ts — attendees, bookings, payments, activity log
  • tables-catalog.ts — groups, modifiers, listing hierarchy, holidays, API keys
  • tables-questions.ts — questions, answers, attributes, built sites, notes
  • tables-content.ts — ledger, communications, pages, contacts, cache
  • listing-aggregates.ts — the pure SQL predicates that decide what counts as a ticket
  • triggers.ts — the database triggers that keep precomputed totals in sync
  • columns.ts — small reusable column fragments shared across tables (new — see below)
  • index.ts — assembles the ordered table list and computes the schema's identity hash

Why

A 1626-line file is hard to navigate and hard to review. Splitting it into focused files means a future change to, say, the attendee tables only touches one small file, and you can tell from the filename which file to open. The project requires files to stay under 400 lines, and the 1000-line ceiling this file was breaking was only suppressible via an override in the linter config.

How it stays safe

The order of the tables in the schema list matters — it's used to compute a hash (SCHEMA_HASH) that detects when the schema has changed and a migration needs to run. The split preserves that order byte-for-byte: the table definitions are sliced into contiguous chunks and concatenated back in the same order, so the hash is unchanged (n2axyb). All 12 places in the codebase that imported from the old file now import from the right small file instead.

Fixing the duplication the split exposed

Our duplication checker (jscpd) runs at a strict 0% threshold. It could not fully scan the old 1626-line file, so two duplicated column sequences were quietly hiding inside it. Once the file was split, those duplicates became visible:

  1. The groups and site_pages tables both start with the same four columns (id, slug, slug_index, name).
  2. The image_uses and site_page_items tables both share the same three columns (item_type, item_id, sort_order).

Both are now shared reusable lists in columns.ts, spread into each table's column list — one definition instead of two copies that can drift apart.

Cleanup

  • Removed the schema.ts entry from the noExcessiveLinesPerFile override in biome.json (the file no longer exists, so the exemption is no longer needed).
  • Two test-file comments that referenced schema.ts by name were updated to point at the new file that holds the relevant declaration.

Checks

All green:

  • Typecheck (deno check src/shared/db/migrations) — passes
  • Lint (biome check --error-on-warnings) — passes
  • Duplication (deno task cpd) — 0 clones, in both source and test
  • Schema hash — unchanged (n2axyb), so no spurious migration is triggered

Summary by CodeRabbit

  • Refactor
    • Reorganized the database migration schema into focused modules (core, attendees, catalog, content, questions) with shared building blocks for columns and listing aggregates.
    • Updated schema/hash and trigger definitions to be sourced from the new schema modules.
    • Preserved existing migration behavior and derived count updates.
  • Tests
    • Updated database, migration, backup, and listing-aggregate test coverage to reference the reorganized schema and trigger sources.

…ionally unfixed)

Split the 1626-line src/shared/db/migrations/schema.ts into a schema/ folder
of focused files (each under 400 lines) so it passes the 1000-line ceiling:

  schema/types.ts            — Column, Index, Table, Trigger types
  schema/version.ts           — LATEST_UPDATE, SCHEMA_MIGRATIONS_TABLE
  schema/tables-core.ts       — settings, auth, listings, assets, rate limits
  schema/tables-attendees.ts  — attendees, bookings, payments, activity
  schema/tables-catalog.ts    — groups, modifiers, listing hierarchy
  schema/tables-questions.ts  — questions, answers, attributes, built sites
  schema/tables-content.ts    — ledger, comms, pages, contacts, cache
  schema/listing-aggregates.ts — pure ticket-count predicates/expressions
  schema/triggers.ts          — aggregate trigger arrays + TRIGGERS
  schema/index.ts             — assembles ordered SCHEMA, computes SCHEMA_HASH

Table order is preserved byte-for-byte (contiguous chunks concatenated in FK
order), so SCHEMA_HASH stays "n2axyb" — the schema-change guard is untouched.
All 12 callers migrated to import each symbol from its new home; the biome
noExcessiveLinesPerFile override for schema.ts is removed.

NOT GREEN on cpd (committed --no-verify): `deno task cpd` reports 2 clones
(0.01%) that the split surfaced from the old monolith (jscpd could not fully
scan the 1626-line file). Per instructions these are left unfixed for review:
  1. tables-content.ts:181 (site_pages cols) vs tables-catalog.ts:7 (groups cols)
     — both start id/slug/slug_index/name/...
  2. tables-content.ts:243 (site_page_items cols) vs tables-core.ts:209 (image_uses cols)
     — both share item_type/item_id/sort_order link columns

deno check src/shared/db (+ test files) and biome lint:ci both pass.
…pers

The schema split exposed two duplicated column sequences that jscpd could not
see inside the 1626-line monolith. Both are now shared const arrays in
schema/columns.ts, spread into each table's columns — one definition, no drift:

- slugNamedEntityColumns (id/slug/slug_index/name) — shared by groups and
  site_pages, both slug-addressed named entities.
- itemLinkColumns (item_type/item_id/sort_order) — shared by image_uses and
  site_page_items, both ordered link tables keyed by a polymorphic item ref.

SCHEMA_HASH unchanged (n2axyb): spreading a const array produces the same
runtime columns array, so JSON.stringify — and the hash — are identical.

deno task cpd: 0 clones (src + test). deno check: pass. biome lint: pass.
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@stefan-burke, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 4 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 433e54f9-ed9a-4494-a003-d8bcb10a101b

📥 Commits

Reviewing files that changed from the base of the PR and between 41e34df and 850eb1a.

📒 Files selected for processing (5)
  • biome.json
  • src/shared/db/attendees/delete.ts
  • src/shared/db/migrations.ts
  • test/lib/db/listing-aggregates.test.ts
  • test/shared/db/backup.test.ts
📝 Walkthrough

Walkthrough

The database migration schema is split from one module into typed, domain-specific table, trigger, aggregate, version, and assembly modules. Runtime imports, migration exports, tests, and Biome configuration are updated to reference the new module paths.

Changes

Database schema modularization

Layer / File(s) Summary
Schema foundations
src/shared/db/migrations/schema/types.ts, src/shared/db/migrations/schema/columns.ts, src/shared/db/migrations/schema/version.ts
Adds declarative schema types, reusable column fragments, and migration bookkeeping constants.
Domain table definitions
src/shared/db/migrations/schema/tables-*.ts
Defines core, attendee, catalog, content, and question table collections with their columns, constraints, and indexes.
Aggregate SQL and triggers
src/shared/db/migrations/schema/listing-aggregates.ts, src/shared/db/migrations/schema/triggers.ts
Extracts listing ticket-count helpers and defines trigger sets for derived listing, modifier, answer, and string aggregates plus attendee-answer validation.
Schema assembly and consumer wiring
src/shared/db/migrations/schema/index.ts, src/shared/db/migrations.ts, src/shared/db/migrations/schema-sync.ts, src/shared/db/*, test/*, biome.json
Assembles the ordered schema and hash, removes the monolithic schema entrypoint, and updates runtime, test, and configuration imports to the dedicated modules.

Estimated code review effort: 4 (Complex) | ~45 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: splitting the monolithic schema file into focused files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch split-schema

Comment @coderabbitai help to get the list of available commands.

# Conflicts:
#	biome.json
#	test/lib/servicing/listing-aggregates.test.ts
@stefan-burke
stefan-burke enabled auto-merge July 11, 2026 08:16
@stefan-burke
stefan-burke added this pull request to the merge queue Jul 11, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Jul 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant