UNPKG

@mmstack/form-validation

Version:

Provides a type-safe, composable, and localizable validation system designed specifically for use with [@mmstack/form-core](https://www.npmjs.com/package/@mmstack/form-core). It enables defining validation rules clearly within your TypeScript code and int

1 lines 86.1 kB
{"version":3,"file":"mmstack-form-validation.mjs","sources":["../../../../../packages/form/validation/src/lib/merge-validators.ts","../../../../../packages/form/validation/src/lib/array/max-length.ts","../../../../../packages/form/validation/src/lib/array/min-length.ts","../../../../../packages/form/validation/src/lib/array/index.ts","../../../../../packages/form/validation/src/lib/boolean/must-be-true.ts","../../../../../packages/form/validation/src/lib/boolean/index.ts","../../../../../packages/form/validation/src/lib/general/must-be.ts","../../../../../packages/form/validation/src/lib/general/not.ts","../../../../../packages/form/validation/src/lib/general/not-one-of.ts","../../../../../packages/form/validation/src/lib/general/one-of.ts","../../../../../packages/form/validation/src/lib/general/required.ts","../../../../../packages/form/validation/src/lib/general/index.ts","../../../../../packages/form/validation/src/lib/date/is-date.ts","../../../../../packages/form/validation/src/lib/date/max-date.ts","../../../../../packages/form/validation/src/lib/date/min-date.ts","../../../../../packages/form/validation/src/lib/date/util.ts","../../../../../packages/form/validation/src/lib/date/index.ts","../../../../../packages/form/validation/src/lib/date/date-range.ts","../../../../../packages/form/validation/src/lib/number/integer.ts","../../../../../packages/form/validation/src/lib/number/is-number.ts","../../../../../packages/form/validation/src/lib/number/max.ts","../../../../../packages/form/validation/src/lib/number/min.ts","../../../../../packages/form/validation/src/lib/number/multiple-of.ts","../../../../../packages/form/validation/src/lib/number/index.ts","../../../../../packages/form/validation/src/lib/string/email.ts","../../../../../packages/form/validation/src/lib/string/is-string.ts","../../../../../packages/form/validation/src/lib/string/max-chars.ts","../../../../../packages/form/validation/src/lib/string/min-chars.ts","../../../../../packages/form/validation/src/lib/string/pattern.ts","../../../../../packages/form/validation/src/lib/string/trimmed.ts","../../../../../packages/form/validation/src/lib/string/uri.ts","../../../../../packages/form/validation/src/lib/string/index.ts","../../../../../packages/form/validation/src/lib/validators.ts","../../../../../packages/form/validation/src/mmstack-form-validation.ts"],"sourcesContent":["import { Validator } from './validator.type';\n\nconst INTERNAL_ERROR_MERGE_DELIM = '::INTERNAL_MMSTACK_MERGE_DELIM::';\n\nexport function defaultMergeMessage(errors: string[]): {\n error: string;\n tooltip: string;\n} {\n const first = errors.at(0);\n\n if (!first)\n return {\n error: '',\n tooltip: '',\n };\n\n if (errors.length === 1) {\n if (first.length > 60) {\n return {\n error: `${first.slice(0, 60)}...`,\n tooltip: first,\n };\n }\n\n return {\n error: first,\n tooltip: '',\n };\n }\n\n if (first.length > 40) {\n return {\n error: `${first.slice(0, 40)}..., +${errors.length - 1} issues`,\n tooltip: errors.join('\\n'),\n };\n }\n\n return {\n error: `${first}, +${errors.length - 1} issues`,\n tooltip: errors.join('\\n'),\n };\n}\n\nfunction toTooltipFn(\n merge: (errors: string[]) => string | { tooltip: string; error: string },\n) {\n return (errors: string[]) => {\n const result = merge(errors);\n if (typeof result === 'string') {\n return {\n error: result,\n tooltip: '',\n merged: errors.join(INTERNAL_ERROR_MERGE_DELIM),\n };\n }\n\n return result;\n };\n}\n\nexport function mergeValidators<T>(\n ...validators: Validator<T>[]\n): (value: T) => string[] {\n if (!validators.length) return () => [];\n\n return (value) => validators.map((val) => val(value)).filter(Boolean);\n}\n\ntype MergeFn<T> = ((value: T) => string) & {\n resolve: (mergedError: string) => {\n error: string;\n tooltip: string;\n };\n};\n\nexport function createMergeValidators(\n merge?: (errors: string[]) =>\n | string\n | {\n tooltip: string;\n error: string;\n },\n) {\n const mergeFn = merge ? toTooltipFn(merge) : defaultMergeMessage;\n\n return <T>(validators: Validator<T>[]) => {\n const validate = mergeValidators(...validators);\n\n const fn = ((value: T) =>\n validate(value).join(INTERNAL_ERROR_MERGE_DELIM)) as MergeFn<T>;\n\n fn.resolve = (mergedError: string) => {\n return mergeFn(mergedError.split(INTERNAL_ERROR_MERGE_DELIM));\n };\n\n return fn;\n };\n}\n","import { Validator } from '../validator.type';\n\nexport function defaultMaxLengthMessageFactory(\n max: number,\n elementsLabel?: string,\n) {\n return `Max ${max} ${elementsLabel}`;\n}\n\nexport function createMaxLengthValidator(\n createMsg: (max: number, elementsLabel?: string) => string,\n): <T extends string | any[] | null>(\n max: number,\n elementsLabel?: string,\n) => Validator<T> {\n return (max, elementsLabel = 'items') => {\n const msg = createMsg(max, elementsLabel);\n return (value) => {\n const length = value?.length ?? 0;\n\n if (length > max) return msg;\n return '';\n };\n };\n}\n","import { Validator } from '../validator.type';\n\nexport function defaultMinLengthMessageFactory(\n min: number,\n elementsLabel?: string,\n) {\n return `Min ${min} ${elementsLabel}`;\n}\n\nexport function createMinLengthValidator(\n createMsg: (min: number, elementsLabel?: string) => string,\n): <T extends string | any[] | null>(\n min: number,\n elementsLabel?: string,\n) => Validator<T> {\n return (min, elementsLabel = 'items') => {\n const msg = createMsg(min, elementsLabel);\n return (value) => {\n const length = value?.length ?? 0;\n\n if (length < min) return msg;\n return '';\n };\n };\n}\n","import { createMergeValidators } from '../merge-validators';\nimport { Validator } from '../validator.type';\nimport {\n createMaxLengthValidator,\n defaultMaxLengthMessageFactory,\n} from './max-length';\nimport {\n createMinLengthValidator,\n defaultMinLengthMessageFactory,\n} from './min-length';\n\nexport type ArrayMessageFactories = {\n minLength: Parameters<typeof createMinLengthValidator>[0];\n maxLength: Parameters<typeof createMaxLengthValidator>[0];\n};\n\nconst DEFAULT_MESSAGES: ArrayMessageFactories = {\n minLength: defaultMinLengthMessageFactory,\n maxLength: defaultMaxLengthMessageFactory,\n};\n\n/**\n * Configuration options for creating a combined array validator using the\n * `.all()` method returned by `createArrayValidators` (accessed via `injectValidators().array.all`).\n */\nexport type ArrayValidatorOptions = {\n /**\n * Minimum allowed array length.\n * Validation fails if the array has fewer elements than this number.\n * @example { minLength: 1 } // Array must not be empty\n */\n minLength?: number;\n\n /**\n * Maximum allowed array length.\n * Validation fails if the array has more elements than this number.\n * @example { maxLength: 5 } // Array can have at most 5 items\n */\n maxLength?: number;\n\n /**\n * Optional label for the array elements used in generated error messages\n * (e.g., 'items', 'users', 'tags'). Defaults typically to 'items'.\n * @example { minLength: 2, elementsLabel: 'tags' } // Error might be \"Min 2 tags\"\n */\n elementsLabel?: string;\n};\n\nexport function createArrayValidators(\n factories?: Partial<ArrayMessageFactories>,\n merger = createMergeValidators(),\n) {\n const t = { ...DEFAULT_MESSAGES, ...factories };\n const base = {\n minLength: createMinLengthValidator(t.minLength),\n maxLength: createMaxLengthValidator(t.maxLength),\n };\n\n return {\n ...base,\n all: <T extends any[]>(opt: ArrayValidatorOptions) => {\n const validators: Validator<T>[] = [];\n\n if (opt.minLength !== undefined)\n validators.push(base.minLength(opt.minLength, opt.elementsLabel));\n if (opt.maxLength !== undefined)\n validators.push(base.maxLength(opt.maxLength, opt.elementsLabel));\n\n return merger(validators);\n },\n };\n}\n","import { Validator } from '../validator.type';\n\nexport function defaultMustBeTreFactory() {\n return `Must be true`;\n}\n\nexport function createMustBeTrueValidator(\n createMsg: () => string,\n): () => Validator<boolean> {\n return () => {\n const msg = createMsg();\n return (value) => {\n if (value !== true) return msg;\n return '';\n };\n };\n}\n","import {\n createMustBeTrueValidator,\n defaultMustBeTreFactory,\n} from './must-be-true';\n\nexport type BooleanMessageFactories = {\n mustBeTrue: Parameters<typeof createMustBeTrueValidator>[0];\n};\n\nconst DEFAULT_MESSAGES: BooleanMessageFactories = {\n mustBeTrue: defaultMustBeTreFactory,\n};\n\nexport function createBooleanValidators(\n factories?: Partial<BooleanMessageFactories>,\n) {\n const t = { ...DEFAULT_MESSAGES, ...factories };\n\n return {\n mustBeTrue: createMustBeTrueValidator(t.mustBeTrue),\n };\n}\n","import { Validator } from '../validator.type';\n\nexport function defaultMustBeMessageFactory(valueLabel: string) {\n return `Must be ${valueLabel}`;\n}\n\nexport function createMustBeValidator(\n createMessage: (valueLabel: string) => string,\n) {\n return <T>(\n value: T,\n valueLabel = `${value}`,\n matcher: (a: T, b: T) => boolean = Object.is,\n ): Validator<T> => {\n const msg = createMessage(valueLabel);\n\n return (currentValue) => {\n if (!matcher(value, currentValue)) return msg;\n return '';\n };\n };\n}\n\nexport function defaultMustBeEmptyMessageFactory() {\n return defaultMustBeMessageFactory('empty');\n}\n\nexport function createMustBeEmptyValidator(createMessage: () => string) {\n return <T>() => createMustBeValidator(createMessage)<T>(null as T);\n}\n","import { Validator } from '../validator.type';\n\nexport function defaultNotMessageFactory(valueLabel: string) {\n return `Cannot be ${valueLabel}`;\n}\n\nexport function createNotValidator(\n createMessage: (valueLabel: string) => string,\n) {\n return <T>(\n value: T,\n valueLabel = `${value}`,\n matcher: (a: T, b: T) => boolean = Object.is,\n ): Validator<T> => {\n const msg = createMessage(valueLabel);\n\n return (currentValue) => {\n if (matcher(value, currentValue)) return msg;\n return '';\n };\n };\n}\n","import { Validator } from '../validator.type';\n\nexport function defaultNotOneOfMessageFactory(values: string) {\n return `Cannot be one of: ${values}`;\n}\n\nfunction defaultToLabel<T>(value: T): string {\n return `${value}`;\n}\n\nexport function createNotOneOfValidator(\n createMessage: (values: string) => string,\n) {\n return <T>(\n values: T[],\n toLabel: (value: T) => string = defaultToLabel,\n identity: (a: T) => string = defaultToLabel,\n delimiter = ', ',\n ): Validator<T> => {\n const valuesLabel = values.map(toLabel).join(delimiter);\n const msg = createMessage(valuesLabel);\n\n const map = new Map(values.map((v) => [identity(v), v]));\n\n return (currentValue) => {\n if (map.has(identity(currentValue))) return msg;\n return '';\n };\n };\n}\n","import { Validator } from '../validator.type';\n\nexport function defaultOneOfMessageFactory(values: string) {\n return `Must be one of: ${values}`;\n}\n\nfunction defaultToLabel<T>(value: T): string {\n return `${value}`;\n}\n\nexport function createOneOfValidator(\n createMessage: (values: string) => string,\n) {\n return <T>(\n values: T[],\n toLabel: (value: T) => string = defaultToLabel,\n identity: (a: T) => string = defaultToLabel,\n delimiter = ', ',\n ): Validator<T> => {\n const valuesLabel = values.map(toLabel).join(delimiter);\n const msg = createMessage(valuesLabel);\n\n const map = new Map(values.map((v) => [identity(v), v]));\n\n return (currentValue) => {\n if (!map.has(identity(currentValue))) return msg;\n return '';\n };\n };\n}\n","import { Validator } from '../validator.type';\n\nexport function defaultRequiredMessageFactory(label = 'Field') {\n return `${label} is required`;\n}\n\nfunction requiredNumber(value: number) {\n return value !== null && value !== undefined;\n}\n\nexport function createRequiredValidator(\n createMsg: (label?: string) => string,\n): <T>(label?: string) => Validator<T> {\n return (label = 'Field') => {\n const msg = createMsg(label);\n\n return (value) => {\n if (typeof value === 'number' && !requiredNumber(value)) return msg;\n if (!value) return msg;\n return '';\n };\n };\n}\n","import { Validator } from '../validator.type';\nimport {\n createMustBeEmptyValidator,\n createMustBeValidator,\n defaultMustBeEmptyMessageFactory,\n defaultMustBeMessageFactory,\n} from './must-be';\nimport { createNotValidator, defaultNotMessageFactory } from './not';\nimport {\n createNotOneOfValidator,\n defaultNotOneOfMessageFactory,\n} from './not-one-of';\nimport { createOneOfValidator, defaultOneOfMessageFactory } from './one-of';\nimport {\n createRequiredValidator,\n defaultRequiredMessageFactory,\n} from './required';\n\nexport type GeneralMessageFactories = {\n required: Parameters<typeof createRequiredValidator>[0];\n mustBe: Parameters<typeof createMustBeValidator>[0];\n mustBeNull: Parameters<typeof createMustBeEmptyValidator>[0];\n not: Parameters<typeof createNotValidator>[0];\n oneOf: Parameters<typeof createOneOfValidator>[0];\n notOneOf: Parameters<typeof createNotOneOfValidator>[0];\n};\n\nconst DEFAULT_MESSAGES: GeneralMessageFactories = {\n required: defaultRequiredMessageFactory,\n mustBe: defaultMustBeMessageFactory,\n mustBeNull: defaultMustBeEmptyMessageFactory,\n not: defaultNotMessageFactory,\n oneOf: defaultOneOfMessageFactory,\n notOneOf: defaultNotOneOfMessageFactory,\n};\n\n/**\n * Represents the consolidated object containing all available validator generators,\n * configured with appropriate localization and date handling for the specified `TDate` type.\n *\n * Obtain this object using `injectValidators()` within an Angular injection context.\n * Use the nested `.all()` methods (e.g., `validators.string.all({...})`) with their\n * corresponding options types (e.g., `StringValidatorOptions`) to create combined\n * validator functions for your form controls.\n *\n * Individual validators (e.g., `validators.general.required()`) are also available.\n *\n * @template TDate The type used for date values (e.g., Date, Luxon DateTime). Defaults to `Date`.\n */\nexport function createGeneralValidators(\n factories?: Partial<GeneralMessageFactories>,\n) {\n const t = { ...DEFAULT_MESSAGES, ...factories };\n\n const mustBeNull = createMustBeEmptyValidator(t.mustBeNull);\n\n return {\n required: createRequiredValidator(t.required),\n mustBe: createMustBeValidator(t.mustBe),\n mustBeNull: createMustBeEmptyValidator(t.mustBeNull),\n not: createNotValidator(t.not),\n oneOf: <T>(\n values: T[],\n toLabel?: (value: T) => string,\n identity?: (a: T) => string,\n delimiter?: string,\n ): Validator<T> => {\n if (!values.length) return mustBeNull<T>();\n\n return createOneOfValidator(t.oneOf)(\n values,\n toLabel,\n identity,\n delimiter,\n );\n },\n notOneOf: createNotOneOfValidator(t.notOneOf),\n };\n}\n","import { Validator } from '../validator.type';\n\nexport function defaultIsDateMessageFactory() {\n return `Must be a valid date`;\n}\n\nexport function createIsDateValidator<TDate = Date>(\n createMsg: () => string,\n toDate: (date: TDate | string) => Date,\n): () => Validator<TDate | string | null> {\n const msg = createMsg();\n return () => {\n return (value) => {\n if (value === null) return '';\n\n const date = toDate(value);\n if (isNaN(date.getTime())) return msg;\n\n return '';\n };\n };\n}\n","import { Validator } from '../validator.type';\n\nexport function defaultMaxDateMessageFactory(dateLabel: string) {\n return `Must be before ${dateLabel}`;\n}\n\nexport function createMaxDateValidator<TDate = Date>(\n createMsg: (dateLabel: string) => string,\n toDate: (date: TDate | string) => Date,\n formatDate: (date: Date, locale: string) => string,\n locale: string,\n): (date: TDate | string) => Validator<TDate | string | null> {\n return (date) => {\n const d = toDate(date);\n const matchTime = d.getTime();\n\n const msg = createMsg(formatDate(d, locale));\n return (value) => {\n if (value === null) return '';\n if (toDate(value).getTime() > matchTime) return msg;\n return '';\n };\n };\n}\n","import { Validator } from '../validator.type';\n\nexport function defaultMinDateMessageFactory(dateLabel: string) {\n return `Must be after ${dateLabel}`;\n}\n\nexport function createMinDateValidator<TDate = Date>(\n createMsg: (dateLabel: string) => string,\n toDate: (date: TDate | string) => Date,\n formatDate: (date: Date, locale: string) => string,\n locale: string,\n): (date: TDate | string) => Validator<TDate | string | null> {\n return (date) => {\n const d = toDate(date);\n const matchTime = d.getTime();\n\n const msg = createMsg(formatDate(d, locale));\n return (value) => {\n if (value === null) return '';\n if (toDate(value).getTime() < matchTime) return msg;\n return '';\n };\n };\n}\n","import { formatDate } from '@angular/common';\n\ntype LuxonDateTimeLike = {\n toJSDate(): Date;\n toUnixInteger(): number;\n};\n\ntype MomentDateTimeLike = {\n toDate(): Date;\n unix(): number;\n};\n\nfunction isLuxonLike(date: object): date is LuxonDateTimeLike {\n if (!date) return false;\n\n return (\n 'toJSDate' in date &&\n 'toUnixInteger' in date &&\n typeof date.toJSDate === 'function' &&\n typeof date.toUnixInteger === 'function'\n );\n}\n\nfunction isMomentLike(date: object): date is MomentDateTimeLike {\n if (!date) return false;\n return (\n 'toDate' in date &&\n 'unix' in date &&\n typeof date.toDate === 'function' &&\n typeof date.unix === 'function'\n );\n}\n\nexport function defaultToDate<TDate>(date: TDate | string): Date {\n if (date instanceof Date) return date;\n\n if (typeof date === 'string' || typeof date === 'number')\n return new Date(date);\n\n if (!date) return new Date();\n\n if (typeof date !== 'object')\n throw new Error('Date is not number, string, null, undefined or object');\n\n if (isMomentLike(date)) return date.toDate();\n\n if (isLuxonLike(date)) return date.toJSDate();\n\n return new Date();\n}\n\nexport function defaultFormatDate(\n date: Date,\n locale: string,\n format = 'mediumDate',\n) {\n return formatDate(date, format, locale);\n}\n","import { createGeneralValidators } from '../general';\nimport { createMergeValidators } from '../merge-validators';\nimport { Validator } from '../validator.type';\nimport { createIsDateValidator, defaultIsDateMessageFactory } from './is-date';\nimport {\n createMaxDateValidator,\n defaultMaxDateMessageFactory,\n} from './max-date';\nimport {\n createMinDateValidator,\n defaultMinDateMessageFactory,\n} from './min-date';\nimport { defaultFormatDate, defaultToDate } from './util';\n\nexport type DateMessageFactories = {\n min: Parameters<typeof createMinDateValidator>[0];\n max: Parameters<typeof createMaxDateValidator>[0];\n isDate: Parameters<typeof createIsDateValidator>[0];\n};\n\nexport const DEFAULT_DATE_MESSAGES: DateMessageFactories = {\n min: defaultMinDateMessageFactory,\n max: defaultMaxDateMessageFactory,\n isDate: defaultIsDateMessageFactory,\n};\n\n/**\n * Configuration options for creating a combined date validator using the `.all()`\n * method returned by `createDateValidators` (accessed via `injectValidators().date.all`).\n *\n * Pass an object of this type to `validators.date.all({...})` to specify which\n * date-related validations should be included in the resulting validator function.\n * The `toDate` function provided via `provideValidatorConfig` will be used internally\n * to handle comparisons if `string` or custom date object types are used for the options\n * or the control's value.\n *\n * The validation function returned by `.all()` expects an input value of type `TDate | null`,\n * where `TDate` matches the type configured via `provideValidatorConfig`.\n */\nexport type DateValidatorOptions = {\n /**\n * If `true`, the date value must not be `null` or `undefined`.\n * Uses the configured 'required' validation message.\n * @see Validators.general.required\n * @example\n * { required: true }\n */\n required?: boolean;\n\n /**\n * Specifies the minimum allowed date (inclusive).\n * Validation fails if the input date is earlier than this date.\n * Accepts a `Date` object or a date string parseable by the configured `toDate` function.\n * Uses the configured 'min date' validation message.\n * @example\n * { min: new Date('2024-01-01') }\n * { min: '2024-01-01T00:00:00.000Z' }\n * @see Validators.date.min\n */\n min?: Date | string; // Accepts string or Date for config, internal `toDate` handles it\n\n /**\n * Specifies the maximum allowed date (inclusive).\n * Validation fails if the input date is later than this date.\n * Accepts a `Date` object or a date string parseable by the configured `toDate` function.\n * Uses the configured 'max date' validation message.\n * @example\n * { max: '2025-12-31' }\n * @see Validators.date.max\n */\n max?: Date | string;\n\n /**\n * The date value must be exactly equal to the specified value (which can be `null`).\n * Uses the configured `mustBe` validation message.\n * Date comparison uses the configured `toDate` function internally.\n * @example\n * { mustBe: new Date('2024-07-04') } // Must be exactly July 4th, 2024\n * { mustBe: null } // Must be exactly null\n * @see Validators.general.mustBe\n * @see Validators.general.mustBeNull\n */\n mustBe?: Date | string | null;\n\n /**\n * The date value must *not* be equal to the specified value (which can be `null`).\n * Uses the configured `not` validation message.\n * Date comparison uses the configured `toDate` function internally.\n * @example\n * { not: new Date('2000-01-01') } // Cannot be the start of the millennium\n * @see Validators.general.not\n */\n not?: Date | string | null;\n\n /**\n * The date value must be one of the dates (or `null`) included in the specified array.\n * Uses the configured `oneOf` validation message.\n * Date comparison uses the configured `toDate` function internally.\n * @example\n * { oneOf: [new Date('2024-12-25'), null] } // Must be Christmas 2024 or null\n * @see Validators.general.oneOf\n */\n oneOf?: (Date | string | null)[];\n\n /**\n * The date value must *not* be any of the dates (or `null`) included in the specified array.\n * Uses the configured `notOneOf` validation message.\n * Date comparison uses the configured `toDate` function internally.\n * @example\n * { notOneOf: [new Date('2024-04-01')] } // Cannot be April Fool's Day 2024\n * @see Validators.general.notOneOf\n */\n notOneOf?: (Date | string | null)[];\n\n /**\n * Optional configuration passed down to specific message factories.\n * Currently primarily used by the `required` validator's message factory.\n */\n messageOptions?: {\n /**\n * An optional label for the field (e.g., 'Start Date', 'End Date').\n * This can be incorporated into the 'required' error message by its factory\n * (e.g., \"Start Date is required\" instead of just \"Field is required\").\n * @example\n * { required: true, messageOptions: { label: 'Start Date' } }\n */\n label?: string;\n };\n};\n\nexport function createDateValidators<TDate = Date>(\n factories?: Partial<DateMessageFactories>,\n toDate = defaultToDate<TDate>,\n formatDate = defaultFormatDate,\n locale = 'en-US',\n generalValidators = createGeneralValidators(),\n merger = createMergeValidators(),\n) {\n const t = { ...DEFAULT_DATE_MESSAGES, ...factories };\n const base = {\n min: createMinDateValidator(t.min, toDate, formatDate, locale),\n max: createMaxDateValidator(t.max, toDate, formatDate, locale),\n isDate: createIsDateValidator(t.isDate, toDate),\n };\n\n const toLabel = (d: TDate | null) =>\n d ? formatDate(toDate(d), locale) : 'null';\n\n return {\n ...base,\n all: (opt: DateValidatorOptions) => {\n const validators: Validator<TDate | null>[] = [];\n\n if (opt.required)\n validators.push(generalValidators.required(opt.messageOptions?.label));\n\n validators.push(base.isDate());\n\n if (opt.mustBe !== undefined) {\n if (opt.mustBe === null)\n validators.push(generalValidators.mustBeNull<TDate | null>());\n else {\n const d = toDate(opt.mustBe as TDate | string);\n const formatted = formatDate(d, locale);\n validators.push(\n generalValidators.mustBe<TDate | null>(\n d as TDate,\n formatted,\n (a, b) => {\n if (!a && !b) return true;\n if (!a || !b) return false;\n\n return toDate(a).getTime() === toDate(b).getTime();\n },\n ),\n );\n }\n }\n\n if (opt.not !== undefined) {\n if (opt.not === null)\n validators.push(generalValidators.not<TDate | null>(null));\n else {\n const d = toDate(opt.not as TDate);\n const formatted = formatDate(d, locale);\n validators.push(\n generalValidators.not<TDate | null>(\n d as TDate,\n formatted,\n (a, b) => {\n if (!a && !b) return true;\n if (!a || !b) return false;\n\n return toDate(a).getTime() === toDate(b).getTime();\n },\n ),\n );\n }\n }\n\n if (opt.min !== undefined) validators.push(base.min(opt.min as TDate));\n if (opt.max !== undefined) validators.push(base.max(opt.max as TDate));\n\n if (opt.oneOf) {\n const dates = opt.oneOf.map((d) => (d ? toDate(d as TDate) : null));\n\n validators.push(\n generalValidators.oneOf<TDate | null>(\n dates as TDate[],\n toLabel,\n (a) =>\n a === null || a === undefined\n ? 'null'\n : toDate(a).getTime().toString(),\n ),\n );\n }\n\n if (opt.notOneOf) {\n const dates = opt.notOneOf.map((d) => (d ? toDate(d as TDate) : null));\n validators.push(\n generalValidators.notOneOf<TDate | null>(\n dates as TDate[],\n toLabel,\n (a) =>\n a === null || a === undefined\n ? 'null'\n : toDate(a).getTime().toString(),\n ),\n );\n }\n\n return merger(validators as Validator<TDate | null>[]);\n },\n util: {\n toDate,\n formatDate,\n },\n };\n}\n","import {\n DateMessageFactories,\n DateValidatorOptions,\n DEFAULT_DATE_MESSAGES,\n} from '.';\nimport { createGeneralValidators } from '../general';\nimport { createMergeValidators } from '../merge-validators';\nimport { Validator } from '../validator.type';\nimport { createIsDateValidator } from './is-date';\nimport { createMaxDateValidator } from './max-date';\nimport { createMinDateValidator } from './min-date';\nimport { defaultFormatDate, defaultToDate } from './util';\n\n/**\n * Represents a date range with two date values: `from` and `to`.\n * Both values can be `null` to indicate an empty range.\n * @template TDate The type of the date values (e.g., `Date`, Luxon `DateTime`, Moment).\n */\nexport type DateRange<TDate = Date> = {\n /** The start date of the range. Can be `null` if not set. */\n start: TDate | null;\n /** The end date of the range. Can be `null` if not set. */\n end: TDate | null;\n};\n\nexport function createDateRangeValidators<TDate = Date>(\n factories?: Partial<DateMessageFactories>,\n toDate = defaultToDate<TDate>,\n formatDate = defaultFormatDate,\n locale = 'en-US',\n generalValidators = createGeneralValidators(),\n merger = createMergeValidators(),\n) {\n const t = { ...DEFAULT_DATE_MESSAGES, ...factories };\n const base = {\n min: createMinDateValidator(t.min, toDate, formatDate, locale),\n max: createMaxDateValidator(t.max, toDate, formatDate, locale),\n isDate: createIsDateValidator(t.isDate, toDate),\n };\n\n return {\n ...base,\n all: (\n opt: Pick<\n DateValidatorOptions,\n 'required' | 'min' | 'max' | 'messageOptions'\n >,\n ) => {\n const validators: Validator<DateRange<TDate>>[] = [];\n\n if (opt.required) {\n const val = generalValidators.required(opt.messageOptions?.label);\n\n validators.push((value) => val(value.start) || val(value.end));\n }\n\n const isDateValidator = base.isDate();\n\n validators.push(\n (value) => isDateValidator(value.start) || isDateValidator(value.end),\n );\n\n if (opt.min !== undefined) {\n const minVal = base.min(opt.min as TDate);\n validators.push((value) => minVal(value.start));\n }\n\n if (opt.max !== undefined) {\n const maxVal = base.max(opt.max as TDate);\n validators.push((value) => maxVal(value.end));\n }\n\n validators.push((value) => {\n if (value.start === null || value.end === null) return '';\n\n const minVal = base.min(value.start);\n const maxVal = base.max(value.end);\n\n return minVal(value.end) || maxVal(value.start);\n });\n\n return merger(validators);\n },\n util: {\n toDate,\n formatDate,\n },\n };\n}\n","import { Validator } from '../validator.type';\n\nexport function defaultIntegerMessageFactory() {\n return `Must be an integer`;\n}\n\nexport function createIntegerValidator(\n createMsg: () => string,\n): () => Validator<number | null> {\n return () => {\n const msg = createMsg();\n return (value) => {\n if (value === null) return '';\n if (!Number.isInteger(value)) return msg;\n return '';\n };\n };\n}\n","import { Validator } from '../validator.type';\n\nexport function defaultIsNumberMessageFactory() {\n return `Must be a number`;\n}\n\nexport function createIsNumberValidator(\n createMsg: () => string,\n): () => Validator<number | null> {\n return () => {\n const msg = createMsg();\n return (value) => {\n if (value === null) return '';\n if (typeof value !== 'number' || isNaN(value)) return msg;\n return '';\n };\n };\n}\n","import { Validator } from '../validator.type';\n\nexport function defaultMaxMessageFactory(max: number) {\n return `Must be at most ${max}`;\n}\n\nexport function createMaxValidator(\n createMsg: (max: number) => string,\n): (max: number) => Validator<number | null> {\n return (max) => {\n const msg = createMsg(max);\n return (value) => {\n if (value === null) return '';\n if (value > max) return msg;\n return '';\n };\n };\n}\n","import { Validator } from '../validator.type';\n\nexport function defaultMinMessageFactory(min: number) {\n return `Must be at least ${min}`;\n}\n\nexport function createMinValidator(\n createMsg: (min: number) => string,\n): (min: number) => Validator<number | null> {\n return (min) => {\n const msg = createMsg(min);\n return (value) => {\n if (value === null) return '';\n if (value < min) return msg;\n return '';\n };\n };\n}\n","import { Validator } from '../validator.type';\n\nexport function defaultMultipleOfMessageFactory(multipleOf: number) {\n return `Must be a multiple of ${multipleOf}`;\n}\n\nexport function createMultipleOfValidator(\n createMsg: (multipleOf: number) => string,\n): (multipleOf: number) => Validator<number | null> {\n return (multipleOf) => {\n const msg = createMsg(multipleOf);\n\n return (value) => {\n if (value === null) return '';\n if (value % multipleOf !== 0) return msg;\n return '';\n };\n };\n}\n","import { createGeneralValidators } from '../general';\nimport { createMergeValidators } from '../merge-validators';\nimport { Validator } from '../validator.type';\nimport {\n createIntegerValidator,\n defaultIntegerMessageFactory,\n} from './integer';\nimport {\n createIsNumberValidator,\n defaultIsNumberMessageFactory,\n} from './is-number';\nimport { createMaxValidator, defaultMaxMessageFactory } from './max';\nimport { createMinValidator, defaultMinMessageFactory } from './min';\nimport {\n createMultipleOfValidator,\n defaultMultipleOfMessageFactory,\n} from './multiple-of';\n\nexport type NumberMessageFactories = {\n min: Parameters<typeof createMinValidator>[0];\n max: Parameters<typeof createMaxValidator>[0];\n isNumber: Parameters<typeof createIsNumberValidator>[0];\n multipleOf: Parameters<typeof createMultipleOfValidator>[0];\n integer: Parameters<typeof createIntegerValidator>[0];\n};\n\nconst DEFAULT_MESSAGES: NumberMessageFactories = {\n min: defaultMinMessageFactory,\n max: defaultMaxMessageFactory,\n isNumber: defaultIsNumberMessageFactory,\n multipleOf: defaultMultipleOfMessageFactory,\n integer: defaultIntegerMessageFactory,\n};\n\n/**\n * Configuration options for creating a combined number validator using the `.all()`\n * method returned by `createNumberValidators` (accessed via `injectValidators().number.all`).\n *\n * Pass an object of this type to `validators.number.all({...})` to specify which\n * number-related validations should be included in the resulting validator function.\n * The validation function returned by `.all()` expects an input value of type `number | null`.\n */\nexport type NumberValidatorOptions = {\n /**\n * If `true`, the number value must not be `null` or `undefined`.\n * Uses the configured 'required' validation message.\n * @see Validators.general.required\n * @example { required: true }\n */\n required?: boolean;\n\n /**\n * Specifies the minimum allowed value (inclusive).\n * Validation fails if the input number is strictly less than `min`.\n * Uses the configured 'min number' validation message.\n * @example { min: 0 } // Number must be 0 or greater\n * @see Validators.number.min\n */\n min?: number;\n\n /**\n * Specifies the maximum allowed value (inclusive).\n * Validation fails if the input number is strictly greater than `max`.\n * Uses the configured 'max number' validation message.\n * @example { max: 100 } // Number must be 100 or less\n * @see Validators.number.max\n */\n max?: number;\n\n /**\n * If `true`, the number value must be an integer (i.e., have no fractional part).\n * Uses the configured 'integer' validation message.\n * Note: `null` or `undefined` values typically pass this check; combine with `required: true` if needed.\n * @example { integer: true }\n * @see Validators.number.integer\n */\n integer?: boolean;\n\n /**\n * The number value must be a multiple of the specified number (`multipleOf`).\n * For example, if `multipleOf` is 5, valid numbers are 0, 5, 10, -5, etc.\n * Uses the configured 'multipleOf' validation message.\n * Note: `null` or `undefined` values typically pass this check.\n * @example { multipleOf: 5 } // Must be a multiple of 5\n * @example { multipleOf: 0.01 } // Useful for currency (max 2 decimal places)\n * @see Validators.number.multipleOf\n */\n multipleOf?: number;\n\n /**\n * The number value must be exactly equal to the specified value (or `null`).\n * Uses the configured `mustBe` validation message.\n * @example { mustBe: 42 } // Must be exactly 42\n * @example { mustBe: null } // Must be exactly null\n * @see Validators.general.mustBe\n * @see Validators.general.mustBeNull\n */\n mustBe?: number | null;\n\n /**\n * The number value must *not* be equal to the specified value (or `null`).\n * Uses the configured `not` validation message.\n * @example { not: 0 } // Cannot be zero\n * @see Validators.general.not\n */\n not?: number | null;\n\n /**\n * The number value must be one of the numbers (or `null`) included in the specified array.\n * Uses the configured `oneOf` validation message.\n * @example { oneOf: [1, 3, 5, null] } // Must be 1, 3, 5, or null\n * @see Validators.general.oneOf\n */\n oneOf?: (number | null)[];\n\n /**\n * The number value must *not* be any of the numbers (or `null`) included in the specified array.\n * Uses the configured `notOneOf` validation message.\n * @example { notOneOf: [0, 13] } // Cannot be 0 or 13\n * @see Validators.general.notOneOf\n */\n notOneOf?: (number | null)[];\n\n /**\n * Optional configuration passed down to specific message factories.\n * Primarily used by the `required` validator's message factory.\n */\n messageOptions?: {\n /**\n * An optional label for the field (e.g., 'Age', 'Quantity')\n * which can be incorporated into the 'required' error message by its factory.\n * @example { required: true, messageOptions: { label: 'Quantity' } } // Error might be \"Quantity is required\"\n */\n label?: string;\n };\n};\n\nexport function createNumberValidators(\n factories?: Partial<NumberMessageFactories>,\n generalValidators = createGeneralValidators(),\n merger = createMergeValidators(),\n) {\n const t = { ...DEFAULT_MESSAGES, ...factories };\n\n const base = {\n min: createMinValidator(t.min),\n max: createMaxValidator(t.max),\n isNumber: createIsNumberValidator(t.isNumber),\n multipleOf: createMultipleOfValidator(t.multipleOf),\n integer: createIntegerValidator(t.integer),\n };\n\n return {\n ...base,\n all: (opt: NumberValidatorOptions) => {\n const validators: Validator<number | null>[] = [];\n\n if (opt.required)\n validators.push(generalValidators.required(opt.messageOptions?.label));\n\n validators.push(base.isNumber());\n\n if (opt.mustBe !== undefined)\n validators.push(generalValidators.mustBe(opt.mustBe));\n\n if (opt.not !== undefined)\n validators.push(generalValidators.not(opt.not));\n\n if (opt.integer) validators.push(base.integer());\n\n if (opt.min !== undefined) validators.push(base.min(opt.min));\n\n if (opt.max !== undefined) validators.push(base.max(opt.max));\n\n if (opt.multipleOf !== undefined)\n validators.push(base.multipleOf(opt.multipleOf));\n\n if (opt.oneOf) validators.push(generalValidators.oneOf(opt.oneOf));\n\n if (opt.notOneOf)\n validators.push(generalValidators.notOneOf(opt.notOneOf));\n\n return merger(validators);\n },\n };\n}\n","import { Validator } from '../validator.type';\n\nconst EMAIL_REGEXP =\n /^(?=.{1,254}$)(?=.{1,64}@)[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;\n\nexport function defaultEmailMessageFactory() {\n return `Must be a valid email`;\n}\n\nexport function createEmailValidator(\n createMsg: () => string,\n): () => Validator<string | null> {\n return () => {\n const msg = createMsg();\n\n return (value) => {\n if (value === null) return '';\n if (!EMAIL_REGEXP.test(value)) return msg;\n return '';\n };\n };\n}\n","import { Validator } from '../validator.type';\n\nexport function defaultIsStringMessageFactory() {\n return `Must be a string`;\n}\n\nexport function createIsStringValidator(\n createMsg: () => string,\n): () => Validator<string | null> {\n return () => {\n const msg = createMsg();\n return (value) => {\n if (value === null) return '';\n if (typeof value !== 'string') return msg;\n return '';\n };\n };\n}\n","import {\n createMaxLengthValidator as arrayCreateMaxValidator,\n defaultMaxLengthMessageFactory as arrayDefaultMessageFactory,\n} from '../array/max-length';\nimport { Validator } from '../validator.type';\n\nexport function defaultMaxLengthMessageFactory(max: number) {\n return arrayDefaultMessageFactory(max, 'characters');\n}\n\nexport function createMaxLengthValidator(\n createMsg: (max: number) => string,\n): (max: number) => Validator<string | null> {\n return arrayCreateMaxValidator((max) => createMsg(max));\n}\n","import {\n createMinLengthValidator as arrayCreateMinValidator,\n defaultMinLengthMessageFactory as arrayDefaultMessageFactory,\n} from '../array/min-length';\nimport { Validator } from '../validator.type';\n\nexport function defaultMinLengthMessageFactory(min: number) {\n return arrayDefaultMessageFactory(min, 'characters');\n}\n\nexport function createMinLengthValidator(\n createMsg: (min: number) => string,\n): (min: number) => Validator<string | null> {\n return arrayCreateMinValidator((min) => createMsg(min));\n}\n","import { Validator } from '../validator.type';\n\nexport function defaultPatternMessageFactory(patternLabel: string) {\n return `Must match pattern ${patternLabel}`;\n}\n\nexport function createPatternValidator(\n createMsg: (patternLabel: string) => string,\n): (pattern: string | RegExp) => Validator<string | null> {\n return (pattern: string | RegExp) => {\n const regex = new RegExp(pattern);\n const msg = createMsg(regex.source);\n\n return (value) => {\n if (value === null) return '';\n if (!regex.test(value)) return msg;\n return '';\n };\n };\n}\n","import { Validator } from '../validator.type';\n\nexport function defaultTrimmedMessageFactory() {\n return `Cannot contain leading or trailing whitespace`;\n}\n\nexport function createTrimmedValidator(\n createMsg: () => string,\n): () => Validator<string | null> {\n return () => {\n const msg = createMsg();\n\n return (value) => {\n if (value === null) return '';\n if (value.trim().length !== value.length) return msg;\n return '';\n };\n };\n}\n","import { Validator } from '../validator.type';\n\nconst URI_REGEXP = /^(https?|ftp):\\/\\/[^\\s/$.?#].[^\\s]*$/;\n\nexport function defaultURIMessageFactory() {\n return `Must be a valid URI`;\n}\n\nexport function createURIValidator(\n createMsg: () => string,\n): () => Validator<string | null> {\n return () => {\n const msg = createMsg();\n\n return (value) => {\n if (value === null) return '';\n if (!URI_REGEXP.test(value)) return msg;\n return '';\n };\n };\n}\n","import { createGeneralValidators } from '../general';\nimport { createMergeValidators } from '../merge-validators';\nimport { Validator } from '../validator.type';\nimport { createEmailValidator, defaultEmailMessageFactory } from './email';\nimport {\n createIsStringValidator,\n defaultIsStringMessageFactory,\n} from './is-string';\nimport {\n createMaxLengthValidator,\n defaultMaxLengthMessageFactory,\n} from './max-chars';\nimport {\n createMinLengthValidator,\n defaultMinLengthMessageFactory,\n} from './min-chars';\nimport {\n createPatternValidator,\n defaultPatternMessageFactory,\n} from './pattern';\nimport {\n createTrimmedValidator,\n defaultTrimmedMessageFactory,\n} from './trimmed';\nimport { createURIValidator, defaultURIMessageFactory } from './uri';\n\nexport type StringMessageFactories = {\n email: Parameters<typeof createEmailValidator>[0];\n uri: Parameters<typeof createURIValidator>[0];\n pattern: Parameters<typeof createPatternValidator>[0];\n trimmed: Parameters<typeof createTrimmedValidator>[0];\n isString: Parameters<typeof createIsStringValidator>[0];\n minLength: Parameters<typeof createMinLengthValidator>[0];\n maxLength: Parameters<typeof createMaxLengthValidator>[0];\n};\n\nconst DEFAULT_MESSAGES: StringMessageFactories = {\n email: defaultEmailMessageFactory,\n uri: defaultURIMessageFactory,\n pattern: defaultPatternMessageFactory,\n trimmed: defaultTrimmedMessageFactory,\n isString: defaultIsStringMessageFactory,\n minLength: defaultMinLengthMessageFactory,\n maxLength: defaultMaxLengthMessageFactory,\n};\n\n/**\n * Configuration options for creating a combined string validator using the `.all()`\n * method returned by `createStringValidators` (accessed via `injectValidators().string.all`).\n *\n * Pass an object of this type to `validators.string.all({...})` to specify which\n * string-related validations should be included in the resulting validator function.\n * The validation function returned by `.all()` expects an input value of type `string | null`.\n */\nexport type StringValidatorOptions = {\n /**\n * If `true`, the string value must not be `null`, `undefined`, or an empty string (`''`).\n * Uses the configured 'required' validation message.\n * Note: This behavior (checking for empty string) might differ from a generic `required`\n * check on other types.\n * @see Validators.general.required\n * @example { required: true }\n */\n required?: boolean;\n\n /**\n * Specifies the minimum allowed length of the string (inclusive).\n * Validation fails if `value.length < minLength`.\n * Note: Behavior with leading/trailing whitespace depends on whether `trimmed` is also used\n * or if the underlying implementation trims by default. Assumed to operate on raw length unless specified otherwise.\n * Uses the configured 'minLength' validation message.\n * @example { minLength: 3 } // Must be at least 3 characters long\n * @see Validators.string.minLength\n */\n minLength?: number;\n\n /**\n * Specifies the maximum allowed length of the string (inclusive).\n * Validation fails if `value.length > maxLength`.\n * Uses the configured 'maxLength' validation message.\n * @example { maxLength: 50 } // Cannot exceed 50 characters\n * @see Validators.string.maxLength\n */\n maxLength?: number;\n\n /**\n * If `true`, the string value must not have leading or trailing whitespace.\n * Validation fails if `value !== value.trim()`.\n * Uses the configured 'trimmed' validation message.\n * @example { trimmed: true } // Value like \" test \" would be invalid\n * @see Validators.string.trimmed\n */\n trimmed?: boolean;\n\n /**\n * Requires the string to match a specific pattern. Accepts:\n * - A `RegExp` object for custom patterns (e.g., `/^[a-z]+$/i`).\n * - The string literal `'email'` to use a built-in email format validator.\n * - The string literal `'uri'` to use a built-in URI/URL format validator.\n * - Potentially other string representations of regex patterns (implementation dependent).\n * Uses the configured 'pattern', 'email', or 'uri' validation message.\n * @example { pattern: /^\\d{3}-\\d{2}-\\d{4}$/ } // SSN format\n * @example { pattern: 'email' } // Standard email format\n * @example { pattern: 'uri' } // Standard URI format\n * @see Validators.string.pattern\n * @see Validators.string.email\n * @see Validators.string.uri\n */\n pattern?: RegExp | 'email' | 'uri' | Omit<string, 'email' | 'uri'>;\n\n /**\n * The string value must be exactly equal to the specified string (or `null`).\n * Case-sensitive comparison.\n * Uses the configured `mustBe` validation message.\n * @example { mustBe: \"CONFIRMED\" }\n * @example { mustBe: null }\n * @see Validators.general.mustBe\n * @see Validators.general.mustBeNull\n */\n mustBe?: string | null;\n\n /**\n * The string value must *not* be equal to the specified string (or `null`).\n * Case-sensitive comparison.\n * Uses the configured `not` validation message.\n * @example { not: \"password\" } // Cannot be the literal string \"password\"\n * @see Validators.general.not\n */\n not?: string | null;\n\n /**\n * The string value must be one of the strings (or `null`) included in the specified array.\n * Case-sensitive comparison.\n * Uses the configured `oneOf` validation message.\n * @example { oneOf: [\"PENDING\", \"APPROVED\", \"REJECTED\", null] }\n * @see Validators.general.oneOf\n */\n oneOf?: (string | null)[];\n\n /**\n * The string value must *not* be any of the strings (or `null`) included in the specified array.\n * Case-sensitive comparison.\n * Uses the configured `notOneOf` validation message.\n * @example { notOneOf: [\"admin\", \"root\"] }\n * @see Validators.general.notOneOf\n */\n notOneOf?: (string | null)[];\n\n /**\n * Optional configuration passed down to specific message factories.\n * Primarily used by the `required` validator's message factory.\n */\n messageOptions?: {\n /**\n * An optional label for the field (e.g., 'Username', 'Email Address')\n * which can be incorporated into the 'required' error message by its factory.\n * @example { required: true, messageOptions: { label: 'Email Address' } } // Error might be \"Email Address is required\"\n */\n label?: string;\n };\n};\n\nexport function createStringValidators(\n factories?: Partial<StringMessageFactories>,\n generalValidators = createGeneralValidators(),\n merger = createMergeValidators(),\n) {\n const t = { ...DEFAULT_MESSAGES, ...factories };\n\n const base = {\n email: createEmailValidator(t.email),\n uri: createURIValidator(t.uri),\n pattern: createPatternValidator(t.pattern),\n trimmed: createTrimmedValidator(t.trimmed),\n isString: createIsStringValidator(t.isString),\n minLength: createMinLengthValidator(t.minLength),\n maxLength: createMaxLengthValidator(t.maxLength),\n };\n\n return {\n ...base,\n all: (opt: StringValidatorOptions) => {\n const validators: Validator<string | null>[] = [];\n\n if (opt.required)\n validators.push(generalValidators.required(opt?.messageOption