@@ -5,7 +5,22 @@ import { isEqual, isNil } from 'skyroc-utils';
55
66import type { StoreValue } from '../types/formStore' ;
77
8- /* ---------- Types ---------- */
8+ /**
9+ * Discriminated union describing the expected value type for validation.
10+ * Determines which built-in type checks will run for a rule.
11+ *
12+ * - 'string': validates string length and pattern
13+ * - 'number': validates numeric range and equality
14+ * - 'integer': validates integers only with optional min/max
15+ * - 'float': validates finite non-integer numbers
16+ * - 'boolean': validates a boolean primitive
17+ * - 'date': validates a Date or date-like value with optional min/max
18+ * - 'enum': validates membership in a provided enum array (deep-equals)
19+ * - 'email': validates a simple email pattern
20+ * - 'hex': validates hex color strings (#RGB[A], #RRGGBB[AA])
21+ * - 'regexp': validates a RegExp instance or a compilable pattern string
22+ * - 'url': validates a URL parsable by the URL constructor
23+ */
924export type RuleType =
1025 | 'boolean'
1126 | 'date'
@@ -19,53 +34,91 @@ export type RuleType =
1934 | 'string'
2035 | 'url' ;
2136
37+ /** Custom validator signature. Return a message string to fail, or a falsy value to pass. May be async. */
2238type Validator = (
2339 rule : Rule ,
2440 value : StoreValue ,
2541 values : StoreValue
2642) => Promise < string | any > | string | undefined | null ;
2743
44+ /** Validation rule configuration combining base, type-specific, and custom validations. */
2845export interface Rule {
46+ /** Optional debounce time in milliseconds for async validators. */
2947 debounceMs ?: number ;
48+ /** Allowed values for type 'enum'. Compared by deep equality. */
3049 enum ?: StoreValue [ ] ;
50+ /** Exact numeric value or exact string length, depending on type. */
3151 len ?: number ;
52+ /** Maximum numeric value or latest date/time allowed. */
3253 max ?: number | Date | string ;
54+ /** Maximum string length allowed (type 'string'). */
3355 maxLength ?: number ;
56+ /** Override default error/warning message. */
3457 message ?: string ;
58+ /** Minimum numeric value or earliest date/time allowed. */
3559 min ?: number | Date | string ;
60+ /** Minimum string length allowed (type 'string'). */
3661 minLength ?: number ;
62+ /** Regular expression to test a string value against. */
3763 pattern ?: RegExp ;
64+ /** Whether the field is required (non-empty). */
3865 required ?: boolean ;
66+ /** If true (default), skip checks when empty and not required. */
3967 skipIfEmpty ?: boolean ;
68+ /** Transform the raw value before validation. */
4069 transform ?: ( value : StoreValue ) => StoreValue ;
70+ /** Declares which built-in type validators should run. Defaults to 'string'. */
4171 type ?: RuleType ;
72+ /** Which trigger(s) cause this rule to run (e.g., 'change', 'blur'). */
4273 validateTrigger ?: string | string [ ] ;
74+ /** Custom validator executed after base and type-specific checks. */
4375 validator ?: Validator ;
76+ /** Treat failures as warnings instead of errors when true. */
4477 warningOnly ?: boolean ;
78+ /** Disallow strings that contain only whitespace when true. */
4579 whitespace ?: boolean ;
4680}
4781
82+ /** Execution strategy for running multiple rules. */
4883export type RunMode = 'parallelAll' | 'parallelFirst' | 'serial' ;
4984
85+ /** Options controlling validation execution, including mode and triggers. */
5086export type ValidateOptions = {
87+ /** Rule execution mode; see RunMode. */
5188 mode ?: RunMode ;
89+ /** Which trigger(s) initiated validation; used to filter rules. */
5290 trigger ?: string | string [ ] ;
5391} ;
5492
93+ /** Result of a single rule execution: error or warning message. */
5594type Res = { err ?: string ; warn ?: string } ;
95+ /** Context passed to a check function, including the rule and all form values. */
5696type Ctx = { rule : Rule ; value : any ; values : StoreValue } ;
97+ /** Function type for an individual check strategy. */
5798type Check = ( ctx : Ctx ) => Promise < Res > | Res ;
5899
59- /* ---------- Utils ---------- */
100+ /**
101+ * Utility functions for validation
102+ * Contains helper functions for checking empty values, whitespace, dates, and other common validations
103+ */
104+ /** Successful validation result (no error and no warning). */
60105const ok = ( ) : Res => ( { } ) ;
106+ /** Build a failure result, honoring rule.warningOnly and rule.message overrides. */
61107const fail = ( r : Rule , dft : string ) : Res => ( r . warningOnly ? { warn : r . message || dft } : { err : r . message || dft } ) ;
62108
109+ /** True when value is null/undefined, empty string, or empty array. */
63110const isEmpty = ( v : any ) =>
64111 isNil ( v ) || ( typeof v === 'string' && v ?. length === 0 ) || ( Array . isArray ( v ) && v ?. length === 0 ) ;
65112
113+ /** True when a string contains only whitespace characters. */
66114const onlyWhitespace = ( s : string ) => s ?. length > 0 && s ?. trim ( ) . length === 0 ;
115+ /** True for finite numbers (not NaN/Infinity). */
67116const isFiniteNumber = ( v : any ) => Number . isFinite ( v ) ;
68117
118+ /**
119+ * Normalize a date-like value to a millisecond timestamp.
120+ * Returns null when the value cannot be interpreted as a valid date/time.
121+ */
69122const toDateMs = ( d : number | string | Date ) : number | null => {
70123 if ( d instanceof Date ) return Number . isNaN ( d . getTime ( ) ) ? null : d . getTime ( ) ;
71124 if ( typeof d === 'number' ) return Number . isFinite ( d ) ? d : null ;
@@ -76,8 +129,11 @@ const toDateMs = (d: number | string | Date): number | null => {
76129 return null ;
77130} ;
78131
132+ /** Simple email validation based on a non-whitespace/local@domain.tld pattern. */
79133const isEmail = ( s : string ) => / ^ [ ^ \s @ ] + @ [ ^ \s @ ] + \. [ ^ \s @ ] + $ / . test ( s ) ;
134+ /** Hex color validator supporting 3/4/6/8 digit forms with optional leading '#'. */
80135const isHexColor = ( s : string ) => / ^ # ? (?: [ A - F a - f 0 - 9 ] { 3 , 4 } | [ A - F a - f 0 - 9 ] { 6 } | [ A - F a - f 0 - 9 ] { 8 } ) $ / . test ( s ) ;
136+ /** Validates that a string can be parsed by the URL constructor. */
81137const isURL = ( s : string ) => {
82138 try {
83139 new URL ( s ) ;
@@ -87,22 +143,33 @@ const isURL = (s: string) => {
87143 }
88144} ;
89145
90- /* ---------- Strategy Manager ---------- */
146+ /**
147+ * Strategy Manager for Rule Validation
148+ * Implements a flexible validation system using the Strategy pattern
149+ * Manages different types of validation rules and their execution
150+ */
91151class RuleChecker {
92152 private baseChecks : Check [ ] = [ ] ;
93153 private typeChecks : Record < RuleType , Check [ ] > = { } as any ;
94154 private customCheck : Check | null = null ;
95155
156+ /** Register a base check that always runs before type-specific and custom checks. */
96157 registerBase ( check : Check ) {
97158 this . baseChecks . push ( check ) ;
98159 }
160+ /** Register one or more checks for a specific RuleType. */
99161 registerType ( type : RuleType , ...checks : Check [ ] ) {
100162 this . typeChecks [ type ] = checks ;
101163 }
164+ /** Register a single custom check that will run after base and type checks. */
102165 registerCustom ( check : Check ) {
103166 this . customCheck = check ;
104167 }
105168
169+ /**
170+ * Run all applicable checks for a single rule against a value.
171+ * Applies transform first, then base checks, type checks, and finally the custom check.
172+ */
106173 async check ( value : any , rule : Rule , allValues : StoreValue ) : Promise < Res > {
107174 const r = rule || { } ;
108175 const v = typeof r . transform === 'function' ? r . transform ( value ) : value ;
@@ -239,10 +306,19 @@ checker.registerCustom(async ({ rule: r, value: v, values }) => {
239306} ) ;
240307
241308/* ---------- API ---------- */
309+ /**
310+ * Validate a single rule against a value.
311+ */
242312export async function checkOneRule ( value : any , rule : Rule , allValues : StoreValue ) : Promise < Res > {
243313 return checker . check ( value , rule , allValues ) ;
244314}
245315
316+ /**
317+ * Validate multiple rules using the specified execution mode.
318+ * - serial: run sequentially and stop on first non-warning error
319+ * - parallelFirst: run in parallel and resolve on first non-warning error
320+ * - parallelAll: run all in parallel, collecting all errors and warnings
321+ */
246322export async function runRulesWithMode (
247323 value : any ,
248324 rules : Rule [ ] ,
0 commit comments