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,115 @@
# schema-registry
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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<T>(name, schema, description?): T`
- `get<T>(name): T | undefined`
- `has(name): boolean`
- `getDescription(name): string | undefined`
- `getNames(): string[]`
- `getAll(): Map<string, z.ZodType>`
- `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<T>(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.