http/route_spec.ts view source
(app: Hono<BlankEnv, BlankSchema, "/">, specs: MiddlewareSpec[]): void Apply named middleware specs to a Hono app.
app
Hono<BlankEnv, BlankSchema, "/">specs
MiddlewareSpec[]returns
void Introspectable route spec system for Hono apps.
Routes are defined as data (method, path, auth, input/output schemas, handler), then applied to Hono. The attack surface is generated from the specs — always accurate, always complete.
Input/output schemas align with SAES ActionSpec conventions:
- input: Zod schema for the request body (z.null() for no body)
- output: Zod schema for the success response body
- z.strictObject() for inputs (reject unknown keys)
13 declarations
http/route_spec.ts view source
(app: Hono<BlankEnv, BlankSchema, "/">, specs: MiddlewareSpec[]): void Apply named middleware specs to a Hono app.
appHono<BlankEnv, BlankSchema, "/">specsMiddlewareSpec[]void http/route_spec.ts view source
(app: Hono<BlankEnv, BlankSchema, "/">, specs: RouteSpec[], resolve_auth_guards: AuthGuardResolver, log: Logger, db: Db, authorize?: AuthorizationHandler | undefined): void Apply route specs to a Hono app.
For each spec: resolves auth to guards via the provided resolver, adds input validation middleware (for routes with non-null input schemas), runs the optional authorization phase to resolve the acting actor + build the request context, wraps handler with DEV-only output and error validation, wraps with error catch layer (catches ThrownJsonrpcError and generic errors), and registers the route.
Per-route middleware order: params → query → pre-validation auth
guards (401) → input validation (400) → authorization phase →
post-authorization auth guards (403) → handler. The 401 check runs
before any body parsing so unauthenticated callers never see
route-shape information from parse failures. Input validation runs
before the authorization phase (validate first, authorize after) so
the authorization phase reads c.var.validated_input.acting as a
typed Zod field instead of pre-parsing the raw body. Role /
credential-type denials still surface 403 last; trade-off is that
authenticated-but-unauthorized callers can distinguish 400
(validation) from 403 (authorization), a defense-in-depth concession
deemed acceptable because the route surface is public via
spec/codegen JSON.
Each handler receives a RouteContext with:
- db: transaction-scoped when RouteSpec.transaction is true; pool-level otherwise
- pending_effects: eager fire-and-forget pool-write queue
- post_commit_effects: deferred-thunk queue (push via emit_after_commit)
Also enforces registry-time invariant 2 from the auth-shape design:
auth.actor !== 'none' ⟺ input or query declares acting?: ActingActor.
REST is bi-located (GETs declare acting on query, mutations on
input), so the check passes both slots; the action-dispatcher
registries (compile_action_registry) share the same helper with
input only — ActionSpec has no query shape.
appHono<BlankEnv, BlankSchema, "/">specsRouteSpec[]resolve_auth_guardsmaps RouteAuth to middleware — use fuz_auth_guard_resolver from auth/auth_guard_resolver.ts
logLoggerdbused for transaction wrapping and RouteContext
authorize?optional authorization phase; runs after input validation
AuthorizationHandler | undefinedvoid Error - if two specs share the same `method` + `path` (each combination must be unique), or if any spec violates the actor-acting biconditionalhttp/route_spec.ts view source
AuthGuardResolver Resolves a RouteAuth to middleware guard handlers.
Injected into apply_route_specs to decouple route registration from auth-specific middleware. See fuz_auth_guard_resolver in auth/auth_guard_resolver.ts for the standard implementation.
http/route_spec.ts view source
AuthGuards Two-phase auth guard set returned by AuthGuardResolver.
pre_validation runs before input validation — 401 checks live here
so unauthenticated callers never see route-shape information from
input parsing failures. post_authorization runs after the
authorization phase has populated RequestContext — role / keeper
checks live here because they read c.var.request_context.role_grants.
pre_validationArray<MiddlewareHandler>post_authorizationArray<MiddlewareHandler>http/route_spec.ts view source
AuthorizationHandler Per-route authorization phase. Runs after pre-validation auth guards
AND input validation; resolves the acting actor (when auth.actor !== 'none')
by reading c.var.validated_input.acting and sets the request context on
the Hono context. Per-route order in apply_route_specs: params → query
→ pre-validation auth (401) → input validation (400) → authorization
phase → post-authorization auth (403) → handler.
Returns a Response to short-circuit (resolution failure → 400 / 500),
or void to continue. The http framework stays auth-agnostic — fuz_app
provides the implementation via create_fuz_authorization_handler in
auth/request_context.ts.
http/route_spec.ts view source
<S extends z.ZodType>(c: Context<any, any, {}>, schema: S): output<S> Get validated input from the Hono context.
Call after the input validation middleware has run. Pass the route's
input Zod schema to infer the typed shape directly:
const input = get_route_input(c, my_route_spec.input);Or pass an explicit type argument when the schema isn't in scope:
const input = get_route_input<MyInput>(c);cContext<any, any, {}>schemaSoutput<S> Sz.ZodTypehttp/route_spec.ts view source
<S extends z.ZodType>(c: Context<any, any, {}>, schema: S): output<S> Get validated URL path params from the Hono context.
Call after the params validation middleware has run. Pass the route's
params schema to infer the typed shape, or supply an explicit type
argument. See get_route_input for the two call shapes.
cContext<any, any, {}>schemaSoutput<S> Sz.ZodTypehttp/route_spec.ts view source
<S extends z.ZodType>(c: Context<any, any, {}>, schema: S): output<S> Get validated URL query params from the Hono context.
Call after the query validation middleware has run. Pass the route's
query schema to infer the typed shape, or supply an explicit type
argument. See get_route_input for the two call shapes.
cContext<any, any, {}>schemaSoutput<S> Sz.ZodTypehttp/route_spec.ts view source
(prefix: string, specs: RouteSpec[]): RouteSpec[] Prepend a prefix to all route spec paths.
prefixthe path prefix (e.g. /api/account)
stringspecsRouteSpec[]RouteSpec[] a new array — the input specs are not mutated
http/route_spec.ts view source
RouteContext Per-request deps provided by the framework to route handlers.
Audit writes and other rollback-resilient fire-and-forget calls run
through AppDeps.audit.emit(ctx, input) (see auth/audit_emitter.ts),
which captures the pool inside its closure — handlers can never
accidentally write audits against the request transaction.
Routes whose body manages its own transaction (signup, bootstrap)
declare transaction: false on the spec, which makes route.db the
pool — they reach for it directly.
dbTransaction-scoped when RouteSpec.transaction is true (the default
for non-GET); pool-level otherwise.
pending_effectsEager fire-and-forget queue — push the in-flight Promise<void> for
pool writes already running (audit emits, session touch, api-token
usage tracking). The flush middleware drains via
flush_pending_effects after the handler returns.
Array<Promise<void>>post_commit_effectsDeferred post-commit thunks — do not push directly; reach for
emit_after_commit(ctx, fn) from pending_effects.ts. The flush
middleware invokes each thunk after the handler (and any wrapping
db.transaction) returns, closing the microtask-ordering window
that an eager Promise.resolve().then(fn) leaves open inside the
transaction.
Array<() => void | Promise<void>>http/route_spec.ts view source
RouteHandler Route handler function — receives the Hono context and a RouteContext with per-request deps (db, pending_effects).
TypeScript allows fewer params, so handlers that don't need route
can use (c) => ... without changes.
http/route_spec.ts view source
RouteMethod HTTP methods supported by route specs.
http/route_spec.ts view source
RouteSpec A single route definition — the unit of the surface map.
input and output schemas align with SAES ActionSpec naming.
Use z.null() for routes with no request body (GET, DELETE without body).
methodpathstringauthhandlerdescriptionstringparamsURL path parameter schema. Use z.strictObject() with string fields matching :param segments.
REST-only — actions dispatch through a single JSON-RPC endpoint and encode
everything in input, so params doesn't appear on ActionSpec.
z.ZodObjectqueryURL query parameter schema. Use z.strictObject() with string fields.
z.ZodObjectinputRequest body schema. Use z.null() for routes with no body.
z.ZodTypeoutputSuccess response body schema.
z.ZodTyperate_limitRate limit key type — declares what this route's rate limiter is keyed on.
When set, 429 (RateLimitError) is auto-derived in derive_error_schemas. The actual RateLimiter instance is still wired imperatively in the handler — this field is metadata for surface introspection and policy invariants.
errorsHandler-specific error response schemas keyed by HTTP status code.
Middleware errors (auth 401/403, validation 400, rate limit 429) are
auto-derived from auth, input, and rate_limit. Declare handler-specific
errors here (e.g., 404 for not-found, 409 for conflicts).
Explicit entries override auto-derived ones for the same status code.
transactionWhether to wrap the handler in a database transaction.
When omitted, defaults are derived from the HTTP method:
- GET → false (read-only, no transaction)
- All others (POST, PUT, DELETE, PATCH) → true
Set explicitly to override the default (e.g., false for a POST
that manages its own transaction like signup).
boolean