Docs
Zod

Zod

Additional helpers for the zod library.

Installation

Create lib/zod.ts

import { z } from "zod";
 
/**
 * Shortcut for obligatory database id.
 * @returns
 */
function id() {
  return z.coerce.string().min(1);
}
 
/**
 * Returns a Zod string validator that checks if the input matches the specified regular expression.
 * @param format The regular expression to match against.
 * @param options An object containing options for the validator.
 * @param options.required An optional error message to use if the input is empty or undefined.
 * @param options.invalid The error message to use if the input does not match the regular expression.
 * @returns A Zod string validator.
 */
function regex(
  format: RegExp,
  options: {
    required: string | undefined;
    invalid: string;
  },
) {
  let string = z.string();
 
  if (options.required) {
    string = string.min(1, { message: options.required });
  }
 
  return string.regex(format, options.invalid);
}
 
/**
 * Returns a Zod string validator that matches a date string in the format "YYYY-MM-DD".
 * @param args An optional object containing the following properties:
 * - `required`: A custom error message to use when the value is undefined or an empty string.
 * - `invalid`: A custom error message to use when the value is not a valid date string.
 * @returns A Zod string validator.
 */
function date(args?: { required: string; invalid: string }) {
  return regex(/^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})$/, {
    required: args?.required,
    invalid: args?.invalid ?? "Invalid date",
  });
}
 
/* ------------------------------------ - ----------------------------------- */
type StringToZodLiteral<T extends string> = z.ZodLiteral<T>;
 
type ReadonlyArrayToZodLiteralTuple<T extends readonly string[]> = {
  [K in keyof T]: StringToZodLiteral<T[K]>;
};
 
/**
 * Converts a readonly array of string literals to a tuple of Zod literals.
 * @param readonlyArray The readonly array of string literals.
 * @returns A tuple of Zod literals.
 */
function unionFromArray<TArray extends readonly string[]>(
  readonlyArray: TArray,
): ReadonlyArrayToZodLiteralTuple<TArray> {
  // eslint-disable-next-line @typescript-eslint/no-unsafe-return
  return readonlyArray.map((value) => z.literal(value)) as any;
}
/* ------------------------------------ - ----------------------------------- */
 
/**
 * Returns null if the given string is empty or whitespace-only, otherwise returns the original string.
 * @param value - The string to nullify.
 * @returns The original string or null.
 */
function nullificate(value: string) {
  if (value.trim() === "") {
    return null;
  }
 
  return value;
}
 
function selectOption<TSchema extends z.ZodType>(
  valueSchema: TSchema,
  params?: z.RawCreateParams,
) {
  return z.object(
    {
      label: z.string(),
      value: valueSchema,
      disabled: z.boolean().optional(),
      group: z.string().optional(),
    },
    params,
  );
}
 
function selectOptions<TSchema extends z.ZodType>(
  dataSchema: TSchema,
  args?: {
    required?: string;
  },
) {
  const object = z
    .object(
      {
        label: z.string(),
        value: dataSchema,
        disabled: z.boolean().optional(),
        group: z.string().optional(),
      },
      {
        required_error: args?.required ?? "Choose an option",
      },
    )
    .array();
 
  return object;
}
 
function bool(val: boolean, message = "Invalid boolean value.") {
  return z.boolean().refine((value) => value === val, {
    message,
  });
}
/* ------------------------------------ export ----------------------------------- */
 
export const zod = {
  id,
  regex,
  bool,
  date,
  unionFromArray,
  nullificate,
  selectOption,
  selectOptions,
};