Migracje bazy danych (SQLite)
Dokument opisuje, jak schemat bazy danych jest budowany i aktualizowany, oraz jak uruchomić migracje lokalnie i na serwerze VPS (scripts/migrate.mjs). Historia migracji z czasów Cloudflare D1 (tabela d1_migrations) została zachowana, więc baza zaimportowana z D1 stosuje tylko brakujące pliki.
👤 Instrukcja dla developera
Lokalna baza
-
Zastosuj wszystkie migracje na pustym pliku SQLite:
yarn db:migratePlik powstaje w
./.data/dev.db(katalog jest w.gitignore). Pełny zestaw 282 migracji wykonuje się w około 0,7 s. -
Uruchom aplikację na tym pliku:
yarn devgetRuntimeEnv()zwraca bazę i cache z./.data/. Sprawdzenie:curl http://localhost:3000/api/healthodpowiada{"status":"ok","runtime":"node",...}. -
Dane testowe i czyszczenie działają na tej samej bazie:
yarn db:seedyarn db:cleanSkrypty (
scripts/seed-test-data.js,scripts/clean-local-db.js,scripts/seed-camp-registrations.js), fixture Playwright (tests/fixtures/database-setup.ts) oraz serwery deweloperskie hardware (server.ts,scripts/hardware-ws-dev.ts) wybierają plik bazy w jednej kolejności (scripts/lib/local-database.js):DATABASE_PATH, jeśli ustawione,./.data/dev.db, jeśli istnieje.
Bez żadnej bazy skrypty kończą się komunikatem, jak ją utworzyć.
Po pobraniu nowych migracji z develop
yarn db:migrate:status # ile migracji czeka
yarn db:migrate # jedna migracja — bez dodatkowych flag
yarn db:migrate --allow-multiple # więcej niż jedna
Runner odmawia zastosowania kilku migracji naraz, gdy baza ma już historię — to ochrona przed przypadkowym przeskokiem wielu wersji na produkcji. Na pustej bazie i w CI (CI=true) flaga nie jest wymagana.
Dodawanie migracji
Zasady numerowania i kolejności są w CLAUDE.md (sekcja Database Migrations). Nowy plik migrations/NNNN_nazwa.sql jest wykrywany automatycznie; sprawdź go lokalnie:
yarn db:migrate --dry-run # pokaże nazwę jako oczekującą
yarn db:migrate # zastosuje; błąd SQL = rollback całego pliku
🛠 Dokumentacja techniczna
scripts/migrate.mjs
Samodzielny skrypt ESM na node:sqlite (bez zależności z node_modules, więc działa w obrazie produkcyjnym po yarn install --production).
| Opcja | Znaczenie |
|---|---|
apply (domyślnie) | stosuje oczekujące migracje |
status | wypisuje liczbę zastosowanych i listę oczekujących, nic nie zmienia |
--database <ścieżka> | plik SQLite; domyślnie DATABASE_PATH albo ./.data/dev.db; katalog nadrzędny jest tworzony |
--migrations-dir <kat.> | domyślnie migrations |
--to <nazwa> | zatrzymuje się po wskazanej migracji; nazwa z .sql lub bez, albo sam numer, jeśli jest jednoznaczny (0265 nie jest) |
--dry-run | jak status |
--allow-multiple | wymagane poza CI, gdy oczekuje więcej niż jedna migracja, a baza ma już zastosowane |
Zachowanie:
- Kolejność — leksykograficzna po pełnej nazwie pliku (tak jak wrangler). W repozytorium istnieją dwa pliki
0265_*, więc sam numer nie jest kluczem. - Tabela historii —
d1_migrations(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE, applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL), identyczna z tabelą tworzoną przezwrangler d1 migrations apply. Baza wyeksportowana z D1 od razu „wie”, które migracje ma, a runner stosuje tylko brakujące. - Transakcja na plik —
BEGIN IMMEDIATE→exec(plik)→INSERT INTO d1_migrations→COMMIT; każdy błąd toROLLBACKcałego pliku, wpis w historii nie powstaje, kod wyjścia 1.BEGIN IMMEDIATEblokuje zapis, więc dwa równoległe runnery (np. dwa kontenery przy deployu) nie wejdą sobie w drogę — drugi czeka dobusy_timeout(5 s). - PRAGMA w plikach —
PRAGMA foreign_keys = OFFwewnątrz transakcji jest w SQLite no-opem (tak samo było na D1);PRAGMA defer_foreign_keys = ONdziała i jest używane przez migracje przebudowujące tabele (np. 0137, 0262). Po pełnym przebieguPRAGMA foreign_key_checkzwraca 0 wierszy,integrity_check=ok. - Triggery — ciała
CREATE TRIGGER … BEGIN … ENDsą wykonywane przezexec()całego pliku, więc wielowyrażeniowe triggery (26 plików) nie wymagają dzielenia SQL na instrukcje. - Połączenie —
journal_mode = WAL,busy_timeout = 5000,foreign_keys = ON(te same ustawienia colib/runtime/sqlite.ts).
Wybór lokalnej bazy — scripts/lib/local-database.js
resolveLocalDatabasePath({ cwd, env }) zwraca ścieżkę albo null; describeMissingDatabase() zwraca komunikat z instrukcją. Moduł jest CommonJS, żeby działał zarówno w skryptach node scripts/*.js, jak i w plikach TypeScript (server.ts, fixture Playwright) przez tsx/Playwright.
Skrypty package.json
| Skrypt | Runtime | Polecenie |
|---|---|---|
yarn db:migrate | Node | node scripts/migrate.mjs (yarn db:update to alias) |
yarn db:migrate:status | Node | node scripts/migrate.mjs status |
yarn db:add-migration | Node | node scripts/new-migration.mjs <opis> — tworzy migrations/NNNN_opis.sql z nagłówkiem -- Migration number |
Na VPS entrypoint kontenera web uruchamia node dist/migrate.js (kopia pre-deploy VACUUM INTO przed nim) — opis w planie migracji, pkt 4.11–4.12.
Testy
__tests__/scripts/migrate.test.ts uruchamia runner jako proces potomny na tymczasowym katalogu migracji (kolejność, tabela zgodna z wranglerem, rollback, --to, --dry-run, --allow-multiple, CI) oraz stosuje pełny zestaw z migrations/ na pustym pliku i sprawdza foreign_key_check / integrity_check. __tests__/scripts/local-database.test.ts pokrywa kolejność wyboru bazy.