Adapters
oRPC
Procedure-keyed changes via two interceptors — up/down around the decoded input and output, sunset headers at the adapter level.
Wiring
import { RPCHandler } from "@orpc/server/fetch";
import {
versionlessContext,
versionlessClientInterceptor,
versionlessAdapterInterceptor,
} from "@versionless/adapter-orpc";
import { v } from "./versionless";
const handler = new RPCHandler(router, {
adapterInterceptors: [versionlessAdapterInterceptor(v)], // sunset headers
clientInterceptors: [versionlessClientInterceptor()], // up/down per procedure
});
// wherever you handle the request:
const { matched, response } = await handler.handle(request, {
prefix: "/rpc",
context: { ...versionlessContext(v, { request }) },
});This is exactly how the repo's demo app (apps/demo) wires its oRPC surface. Changes are keyed by procedure, exactly like the tRPC adapter:
v.change("2026-03-01", {
describe: "user.create: name -> firstName/lastName",
procedures: ["user.create"],
request: { up: ... },
response: { down: ... },
});How it works
- Context stash. One HTTP request may batch several procedure calls, and
the exchange is procedure-keyed — so
versionlessContextstashes the instance + header getter on the context, and the client interceptor opens the per-procedure exchange itself, wherepathis known. PassvtoversionlessClientInterceptor(v)to override the stashed instance. clientInterceptors, not middleware. oRPC middleware cannot replace the input a handler receives. Client interceptors run after input decoding and before output encoding — exactly whereupanddownbelong, and before.input()validation, so old-shape inputs pass current-shape schemas.- Errors. A thrown
ORPCErroris rebuilt througherror.downchains (its{ code, status, message, data }shape is what your transforms see). A version past its sunset cutoff throwsGONE(410) with the structured body asdata; an invalid pin throwsBAD_REQUEST. adapterInterceptorsfor sunset headers. Rootinterceptorsrun below oRPC's error-to-response conversion, so thrown errors would bypass them — the adapter-level interceptor sees the final webResponseon every path and stampsSunset/Deprecationonto it.
Telemetry
One event per procedure call, with route: "trpc:user.create"-style keys
(procedure-keyed routes share the trpc: namespace) and adapter: "orpc".
Under VERCEL the flush fires per call so events survive the function
freezing.