Skip to content

ADR-0008: Technology Stack ​

  • Status: Active
  • Date: 2026-09-24
  • Deciders: Project maintainer
  • Related: ADR-0001, ADR-0002, ADR-0007, ADR-0009, ADR-0010

Context ​

The project is self-funded, public (AGPL-3.0, ADR-0009), and must be easy for other clubs to self-host on a single small VM. We need server-side rendering with progressive enhancement (CLAUDE.md lightweight budget), PostgreSQL row-level security (ADR-0001), and one language across client and server so a small team (or one person plus agents) can maintain it.

Decision ​

Runtime & framework ​

ConcernChoiceLicenceNotes
LanguageTypeScript 6.0 (strict)Apache-2.0Stay on 6.x until SvelteKit and typescript-eslint support 7.
RuntimeNode.js 26 (LTS from October 2026)MITPinned in .nvmrc and engines. Upgrade to each new LTS. Node ≥ 25 no longer bundles Corepack; install it with npm i -g corepack (MIT).
FrameworkSvelteKit 2 + Svelte 5MITSSR, form actions that work without JS, small client bundles.
Adapter@sveltejs/adapter-nodeMITSingle Node process behind a reverse proxy.
Package managerpnpmMITLockfile committed; pnpm-workspace.yaml (app at root, docs/ as a workspace package).

Data ​

ConcernChoiceLicenceNotes
DatabasePostgreSQL ≥ 16PostgreSQLCI tests the oldest supported and the latest major.
DriverpgMIT
QueriesKyselyMITTyped query builder; raw SQL allowed via sql tag for RLS/advanced features.
Types from schemakysely-codegen (dev)MITGenerated types committed.
MigrationsPlain, forward-only .sql files in db/migrations/ + small in-house runner—RLS policies, grants and roles are SQL anyway; no ORM migration DSL. The runner takes a Postgres advisory lock and runs on app start (opt-out: RA_MIGRATE_ON_START=false).

Libraries (runtime) ​

ConcernChoiceLicence
Input validationValibotMIT
Password hashing@node-rs/argon2MIT
SMTP (optional)NodemailerMIT-0
QR codes for hand-over linksqrcodeMIT
Image re-encoding (logos, photos)sharpApache-2.0 (bundles libvips, LGPL-3.0, dynamically linked)
IconsLucide (inline SVG, tree-shaken)ISC
ChartsHand-written SVG components—
FontsSystem font stacks only—

No CSS framework, no component kit, no state-management library, no CSS-in-JS: plain Svelte components with scoped CSS and the --ra-* tokens (ADR-0002).

Tooling (development only) ​

ConcernChoiceLicence
Unit/integration testsVitestMIT
E2E testsPlaywrightApache-2.0
Accessibility checks@axe-core/playwrightMPL-2.0 (dev only, not distributed)
Lint / formatESLint, Prettier, svelte-checkMIT
Local Postgresscripts/dev-db.ts running postgres:18-alpine via docker run (Docker Engine or Podman; no Compose plugin needed)Apache-2.0
Docs siteVitePress (+ Mermaid)MIT

Process model ​

  • One Node process serves SSR pages, form actions and JSON endpoints (+server.ts).
  • Periodic housekeeping (expired sessions/tokens cleanup, inactivity reminders) runs in-process on a timer guarded by a Postgres advisory lock. No external queue or cron.
  • Rate limiting is stored in Postgres (a small counter table) so it works across restarts and multiple instances.
  • Configuration comes only from environment variables, parsed and validated at startup by one config module; the process refuses to start on invalid config.

Repository layout ​

src/
  lib/server/        # server-only: db, tenancy, auth, rbac, gdpr, forecasting adapters
  lib/domain/        # pure logic (forecasting engine, permission catalogue, theme validation)
  lib/components/    # UI components
  lib/i18n/          # t(), formatters
  routes/            # /t/[tenant]/…, /platform/…, /auth/…
db/migrations/       # NNNN_description.sql
locales/{nl,en}/     # dictionaries (ADR-0007)
docs/                # VitePress site + SYSTEM_SPEC.md (ADR-0010)
ai-docs/             # ADRs and agent prompts
tests/               # integration, isolation, e2e

Consequences ​

Positive ​

  • Everything is free and open source; every licence is compatible with AGPL-3.0 (ADR-0009).
  • Self-hosting is one container + Postgres.
  • Server-first rendering keeps JS small and makes core flows work on poor connections.

Negative / trade-offs ​

  • SvelteKit major upgrades may require migration work.
  • sharp ships native binaries; unusual platforms may need a build from source. It's the only native dependency besides @node-rs/argon2 (which ships prebuilt binaries).
  • Writing our own migration runner and charts is extra code, but little code.

Follow-ups ​

  • ADR-0001's minimum PostgreSQL version is raised to 16 by this ADR.

Released under the GNU AGPL-3.0-or-later.