UNPKG

@ordojs/forms

Version:

Comprehensive form handling system for OrdoJS

457 lines 14.7 kB
/** * Comprehensive validation system with schema integration */ /** * Built-in validation rules */ export class ValidationRules { static required(message = 'This field is required') { return { type: 'required', message, validate: (value) => { if (value === null || value === undefined) return false; if (typeof value === 'string') return value.trim().length > 0; if (typeof value === 'number') return !isNaN(value); if (typeof value === 'boolean') return true; if (value instanceof Date) return !isNaN(value.getTime()); if (value instanceof File) return value.size > 0; return Boolean(value); } }; } static minLength(min, message) { return { type: 'minLength', message: message || `Must be at least ${min} characters`, validate: (value) => { if (value === null || value === undefined) return true; const str = String(value); return str.length >= min; } }; } static maxLength(max, message) { return { type: 'maxLength', message: message || `Must be no more than ${max} characters`, validate: (value) => { if (value === null || value === undefined) return true; const str = String(value); return str.length <= max; } }; } static min(min, message) { return { type: 'min', message: message || `Must be at least ${min}`, validate: (value) => { if (value === null || value === undefined) return true; const num = Number(value); return !isNaN(num) && num >= min; } }; } static max(max, message) { return { type: 'max', message: message || `Must be no more than ${max}`, validate: (value) => { if (value === null || value === undefined) return true; const num = Number(value); return !isNaN(num) && num <= max; } }; } static email(message = 'Must be a valid email address') { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return { type: 'email', message, validate: (value) => { if (value === null || value === undefined || value === '') return true; return emailRegex.test(String(value)); } }; } static url(message = 'Must be a valid URL') { return { type: 'url', message, validate: (value) => { if (value === null || value === undefined || value === '') return true; try { new URL(String(value)); return true; } catch { return false; } } }; } static pattern(regex, message = 'Invalid format') { return { type: 'pattern', message, validate: (value) => { if (value === null || value === undefined || value === '') return true; return regex.test(String(value)); } }; } static oneOf(options, message) { return { type: 'oneOf', message: message || `Must be one of: ${options.join(', ')}`, validate: (value) => { if (value === null || value === undefined) return true; return options.includes(value); } }; } static custom(validator, message = 'Invalid value') { return { type: 'custom', message, validate: validator }; } static async asyncCustom(validator, message = 'Invalid value') { return { type: 'asyncCustom', message, validate: validator }; } static equalTo(fieldName, message) { return { type: 'equalTo', message: message || `Must match ${fieldName}`, validate: (value, formValues) => { if (!formValues || value === null || value === undefined) return true; return value === formValues[fieldName]; } }; } static date(message = 'Must be a valid date') { return { type: 'date', message, validate: (value) => { if (value === null || value === undefined || value === '') return true; if (value instanceof Date) return !isNaN(value.getTime()); const date = new Date(String(value)); return !isNaN(date.getTime()); } }; } static file(options = {}) { return { type: 'file', message: options.message || 'Invalid file', validate: (value) => { if (value === null || value === undefined) return true; if (!(value instanceof File)) return false; // Check file size if (options.maxSize && value.size > options.maxSize) { return false; } // Check file type if (options.allowedTypes && !options.allowedTypes.includes(value.type)) { return false; } return true; } }; } } /** * Schema adapter for Zod integration */ export class ZodSchemaAdapter { constructor(schema) { this.schema = schema; } async validate(values) { try { this.schema.parse(values); return {}; } catch (error) { const errors = {}; if (error.issues) { for (const issue of error.issues) { const fieldName = issue.path.join('.'); errors[fieldName] = { type: issue.code || 'validation_error', message: issue.message, field: fieldName }; } } return errors; } } async validateField(name, value, values) { try { // Create a partial object with just this field const testValues = { ...values, [name]: value }; const result = this.schema.safeParse(testValues); if (result.success) { return null; } // Find error for this specific field const fieldError = result.error.issues.find((issue) => issue.path.join('.') === name); if (fieldError) { return { type: fieldError.code || 'validation_error', message: fieldError.message, field: name }; } return null; } catch (error) { return { type: 'validation_error', message: error.message || 'Validation failed', field: name }; } } } /** * Schema adapter for Yup integration */ export class YupSchemaAdapter { constructor(schema) { this.schema = schema; } async validate(values) { try { await this.schema.validate(values); return {}; } catch (error) { const errors = {}; if (error.inner) { for (const validationError of error.inner) { const fieldName = validationError.path || 'unknown'; errors[fieldName] = { type: validationError.type || 'validation_error', message: validationError.message, field: fieldName }; } } else if (error.path) { errors[error.path] = { type: error.type || 'validation_error', message: error.message, field: error.path }; } return errors; } } async validateField(name, value, values) { try { const testValues = { ...values, [name]: value }; await this.schema.validateAt(name, testValues); return null; } catch (error) { return { type: error.type || 'validation_error', message: error.message || 'Validation failed', field: name }; } } } /** * Schema adapter for Joi integration */ export class JoiSchemaAdapter { constructor(schema) { this.schema = schema; } async validate(values) { const result = this.schema.validate(values, { abortEarly: false }); if (!result.error) { return {}; } const errors = {}; if (result.error.details) { for (const detail of result.error.details) { const fieldName = detail.path.join('.'); errors[fieldName] = { type: detail.type || 'validation_error', message: detail.message, field: fieldName }; } } return errors; } async validateField(name, value, values) { try { const testValues = { ...values, [name]: value }; const result = this.schema.validate(testValues); if (!result.error) { return null; } // Find error for this specific field const fieldError = result.error.details?.find(detail => detail.path.join('.') === name); if (fieldError) { return { type: fieldError.type || 'validation_error', message: fieldError.message, field: name }; } return null; } catch (error) { return { type: 'validation_error', message: error.message || 'Validation failed', field: name }; } } } /** * Validation manager for handling different validation scenarios */ export class ValidationManager { constructor() { this.customValidators = new Map(); } static getInstance() { if (!ValidationManager.instance) { ValidationManager.instance = new ValidationManager(); } return ValidationManager.instance; } /** * Register a custom validator globally */ registerValidator(name, rule) { this.customValidators.set(name, rule); } /** * Get a registered validator */ getValidator(name) { return this.customValidators.get(name); } /** * Create schema adapter from different schema types */ createSchemaAdapter(schema) { // Try to detect schema type if (schema && typeof schema.parse === 'function' && typeof schema.safeParse === 'function') { return new ZodSchemaAdapter(schema); } if (schema && typeof schema.validate === 'function' && typeof schema.validateAt === 'function') { return new YupSchemaAdapter(schema); } if (schema && typeof schema.validate === 'function' && typeof schema.validateAsync === 'function') { return new JoiSchemaAdapter(schema); } throw new Error('Unsupported schema type. Please use Zod, Yup, or Joi schemas.'); } /** * Validate a single field with multiple rules */ async validateField(value, rules, formValues) { for (const rule of rules) { try { const result = rule.validate(value, formValues); const isValid = result instanceof Promise ? await result : result; if (!isValid) { return { type: rule.type, message: rule.message, field: 'unknown' }; } } catch (error) { return { type: 'validation_error', message: error instanceof Error ? error.message : 'Validation failed', field: 'unknown' }; } } return null; } /** * Combine multiple validation rules */ combineRules(...rules) { return rules; } /** * Create conditional validation rule */ when(condition, thenRule, elseRule) { return { type: 'conditional', message: thenRule.message, validate: (value, formValues) => { if (!formValues) return true; const shouldApplyThenRule = condition(formValues); const ruleToApply = shouldApplyThenRule ? thenRule : elseRule; if (!ruleToApply) return true; return ruleToApply.validate(value, formValues); } }; } } ValidationManager.instance = null; /** * Utility functions for validation */ export const validation = { rules: ValidationRules, manager: ValidationManager.getInstance(), // Helper functions createZodAdapter: (schema) => new ZodSchemaAdapter(schema), createYupAdapter: (schema) => new YupSchemaAdapter(schema), createJoiAdapter: (schema) => new JoiSchemaAdapter(schema), // Common validation patterns email: () => ValidationRules.email(), required: () => ValidationRules.required(), minLength: (min) => ValidationRules.minLength(min), maxLength: (max) => ValidationRules.maxLength(max), pattern: (regex, message) => ValidationRules.pattern(regex, message), // Async validation helper async: (validator, message) => ValidationRules.custom(validator, message) }; //# sourceMappingURL=validation.js.map