2.3 KiB
Server architecture
Conventions for code in app/server/. The README covers the
layout, the entry points and how services reach routes.
Services
A long-lived service is a factory function. It takes its dependencies as
arguments, keeps its state in closure variables and returns a plain object
typed by an interface, such as createOidcService in oidc/provider.ts,
createAuthService in web/auth.ts and createLiveStore in
headscale/live-store.ts.
- Pass dependencies in. A service never imports another service's module to reach its state, and there's no module-level state.
- A service with background work exposes
start/stopordispose, as the auth service's session pruning does.createAppContextregisters it with the context's disposers. - Classes are the exception: the
Integrationbase inconfig/integration/abstract.tsthat the Docker, Kubernetes and proc integrations extend, error types such asActionErrorandConfigError, andHeadscaleDNSConfig.
Wiring
createAppContext in context.ts builds every service once at startup from
the config alone. createRouterContext seeds a named React Router context for
each, app.ts calls it for every request, and routes read them with
context.get(authContext) and so on. Tests build the context the same way, so
there's nothing to override.
A service that can be switched off is a Feature<T> (feature.ts): either
enabled with the service, or disabled with a reason the UI can show, as
the agent settings page and the OIDC callback do.
Errors
- A service operation with expected failures returns
Result<T, E>(result.ts) instead of throwing. The error carries a typedcode, such asOidcErrorCode, that maps to a log message, the UI and a troubleshooting section in the docs. - Route actions are built with
createActionand throwActionErrorfor any state of the world. See "Action errors" inAGENTS.md. - Invalid configuration at startup throws
ConfigError.
Tests
Unit tests (tests/unit/) create a fresh service with the config they need,
so no module mocking or resets are involved. Anything that talks to Headscale
is an integration test (tests/integration/) against real containers: a
disposable tailnet with Tailscale nodes, Dex for OIDC, and the agent. See
tests/README.md.