// THE ZOD SEAMS FOR THE THREE SANITIZERS THAT LIVE OUTSIDE settings.ts. // // `settings.json` has three blocks whose parsers have homes of their own and // callers of their own: `workers` (lib/workers.ts — the pool and the settings // form), `channelPriority` (lib/channelPriority.ts — storageWatch and the // channels actions call `sanitizeChannelPriority(value: unknown)` directly) and // `autoQueue` (lib/autoQueueSchema.ts — the picker's tests pin it). Each keeps // its home and its signature. What this file adds is ONE schema per block, so // `lib/settingsSchema.ts` can compose them with the other twenty-eight fields // the same way it composes everything: zod supplies the plumbing (the key, the // strip of unknown siblings), the existing sanitizer supplies the arithmetic — // and the totality. Nothing was re-implemented, so nothing could drift. // // WHY A SEPARATE FILE, and not a `workersSchema` export beside each sanitizer: // all three homes are imported AS VALUES by `"use client"` forms // (WorkersConfigForm, ChannelTierSelect, LadderRung, …). A module-level // `import { z } from "zod"` there would put zod in a browser bundle for no // reason. Only server code imports this file — lib/settingsSchema.ts, which // itself is only reached through lib/settings.ts (node:fs at module scope). import { z } from "zod"; import { sanitizeWorkers, type Worker } from "./workers"; import { sanitizeChannelPriority, type ChannelPriority, } from "./channelPriority"; import { sanitizeAutoQueue } from "./autoQueueSchema"; import type { AutoQueueSettings } from "./autoQueueTypes"; // A settings FIELD: any JSON value in, a legal value out. // // TOTALITY COMES FROM THE COERCION, NOT FROM ZOD. `z.unknown()` accepts every // input, so its `.catch(undefined)` never fires, and zod does not guard the // transform: a coercion that threw would throw out of `parse`. What makes a // settings read never throw is that every coercion passed here — each clamp and // sanitizer — is itself total over `unknown`. The `.catch` is kept only so the // field keeps its shape if `z.unknown()` is ever swapped for a validating // schema. // // What zod does contribute: `z.unknown()` accepts a missing key, and zod 4 // still runs the transform for it and emits the key — so an absent field is // DEFAULTED, not dropped, exactly as `defaults()` used to fill it — and unknown // sibling keys are stripped by the enclosing object. // // There is deliberately no `.default()` anywhere: a default applies only to // `undefined`, and every coercion here already decides that case itself — the // place "a zero limit is a hold" lives is inside the clamp, not in a fallback // zod would apply around it. export function settingsField(coerce: (value: unknown) => T) { return z.unknown().catch(undefined).transform((value): T => coerce(value)); } export const workersSchema = settingsField( (value): Worker[] => sanitizeWorkers(value), ); export const channelPrioritySchema = settingsField( (value): ChannelPriority => sanitizeChannelPriority(value), ); export const autoQueueSchema = settingsField( (value): AutoQueueSettings => sanitizeAutoQueue(value), );