Skip to content
Flare 0.1.x is pre-release. Expect breaking changes before 1.0 — see the changelog.

Failure modes

Flare separates four failure phases. Knowing which phase threw tells you whether to fix a registration call, a dependency graph, pipeline wiring, or a single request.

PhaseWhenError type
1. Registrationhost.scoped(), host.http.use(), etc.plain Error
2. host.build()config parse, then validatorsplain Error or FlareValidationError
3. HTTP compileend of host.build(), after validators passplain Error
4. Requestapp.fetch() / runtime trafficFlareResponse or plain Error

Test-only failures (FlareTestError during app.test() / app.reset()) are a separate track; see Test mode extras below.

These throw when you call a registration API, not at build():

TriggerMessage / behavior
Service without static deps${Service} is missing static 'deps'. at host.scoped() / host.singleton()
Controller/middleware without static depsSame at host.http.use() / .controller()
Controller/middleware without static state${Class} is missing static 'state'. (or combined deps + state)
Error handler class without static deps${Class} is missing static 'deps'. at host.http.error()
host.singleton() on Workers[flare] host.singleton() is not supported on Cloudflare Workers; edge runtimes have no long-lived process — use host.scoped() instead.
Invalid controller classInvalid controller argument for path ${path}. Must be a ControllerClass.
Invalid middleware classInvalid middleware argument. Must be a MiddlewareClass.
Bad route path at registerPath must start with "/": ${path} / Path must not end with "/": ${path} / Path must not contain empty segments (double slash): ${path} (also Group prefix must … for host.http.group())
Missing handler functionMissing ${label} function. when a route, middleware, or error-handler overload omits the callback
Route path is "/" on a handlerPath cannot be "/". Omit the argument for controller root routes. (@Get / @Post decorators)
Unsupported HTTP method on handlerUnsupported HTTP method "${method}" on route "${Class}.${path}". Supported methods are: … (decorator evaluation)
Duplicate method on same path at registerDuplicate route registration for ${method} ${path}. Each route can only have one handler per HTTP method. (host.http.route() synthetic controller)

Fix: declare public static override deps = [] (or real deps), declare static state = [] when the class uses state: [...], register only valid controller/middleware classes, and use host.scoped() on Cloudflare Workers instead of host.singleton().

Config parse (plain Error, before validators)

Section titled “Config parse (plain Error, before validators)”

If a registered config section fails schema parse during the config step at the start of host.build(), build() throws before any validator runs:

Config validation failed: …

The payload is JSON-serialized parse errors. There is no FlareValidationError code for this failure.

After config and logger compile, host.build() runs service, HTTP, and config validator suites. Entries with severity: "error" throw FlareValidationError:

[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().

(Node and test runners typically prefix this with Error: when printing the exception.)

Each entry has a code, message, and optional hint. The thrown message lists only severity: "error" entries. On a failed host.build(), the thrown error’s errors array contains only those error-severity entries (warnings are not included). After a successful build, warnings log through the configured logger transports and never appear on the validation error.

FlareValidationError is exported from @flare-ts/core/errors (and @flare-ts/core). Catch with instanceof FlareValidationError or inspect err.errors:

import { FlareValidationError } from "@flare-ts/core/errors";
try {
host.build();
} catch (err) {
if (err instanceof FlareValidationError) {
for (const entry of err.errors) {
console.error(entry.code, entry.message);
}
}
}

Decorator route paths (@Get("/a//b"), etc.) are not checked for double slashes at evaluation time; the same shape problem surfaces at host.build() as ROUTE_EMPTY_SEGMENT instead.

See Composition & startup validation for validator ordering. Warning codes (ORPHANED_CONTRACT_ENTRY, DEAD_MIDDLEWARE) are listed in the HTTP table below; they never throw.

CodeMeaning
UNDECLARED_DEPENDENCYstatic deps references a service never registered
CIRCULAR_DEPENDENCYDependency cycle in the service graph
CAPTIVE_DEPENDENCYSingleton depends on a scoped service
CONTROLLER_UNREGISTERED_DEPRoute/controller deps references unregistered service
MIDDLEWARE_UNREGISTERED_DEPMiddleware deps references unregistered service
INVALID_LIFECYCLE_HOOKHook on wrong lifetime (e.g. dispose() on a singleton: use onStop() instead, or switch to host.scoped())
CONTRADICTORY_LIFECYCLE_HOOKSConflicting hook declarations (e.g. onStart + dispose on the same class)
CONTROLLER_LIFECYCLE_HOOKonStart / onStop / dispose on a controller
MIDDLEWARE_LIFECYCLE_HOOKLifecycle hook on middleware
CodeMeaning
DUPLICATE_ROUTE_METHODSame method registered twice on one path
DUPLICATE_ROUTE_PATTERNConflicting path patterns
DUPLICATE_ROUTE_PIPELINEDuplicate pipeline identity
ROUTE_EMPTY_SEGMENTEmpty path segment
ROUTE_MISSING_PARAM_NAME:param segment without a name
ROUTE_INVALID_PARAM_NAMEInvalid param identifier
ROUTE_MISSING_WILDCARD_NAME*segment without a name
ROUTE_WILDCARD_NOT_LAST*segment not final
DUPLICATE_ROUTE_PARAMSame param name twice on one path
ROUTE_QUERY_PARAM_COLLISIONQuery key collides with route param name
MIDDLEWARE_STATE_CYCLEGlobal middleware state/provides cycle
CORS_CREDENTIALS_WILDCARDInvalid CORS credentials + * origin
CORS_NEGATIVE_MAX_AGENegative maxAge
CORS_PARTIAL_WILDCARDInvalid partial wildcard origin
ORPHANED_CONTRACT_ENTRYWarning: contract entry with no handler
DEAD_MIDDLEWAREWarning: global middleware excluded from every route
CodeMeaning
UNREGISTERED_CONFIG_TOKENClass uses static config but host.cfg() never called
MISSING_CONFIG_KEYflare.json section missing
MISSING_CONFIG_FIELDRequired field absent after merge

Accessing host.logger before logger bootstrap completes (including before host.build() finishes) throws:

Logger not initialized yet. Accessing the host logger before build completes logger bootstrap is not allowed.

After validators pass, the HTTP arc compiles pipelines. Failures here are plain Error, not FlareValidationError:

TriggerMessage
State not provided${Consumer} requires state token ${Token} that is not provided by any preceding middleware. Please ensure that a preceding middleware in the chain provides this state token.
Duplicate state providerDuplicate state token provided by middleware ${name}: ${Token} already provided by middleware ${other}. Each state token can only be provided by one middleware in the chain.
Empty route tableno routes provided
Route cap exceeded1025 routes exceeds maximum of 1024
No routes on controllerController ${name} has no route handlers. Add at least one decorated method.
Middleware with no hooksMiddleware ${name} must implement at least one of the before(), after(), or finally() lifecycle hooks.
Invalid route primitive in contractHandler ${name} defines a route parameter "${key}" with unsupported type "float". Route parameters can only be string or integer primitives.
Duplicate handler at compileDuplicate route registration for GET /api/foo. Each route can only have one handler per HTTP method.
Group excludes unknown middleware[flare] Group tried to exclude middleware "${name}" but it is not registered in the global middleware chain.

Fix: add provides: [Token] on middleware that runs before any route or middleware class that lists Token in static state. See State.

These return a FlareResponse directly. Custom host.http.error() handlers are not invoked for contract validation failures.

SituationStatusBody
Inbound path malformed (no leading /, trailing /, or //)400{ error: "Invalid request path. Paths must start with \"/\", must not contain empty segments (\"//\"), and must not end with a trailing slash except for \"/\"." } (same body for all rejected shapes)
Path not found404"Not Found" (plain string)
Path found, method missing405"Method Not Allowed" (plain string) + Allow header listing methods registered on that path (includes HEAD when GET exists)
App not built503{ error: "Application not ready. Call host.build() before handling requests." }
Body over limit413{ error: "ContentTooLarge", code: 413, detail: { maxBytes } }
Contract route params invalid400{ error: "Invalid route parameters. Check that your URL path matches the expected format." }
Contract query invalid400{ error: "Invalid query parameters. Check that your URL query string matches the expected format." }
Contract body invalid (schema)400{ error: "Invalid request body", details: … }
Contract body invalid (other)400{ error: "Invalid request body" }
Handler throwsYour host.http.error() mapper, or framework default 500 { error: "Internal Server Error" } when unmapped

For middleware, handler, body-prep, and finally throws that go through host.http.error(), see HTTP errors.

These throw plain Error during pipeline execution without going through host.http.error(). Node and Cloudflare adapters catch them at the runtime boundary and return 500 { error: "Internal Server Error" }.

SituationMessage
Handler returns nothing / bad typeHandler returned null/undefined. Did you forget to return a response? / Handler returned an unsupported type. Use a response helper or return a FlareResponse.
ctx.state.set() invalid value[flare] State values cannot contain circular references. / [flare] State values must be primitives, arrays, or plain objects. Store mutable resources in an injected service instead.
Unsupported route primitive at runtimeOnly float is rejected at HTTP compile. Route descriptors are typed as string or int; if an unsupported primitive still reaches the runtime parser, the client receives 400 with the generic invalid route-parameters message above.
this.inject() undeclared token[flare] ${Class} called inject("${Token}") but "${Token}" is not declared in ${Class}.deps. Add it to the static deps array.
this.config() with no static config[flare] ${Class} called config("${token.key}") but ${Class} does not declare a static config array. Add "static config = [/* tokens */]" to the class.
this.config() undeclared token[flare] ${Class} called config() with token "${token.key}" but "${token.key}" is not declared in ${Class}.config. Add it to the static config array.
ctx.state.require() missing tokenStateToken ${Token} not found in FlareHttpContext state.
this.inject() with an unregistered tokenServiceToken ${Token} not registered in container.

Once FLARE_MODE=test is set before the host module loads, host.build() returns a test app and the rows below apply. The first row is the exception: calling app.test() on a production app (no test mode at host construction).

SituationResult
app.test() on a production app (host.build() without FLARE_MODE=test at host construction)plain Error: [flare] app.test() called on a non-test app. Set FLARE_MODE=test in your test runner env before importing the host module (e.g. vitest: test.env.FLARE_MODE = 'test').
Second app.test() on same hostFlareTestError: app.test() may only be called once per host instance. Use handle.reset({ replace }) to swap services between scenarios.
app.test({ replace }) fails validatorsFlareTestError: app.test() validation failed: + [CODE] … lines
Invalid replace targetFlareTestError: ${Token} is not a registered service. Replace targets must be registered via host.singleton() or host.scoped() / ${Replacement} does not extend ${Token}
app.reset() misuseFlareTestError: app.reset() called without FLARE_MODE=test. or app.reset() called before app.test(); nothing to reset.
handle.reset() before app.test()FlareTestError: handle.reset() called before app.test(); nothing to reset.
Bad fetch("METHOD /path") targetFlareTestError: Invalid target "…". Expected "METHOD /path". or Invalid target "…". Path must start with "/".
mockContext bad state keyFlareTestError: mockContext received an invalid state key at index …: expected a StateToken, got …

FlareTestError is exported from @flare-ts/core/testing. Import it when you need instanceof checks in tests.