houserules/packages/chess/src/modifiers/schema.ts
2026-04-19 14:00:10 -06:00

94 lines
3.1 KiB
TypeScript

/**
* Zod schemas for ModifierProfile serialization and validation.
*
* The `value` field on TypeModifier and InstanceModifier uses `z.any()`
* rather than `z.unknown()` — Zod v4 infers `z.unknown()` object fields as
* optional (`value?: unknown`) which conflicts with the required `value:
* unknown` in the TypeModifier/InstanceModifier interfaces. `z.any()` infers
* as a required `any` field while still accepting all values.
*
* Per-kind value validation happens at descriptor registration time, not at
* profile-parse time. This keeps the schema forwards-compatible.
*/
import { z } from "zod";
import type { ModifierProfile } from "./types.js";
// ---------------------------------------------------------------------------
// Primitives
// ---------------------------------------------------------------------------
const ModifierKindIdSchema = z.enum([
"hp-bonus",
"range-bonus",
"direction-additions",
"capture-flags",
"promotion-override",
"damage-resistance",
]);
const PieceTypeSchema = z.enum([
"pawn",
"knight",
"bishop",
"rook",
"queen",
"king",
]);
// PieceColor extended with "both" for TypeModifier.color
const PieceColorExtSchema = z.enum(["white", "black", "both"]);
/** Algebraic notation square: "a1".."h8" */
const SquareStringSchema = z
.string()
.regex(/^[a-h][1-8]$/, "Must be algebraic notation (a1-h8)");
// ---------------------------------------------------------------------------
// TypeModifier
// ---------------------------------------------------------------------------
export const TypeModifierSchema = z.object({
kind: ModifierKindIdSchema,
pieceType: PieceTypeSchema,
color: PieceColorExtSchema,
value: z.any() as z.ZodType<unknown>,
});
// ---------------------------------------------------------------------------
// InstanceModifier
// ---------------------------------------------------------------------------
export const InstanceModifierSchema = z.object({
kind: ModifierKindIdSchema,
square: SquareStringSchema,
value: z.any() as z.ZodType<unknown>,
});
// ---------------------------------------------------------------------------
// ModifierProfile
// ---------------------------------------------------------------------------
export const ModifierProfileSchema = z.object({
id: z.string().min(1),
name: z.string().min(1),
description: z.string(),
layoutId: z.string().optional(),
perType: z.array(TypeModifierSchema),
perInstance: z.array(InstanceModifierSchema),
version: z.literal(1),
source: z.enum(["premade", "custom"]),
});
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/** Parse an unknown value as a ModifierProfile. Throws ZodError on failure. */
export function parseModifierProfile(raw: unknown): ModifierProfile {
return ModifierProfileSchema.parse(raw) as ModifierProfile;
}
/** Serialize a ModifierProfile to a JSON-safe plain object. */
export function serializeModifierProfile(profile: ModifierProfile): unknown {
return ModifierProfileSchema.parse(profile);
}