3.4 KiB
route-options
(no submodules)
Purpose
The route-options module is the Zod-validated boundary for every per-route
configuration block. It widens the minimal RouteOptions type from
@suaveplan/api-types with the policy switches every Stage-1-and-later
adapter must honour: runtime Zod validation toggle, OpenAPI inclusion,
streaming mode, CSRF, idempotency, and a discriminated rate-limit shape.
Centralising the schema here lets adapters, plugin-loader, and the host all parse the same shape, so a route option declared in a plugin manifest, a TypeScript builder, or a JSON config file gets the same defaults.
Features
routeOptionsSchema—z.strictObjectrejecting unknown keys.- Defaults:
runtimeValidation: true,openapi: true,streaming: false. request.body/request.params/request.queryaccept any Zod schema (structural check — no instanceof, since twozinstances in the same process may not share constructors).responsesis aRecord<status, { body: ZodSchema }>so adapters can map each documented status to its validator.rateLimitis a discriminated union: either{ rpm }or{ rps }.
Usage: Basic
import { parseRouteOptions } from "@suaveplan/api-router/route-options";
const opts = parseRouteOptions({ auth: "users.read", summary: "List users" });
// opts.runtimeValidation === true (default applied)
// opts.openapi === true
// opts.streaming === false
Usage: Advanced
import z from "zod";
import { parseRouteOptions } from "@suaveplan/api-router/route-options";
const opts = parseRouteOptions({
auth: "users.write",
summary: "Create user",
tags: ["users", "admin"],
request: {
body: z.object({ name: z.string(), email: z.email() }),
query: z.object({ dryRun: z.boolean().optional() }),
},
responses: {
201: { body: z.object({ id: z.string() }) },
409: { body: z.object({ code: z.string() }) },
},
streaming: false,
csrf: true,
idempotent: true,
rateLimit: { rpm: 60 },
});
API Reference
routeOptionsSchema: z.ZodObject<…>— the validated schema. Use.parse(...),.safeParse(...), or.shapefor adapter-side introspection.parseRouteOptions(input: unknown): RouteOptions— throw-on-fail.safeParseRouteOptions(input: unknown): ZodSafeParseResult<RouteOptions>— non-throwing variant.RouteOptions—z.outputof the schema (post-defaults shape).RouteOptionsInput—z.input(caller-supplied shape).
Implementation Notes
The schema uses z.custom with a structural predicate (looking for
safeParse / _zod) rather than instanceof z.ZodType so plugins that
import zod through their own dependency cone still pass the check. This is
deliberate: two copies of zod in the dependency graph would otherwise break
instanceof-based validation in surprising ways.
z.strictObject rejects unknown keys so typos in a plugin's route options
surface at registration time, not at the first failing request. Adapters
that want a permissive parse can call routeOptionsSchema.passthrough()
instead — but the default contract is strict.
The rateLimit union is intentionally narrow: per-minute and per-second
are the two real-world axes. Other windows (per-hour, per-day) belong in
dedicated rate-limit plugins, not in the route schema.