/** * Domain Toolkit - Validation Helpers * * Common validation utilities that can be reused across all domains. */ import { z } from "zod"; // ============================================================================ // ID Validation // ============================================================================ /** * Validate that an ID is a positive integer */ export function isValidPositiveId(id: number): boolean { return Number.isInteger(id) && id > 0; } /** * Validate Salesforce ID (18 characters, alphanumeric) */ export function isValidSalesforceId(id: string): boolean { return /^[A-Za-z0-9]{18}$/.test(id); } // ============================================================================ // Pagination Validation // ============================================================================ /** * Validate and sanitize pagination parameters */ export function sanitizePagination(options: { page?: number; limit?: number; minLimit?: number; maxLimit?: number; defaultLimit?: number; }): { page: number; limit: number; } { const { page = 1, limit = options.defaultLimit ?? 10, minLimit = 1, maxLimit = 100 } = options; return { page: Math.max(1, Math.floor(page)), limit: Math.max(minLimit, Math.min(maxLimit, Math.floor(limit))), }; } /** * Check if pagination offset is valid */ export function isValidPaginationOffset(offset: number): boolean { return Number.isInteger(offset) && offset >= 0; } // ============================================================================ // String Validation // ============================================================================ /** * Check if string is non-empty after trimming */ export function isNonEmptyString(value: unknown): value is string { return typeof value === "string" && value.trim().length > 0; } /** * Check if value is a valid enum member */ export function isValidEnumValue>( value: unknown, enumObj: T ): value is T[keyof T] { return Object.values(enumObj).some(enumValue => enumValue === value); } // ============================================================================ // Array Validation // ============================================================================ /** * Check if array is non-empty */ export function isNonEmptyArray(value: unknown): value is T[] { return Array.isArray(value) && value.length > 0; } /** * Check if all array items are unique */ export function hasUniqueItems(items: T[]): boolean { return new Set(items).size === items.length; } // ============================================================================ // Number Validation // ============================================================================ /** * Check if number is within range (inclusive) */ export function isInRange(value: number, min: number, max: number): boolean { return value >= min && value <= max; } /** * Check if number is a valid positive integer */ export function isPositiveInteger(value: unknown): value is number { return typeof value === "number" && Number.isInteger(value) && value > 0; } /** * Check if number is a valid non-negative integer */ export function isNonNegativeInteger(value: unknown): value is number { return typeof value === "number" && Number.isInteger(value) && value >= 0; } // ============================================================================ // Date Validation // ============================================================================ /** * Check if string is a valid ISO date time */ export function isValidIsoDateTime(value: string): boolean { const isoRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?$/; if (!isoRegex.test(value)) return false; const date = new Date(value); return !isNaN(date.getTime()); } /** * Check if string is a valid date in YYYYMMDD format */ export function isValidYYYYMMDD(value: string): boolean { return /^\d{8}$/.test(value); } // ============================================================================ // Zod Schema Helpers // ============================================================================ /** * Create a schema for a positive integer ID */ export const positiveIdSchema = z.number().int().positive(); /** * Create a schema for pagination parameters */ export function createPaginationSchema(options?: { minLimit?: number; maxLimit?: number; defaultLimit?: number; }) { const { minLimit = 1, maxLimit = 100, defaultLimit = 10 } = options ?? {}; return z.object({ page: z.coerce.number().int().positive().optional().default(1), limit: z.coerce.number().int().min(minLimit).max(maxLimit).optional().default(defaultLimit), offset: z.coerce.number().int().nonnegative().optional(), }); } /** * Create a schema for sortable queries */ export const sortableQuerySchema = z.object({ sortBy: z.string().optional(), sortOrder: z.enum(["asc", "desc"]).optional(), }); // ============================================================================ // Type Guards // ============================================================================ /** * Type guard for checking if value is a record */ export function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } /** * Type guard for checking if error is a Zod error */ export function isZodError(error: unknown): error is z.ZodError { if (!isRecord(error)) return false; return Array.isArray(error.issues); }