# `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.strictObject` rejecting unknown keys. - Defaults: `runtimeValidation: true`, `openapi: true`, `streaming: false`. - `request.body` / `request.params` / `request.query` accept any Zod schema (structural check — no instanceof, since two `z` instances in the same process may not share constructors). - `responses` is a `Record` so adapters can map each documented status to its validator. - `rateLimit` is a discriminated union: either `{ rpm }` or `{ rps }`. ## Usage: Basic ```ts 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 ```ts 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 `.shape` for adapter-side introspection. - `parseRouteOptions(input: unknown): RouteOptions` — throw-on-fail. - `safeParseRouteOptions(input: unknown): ZodSafeParseResult` — non-throwing variant. - `RouteOptions` — `z.output` of 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.