/** * WHMCS Zod Schema Primitives (domain-internal) * * Coercing schema helpers for WHMCS API responses. * WHMCS (PHP) is loosely typed — fields documented as strings may arrive * as numbers (and vice-versa). These primitives absorb that inconsistency * at the parsing boundary so the rest of the codebase sees clean types. */ import { z } from "zod"; /** * Coercing string — accepts string or number, always outputs string. * Use for any WHMCS response field that should be a string. */ export const whmcsString = z.coerce.string(); /** * Accepts number or string (e.g. "123"), keeps the raw union type. * Use when downstream code handles both types (e.g. IDs you'll parse later). */ export const whmcsNumberLike = z.union([z.number(), z.string()]); /** * Accepts boolean, number (0/1), or string ("true"/"false"/etc). * Use for WHMCS boolean flags that arrive in varying formats. */ export const whmcsBooleanLike = z.union([z.boolean(), z.number(), z.string()]); /** * Coercing required number — accepts number or numeric string, always outputs number. * Use for WHMCS fields that must be a number but may arrive as a string. */ export const whmcsRequiredNumber = z.preprocess(value => { if (typeof value === "number") return value; if (typeof value === "string" && value.trim().length > 0) { const parsed = Number(value); return Number.isFinite(parsed) ? parsed : value; } return value; }, z.number()); /** * Coercing optional number — accepts number, numeric string, null, undefined, or empty string. * Returns undefined for missing/empty values. * Use for WHMCS fields that are optional numbers but may arrive as strings. */ export const whmcsOptionalNumber = z.preprocess((value): number | undefined => { if (value === undefined || value === null || value === "") return undefined; if (typeof value === "number") return value; if (typeof value === "string") { const parsed = Number(value); return Number.isFinite(parsed) ? parsed : undefined; } return undefined; }, z.number().optional()); // --------------------------------------------------------------------------- // Boolean coercion helpers // --------------------------------------------------------------------------- /** * Coerce a WHMCS boolean-like value to a real boolean. * * Truthy: `true`, `1`, `"1"`, `"true"`, `"yes"`, `"on"` * Everything else → `false` */ function toBool(value: unknown): boolean { if (typeof value === "boolean") return value; if (typeof value === "number") return value === 1; if (typeof value === "string") { const n = value.trim().toLowerCase(); return n === "1" || n === "true" || n === "yes" || n === "on"; } return false; } /** * Coercing required boolean — accepts boolean, number (0/1), or string, always outputs boolean. * Use for WHMCS boolean flags that must resolve to true/false. */ export const whmcsBoolean = z.preprocess(toBool, z.boolean()); /** * Coercing optional boolean — accepts boolean, number, string, null, or undefined. * Returns `undefined` for null/undefined, coerces everything else to boolean. * Use for WHMCS boolean flags that may be absent. */ export const whmcsOptionalBoolean = z.preprocess((value): boolean | undefined => { if (value === undefined || value === null) return undefined; return toBool(value); }, z.boolean().optional());