http/route_spec.ts

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)

Declarations
#

13 declarations

view source

apply_middleware_specs
#

http/route_spec.ts view source

(app: Hono<BlankEnv, BlankSchema, "/">, specs: MiddlewareSpec[]): void

Apply named middleware specs to a Hono app.

app

type Hono<BlankEnv, BlankSchema, "/">

specs

type MiddlewareSpec[]

returns

void

apply_route_specs
#

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.

app

type Hono<BlankEnv, BlankSchema, "/">

specs

type RouteSpec[]

resolve_auth_guards

log

type Logger

db

used for transaction wrapping and RouteContext

type Db

authorize?

optional authorization phase; runs after input validation

type AuthorizationHandler | undefined
optional

returns

void

throws

  • Error - if two specs share the same `method` + `path` (each combination must be unique), or if any spec violates the actor-acting biconditional

AuthGuardResolver
#

AuthGuards
#

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_validation

type Array<MiddlewareHandler>

post_authorization

type Array<MiddlewareHandler>

AuthorizationHandler
#

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.

get_route_input
#

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);

c

type Context<any, any, {}>

schema

type S

returns

output<S>

generics

S

constraint z.ZodType

get_route_params
#

http/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.

c

type Context<any, any, {}>

schema

type S

returns

output<S>

generics

S

constraint z.ZodType

get_route_query
#

http/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.

c

type Context<any, any, {}>

schema

type S

returns

output<S>

generics

S

constraint z.ZodType

prefix_route_specs
#

http/route_spec.ts view source

(prefix: string, specs: RouteSpec[]): RouteSpec[]

Prepend a prefix to all route spec paths.

prefix

the path prefix (e.g. /api/account)

type string

specs

type RouteSpec[]

returns

RouteSpec[]

a new array — the input specs are not mutated

RouteContext
#

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.

db

Transaction-scoped when RouteSpec.transaction is true (the default for non-GET); pool-level otherwise.

type Db

pending_effects

Eager 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.

type Array<Promise<void>>

post_commit_effects

Deferred 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.

type Array<() => void | Promise<void>>

RouteHandler
#

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.

RouteMethod
#

RouteSpec
#

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

method

path

type string

auth

handler

description

type string

params

URL 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.

type z.ZodObject

query

URL query parameter schema. Use z.strictObject() with string fields.

type z.ZodObject

input

Request body schema. Use z.null() for routes with no body.

type z.ZodType

output

Success response body schema.

type z.ZodType

rate_limit

Rate 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.

errors

Handler-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.

transaction

Whether to wrap the handler in a database transaction.

When omitted, defaults are derived from the HTTP method: - GETfalse (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).

type boolean

Depends on
#

Imported by
#