first commit
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
# control-surface-schema (Zod)
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
This module owns the Zod schemas that mirror every TypeScript type in the
|
||||
package: `TransformSchema`, `BindingSchema`, `RangeSchema`,
|
||||
`AppearanceSchema`, `BehaviourSchema`, `GroupLayoutSchema`,
|
||||
`ControlNodeSchema`, `ControlSurfaceSchema`, and the loose
|
||||
`ControlSurfaceVersionedSchema` used inside the upgrade pipeline. The
|
||||
schemas are the runtime source of truth for `validateSurface` and
|
||||
`upgradeSurface`.
|
||||
|
||||
## Features
|
||||
|
||||
- One schema per exported type — symmetrical with the type module.
|
||||
- Discriminated unions on `kind` (`Binding`, `GroupLayout`, `Action`)
|
||||
and on `type` (`ControlNode`), so parse errors point at the precise
|
||||
variant that failed.
|
||||
- Recursive `ControlNodeSchema` via `z.lazy` so groups can nest to any
|
||||
depth without exploding the schema graph.
|
||||
- MIDI / DMX numeric ranges encoded with `.int().min().max()` matching
|
||||
the hardware envelope; the validator rejects out-of-range channel and
|
||||
CC values at parse time.
|
||||
- Compound bindings use `LeafBindingSchema` (no nested compound) to
|
||||
enforce the flat-only invariant declaratively.
|
||||
- `ControlSurfaceSchema` pins `version` to `CURRENT_SCHEMA_VERSION` so a
|
||||
document at a different version fails parse; `ControlSurfaceVersionedSchema`
|
||||
loosens this to any non-empty string for use inside migrations.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
Parse an unknown document via `safeParse`:
|
||||
|
||||
```ts
|
||||
import { ControlSurfaceSchema } from "@suaveplan/control-surface-schema";
|
||||
|
||||
const result = ControlSurfaceSchema.safeParse(raw);
|
||||
if (!result.success) {
|
||||
for (const issue of result.error.issues) {
|
||||
console.error(issue.path.join("."), issue.message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
Compose a sub-schema for an editor that only wants to validate the root
|
||||
group, not the full surface envelope:
|
||||
|
||||
```ts
|
||||
import { ControlNodeSchema, GroupLayoutSchema } from "@suaveplan/control-surface-schema";
|
||||
import z from "zod";
|
||||
|
||||
const RootGroupOnlySchema = z.object({
|
||||
layout: GroupLayoutSchema,
|
||||
children: z.array(ControlNodeSchema),
|
||||
});
|
||||
|
||||
const parsed = RootGroupOnlySchema.parse(payload);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `TransformSchema`, `RangeSchema`, `AppearanceSchema`, `BehaviourSchema`,
|
||||
`BindingSchema`, `GroupLayoutSchema`, `ControlKindSchema`,
|
||||
`ControlNodeSchema`, `ControlSurfaceSchema`,
|
||||
`ControlSurfaceVersionedSchema` — Zod schemas mirroring the
|
||||
corresponding types in `src/types/`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Validators downstream call `safeParse`, never `parse`, so a malformed
|
||||
input becomes a typed `SchemaError("INVALID_DOCUMENT")` rather than a
|
||||
raw thrown Zod error. The recursive `ControlNodeSchema` is typed as
|
||||
`z.ZodType<unknown>` so the `exactOptionalPropertyTypes` mismatch
|
||||
between Zod's `.optional()` (which yields `T | undefined`) and the
|
||||
schema's `T?` fields doesn't force the lazy boundary to a less precise
|
||||
type; the runtime check still rejects malformed nodes, and the consumer
|
||||
casts `parsed.data` to `ControlSurface` after the structural checks
|
||||
(z-axis, duplicate ids, binding refs) confirm the document is valid.
|
||||
Reference in New Issue
Block a user