# common-schemas ## Purpose Library of common Zod schemas decorated with OpenAPI `example` metadata. Covers the universal HTTP query / path / pagination primitives that every API plugin reaches for — UUID, email, URL, ISO datetime, signed and unsigned integers, page/limit/sort order. Pulling these into a single module ensures every plugin documents the same constraints, prevents drift, and makes the OpenAPI spec consistent across the entire multi-plugin server. The schemas are exported as a single `CommonSchemas` const object so consumers can spread, destructure, or `.openapi(...)`-extend any subset. They are deliberately not pre-registered against any `SchemaRegistry` — each consumer chooses whether (and under which name) to register them. ## Features - `uuid`, `email`, `url`, `datetime` — string formats with examples. - `positiveInt`, `nonNegativeInt` — integer constraints. - `page` / `limit` — coercing pagination inputs with sane defaults. - `sortOrder` — `"asc" | "desc"` enum, default `"desc"`. - Every entry carries an OpenAPI `example` so the generated spec is rich. ## Usage: Basic ```ts import z from "zod"; import { CommonSchemas } from "@suaveplan/api-openapi/schema-registry"; const ListQuery = z.object({ page: CommonSchemas.page, limit: CommonSchemas.limit, sort: CommonSchemas.sortOrder, }); ``` ## Usage: Advanced ```ts import z from "zod"; import { CommonSchemas, createSchemaRegistry, } from "@suaveplan/api-openapi/schema-registry"; const registry = createSchemaRegistry(); // Register pagination primitives once under shared names so OpenAPI emits // `#/components/schemas/pagination.page` references rather than inlining // the same constraint in every operation. registry.register("pagination.page", CommonSchemas.page); registry.register("pagination.limit", CommonSchemas.limit); registry.register("pagination.sortOrder", CommonSchemas.sortOrder); const ListUsersQuery = z.object({ page: registry.get("pagination.page")!, limit: registry.get("pagination.limit")!, sort: registry.get("pagination.sortOrder")!, q: z.string().optional(), }); ``` ## API Reference - `CommonSchemas.uuid: ZodString` — UUID string with example. - `CommonSchemas.email: ZodString` — email string. - `CommonSchemas.url: ZodString` — URL string. - `CommonSchemas.datetime: ZodString` — ISO-8601 datetime. - `CommonSchemas.positiveInt: ZodNumber` — `> 0`, integer. - `CommonSchemas.nonNegativeInt: ZodNumber` — `≥ 0`, integer. - `CommonSchemas.page: ZodCoerce` — `min 1`, default `1`. - `CommonSchemas.limit: ZodCoerce` — `min 1, max 100`, default `20`. - `CommonSchemas.sortOrder: ZodEnum<"asc" | "desc">` — default `"desc"`. ## Implementation Notes `page` and `limit` use `z.coerce.number()` rather than `z.number()` so they accept query-string inputs (always strings on the wire). Without coercion every plugin would have to wrap them in `.transform(Number)` manually, defeating the point of a shared catalogue. The `.openapi({ example: ... })` annotation surfaces in the generated spec without affecting validation. Consumers that want richer descriptions (`{ description: "..." }`) call `.openapi(...)` on a copy rather than mutating the shared schema. `sortOrder` defaults to `"desc"` because the most common pagination pattern is "latest first". Plugins that want a different default override locally with `CommonSchemas.sortOrder.default("asc")`. `CommonSchemas` is `as const` — TypeScript surfaces every field with its narrowest literal type. Consumers can `typeof CommonSchemas` to derive per-field types without importing each schema individually.