first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,92 @@
# `route-options`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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<status, { body: ZodSchema }>` 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<RouteOptions>`
— 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.