Compare commits

..

2 commits

Author SHA1 Message Date
Daniel Michelberger
2540666e1a feat: Typen aus der Schema-Migration erzeugen statt aus einer Instanz
npm run generate-pocketbase-types liest das Collections-Array jetzt direkt
aus backend/pb_migrations/ (scripts/schema-to-json.mjs) und speist es über
pocketbase-typegen --json ein. Keine laufende Instanz, kein Token, kein
Netzwerkzugriff mehr nötig.

Damit stammen Schema und Typen aus derselben versionierten Quelle und können
nicht auseinanderlaufen. Vorher zeigten sie auf eine Remote-Instanz, deren
Stand niemand garantieren konnte — nach dem Neu-Deployment lieferte sie
prompt Typen ohne die fachlichen Collections.

frontend/.env enthält dadurch nur noch PUBLIC_PB_URL. PB_TYPEGEN_URL und
PB_TYPEGEN_TOKEN entfallen (letztere wurde ohnehin nirgends gelesen),
PB_SUPERUSER_TOKEN wird nicht mehr gebraucht. Die Datei enthält damit kein
Geheimnis mehr.

Verifiziert: ohne .env, ohne Token und ohne laufende Instanz entstehen alle
sechs Collections, svelte-check meldet 0 Fehler und 0 Warnungen.

Die System-Collections (_superusers, _mfas, ...) fehlen in den Typen, weil
sie bewusst nicht in der Migration stehen. Die Anwendung verwendet sie nicht.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 14:20:15 +02:00
Daniel Michelberger
132ce04e0c fix: pb_migrations nicht mounten — Deployment startete ohne Collections
Der in 22cd427 ergänzte Bind-Mount ./pb_migrations:/pb/pb_migrations
überdeckt das Verzeichnis aus dem Image. Auf einem Deployment-Host, neben
dessen compose-Datei kein ausgechecktes Repo liegt, legt Docker dort ein
leeres Verzeichnis an: PocketBase findet keine Migration und startet mit
einer Datenbank ohne Collections. Genau das ist auf der neu deployten
Instanz passiert — users war da (legt PocketBase selbst an), events, teams,
riders, runs und times fehlten.

Reproduziert und beide Richtungen verifiziert: mit leerem Mount 404 auf
allen fünf Collections, ohne Mount kommen alle sechs aus dem Image.

Die Migrationen kommen damit wieder ausschließlich über COPY ins Image. Der
Preis ist der Weg zurück: Im Admin-UI erzeugte Migrationen liegen nur im
Container und müssen mit "docker compose cp" ins Repo geholt werden. Das
steht jetzt in backend/README.md und CLAUDE.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 14:06:30 +02:00
10 changed files with 161 additions and 147 deletions

3
.gitignore vendored
View file

@ -22,6 +22,9 @@ Thumbs.db
vite.config.js.timestamp-* vite.config.js.timestamp-*
vite.config.ts.timestamp-* vite.config.ts.timestamp-*
# Zwischendatei der Typgenerierung
.schema.tmp.json
# PocketBase-Laufzeitdaten # PocketBase-Laufzeitdaten
backend/pb_data/ backend/pb_data/
*.db *.db

View file

@ -30,7 +30,8 @@ npm run check
# Type-check with watch mode # Type-check with watch mode
npm run check:watch npm run check:watch
# Generate TypeScript types from PocketBase schema # Generate TypeScript types from the schema migration in backend/pb_migrations
# (no running instance, no token, no network access needed)
npm run generate-pocketbase-types npm run generate-pocketbase-types
``` ```
@ -53,8 +54,11 @@ docker compose down
- **API Base URL**: konfigurierbar über `PUBLIC_PB_URL` in `frontend/.env`. - **API Base URL**: konfigurierbar über `PUBLIC_PB_URL` in `frontend/.env`.
Default ist die produktive Instanz `https://api.stammtisch-hersbruck.de`; Default ist die produktive Instanz `https://api.stammtisch-hersbruck.de`;
für das lokale Backend aus `backend/` auf `http://127.0.0.1:8090` umstellen. für das lokale Backend aus `backend/` auf `http://127.0.0.1:8090` umstellen.
- **Schema**: versioniert als Migration in `backend/pb_migrations/`. Änderungen - **Schema**: versioniert als Migration in `backend/pb_migrations/`, kommt per
im Admin-UI erzeugen dort neue Dateien, die committet werden müssen. `COPY` ins Image (kein Bind-Mount — der würde das Image-Verzeichnis
überdecken). Im Admin-UI erzeugte Migrationen liegen deshalb zunächst nur im
Container und müssen mit `docker compose cp` ins Repo geholt werden; siehe
`backend/README.md`.
- **Type-safe client**: The PocketBase client is typed using auto-generated types in `frontend/src/lib/types.d.ts` - **Type-safe client**: The PocketBase client is typed using auto-generated types in `frontend/src/lib/types.d.ts`
- **Collections**: users, teams, events, runs, riders, times - **Collections**: users, teams, events, runs, riders, times
- **Authentication**: Handled through `src/lib/stores/pocketbase.svelte.ts` with the `AuthStore` class - **Authentication**: Handled through `src/lib/stores/pocketbase.svelte.ts` with the `AuthStore` class
@ -96,7 +100,7 @@ The app uses Svelte 5 runes for state management with a custom store pattern:
- `$derived()` for computed values - `$derived()` for computed values
- `{@render children?.()}` for slot content - `{@render children?.()}` for slot content
**Type Generation**: After modifying PocketBase schema, run `npm run generate-pocketbase-types` to update TypeScript types. **Type Generation**: After modifying the schema migration in `backend/pb_migrations/`, run `npm run generate-pocketbase-types` to update `frontend/src/lib/types.d.ts`. The script reads the collections array straight out of the migration (`frontend/scripts/schema-to-json.mjs`) — schema and types come from the same versioned source and cannot drift apart. Commit the regenerated file.
**Async Data Loading**: Root layout (`src/routes/+layout.svelte`) shows pattern: **Async Data Loading**: Root layout (`src/routes/+layout.svelte`) shows pattern:
```svelte ```svelte

View file

@ -28,8 +28,24 @@ per `GET /api/collections` abgezogen und ruft `importCollections` mit
`deleteMissing = false` auf — die Migration legt an und aktualisiert, löscht `deleteMissing = false` auf — die Migration legt an und aktualisiert, löscht
aber nichts, was nicht im Snapshot steht. aber nichts, was nicht im Snapshot steht.
Schema-Änderungen im Admin-UI erzeugen in `pb_migrations/` neue Dateien; diese ### Schema-Änderungen aus dem Admin-UI holen
müssen committet werden.
`pb_migrations/` wird **nicht** in den Container gemountet — die Migrationen
kommen über `COPY` ins Image (siehe `Dockerfile`). Das ist Absicht: Ein
Bind-Mount überdeckt das Image-Verzeichnis, und auf einem Deployment-Host ohne
ausgechecktes Repo legt Docker dort ein leeres Verzeichnis an. PocketBase
findet dann keine Migration und startet mit einer Datenbank ohne Collections.
Die Kehrseite: Eine im Admin-UI erzeugte Migration liegt zunächst nur im
Container und muss von Hand herausgeholt werden:
# Welche Dateien sind dazugekommen?
docker compose exec pocketbase ls /pb/pb_migrations
# Die neue Datei ins Repo kopieren und committen
docker compose cp pocketbase:/pb/pb_migrations/<datei> ./pb_migrations/
Ohne diesen Schritt ist die Änderung beim nächsten `--build` verloren.
Ein frisch gestarteter Container hat damit dasselbe Schema wie die produktive Ein frisch gestarteter Container hat damit dasselbe Schema wie die produktive
Instanz — aber **keine** Daten. Instanz — aber **keine** Daten.

View file

@ -14,12 +14,17 @@ services:
# Redeploy alles weg — das Schema käme zwar aus pb_migrations zurück, # Redeploy alles weg — das Schema käme zwar aus pb_migrations zurück,
# die Daten aber nicht. # die Daten aber nicht.
- ./pb_data:/pb/pb_data - ./pb_data:/pb/pb_data
# Migrationen zurück auf den Host. Ohne dieses Mapping schreibt PocketBase # ACHTUNG: pb_migrations wird bewusst NICHT gemountet. Ein Bind-Mount
# die bei Schema-Änderungen im Admin-UI erzeugten Dateien nur ins # überdeckt das Verzeichnis aus dem Image. Liegt auf dem Host kein
# Container-Dateisystem — sie wären beim nächsten Build verloren und # ausgechecktes Repo daneben, legt Docker ein leeres Verzeichnis an —
# könnten nie committet werden. Das COPY im Dockerfile bleibt trotzdem # PocketBase findet dann keine einzige Migration und startet mit einer
# bestehen, damit das Image auch ohne Bind-Mount das Schema mitbringt. # Datenbank ohne Collections. Genau das ist bei einem Deployment
- ./pb_migrations:/pb/pb_migrations # passiert, das nur die compose-Datei kennt.
#
# Die Migrationen kommen deshalb ausschließlich über COPY ins Image
# (siehe Dockerfile). Im Admin-UI erzeugte Migrationen landen damit nur
# im Container und müssen von Hand herausgeholt werden:
# docker compose cp pocketbase:/pb/pb_migrations/<datei> ./pb_migrations/
environment: environment:
# Superuser wird nur beim allerersten Start angelegt (siehe entrypoint.sh) # Superuser wird nur beim allerersten Start angelegt (siehe entrypoint.sh)
SUPERUSER_EMAIL: ${SUPERUSER_EMAIL:-} SUPERUSER_EMAIL: ${SUPERUSER_EMAIL:-}

View file

@ -1,19 +1,11 @@
# Vorlage für die lokale .env — kopieren und ausfüllen: # Vorlage für die lokale .env — kopieren und ausfüllen:
# cp .env.example .env # cp .env.example .env
#
# Die echte .env steht in .gitignore und darf nicht committet werden,
# sie enthält den Superuser-Token.
# Basis-URL der PocketBase-Instanz, die das Frontend anspricht. # Basis-URL der PocketBase-Instanz, die das Frontend anspricht.
# Produktiv: https://api.stammtisch-hersbruck.de # Produktiv: https://api.stammtisch-hersbruck.de
# Lokales Backend aus ../backend: http://127.0.0.1:8090 # Lokales Backend aus ../backend: http://127.0.0.1:8090
#
# Die Variable wird zur Buildzeit eingesetzt ($env/static/public). Ein Wechsel
# der Instanz erfordert deshalb einen Neustart des Dev-Servers bzw. einen
# neuen Build.
PUBLIC_PB_URL=https://api.stammtisch-hersbruck.de PUBLIC_PB_URL=https://api.stammtisch-hersbruck.de
# Ziel für die Typgenerierung (npm run generate-pocketbase-types) und die
# Scripts unter scripts/. Zeigt üblicherweise auf dieselbe Instanz wie oben.
PB_TYPEGEN_URL=https://api.stammtisch-hersbruck.de/
# Superuser-Token für Schema-Zugriffe. Im PocketBase-Admin-UI erzeugen.
# Niemals committen.
PB_SUPERUSER_TOKEN=
PB_TYPEGEN_TOKEN=

View file

@ -35,15 +35,27 @@ Die App läuft dann auf http://stammtisch-hersbruck.de.localhost:31337
Die Variable wird von SvelteKit zur **Buildzeit** eingesetzt Die Variable wird von SvelteKit zur **Buildzeit** eingesetzt
(`$env/static/public`). Ein Wechsel der Instanz erfordert deshalb einen (`$env/static/public`). Ein Wechsel der Instanz erfordert deshalb einen
Neustart des Dev-Servers bzw. einen neuen Build — ein blosser Neustart des Neustart des Dev-Servers bzw. einen neuen Build — ein bloßer Neustart des
Containers genügt nicht. Containers genügt nicht.
`PB_TYPEGEN_URL` und `PB_SUPERUSER_TOKEN` gelten getrennt davon für Es ist die einzige Variable in `.env`; ein Token wird nicht mehr gebraucht.
`npm run generate-pocketbase-types`.
## Schema ## Typen
Das maßgebliche Schema liegt als Migration in `../backend/pb_migrations/`. Das maßgebliche Schema liegt als Migration in `../backend/pb_migrations/`.
Nach einer Schema-Änderung `npm run generate-pocketbase-types` ausführen — das Daraus entstehen auch die TypeScript-Typen:
aktualisiert `src/lib/types.d.ts`, aus dem der PocketBase-Client seine Typen
bezieht. npm run generate-pocketbase-types
Das Script liest das Collections-Array aus der Migration
(`scripts/schema-to-json.mjs`) und erzeugt daraus `src/lib/types.d.ts`. Es
braucht **keine laufende Instanz, keinen Token und keinen Netzwerkzugriff**
Schema und Typen kommen aus derselben versionierten Quelle und können deshalb
nicht auseinanderlaufen.
Nach jeder Schema-Änderung in `../backend/pb_migrations/` einmal ausführen und
die aktualisierte `types.d.ts` mitcommitten.
> Die System-Collections (`_superusers`, `_mfas`, …) stehen bewusst nicht in
> der Migration und fehlen deshalb in den Typen. Die Anwendung verwendet sie
> nicht.

View file

@ -1586,9 +1586,9 @@
} }
}, },
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "1.1.12", "version": "1.1.18",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
@ -1697,6 +1697,7 @@
"version": "6.2.1", "version": "6.2.1",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
"deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"optional": true, "optional": true,
@ -2107,9 +2108,9 @@
} }
}, },
"node_modules/es-object-atoms": { "node_modules/es-object-atoms": {
"version": "1.1.1", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -2228,17 +2229,17 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/form-data": { "node_modules/form-data": {
"version": "4.0.4", "version": "4.0.6",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"asynckit": "^0.4.0", "asynckit": "^0.4.0",
"combined-stream": "^1.0.8", "combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0", "es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2", "hasown": "^2.0.4",
"mime-types": "^2.1.12" "mime-types": "^2.1.35"
}, },
"engines": { "engines": {
"node": ">= 6" "node": ">= 6"
@ -2389,7 +2390,7 @@
"version": "7.2.3", "version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Glob versions prior to v9 are no longer supported", "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"optional": true, "optional": true,
@ -2466,9 +2467,9 @@
"optional": true "optional": true
}, },
"node_modules/hasown": { "node_modules/hasown": {
"version": "2.0.2", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -2627,9 +2628,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/ip-address": { "node_modules/ip-address": {
"version": "10.0.1", "version": "10.4.0",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.4.0.tgz",
"integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", "integrity": "sha512-oSK96Grm3aP6OrS263xVxbNDGVL7rzBtYdpGqlDG8iQdoenDoTs/nkki+DflYbAEE8Xl6o5YxhxlrKvI3nqKXQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
@ -3096,9 +3097,9 @@
} }
}, },
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "3.1.2", "version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"optional": true, "optional": true,
@ -3222,11 +3223,11 @@
"optional": true "optional": true
}, },
"node_modules/minipass-flush": { "node_modules/minipass-flush": {
"version": "1.0.5", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz",
"integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==",
"dev": true, "dev": true,
"license": "ISC", "license": "BlueOak-1.0.0",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"minipass": "^3.0.0" "minipass": "^3.0.0"
@ -3490,9 +3491,9 @@
} }
}, },
"node_modules/node-abi": { "node_modules/node-abi": {
"version": "3.78.0", "version": "3.94.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.78.0.tgz", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.94.0.tgz",
"integrity": "sha512-E2wEyrgX/CqvicaQYU3Ze1PFGjc4QYPGsjUrlYkqAE0WjHEZwgOsGMPMzkMse4LjJbDmaEuDX3CM036j5K2DSQ==", "integrity": "sha512-W5ZNO5KRPB5TkYmGVD9F6YqhsglXJzE6etpbmT+f6EQElhiX/UTG551cnsRGvLG3fyZEg9HwaDmNmj5nwJ4z9g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -3611,6 +3612,7 @@
"version": "6.2.1", "version": "6.2.1",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
"deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"optional": true, "optional": true,
@ -3798,6 +3800,7 @@
"version": "7.1.3", "version": "7.1.3",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
"integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
"deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -3845,9 +3848,9 @@
} }
}, },
"node_modules/pump": { "node_modules/pump": {
"version": "3.0.3", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
"integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -4039,9 +4042,9 @@
"optional": true "optional": true
}, },
"node_modules/semver": { "node_modules/semver": {
"version": "7.7.3", "version": "7.8.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"bin": { "bin": {
@ -4149,14 +4152,14 @@
} }
}, },
"node_modules/socks": { "node_modules/socks": {
"version": "2.8.7", "version": "2.8.9",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz",
"integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"ip-address": "^10.0.1", "ip-address": "^10.1.1",
"smart-buffer": "^4.2.0" "smart-buffer": "^4.2.0"
}, },
"engines": { "engines": {
@ -4273,6 +4276,7 @@
"version": "6.2.1", "version": "6.2.1",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
"deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
@ -4536,9 +4540,9 @@
} }
}, },
"node_modules/tar-fs": { "node_modules/tar-fs": {
"version": "2.1.4", "version": "2.1.5",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.5.tgz",
"integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", "integrity": "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {

View file

@ -10,7 +10,7 @@
"prepare": "svelte-kit sync || echo ''", "prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"generate-pocketbase-types": "set -a && . ./.env && set +a && pocketbase-typegen --url \"$PB_TYPEGEN_URL\" --token \"$PB_SUPERUSER_TOKEN\" --out ./src/lib/types.d.ts" "generate-pocketbase-types": "node scripts/schema-to-json.mjs > .schema.tmp.json && pocketbase-typegen --json .schema.tmp.json --out ./src/lib/types.d.ts && rm -f .schema.tmp.json"
}, },
"devDependencies": { "devDependencies": {
"@internationalized/date": "^3.12.1", "@internationalized/date": "^3.12.1",

View file

@ -0,0 +1,52 @@
/**
* Extrahiert das Collections-Array aus der PocketBase-Migration und schreibt es
* als JSON auf stdout in der Form, die `pocketbase-typegen --json` erwartet.
*
* Damit entstehen die TypeScript-Typen aus dem versionierten Schema im Repo,
* ohne laufende Instanz, ohne Token und ohne Netzwerkzugriff.
*
* Aufruf über npm run generate-pocketbase-types.
*/
import { readFileSync, readdirSync } from 'node:fs'
import { join } from 'node:path'
const MIGRATIONS_DIR = new URL('../../backend/pb_migrations/', import.meta.url).pathname
// Die Snapshot-Migration ist die einzige, die ein vollständiges
// `const collections = [...]` enthält. Bei mehreren Dateien gewinnt die
// jüngste — Migrationen sind nach Zeitstempel benannt.
const candidates = readdirSync(MIGRATIONS_DIR)
.filter((f) => f.endsWith('.js'))
.sort()
.reverse()
const MARKER = 'const collections = '
for (const file of candidates) {
const source = readFileSync(join(MIGRATIONS_DIR, file), 'utf8')
const start = source.indexOf(MARKER)
if (start === -1) continue
// Vom öffnenden [ bis zur zugehörigen schließenden Klammer zählen —
// ein Regex käme an den verschachtelten Arrays der Feldoptionen nicht vorbei.
const from = start + MARKER.length
let depth = 0
for (let i = from; i < source.length; i++) {
if (source[i] === '[') depth++
else if (source[i] === ']') {
depth--
if (depth === 0) {
process.stdout.write(source.slice(from, i + 1))
process.exit(0)
}
}
}
}
console.error(
`Keine Migration mit "${MARKER.trim()}" in ${MIGRATIONS_DIR} gefunden.\n` +
`Ohne sie lassen sich die Typen nicht aus dem Repo erzeugen.`
)
process.exit(1)

View file

@ -6,11 +6,6 @@ import type PocketBase from 'pocketbase'
import type { RecordService } from 'pocketbase' import type { RecordService } from 'pocketbase'
export enum Collections { export enum Collections {
Authorigins = "_authOrigins",
Externalauths = "_externalAuths",
Mfas = "_mfas",
Otps = "_otps",
Superusers = "_superusers",
Events = "events", Events = "events",
Riders = "riders", Riders = "riders",
Runs = "runs", Runs = "runs",
@ -46,55 +41,6 @@ export type AuthSystemFields<T = unknown> = {
// Record types for each collection // Record types for each collection
export type AuthoriginsRecord = {
collectionRef: string
created?: IsoDateString
fingerprint: string
id: string
recordRef: string
updated?: IsoDateString
}
export type ExternalauthsRecord = {
collectionRef: string
created?: IsoDateString
id: string
provider: string
providerId: string
recordRef: string
updated?: IsoDateString
}
export type MfasRecord = {
collectionRef: string
created?: IsoDateString
id: string
method: string
recordRef: string
updated?: IsoDateString
}
export type OtpsRecord = {
collectionRef: string
created?: IsoDateString
id: string
password: string
recordRef: string
sentTo?: string
updated?: IsoDateString
}
export type SuperusersRecord = {
created?: IsoDateString
email: string
emailVisibility?: boolean
id: string
password: string
tokenKey: string
updated?: IsoDateString
verified?: boolean
}
export enum EventsStatusOptions { export enum EventsStatusOptions {
"draft" = "draft", "draft" = "draft",
"active" = "active", "active" = "active",
@ -182,11 +128,6 @@ export type UsersRecord = {
} }
// Response types include system fields and match responses from the PocketBase API // Response types include system fields and match responses from the PocketBase API
export type AuthoriginsResponse<Texpand = unknown> = Required<AuthoriginsRecord> & BaseSystemFields<Texpand>
export type ExternalauthsResponse<Texpand = unknown> = Required<ExternalauthsRecord> & BaseSystemFields<Texpand>
export type MfasResponse<Texpand = unknown> = Required<MfasRecord> & BaseSystemFields<Texpand>
export type OtpsResponse<Texpand = unknown> = Required<OtpsRecord> & BaseSystemFields<Texpand>
export type SuperusersResponse<Texpand = unknown> = Required<SuperusersRecord> & AuthSystemFields<Texpand>
export type EventsResponse<Texpand = unknown> = Required<EventsRecord> & BaseSystemFields<Texpand> export type EventsResponse<Texpand = unknown> = Required<EventsRecord> & BaseSystemFields<Texpand>
export type RidersResponse<Texpand = unknown> = Required<RidersRecord> & BaseSystemFields<Texpand> export type RidersResponse<Texpand = unknown> = Required<RidersRecord> & BaseSystemFields<Texpand>
export type RunsResponse<Texpand = unknown> = Required<RunsRecord> & BaseSystemFields<Texpand> export type RunsResponse<Texpand = unknown> = Required<RunsRecord> & BaseSystemFields<Texpand>
@ -197,11 +138,6 @@ export type UsersResponse<Texpand = unknown> = Required<UsersRecord> & AuthSyste
// Types containing all Records and Responses, useful for creating typing helper functions // Types containing all Records and Responses, useful for creating typing helper functions
export type CollectionRecords = { export type CollectionRecords = {
_authOrigins: AuthoriginsRecord
_externalAuths: ExternalauthsRecord
_mfas: MfasRecord
_otps: OtpsRecord
_superusers: SuperusersRecord
events: EventsRecord events: EventsRecord
riders: RidersRecord riders: RidersRecord
runs: RunsRecord runs: RunsRecord
@ -211,11 +147,6 @@ export type CollectionRecords = {
} }
export type CollectionResponses = { export type CollectionResponses = {
_authOrigins: AuthoriginsResponse
_externalAuths: ExternalauthsResponse
_mfas: MfasResponse
_otps: OtpsResponse
_superusers: SuperusersResponse
events: EventsResponse events: EventsResponse
riders: RidersResponse riders: RidersResponse
runs: RunsResponse runs: RunsResponse
@ -228,11 +159,6 @@ export type CollectionResponses = {
// https://github.com/pocketbase/js-sdk#specify-typescript-definitions // https://github.com/pocketbase/js-sdk#specify-typescript-definitions
export type TypedPocketBase = PocketBase & { export type TypedPocketBase = PocketBase & {
collection(idOrName: '_authOrigins'): RecordService<AuthoriginsResponse>
collection(idOrName: '_externalAuths'): RecordService<ExternalauthsResponse>
collection(idOrName: '_mfas'): RecordService<MfasResponse>
collection(idOrName: '_otps'): RecordService<OtpsResponse>
collection(idOrName: '_superusers'): RecordService<SuperusersResponse>
collection(idOrName: 'events'): RecordService<EventsResponse> collection(idOrName: 'events'): RecordService<EventsResponse>
collection(idOrName: 'riders'): RecordService<RidersResponse> collection(idOrName: 'riders'): RecordService<RidersResponse>
collection(idOrName: 'runs'): RecordService<RunsResponse> collection(idOrName: 'runs'): RecordService<RunsResponse>