@4riders/reform 3.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +266 -0
  3. package/dist/index.d.ts +2715 -0
  4. package/dist/index.es.js +1715 -0
  5. package/dist/index.es.js.map +1 -0
  6. package/package.json +70 -0
  7. package/src/index.ts +90 -0
  8. package/src/reform/ArrayHelper.ts +164 -0
  9. package/src/reform/Form.tsx +81 -0
  10. package/src/reform/FormManager.ts +494 -0
  11. package/src/reform/Reform.ts +15 -0
  12. package/src/reform/components/BaseCheckboxField.tsx +72 -0
  13. package/src/reform/components/BaseDateField.tsx +84 -0
  14. package/src/reform/components/BaseRadioField.tsx +72 -0
  15. package/src/reform/components/BaseSelectField.tsx +103 -0
  16. package/src/reform/components/BaseTextAreaField.tsx +87 -0
  17. package/src/reform/components/BaseTextField.tsx +135 -0
  18. package/src/reform/components/InputHTMLProps.tsx +89 -0
  19. package/src/reform/observers/observer.ts +131 -0
  20. package/src/reform/observers/observerPath.ts +327 -0
  21. package/src/reform/observers/useObservers.ts +232 -0
  22. package/src/reform/useForm.ts +246 -0
  23. package/src/reform/useFormContext.tsx +37 -0
  24. package/src/reform/useFormField.ts +75 -0
  25. package/src/reform/useRender.ts +12 -0
  26. package/src/yop/MessageProvider.ts +204 -0
  27. package/src/yop/Metadata.ts +304 -0
  28. package/src/yop/ObjectsUtil.ts +811 -0
  29. package/src/yop/TypesUtil.ts +148 -0
  30. package/src/yop/ValidationContext.ts +207 -0
  31. package/src/yop/Yop.ts +430 -0
  32. package/src/yop/constraints/CommonConstraints.ts +124 -0
  33. package/src/yop/constraints/Constraint.ts +135 -0
  34. package/src/yop/constraints/MinMaxConstraints.ts +53 -0
  35. package/src/yop/constraints/OneOfConstraint.ts +40 -0
  36. package/src/yop/constraints/TestConstraint.ts +176 -0
  37. package/src/yop/decorators/array.ts +157 -0
  38. package/src/yop/decorators/boolean.ts +69 -0
  39. package/src/yop/decorators/date.ts +73 -0
  40. package/src/yop/decorators/email.ts +66 -0
  41. package/src/yop/decorators/file.ts +69 -0
  42. package/src/yop/decorators/id.ts +35 -0
  43. package/src/yop/decorators/ignored.ts +40 -0
  44. package/src/yop/decorators/instance.ts +110 -0
  45. package/src/yop/decorators/number.ts +73 -0
  46. package/src/yop/decorators/string.ts +90 -0
  47. package/src/yop/decorators/test.ts +41 -0
  48. package/src/yop/decorators/time.ts +112 -0
@@ -0,0 +1,53 @@
1
+ import { Constraint, validateConstraint } from "./Constraint"
2
+ import { InternalValidationContext, ValuedContext } from "../ValidationContext"
3
+
4
+ /**
5
+ * Interface for min and max constraints on a value.
6
+ * @template Value - The type of the value being validated.
7
+ * @template MinMax - The type for min/max values.
8
+ * @template Parent - The type of the parent object.
9
+ * @property min - Minimum constraint for the value, if any.
10
+ * @property max - Maximum constraint for the value, if any.
11
+ * @property isMinMaxType - Type guard for min/max values.
12
+ * @category Shared Constraints
13
+ */
14
+ export interface MinMaxConstraints<Value, MinMax, Parent = unknown> {
15
+ /**
16
+ * Minimum constraint for the value. The value must be greater than or equal to this constraint.
17
+ */
18
+ min?: Constraint<NonNullable<Value>, MinMax | null | undefined, Parent>
19
+ /**
20
+ * Maximum constraint for the value. The value must be less than or equal to this constraint.
21
+ */
22
+ max?: Constraint<NonNullable<Value>, MinMax | null | undefined, Parent>
23
+ /**
24
+ * Optional type guard function to determine if a value is a valid min/max constraint. This can be used to ensure
25
+ * that the constraint values are of the expected type.
26
+ */
27
+ isMinMaxType?: (value: any) => value is MinMax
28
+ }
29
+
30
+ /**
31
+ * Validates min and max constraints for a value.
32
+ * @template Value - The type of the value being validated.
33
+ * @template MinMax - The type for min/max values.
34
+ * @template Parent - The type of the parent object.
35
+ * @param context - The validation context.
36
+ * @param constraints - The min/max constraints to validate.
37
+ * @param isConstraintValue - Type guard for min/max values.
38
+ * @param validateMin - Function to validate the min constraint.
39
+ * @param validateMax - Function to validate the max constraint.
40
+ * @returns True if both min and max constraints pass, false otherwise.
41
+ * @ignore
42
+ */
43
+ export function validateMinMaxConstraints<Value, MinMax, Parent>(
44
+ context: InternalValidationContext<Value, Parent>,
45
+ constraints: MinMaxConstraints<Value, MinMax, Parent>,
46
+ isConstraintValue: (value: any) => value is MinMax,
47
+ validateMin: (value: NonNullable<Value>, min: NonNullable<MinMax>) => boolean,
48
+ validateMax: (value: NonNullable<Value>, max: NonNullable<MinMax>) => boolean) {
49
+ return (
50
+ validateConstraint(context as ValuedContext<Value, Parent>, constraints, "min", isConstraintValue, validateMin) &&
51
+ validateConstraint(context as ValuedContext<Value, Parent>, constraints, "max", isConstraintValue, validateMax)
52
+ )
53
+ }
@@ -0,0 +1,40 @@
1
+ import { Constraint, validateConstraint } from "./Constraint"
2
+ import { InternalValidationContext, ValuedContext } from "../ValidationContext"
3
+
4
+ /**
5
+ * Interface for a constraint that checks if a value is one of a set of allowed values.
6
+ * @template Value - The type of the value being validated.
7
+ * @template Parent - The type of the parent object.
8
+ * @property oneOf - Constraint for allowed values, if any.
9
+ * @category Shared Constraints
10
+ */
11
+ export interface OneOfConstraint<Value, Parent = unknown> {
12
+ oneOf?: Constraint<NonNullable<Value>, NoInfer<NonNullable<Value>>[], Parent>
13
+ }
14
+
15
+ /**
16
+ * Validates the oneOf constraint for a value.
17
+ * @template Value - The type of the value being validated.
18
+ * @template OneOfType - The type for the allowed values array.
19
+ * @template Parent - The type of the parent object.
20
+ * @param context - The validation context.
21
+ * @param constraints - The oneOf constraint to validate.
22
+ * @param isConstraintValue - Type guard for the allowed values array.
23
+ * @param equals - Optional equality function for comparing values.
24
+ * @returns True if the value is one of the allowed values, false otherwise.
25
+ * @ignore
26
+ */
27
+ export function validateOneOfConstraint<Value, OneOfType extends NoInfer<NonNullable<Value>>[], Parent>(
28
+ context: InternalValidationContext<Value, Parent>,
29
+ constraints: OneOfConstraint<Value, Parent>,
30
+ isConstraintValue: (value: any) => value is OneOfType,
31
+ equals?: (value1: NonNullable<Value>, value2: NonNullable<Value>) => boolean
32
+ ) {
33
+ return validateConstraint(
34
+ context as ValuedContext<Value, Parent>,
35
+ constraints,
36
+ "oneOf",
37
+ isConstraintValue,
38
+ (value, array) => equals == null ? array.includes(value) : array.some((item) => equals(value, item))
39
+ )
40
+ }
@@ -0,0 +1,176 @@
1
+ import { InternalValidationContext, Level, ValidationContext, ValidationStatus } from "../ValidationContext"
2
+ import { ConstraintFunction, ConstraintMessage } from "./Constraint"
3
+ import { isFunction, isObject } from "../TypesUtil"
4
+ import { joinPath } from "../ObjectsUtil"
5
+
6
+ /**
7
+ * Type for test constraint messages, which can be a simple message, a tuple of message and level, a boolean, or undefined.
8
+ * @category Shared Constraints
9
+ */
10
+ export type TestConstraintMessage = ConstraintMessage | readonly [ConstraintMessage, Level] | boolean | undefined
11
+
12
+ /**
13
+ * Type for a test constraint function, which can return a test constraint message or be undefined.
14
+ * @template Value - The type of the value being validated.
15
+ * @template Parent - The type of the parent object.
16
+ * @category Shared Constraints
17
+ */
18
+ export type TestConstraintFunction<Value, Parent = unknown> = ConstraintFunction<NonNullable<Value>, TestConstraintMessage, Parent>
19
+
20
+ /**
21
+ * Interface for an asynchronous test constraint.
22
+ * @template Value - The type of the value being validated.
23
+ * @template Parent - The type of the parent object.
24
+ * @property promise - Function returning a promise for the constraint message.
25
+ * @property pendingMessage - Message to show while pending.
26
+ * @property unavailableMessage - Message to show if async service is unavailable.
27
+ * @property dependencies - Function to get dependencies for revalidation.
28
+ * @property revalidate - Function to determine if revalidation is needed.
29
+ * @category Shared Constraints
30
+ */
31
+ export interface AsyncTestConstraint<Value, Parent = unknown> {
32
+ promise: (context: ValidationContext<NonNullable<Value>, Parent>) => Promise<TestConstraintMessage>
33
+ pendingMessage?: ConstraintMessage
34
+ unavailableMessage?: ConstraintMessage
35
+ dependencies?: (context: InternalValidationContext<Value, Parent>) => any[],
36
+ revalidate?: (context: InternalValidationContext<Value, Parent>, previous: any[], current: any[], status: ValidationStatus | undefined) => boolean
37
+ }
38
+
39
+ /**
40
+ * Interface for a test constraint, which can be sync, async, or both.
41
+ * @template Value - The type of the value being validated.
42
+ * @template Parent - The type of the parent object.
43
+ * @property test - The test constraint function or async constraint.
44
+ * @category Shared Constraints
45
+ */
46
+ export interface TestConstraint<Value, Parent = unknown> {
47
+ test?: TestConstraintFunction<Value, Parent> | AsyncTestConstraint<Value, Parent> | readonly [TestConstraintFunction<Value, Parent>, AsyncTestConstraint<Value, Parent>]
48
+ }
49
+
50
+ const defaultGetDependencies = (_context: InternalValidationContext<unknown>) => []
51
+ const defaultShouldRevalidate = (_context: InternalValidationContext<unknown>, previous: any[], current: any[], status: ValidationStatus | undefined) =>
52
+ status?.level !== "unavailable" && current.some((v, i) => v !== previous[i])
53
+
54
+ /**
55
+ * Validates a test constraint for a value, handling sync and async cases.
56
+ * @template Value - The type of the value being validated.
57
+ * @template Parent - The type of the parent object.
58
+ * @param context - The validation context.
59
+ * @param testConstraint - The test constraint to validate.
60
+ * @returns True if the constraint passes, false otherwise.
61
+ * @ignore
62
+ */
63
+ export function validateTestConstraint<Value, Parent>(
64
+ context: InternalValidationContext<Value, Parent>,
65
+ testConstraint: TestConstraint<Value, Parent>
66
+ ) {
67
+ if (context.groups == null)
68
+ return testConstraint.test == null || _validateTestConstraint(context, testConstraint)
69
+
70
+ const groups = Array.isArray(context.groups) ? context.groups : [context.groups]
71
+ for (const group of groups) {
72
+ const test = (group == null ? testConstraint.test : (testConstraint as any).groups?.[group]?.test)
73
+ if (test != null && !_validateTestConstraint(context, { test }))
74
+ return false
75
+ }
76
+ return true
77
+ }
78
+
79
+ /**
80
+ * Internal helper to validate a test constraint (sync and async).
81
+ * @ignore
82
+ */
83
+ function _validateTestConstraint<Value, Parent>(
84
+ context: InternalValidationContext<Value, Parent>,
85
+ testConstraint: TestConstraint<Value, Parent>
86
+ ) {
87
+ const test = testConstraint.test!
88
+
89
+ const syncTest = (isFunction(test) ? test : Array.isArray(test) ? test[0] : undefined) as TestConstraintFunction<Value, Parent> | undefined
90
+ if (syncTest != null && !_validateTestConstraintFunction(context as InternalValidationContext<NonNullable<Value>, Parent>, syncTest))
91
+ return false
92
+
93
+ const asyncTest = (isObject(test) ? test : Array.isArray(test) ? test[1] : undefined) as AsyncTestConstraint<Value, Parent> | undefined
94
+ return asyncTest == null || _validateAsyncTestConstraint(context as InternalValidationContext<NonNullable<Value>, Parent>, asyncTest)
95
+ }
96
+
97
+ /**
98
+ * Internal helper to validate a synchronous test constraint function.
99
+ * @ignore
100
+ */
101
+ function _validateTestConstraintFunction<Value, Parent>(
102
+ context: InternalValidationContext<NonNullable<Value>, Parent>,
103
+ test: TestConstraintFunction<Value, Parent>
104
+ ) {
105
+ let constraint: any = undefined
106
+ let message: any = undefined
107
+ let level: Level | undefined = undefined
108
+
109
+ constraint = test(context)
110
+
111
+ if (Array.isArray(constraint)) {
112
+ const [maybeConstraint, maybeMessage, maybeLevel] = constraint
113
+ constraint = maybeConstraint
114
+ message = maybeMessage
115
+ level = maybeLevel ?? undefined
116
+ }
117
+
118
+ if (constraint == null || constraint === true)
119
+ return true
120
+ return context.setStatus("test", false, typeof constraint === "string" ? constraint : message, level ?? "error") == null
121
+ }
122
+
123
+ /**
124
+ * Internal helper to validate an asynchronous test constraint.
125
+ * @ignore
126
+ */
127
+ function _validateAsyncTestConstraint<Value, Parent>(
128
+ context: InternalValidationContext<NonNullable<Value>, Parent>,
129
+ test: AsyncTestConstraint<Value, Parent>
130
+ ) {
131
+ if (context.settings?.skipAsync)
132
+ return true
133
+
134
+ const getDependencies = test.dependencies ?? defaultGetDependencies
135
+ const dependencies = [context.value].concat(getDependencies(context))
136
+
137
+ const path = joinPath(context.path)
138
+ let asyncStatus = context.yop.asyncStatuses.get(path)
139
+ if (asyncStatus != null) {
140
+ const previousDependencies = asyncStatus.dependencies
141
+ asyncStatus.dependencies = dependencies
142
+ const shouldRevalidate = test.revalidate ?? defaultShouldRevalidate
143
+ if (!shouldRevalidate(context, previousDependencies, dependencies, asyncStatus.status)) {
144
+ if (asyncStatus.status != null) {
145
+ context.statuses.set(path, asyncStatus.status)
146
+ return false
147
+ }
148
+ return true
149
+ }
150
+ }
151
+
152
+ asyncStatus = { dependencies }
153
+
154
+ const promise = test.promise(context)
155
+ .then(message => {
156
+ if (message == null || message === true)
157
+ asyncStatus.status = undefined
158
+ else if (message === false)
159
+ asyncStatus.status = context.createStatus("test", false)
160
+ else if (!Array.isArray(message))
161
+ asyncStatus.status = context.createStatus("test", false, message as ConstraintMessage)
162
+ else {
163
+ const [maybeMessage, maybeLevel] = message as readonly [ConstraintMessage, Level]
164
+ asyncStatus.status = context.createStatus("test", false, maybeMessage, maybeLevel ?? "error")
165
+ }
166
+ return asyncStatus.status
167
+ })
168
+ .catch(error => {
169
+ asyncStatus.status = context.createStatus("test", false, error != null ? String(error) : test.unavailableMessage, "unavailable")
170
+ return Promise.resolve(asyncStatus.status)
171
+ })
172
+
173
+ asyncStatus.status = context.setStatus("test", promise, test.pendingMessage, "pending")
174
+ context.yop.asyncStatuses.set(path, asyncStatus)
175
+ return false
176
+ }
@@ -0,0 +1,157 @@
1
+ import { CommonConstraints, InternalCommonConstraints, validateTypeConstraint } from "../constraints/CommonConstraints"
2
+ import { MinMaxConstraints, validateMinMaxConstraints } from "../constraints/MinMaxConstraints"
3
+ import { TestConstraint, validateTestConstraint } from "../constraints/TestConstraint"
4
+ import { fieldValidationDecorator, getValidationDecoratorKind, Groups, InternalClassConstraints } from "../Metadata"
5
+ import { defineLazyProperty } from "../ObjectsUtil"
6
+ import { ArrayElementType, Constructor, isNumber } from "../TypesUtil"
7
+ import { InternalValidationContext } from "../ValidationContext"
8
+ import { validationSymbol, Yop } from "../Yop"
9
+ import { id } from "./id"
10
+ import { string } from "./string"
11
+ import { number } from "./number"
12
+
13
+ /**
14
+ * Type for an array value, which can be an array, null, or undefined.
15
+ * @ignore
16
+ */
17
+ export type ArrayValue = any[] | null | undefined
18
+
19
+ /**
20
+ * Interface for array field constraints, combining common, min/max, test, and element type constraints.
21
+ * @template Value - The type of the array value.
22
+ * @template Parent - The type of the parent object.
23
+ * @see {@link id}
24
+ * @see {@link CommonConstraints}
25
+ * @see {@link MinMaxConstraints}
26
+ * @see {@link TestConstraint}
27
+ * @category Property Decorators
28
+ */
29
+ export interface ArrayConstraints<Value extends ArrayValue, Parent> extends
30
+ CommonConstraints<Value, Parent>,
31
+ MinMaxConstraints<Value, number, Parent>,
32
+ TestConstraint<Value, Parent> {
33
+ /**
34
+ * A class constructor, a function returning a constructor, a validation decorator, or a class id (see {@link id}) to
35
+ * validate each array element against.
36
+ * @see {@link array}
37
+ */
38
+ of: (
39
+ Constructor<ArrayElementType<Value>> |
40
+ (() => Constructor<ArrayElementType<Value>>) |
41
+ ((_: any, context: ClassFieldDecoratorContext<Value, ArrayElementType<Value>>) => void) |
42
+ string
43
+ )
44
+ }
45
+
46
+ /**
47
+ * Traverses an array field to retrieve element constraints and value at a given index or property.
48
+ * @template Value - The type of the array value.
49
+ * @template Parent - The type of the parent object.
50
+ * @param context - The validation context for the array.
51
+ * @param constraints - The array constraints.
52
+ * @param propertyOrIndex - The property name or array index to traverse.
53
+ * @param traverseNullish - If true, traverses only if value is not nullish; otherwise, returns undefined for nullish values.
54
+ * @returns A tuple of the element constraints (if any) and the value at the given index/property.
55
+ * @ignore
56
+ */
57
+ function traverseArray<Value extends ArrayValue, Parent>(
58
+ context: InternalValidationContext<Value, Parent>,
59
+ constraints: ArrayConstraints<Value, Parent>,
60
+ propertyOrIndex: string | number,
61
+ traverseNullish?: boolean
62
+ ): readonly [InternalCommonConstraints | undefined, any] {
63
+ if (traverseNullish ? context.value != null && (!Array.isArray(context.value) || typeof propertyOrIndex !== "number") : context.value == null)
64
+ return [undefined, undefined]
65
+ const of = constraints.of as any
66
+ const elementConstraints = of?.[Symbol.metadata]?.[validationSymbol]
67
+ return [elementConstraints, context.value?.[propertyOrIndex as number]]
68
+ }
69
+
70
+ /**
71
+ * Validates an array field against its constraints, including type, min/max length, and element validation.
72
+ * @template Value - The type of the array value.
73
+ * @template Parent - The type of the parent object.
74
+ * @param context - The validation context for the array.
75
+ * @param constraints - The array constraints to validate.
76
+ * @returns True if all constraints pass, false otherwise.
77
+ * @ignore
78
+ */
79
+ function validateArray<Value extends ArrayValue, Parent>(context: InternalValidationContext<Value, Parent>, constraints: ArrayConstraints<Value, Parent>) {
80
+ if (!validateTypeConstraint(context, Array.isArray, "array") ||
81
+ !validateMinMaxConstraints(context, constraints, isNumber, (value, min) => value.length >= min, (value, max) => value.length <= max) ||
82
+ constraints.of == null)
83
+ return false
84
+
85
+ let valid = true
86
+
87
+ const elementConstraints = (constraints.of as any)[Symbol.metadata]?.[validationSymbol] as InternalCommonConstraints | undefined
88
+ if (elementConstraints != null) {
89
+ for (const [index, element] of context.value!.entries()) {
90
+ const elementContext = context.createChildContext({
91
+ kind: elementConstraints.kind,
92
+ value: element,
93
+ key: index,
94
+ })
95
+ valid = elementConstraints.validate(elementContext, elementConstraints) && valid
96
+ }
97
+ }
98
+
99
+ return valid && validateTestConstraint(context, constraints)
100
+ }
101
+
102
+ /**
103
+ * Constant representing the kind of array validation decorator.
104
+ * @ignore
105
+ */
106
+ export const arrayKind = "array"
107
+
108
+ /**
109
+ * Decorator for validating a field value as an array of a specified element type. The `of` property can be set to a custom
110
+ * class constructor, or a validation decorator such as {@link string}, {@link number}, etc. Using a validation decorator allows
111
+ * applying constraints to each array element.
112
+ *
113
+ * Example usage:
114
+ * ```tsx
115
+ * class Person {
116
+ *
117
+ * // the array itself must not be null, but its elements may be null
118
+ * @array({ of: Dog, required: true })
119
+ * dogs: Dog[] | null = null
120
+ *
121
+ * // the array must not be null, and all its elements must also be non-null
122
+ * @array({ of: instance({ of: Cat, required: true }), required: true })
123
+ * cats: Cat[] | null = null
124
+ *
125
+ * // a non-empty array of strings with at least 5 characters each
126
+ * @array({ of: string({ required: true, min: 5 }), required: true, min: 1 })
127
+ * names: string[] | null = null
128
+ * }
129
+ * const form = useForm(Person, ...)
130
+ *
131
+ * // the array decorator can also be used as a function to allow standalone validation:
132
+ * Yop.validate([], array({ of: Dog, min: 1 })) // error: "At least 1 element"
133
+ * ```
134
+ *
135
+ * @template Value - The type of the array value.
136
+ * @template Parent - The type of the parent object.
137
+ * @param constraints - The array constraints to apply.
138
+ * @param groups - Optional validation groups.
139
+ * @returns A field decorator function with validation.
140
+ * @category Property Decorators
141
+ */
142
+ export function array<Value extends ArrayValue, Parent>(constraints?: ArrayConstraints<Value, Parent>, groups?: Groups<ArrayConstraints<Value, Parent>>) {
143
+ if (getValidationDecoratorKind(constraints?.of) != null) {
144
+ const of = constraints!.of as ((_: any, context: Partial<ClassFieldDecoratorContext<Value, ArrayElementType<Value>>>) => void)
145
+ defineLazyProperty(constraints, "of", (_this) => {
146
+ const metadata = { [validationSymbol]: {} as InternalClassConstraints }
147
+ of(null, { metadata, name: "of" })
148
+ return { [Symbol.metadata]: { [validationSymbol]: metadata[validationSymbol]!.fields!.of }}
149
+ })
150
+ }
151
+ else if (typeof constraints?.of === "string" || (typeof constraints?.of === "function" && constraints.of.prototype == null)) {
152
+ const of = constraints!.of
153
+ defineLazyProperty(constraints, "of", (_this) => Yop.resolveClass(of))
154
+ }
155
+ return fieldValidationDecorator(arrayKind, constraints ?? ({} as ArrayConstraints<Value, Parent>), groups, validateArray, isNumber, traverseArray)
156
+ }
157
+
@@ -0,0 +1,69 @@
1
+ import { CommonConstraints, validateTypeConstraint } from "../constraints/CommonConstraints"
2
+ import { OneOfConstraint, validateOneOfConstraint } from "../constraints/OneOfConstraint"
3
+ import { TestConstraint, validateTestConstraint } from "../constraints/TestConstraint"
4
+ import { isBoolean, isBooleanArray } from "../TypesUtil"
5
+ import { InternalValidationContext } from "../ValidationContext"
6
+ import { fieldValidationDecorator, Groups } from "../Metadata"
7
+
8
+ /**
9
+ * Type for a boolean value, which can be true, false, null, or undefined.
10
+ * @ignore
11
+ */
12
+ export type BooleanValue = boolean | null | undefined
13
+
14
+ /**
15
+ * Interface for boolean field constraints, combining common, oneOf, and test constraints.
16
+ * @template Value - The type of the boolean value.
17
+ * @template Parent - The type of the parent object.
18
+ * @see {@link CommonConstraints}
19
+ * @see {@link OneOfConstraint}
20
+ * @see {@link TestConstraint}
21
+ * @category Property Decorators
22
+ */
23
+ export interface BooleanConstraints<Value extends BooleanValue, Parent> extends
24
+ CommonConstraints<Value, Parent>,
25
+ OneOfConstraint<Value, Parent>,
26
+ TestConstraint<Value, Parent> {
27
+ }
28
+
29
+ /**
30
+ * Validates a boolean field against its constraints.
31
+ * @template Value - The type of the boolean value.
32
+ * @template Parent - The type of the parent object.
33
+ * @param context - The validation context.
34
+ * @param constraints - The boolean constraints to validate.
35
+ * @returns True if all constraints pass, false otherwise.
36
+ * @ignore
37
+ */
38
+ function validateBoolean<Value extends BooleanValue, Parent>(context: InternalValidationContext<Value, Parent>, constraints: BooleanConstraints<Value, Parent>) {
39
+ return (
40
+ validateTypeConstraint(context, isBoolean, "boolean") &&
41
+ validateOneOfConstraint(context, constraints, isBooleanArray) &&
42
+ validateTestConstraint(context, constraints)
43
+ )
44
+ }
45
+
46
+ /**
47
+ * Decorator for applying validation rules to a boolean field.
48
+ *
49
+ * Example usage:
50
+ * ```tsx
51
+ * class Person {
52
+ * @boolean({ required: true, oneOf: [[true], "Must be an adult"] })
53
+ * adult: boolean | null = null
54
+ * }
55
+ * const form = useForm(Person, ...)
56
+ *
57
+ * // the boolean decorator can also be used as a function to allow standalone validation:
58
+ * Yop.validate(false, boolean({ oneOf: [true] })) // error: "Must be one of: true"
59
+ * ```
60
+ * @template Value - The type of the boolean value.
61
+ * @template Parent - The type of the parent object.
62
+ * @param constraints - The boolean constraints to apply.
63
+ * @param groups - Optional validation groups.
64
+ * @returns A field decorator function with validation.
65
+ * @category Property Decorators
66
+ */
67
+ export function boolean<Value extends BooleanValue, Parent>(constraints?: BooleanConstraints<Value, Parent>, groups?: Groups<BooleanConstraints<Value, Parent>>) {
68
+ return fieldValidationDecorator("boolean", constraints ?? {}, groups, validateBoolean)
69
+ }
@@ -0,0 +1,73 @@
1
+ import { CommonConstraints, validateTypeConstraint } from "../constraints/CommonConstraints"
2
+ import { MinMaxConstraints, validateMinMaxConstraints } from "../constraints/MinMaxConstraints"
3
+ import { OneOfConstraint, validateOneOfConstraint } from "../constraints/OneOfConstraint"
4
+ import { TestConstraint, validateTestConstraint } from "../constraints/TestConstraint"
5
+ import { isDate, isDateArray } from "../TypesUtil"
6
+ import { InternalValidationContext } from "../ValidationContext"
7
+ import { fieldValidationDecorator, Groups } from "../Metadata"
8
+
9
+ /**
10
+ * Type for a date value, which can be a Date object, null, or undefined.
11
+ * @ignore
12
+ */
13
+ export type DateValue = Date | null | undefined
14
+
15
+ /**
16
+ * Interface for date field constraints, combining common, min/max, oneOf, and test constraints.
17
+ * @template Value - The type of the date value.
18
+ * @template Parent - The type of the parent object.
19
+ * @see {@link CommonConstraints}
20
+ * @see {@link MinMaxConstraints}
21
+ * @see {@link OneOfConstraint}
22
+ * @see {@link TestConstraint}
23
+ * @category Property Decorators
24
+ */
25
+ export interface DateConstraints<Value extends DateValue, Parent> extends
26
+ CommonConstraints<Value, Parent>,
27
+ MinMaxConstraints<Value, Date, Parent>,
28
+ OneOfConstraint<Value, Parent>,
29
+ TestConstraint<Value, Parent> {
30
+ }
31
+
32
+ /**
33
+ * Validates a date field against its constraints.
34
+ * @template Value - The type of the date value.
35
+ * @template Parent - The type of the parent object.
36
+ * @param context - The validation context.
37
+ * @param constraints - The date constraints to validate.
38
+ * @returns True if all constraints pass, false otherwise.
39
+ * @ignore
40
+ */
41
+ function validateDate<Value extends DateValue, Parent>(context: InternalValidationContext<Value, Parent>, constraints: DateConstraints<Value, Parent>) {
42
+ return (
43
+ validateTypeConstraint(context, isDate, "date") &&
44
+ validateMinMaxConstraints(context, constraints, isDate, (value, min) => value >= min, (value, max) => value <= max) &&
45
+ validateOneOfConstraint(context, constraints, isDateArray, (date1, date2) => date1.getTime() === date2.getTime()) &&
46
+ validateTestConstraint(context, constraints)
47
+ )
48
+ }
49
+
50
+ /**
51
+ * Decorator for applying validation rules to a Date field.
52
+ *
53
+ * Example usage:
54
+ * ```tsx
55
+ * class Person {
56
+ * @date({ required: true, min: new Date("1900-01-01") })
57
+ * dateOfBirth: Date | null = null
58
+ * }
59
+ * const form = useForm(Person, ...)
60
+ *
61
+ * // the date decorator can also be used as a function to allow standalone validation:
62
+ * Yop.validate(null, date({ required: true })) // error: "Required field"
63
+ * ```
64
+ * @template Value - The type of the date value.
65
+ * @template Parent - The type of the parent object.
66
+ * @param constraints - The date constraints to apply.
67
+ * @param groups - Optional validation groups.
68
+ * @returns A field decorator function with validation.
69
+ * @category Property Decorators
70
+ */
71
+ export function date<Value extends DateValue, Parent>(constraints?: DateConstraints<Value, Parent>, groups?: Groups<DateConstraints<Value, Parent>>) {
72
+ return fieldValidationDecorator("date", constraints ?? {}, groups, validateDate, isDate)
73
+ }
@@ -0,0 +1,66 @@
1
+ import { Message } from "../constraints/Constraint"
2
+ import { InternalValidationContext } from "../ValidationContext"
3
+ import { fieldValidationDecorator, Groups } from "../Metadata"
4
+ import { StringConstraints, StringValue, validateString } from "./string"
5
+ import { isNumber } from "../TypesUtil"
6
+
7
+ /**
8
+ * Interface for email field constraints, extending string constraints but omitting 'match'.
9
+ * @template Value - The type of the string value.
10
+ * @template Parent - The type of the parent object.
11
+ * @property formatError - Custom error message for invalid email format.
12
+ * @see {@link StringConstraints}
13
+ * @category Property Decorators
14
+ */
15
+ export interface EmailConstraints<Value extends StringValue, Parent> extends
16
+ Omit<StringConstraints<Value, Parent>, "match"> {
17
+ /**
18
+ * Custom error message for invalid email format. `formatError` can be a {@link Message} or a function that returns a {@link Message}.
19
+ * @see {@link emailRegex}
20
+ */
21
+ formatError?: Message<Value, Parent>
22
+ }
23
+
24
+ /**
25
+ * Regular expression for validating email addresses (RFC 5322 compliant).
26
+ * @ignore
27
+ */
28
+ export const emailRegex = /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i
29
+
30
+ /**
31
+ * Validates an email field against its constraints and the email regex.
32
+ * @template Value - The type of the string value.
33
+ * @template Parent - The type of the parent object.
34
+ * @param context - The validation context.
35
+ * @param constraints - The email constraints to validate.
36
+ * @returns True if the value is a valid email, false otherwise.
37
+ * @ignore
38
+ */
39
+ export function validateEmail<Value extends StringValue, Parent>(context: InternalValidationContext<Value, Parent>, constraints: EmailConstraints<Value, Parent>) {
40
+ return validateString(context, constraints, emailRegex, constraints.formatError, "email")
41
+ }
42
+
43
+ /**
44
+ * Decorator for applying validation rules to an email field. Emails are validated against a standard email regex pattern (RFC 5322 compliant).
45
+ *
46
+ * Example usage:
47
+ * ```tsx
48
+ * class Person {
49
+ * @email({ required: true, formatError: "Invalid email address" })
50
+ * email: string | null = null
51
+ * }
52
+ * const form = useForm(Person, ...)
53
+ *
54
+ * // the email decorator can also be used as a function to allow standalone validation:
55
+ * Yop.validate(null, email({ required: true })) // error: "Required field"
56
+ * ```
57
+ * @template Value - The type of the string value.
58
+ * @template Parent - The type of the parent object.
59
+ * @param constraints - The email constraints to apply.
60
+ * @param groups - Optional validation groups.
61
+ * @returns A field decorator function with validation.
62
+ * @category Property Decorators
63
+ */
64
+ export function email<Value extends StringValue, Parent>(constraints?: EmailConstraints<Value, Parent>, groups?: Groups<EmailConstraints<Value, Parent>>) {
65
+ return fieldValidationDecorator("email", constraints ?? {}, groups, validateEmail, isNumber)
66
+ }