/** Fluent JSON Schema builders following the classic Workers schema-builder API. */ const SCHEMA: unique symbol = Symbol("schema"); const KIND: unique symbol = Symbol("kind"); declare const VALUE: unique symbol; export type StringFormat = | "date-time" | "date" | "time" | "duration" | "email" | "hostname" | "ipv4" | "ipv6" | "uuid"; type Kind = "object" | "value"; type JsonSchema = { title?: string; description?: string; } & ( | { type: "string" | "number" | "integer" | "boolean" | "null"; enum?: readonly (string | number)[]; format?: StringFormat; } | { type: "array"; items: JsonSchema; minItems?: 0 | 1 } | { type: "object"; properties: Record; required: string[]; additionalProperties: false; } | { anyOf: JsonSchema[] } ); export interface SchemaBuilder { readonly [SCHEMA]: JsonSchema; readonly [KIND]: K; readonly [VALUE]?: T; describe(text: string): SchemaBuilder; label(text: string): SchemaBuilder; nullable(): SchemaBuilder; } export type Infer = S extends SchemaBuilder ? T : never; /** Returns a copy so callers cannot mutate the builder's validation contract. */ export function getSchema(builder: SchemaBuilder): JsonSchema { if (typeof builder !== "object" || builder === null || !(SCHEMA in builder)) { throw new Error("Expected a schema builder; use j.object() for manual inputs"); } return structuredClone(builder[SCHEMA]); } function makeBuilder(schema: JsonSchema, kind: K): SchemaBuilder { return { [SCHEMA]: schema, [KIND]: kind, describe(description) { return makeBuilder({ ...schema, description }, kind); }, label(title) { return makeBuilder({ ...schema, title }, kind); }, nullable() { const { title, description, ...inner } = schema; return makeBuilder( { anyOf: [inner, { type: "null" }], ...(title === undefined ? {} : { title }), ...(description === undefined ? {} : { description }), }, "value", ); }, }; } function string(): SchemaBuilder { return makeBuilder({ type: "string" }, "value"); } function formattedString(format: StringFormat): SchemaBuilder { return makeBuilder({ type: "string", format }, "value"); } function number(): SchemaBuilder { return makeBuilder({ type: "number" }, "value"); } function integer(): SchemaBuilder { return makeBuilder({ type: "integer" }, "value"); } function boolean(): SchemaBuilder { return makeBuilder({ type: "boolean" }, "value"); } function enumBuilder(first: T, ...rest: T[]): SchemaBuilder; function enumBuilder(first: T, ...rest: T[]): SchemaBuilder; function enumBuilder( first: string | number, ...rest: (string | number)[] ): SchemaBuilder { return makeBuilder( { type: typeof first === "string" ? "string" : "number", enum: [first, ...rest] }, "value", ); } function array( items: SchemaBuilder, options?: { minItems?: 0 | 1 }, ): SchemaBuilder { return makeBuilder({ type: "array", items: getSchema(items), ...options }, "value"); } function object

>>( properties: P, ): SchemaBuilder<{ -readonly [K in keyof P]: Infer }, "object"> { return makeBuilder( { type: "object", properties: Object.fromEntries( Object.entries(properties).map(([key, value]) => [key, getSchema(value)]), ), required: Object.keys(properties), additionalProperties: false, }, "object", ); } export const j = { string, number, integer, boolean, enum: enumBuilder, array, object, datetime: () => formattedString("date-time"), date: () => formattedString("date"), time: () => formattedString("time"), duration: () => formattedString("duration"), email: () => formattedString("email"), hostname: () => formattedString("hostname"), ipv4: () => formattedString("ipv4"), ipv6: () => formattedString("ipv6"), uuid: () => formattedString("uuid"), };