Adapters
Hono
One middleware registered before your routes — reads the matched route, swaps the raw request and response, alias routes for rewrites.
Wiring
import { Hono } from "hono";
import { versionless, versionlessRewrites } from "@versionless/adapter-hono";
import { v } from "./versionless";
const app = new Hono();
app.use("*", versionless(v)); // before your routes
app.get("/users/:id", handler); // handlers speak the current shape
app.post("/users", handler);
versionlessRewrites(v, app); // after routes — alias routes for `rewrite:` changesHow it works
- Route detection. Inside an
app.use("*")middleware,c.req.routePathis the middleware's own pattern (/*), not the handler's — so the adapter readsc.req.matchedRoutesand takes the last non-wildcard pattern. When none exists (404, or only catch-alls matched), core falls back to matching the raw path against the set of changed routes. - Request up. The JSON body is parsed, upped, and written back by
replacing
c.req.rawwith a newRequest— the same pattern Hono's ownbodyLimitmiddleware uses. The body cache is cleared so the handler'sc.req.json()sees the transformed body, not a stale cached one. - Response down. After
next(),c.resis replaced with a newResponsecarrying the downed body (status and headers preserved,content-lengthrecomputed). Status ≥ 400 goes througherror.downchains. - Malformed JSON despite a JSON content-type is left untouched, so your handler surfaces its own parse error instead of versionless masking it.
Only application/json bodies under 1MB are transformed; everything else
passes through. Rewrite-matched requests skip body work entirely — the inner
re-dispatched request runs its own exchange.
Rewrites
Hono routes before middleware can intervene on unrouted paths, so — like
Elysia — rewrites need alias routes. versionlessRewrites(v, app) registers
one per rewrite:
- Old-pinned client → re-dispatched through
app.fetchto the rewritten target (method swapped if the rewrite changes it). - Current client →
c.notFound().
Call it after all changes are registered and all routes are wired.
Sunsets, errors, serverless
- Sunset headers go on every response for scheduled versions; past the cutoff
the middleware returns
410with the structured body itself. - Invalid version strings return
400 { "error": "invalid_api_version", "message": ... }. - On Workers/Edge under
VERCEL, the telemetry flush is handed toc.executionCtx.waitUntil; elsewhere it settles as fire-and-forget.