3.6 KiB
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
exampleso the generated spec is rich.
Usage: Basic
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
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<number>—min 1, default1.CommonSchemas.limit: ZodCoerce<number>—min 1, max 100, default20.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.