# `schemas/schemas.ts` — Zod options schema _(no submodules)_ ## Purpose This module owns the runtime validation surface for `@suaveplan/emblem-solver`. It exposes `ConvertOptionsSchema`, a Zod schema that mirrors the static `ConvertOptions` interface, applies sensible defaults, and rejects unknown keys. `convert()` runs every caller-supplied options object through `safeParse` so typos or out-of-range values fail loudly with `EmblemSolverError(INVALID_OPTIONS)` rather than silently degrading the solver. ## Features - `layerOrder` defaults to `"auto"` and is validated as a closed enum over the five supported strategies. - `maxPermutations` defaults to `40320 = 8!` — the threshold beyond which exhaustive permutation search is abandoned in favour of heuristics + random sampling. The schema enforces `int().min(1)` so a zero or negative value is rejected at the boundary. - `rng` is an optional `() => number` validated with `z.custom<…>()`. It is the only injection point for the package — supplying it makes the heuristic + sampling path fully deterministic. - `.strict()` rejects unknown keys, so misspelled options never silently succeed. ## Usage: Basic ```ts import { ConvertOptionsSchema } from "@suaveplan/emblem-solver/schemas"; const parsed = ConvertOptionsSchema.parse({ layerOrder: "area-desc" }); parsed.maxPermutations; // 40320 (default applied) ``` ## Usage: Advanced ```ts import { ConvertOptionsSchema } from "@suaveplan/emblem-solver/schemas"; import { EMBLEM_SOLVER_CODES, EmblemSolverError } from "@suaveplan/emblem-solver/errors"; function safeOptions(raw: unknown) { const r = ConvertOptionsSchema.safeParse(raw); if (!r.success) { throw new EmblemSolverError( EMBLEM_SOLVER_CODES.INVALID_OPTIONS, `bad options: ${r.error.issues .map((i) => `${i.path.join(".")}: ${i.message}`) .join("; ")}` ); } return r.data; } ``` ## API Reference | Symbol | Kind | Description | |---|---|---| | `ConvertOptionsSchema` | `z.ZodObject` | Strict schema for `ConvertOptions` | | `ConvertOptionsParsed` | type | Post-defaults shape of the parsed result | ## Implementation Notes The schema lives in its own module so the rest of the package does not pay the Zod cost at import time when consumers do not need validation (e.g. hot-loop callers that constructed options at startup). `convert()` always runs through this schema, but downstream tooling can call `ConvertOptionsSchema.parse` ahead of time and pass the parsed result into its own caching layer. We use `z.custom<() => number>` rather than `z.function()` because Zod 4 deprecated runtime function validation in favour of a custom predicate; this matches the catalog `zod ^4.x`. The `rng` field stays optional (rather than defaulting to `Math.random`) so the test harness can detect whether a caller injected a deterministic source.