# schema-registry _(no submodules)_ ## Purpose Instance-based registry for reusable Zod schemas. Each schema is bound to a stable name and may be derived into related shapes via `.extend/.partial/.pick/.omit`. The registry is deliberately instance-only — there is no module-level singleton — so multi-tenant servers and parallel tests cannot accidentally share state. Calls into the derivation helpers raise typed `OpenAPIError`s when the base name is unknown or refers to a non-`ZodObject` schema, replacing upstream's silent `undefined` return. ## Features - `register(name, schema, description?)` / `get(name)` / `has(name)` / `getDescription(name)`. - `getNames()`, `getAll()`, `remove(name)`, `clear()`. - `extend(baseName, name, extension)`, `partial(baseName, name)`, `pick(baseName, name, keys[])`, `omit(baseName, name, keys[])`. - `createSchemaRegistry()` factory — no global accessor. - `defineSchema(schema, meta, registry)` — registry argument is required. - Typed `OPENAPI.SCHEMA_NOT_FOUND` / `OPENAPI.INVALID_BASE_SCHEMA` errors from derivation helpers. ## Usage: Basic ```ts import z from "zod"; import { createSchemaRegistry, defineSchema, } from "@suaveplan/api-openapi/schema-registry"; const registry = createSchemaRegistry(); defineSchema(z.object({ id: z.string(), email: z.string() }), { name: "User" }, registry); const User = registry.get("User"); ``` ## Usage: Advanced ```ts import z from "zod"; import { createSchemaRegistry, } from "@suaveplan/api-openapi/schema-registry"; const registry = createSchemaRegistry(); registry.register( "User", z.object({ id: z.string().uuid(), email: z.string().email(), password: z.string(), }), "Internal user shape including hashed password" ); // Public shape — drop `password`. const UserPublic = registry.omit("User", "UserPublic", ["password"]); // Patch shape — every field optional. const UserPatch = registry.partial("User", "UserPatch"); // Extended shape — add audit field. const UserAudit = registry.extend("User", "UserAudit", { lastSeenAt: z.string().datetime(), }); console.log(registry.getNames()); // ["User","UserPublic","UserPatch","UserAudit"] ``` ## API Reference - `class SchemaRegistry` - `register(name, schema, description?): T` - `get(name): T | undefined` - `has(name): boolean` - `getDescription(name): string | undefined` - `getNames(): string[]` - `getAll(): Map` - `remove(name): boolean` - `clear(): void` - `extend(baseName, name, extension): z.ZodObject` — throws `OPENAPI.SCHEMA_NOT_FOUND` or `OPENAPI.INVALID_BASE_SCHEMA` - `partial(baseName, name): z.ZodObject` — same throw semantics - `pick(baseName, name, keys[]): z.ZodObject` — same throw semantics - `omit(baseName, name, keys[]): z.ZodObject` — same throw semantics - `function createSchemaRegistry(): SchemaRegistry` - `function defineSchema(schema: T, meta: {name, description?}, registry: SchemaRegistry): T` ## Implementation Notes `requireObjectBase` is the central guard. It throws `OPENAPI.SCHEMA_NOT_FOUND` when the name is unknown and `OPENAPI.INVALID_BASE_SCHEMA` when the registered schema is not a `ZodObject`. Both branches carry the base name in the error context so the call site can be diagnosed without a stack walk. `pick` and `omit` take a `string[]` keys list rather than the Zod shape-mask object because OpenAPI consumers usually serialise key lists from configuration, not from compile-time literals. The shape mask is constructed internally to match Zod's expected input. `defineSchema` requires the `registry` argument by position (third arg). Calling it without is a compile error. This is the deliberate behavioural divergence from upstream's `defineSchema(schema, meta)` which always wrote to a process-global registry — a footgun that turned every test file into a coupled neighbour. The class uses `private readonly` Maps for `schemas` and `descriptions`. External mutation is impossible without going through `register` / `remove` / `clear`, which gives the registry well-defined ownership of both maps and lets `getAll()` safely return a fresh copy.