Composition & Startup Validation
A Flare app is assembled by registering a graph of tokens, classes, and routes on FlareHost, then calling host.build() to validate the entire graph and compile per-route pipelines before the server accepts traffic. Registration is explicit: call host.cfg(), host.scoped() or host.singleton(), and host.http.* before build(). Registrations after the first successful build() are ignored because later calls return the cached app.
The three pieces
Section titled “The three pieces”import { FlareHost } from "@flare-ts/core";import { node } from "@flare-ts/core/node";
const host = new FlareHost(node);
host.cfg(DbConfig); // 1. config tokenshost.singleton(DbService); // 2. service registrationshost.http.controller("/api", ApiController); // 3. routes / arcs- Config tokens: sections of
flare.jsonorFLARE__env vars you want typed (flareConfig("db", { ... })). Register each withhost.cfg(token)so the host parses and validates that section duringbuild(). - Service registrations: classes the DI container resolves. Use
host.scoped()for per-request instances. Usehost.singleton()for per-process instances on Node (singletons are created atbuild()in production). On Cloudflare Workers,host.singleton()throws at registration time; register the same class withhost.scoped()instead. Both methods require astatic depsarray (may be[]). Omittingstatic depsthrows when you callhost.scoped()orhost.singleton(), not atbuild(). - Routes / arcs: inline handlers, controllers, middleware, and groups on
host.http. State tokens enter the graph when routes declarestate: [...]and middleware declaresprovides: [...].
Logger transports register on host.logging. Error handlers register on host.http.error(). CORS registers on host.http.cors(). See Host for the full registration table.
What build() validates
Section titled “What build() validates”host.build() is synchronous and runs in order: config compilation, logger bootstrap, three composite validator suites (service, then HTTP, then config), then HTTP arc compilation (pipelines, router, state wiring). Validator errors throw FlareValidationError; warnings log after compilation succeeds (such as DEAD_MIDDLEWARE and ORPHANED_CONTRACT_ENTRY).
After validators pass, the HTTP arc compiles pipelines. State provisioning is checked here: if a controller’s static state or a middleware class’s static state includes a token that no earlier middleware provides in a before() hook, compilation throws a plain Error, not a FlareValidationError. See Arc model for provisioning rules.
The sections below summarize the most common codes. See Failure modes for the full catalog, example messages, and registration-time errors.
Service validator
Section titled “Service validator”Covers the dependency graph, captive deps, and lifecycle hooks.
UNDECLARED_DEPENDENCY: a service listsFooinstatic depsbutFoowas never registered. Register it withhost.scoped()orhost.singleton()beforebuild().CIRCULAR_DEPENDENCY: A depends on B depends on A in the service graph. Break the cycle by restructuringstatic depsor introducing an intermediate service.CAPTIVE_DEPENDENCY: a singleton depends on a scoped service. Register the dependency as a singleton too, or make the dependent scoped.INVALID_LIFECYCLE_HOOK/CONTRADICTORY_LIFECYCLE_HOOKS: wrong hook for the lifetime (for exampleonStart()on a scoped service, ordispose()on a singleton). UseonStop()on singletons and avoid conflicting hooks on the same class.CONTROLLER_LIFECYCLE_HOOK/MIDDLEWARE_LIFECYCLE_HOOK: lifecycle hooks declared on a controller or middleware class. Move startup and shutdown logic to services or arc hooks instead.CONTROLLER_UNREGISTERED_DEP/MIDDLEWARE_UNREGISTERED_DEP:static depsreferences a service that was never registered. Register the service beforebuild().
app.test({ replace }) re-runs the service validator against the post-replacement graph and checks that each replacement class extends the token being replaced (throws FlareTestError, not FlareValidationError).
HTTP validator
Section titled “HTTP validator”Covers routes, middleware, contracts, and CORS.
ROUTE_WILDCARD_NOT_LAST/ROUTE_MISSING_PARAM_NAME: malformed path patterns. Fix the path string or param segment names.ROUTE_QUERY_PARAM_COLLISION/DUPLICATE_ROUTE_PARAM: route and query param name collisions. Rename one side.DUPLICATE_ROUTE_METHOD/DUPLICATE_ROUTE_PATTERN/DUPLICATE_ROUTE_PIPELINE: conflicting route registrations. Remove or rename the duplicate.MIDDLEWARE_STATE_CYCLE: mutualstate/providesdependencies in global middleware. Restructure middleware order or split responsibilities.ORPHANED_CONTRACT_ENTRY(warning): contract entry with no matching handler method. Add the handler or remove the contract key.DEAD_MIDDLEWARE(warning): global middleware excluded by every controller. Remove the middleware or adjust group exclusions.CORS_CREDENTIALS_WILDCARD/CORS_PARTIAL_WILDCARD: invalid CORS origin/credentials combinations. Use an explicit origin list whencredentials: true.
Contract body and query schemas validate on each request (invalid bodies return 400). Build time checks contract wiring only: orphaned handler keys warn as ORPHANED_CONTRACT_ENTRY, and unsupported route param types (such as float in a route segment) fail at HTTP compile. See Contracts.
Config validator
Section titled “Config validator”Covers config token registration and required fields.
UNREGISTERED_CONFIG_TOKEN: a class declaresstatic config = [DbConfig]buthost.cfg(DbConfig)was never called. Callhost.cfg(DbConfig)beforebuild().MISSING_CONFIG_KEY/MISSING_CONFIG_FIELD: a registered token’sflare.jsonsection or required descriptor field is absent. Add the section or field to config.- Config schema parsing runs before the validator pass. Invalid types throw a plain
Error(such asConfig validation failed: ...) rather thanFlareValidationError.
Failure before traffic
Section titled “Failure before traffic”Validator and compile failures both happen before the server binds a port:
$ tsx src/main.tsError: NoDepsService is missing static 'deps'. at FlareHost.scoped (...)$ tsx src/main.tsError: [flare] Build failed with 1 validation error:
1. [UNDECLARED_DEPENDENCY] Service GreetService has an undeclared dependency: TagService. Hint: Register TagService with host.scoped() or host.singleton() before calling host.build().$ tsx src/main.tsError: MeController requires state token AuthUser that is not provided by any preceding middleware. Please ensure that a preceding middleware in the chain provides this state token.A deploy that fails this way never accepts traffic with a half-built pipeline.
Idempotent build
Section titled “Idempotent build”host.build() is safe to call multiple times. The second call returns the cached app:
const app = host.build(); // compilesconst same = host.build(); // returns the same app instanceThis matters in tests. A typical entry file calls build() and run() at module scope. When your test imports that module, build() has already run. Calling host.build() again returns the same compiled app; validators and pipeline compilation do not run twice. To drive requests, call app.test() on that cached app to get a TestAppHandle.
In FLARE_MODE=test, scoped and singleton compilation are deferred until app.test({ replace }) so test doubles can substitute classes before any constructor runs. HTTP arc compilation still runs on the first host.build(). When app.test() runs, scoped and singleton registrations compile so substituted classes can instantiate (pipelines and router are not recompiled). See Testing.