@kaliber/forms 2.1.1 → 3.0.0-beta.0
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.
- package/package.json +13 -1
- package/src/components.js +21 -1
- package/src/components.type.test.ts +74 -0
- package/src/fields.js +98 -16
- package/src/fields.type.test.ts +135 -0
- package/src/hooks.js +65 -7
- package/src/hooks.type.test.ts +164 -0
- package/src/normalize.js +78 -25
- package/src/normalize.type.test.ts +125 -0
- package/src/schema.js +39 -11
- package/src/schema.type.test.ts +159 -0
- package/src/snapshot.js +59 -5
- package/src/snapshot.type.test.ts +135 -0
- package/src/state.js +31 -1
- package/src/type-helpers.js +12 -0
- package/src/type.test.helpers.ts +47 -0
- package/src/types.ts +354 -0
- package/src/validation.js +96 -12
- package/validation.js +1 -1
- package/src/types.d.ts +0 -15
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useArrayFormField,
|
|
3
|
+
useBooleanFormField,
|
|
4
|
+
useForm,
|
|
5
|
+
useFormField,
|
|
6
|
+
useFormFieldSnapshot,
|
|
7
|
+
useFormFieldsValues,
|
|
8
|
+
useFormFieldValue,
|
|
9
|
+
useNumberFormField,
|
|
10
|
+
useObjectFormField
|
|
11
|
+
} from './hooks'
|
|
12
|
+
import { array, object } from './schema'
|
|
13
|
+
import { expectAssignable, expectNotAny, expectNotNever, Prepared } from './type.test.helpers.ts'
|
|
14
|
+
import { Field, Snapshot, State } from './types.ts'
|
|
15
|
+
import { optionalT } from './validation'
|
|
16
|
+
|
|
17
|
+
const { form } = useForm({
|
|
18
|
+
fields: {
|
|
19
|
+
a: optionalT('string'),
|
|
20
|
+
b: optionalT('number'),
|
|
21
|
+
c: optionalT('boolean'),
|
|
22
|
+
d: object({
|
|
23
|
+
a: optionalT('string'),
|
|
24
|
+
b: optionalT('number'),
|
|
25
|
+
c: optionalT('boolean'),
|
|
26
|
+
}),
|
|
27
|
+
e: array({
|
|
28
|
+
a: optionalT('string'),
|
|
29
|
+
b: optionalT('number'),
|
|
30
|
+
c: optionalT('boolean'),
|
|
31
|
+
})
|
|
32
|
+
},
|
|
33
|
+
onSubmit() {}
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
expectNotAny(form)
|
|
37
|
+
expectNotNever(form)
|
|
38
|
+
expectAssignable<
|
|
39
|
+
Field.Object<{
|
|
40
|
+
a: Field.Basic<string>,
|
|
41
|
+
b: Field.Basic<number>,
|
|
42
|
+
c: Field.Basic<boolean>,
|
|
43
|
+
d: Field.Object<{
|
|
44
|
+
a: Field.Basic<string>,
|
|
45
|
+
b: Field.Basic<number>,
|
|
46
|
+
c: Field.Basic<boolean>,
|
|
47
|
+
}>,
|
|
48
|
+
e: Field.Array<{
|
|
49
|
+
a: Field.Basic<string>,
|
|
50
|
+
b: Field.Basic<number>,
|
|
51
|
+
c: Field.Basic<boolean>,
|
|
52
|
+
}>
|
|
53
|
+
}>,
|
|
54
|
+
Prepared<typeof form>
|
|
55
|
+
>
|
|
56
|
+
|
|
57
|
+
const formFieldValue = useFormFieldValue(form.fields.a)
|
|
58
|
+
expectAssignable<string, Prepared<typeof formFieldValue>>
|
|
59
|
+
|
|
60
|
+
const formFieldsValues = useFormFieldsValues([form.fields.a, form.fields.b])
|
|
61
|
+
expectAssignable<[string, number], Prepared<typeof formFieldsValues>>
|
|
62
|
+
|
|
63
|
+
const formField = useFormField(form.fields.a)
|
|
64
|
+
expectAssignable<
|
|
65
|
+
{
|
|
66
|
+
name: string
|
|
67
|
+
state: State.Basic<string>
|
|
68
|
+
eventHandlers: {
|
|
69
|
+
onBlur(): void
|
|
70
|
+
onFocus(): void
|
|
71
|
+
onChange(eOrValue: string | Field.Event<string>): void
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
Prepared<typeof formField>
|
|
75
|
+
>
|
|
76
|
+
|
|
77
|
+
const numberFormField = useNumberFormField(form.fields.b)
|
|
78
|
+
expectAssignable<
|
|
79
|
+
{
|
|
80
|
+
name: string
|
|
81
|
+
state: State.Basic<string | number>
|
|
82
|
+
eventHandlers: {
|
|
83
|
+
onBlur(): void
|
|
84
|
+
onFocus(): void
|
|
85
|
+
onChange(e: { target: { value: string } }): void
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
Prepared<typeof numberFormField>
|
|
89
|
+
>
|
|
90
|
+
|
|
91
|
+
const booleanFormField = useBooleanFormField(form.fields.c)
|
|
92
|
+
expectAssignable<
|
|
93
|
+
{
|
|
94
|
+
name: string
|
|
95
|
+
state: State.Basic<boolean>
|
|
96
|
+
eventHandlers: {
|
|
97
|
+
onBlur(): void
|
|
98
|
+
onFocus(): void
|
|
99
|
+
onChange(e: { target: { checked: boolean } }): void
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
Prepared<typeof booleanFormField>
|
|
103
|
+
>
|
|
104
|
+
|
|
105
|
+
const objectFormField = useObjectFormField(form.fields.d)
|
|
106
|
+
expectAssignable<
|
|
107
|
+
{
|
|
108
|
+
name: string
|
|
109
|
+
state: State.Common
|
|
110
|
+
fields: {
|
|
111
|
+
a: Field.Basic<string>
|
|
112
|
+
b: Field.Basic<number>
|
|
113
|
+
c: Field.Basic<boolean>
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
Prepared<typeof objectFormField>
|
|
117
|
+
>
|
|
118
|
+
|
|
119
|
+
const arrayFormField = useArrayFormField(form.fields.e)
|
|
120
|
+
expectAssignable<
|
|
121
|
+
{
|
|
122
|
+
name: string
|
|
123
|
+
state: State.Array<Field.Object<{
|
|
124
|
+
a: Field.Basic<string>
|
|
125
|
+
b: Field.Basic<number>
|
|
126
|
+
c: Field.Basic<boolean>
|
|
127
|
+
}>>
|
|
128
|
+
helpers: {
|
|
129
|
+
add(initialValue: {
|
|
130
|
+
a?: string
|
|
131
|
+
b?: number
|
|
132
|
+
c?: boolean
|
|
133
|
+
}): void
|
|
134
|
+
remove(entry: Field.Object<{
|
|
135
|
+
a: Field.Basic<string>
|
|
136
|
+
b: Field.Basic<number>
|
|
137
|
+
c: Field.Basic<boolean>
|
|
138
|
+
}>): void
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
Prepared<typeof arrayFormField>
|
|
142
|
+
>
|
|
143
|
+
|
|
144
|
+
const formFieldSnapshot = useFormFieldSnapshot(form)
|
|
145
|
+
expectNotAny(formFieldSnapshot)
|
|
146
|
+
expectNotNever(formFieldSnapshot)
|
|
147
|
+
expectAssignable<
|
|
148
|
+
Snapshot.Object<{
|
|
149
|
+
a: Field.Basic<string>,
|
|
150
|
+
b: Field.Basic<number>,
|
|
151
|
+
c: Field.Basic<boolean>,
|
|
152
|
+
d: Field.Object<{
|
|
153
|
+
a: Field.Basic<string>,
|
|
154
|
+
b: Field.Basic<number>,
|
|
155
|
+
c: Field.Basic<boolean>,
|
|
156
|
+
}>,
|
|
157
|
+
e: Field.Array<{
|
|
158
|
+
a: Field.Basic<string>,
|
|
159
|
+
b: Field.Basic<number>,
|
|
160
|
+
c: Field.Basic<boolean>,
|
|
161
|
+
}>
|
|
162
|
+
}>,
|
|
163
|
+
Prepared<typeof formFieldSnapshot>
|
|
164
|
+
>
|
package/src/normalize.js
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/** @import { FieldSchema, NormalizedField, Validate, ValidationContext, ValidationFunction } from './types.ts' */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @template {FieldSchema} T
|
|
5
|
+
*
|
|
6
|
+
* @arg {T} field
|
|
7
|
+
* @arg {string} [name]
|
|
8
|
+
*/
|
|
9
|
+
export function normalize(field, name = '') {
|
|
10
|
+
return /** @type {NormalizedField.FromFieldSchema<T>} */ (
|
|
3
11
|
convertValidationFunction(field, name) ||
|
|
4
12
|
convertValidationArray(field, name) ||
|
|
5
13
|
convertArrayField(field, name) ||
|
|
@@ -8,38 +16,83 @@ export function normalize(field, name) {
|
|
|
8
16
|
)
|
|
9
17
|
}
|
|
10
18
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
19
|
+
/**
|
|
20
|
+
* @arg {FieldSchema} field
|
|
21
|
+
* @arg {string} name
|
|
22
|
+
* @returns {false | NormalizedField.Basic}
|
|
23
|
+
*/
|
|
24
|
+
function convertValidationFunction(field, name) {
|
|
25
|
+
return field instanceof Function &&
|
|
26
|
+
{ type: 'basic', validate: toValidationFunction(field, name) }
|
|
14
27
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @arg {FieldSchema} field
|
|
31
|
+
* @arg {string} name
|
|
32
|
+
* @returns {false | NormalizedField.Basic}
|
|
33
|
+
*/
|
|
34
|
+
function convertValidationArray(field, name) {
|
|
35
|
+
return field instanceof Array &&
|
|
36
|
+
{ type: 'basic', validate: toValidationFunction(field, name) }
|
|
18
37
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @arg {FieldSchema} field
|
|
41
|
+
* @arg {string} name
|
|
42
|
+
* @returns {null | false | NormalizedField.Array}
|
|
43
|
+
*/
|
|
44
|
+
function convertArrayField(field, name) {
|
|
45
|
+
return field && 'type' in field && field.type === 'array' &&
|
|
46
|
+
{ type: 'array', validate: toValidationFunction(field.validate, name), fields: field.fields }
|
|
22
47
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @arg {FieldSchema} field
|
|
51
|
+
* @arg {string} name
|
|
52
|
+
* @returns {null | false | NormalizedField.Object}
|
|
53
|
+
*/
|
|
54
|
+
function convertObjectField(field, name) {
|
|
55
|
+
return field && 'type' in field && field.type === 'object' &&
|
|
56
|
+
{ type: 'object', validate: toValidationFunction(field.validate, name), fields: field.fields }
|
|
26
57
|
}
|
|
27
|
-
|
|
28
|
-
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @arg {FieldSchema} field
|
|
61
|
+
* @arg {string} name
|
|
62
|
+
* @returns {NormalizedField.Basic}
|
|
63
|
+
*/
|
|
64
|
+
function convertSimpleField(field, name) {
|
|
65
|
+
return { type: 'basic', validate: toValidationFunction(field && 'validate' in field && field.validate, name) }
|
|
29
66
|
}
|
|
30
67
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
68
|
+
/**
|
|
69
|
+
* @arg {false | undefined | Validate} fOrArrayOfF
|
|
70
|
+
* @param {string} name
|
|
71
|
+
* @returns {null | ValidationFunction}
|
|
72
|
+
*/
|
|
73
|
+
function toValidationFunction(fOrArrayOfF, name) {
|
|
74
|
+
const result = /** @type {(false | undefined | null | ValidationFunction)[]} */ ([])
|
|
75
|
+
.concat(fOrArrayOfF)
|
|
76
|
+
.reduce(
|
|
77
|
+
/** @arg {null | ValidationFunction} previous */
|
|
78
|
+
(previous, next) => {
|
|
79
|
+
const combined = previous && next && (
|
|
80
|
+
/** @arg {any} value @arg {ValidationContext} context */
|
|
81
|
+
(value, context) => previous(value, context) || next(value, context)
|
|
82
|
+
)
|
|
83
|
+
return combined || next || previous
|
|
84
|
+
},
|
|
85
|
+
null
|
|
86
|
+
)
|
|
39
87
|
|
|
40
|
-
return result
|
|
88
|
+
return result ? withBetterError(result, name) : null
|
|
41
89
|
}
|
|
42
90
|
|
|
91
|
+
/**
|
|
92
|
+
* @arg {ValidationFunction} f
|
|
93
|
+
* @arg {string} name
|
|
94
|
+
* @returns {ValidationFunction}
|
|
95
|
+
*/
|
|
43
96
|
function withBetterError(f, name) {
|
|
44
97
|
return (...args) => {
|
|
45
98
|
try {
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { normalize } from './normalize'
|
|
2
|
+
import { array, object } from './schema'
|
|
3
|
+
import { asConst } from './type-helpers'
|
|
4
|
+
import { expectAssignable, expectNotAny, expectNotNever, Prepared } from './type.test.helpers'
|
|
5
|
+
import { ValidationFunction } from './types'
|
|
6
|
+
import { email, number, optional, required } from './validation'
|
|
7
|
+
|
|
8
|
+
const noValidation = optional
|
|
9
|
+
const singleValidation = email
|
|
10
|
+
const multipleValidation = asConst([required, number])
|
|
11
|
+
const objectInput = {
|
|
12
|
+
noValidation,
|
|
13
|
+
singleValidation,
|
|
14
|
+
multipleValidation,
|
|
15
|
+
}
|
|
16
|
+
const simpleObjectSchema = object(objectInput)
|
|
17
|
+
const simpleArraySchema = array(objectInput)
|
|
18
|
+
const simpleObjectWithValidationSchema = object(value => validate(value), objectInput)
|
|
19
|
+
const simpleArrayWithValidationSchema = array(value => validate(value), objectInput)
|
|
20
|
+
type BaseObjectNormalizedField = {
|
|
21
|
+
type: 'object',
|
|
22
|
+
fields: {
|
|
23
|
+
noValidation: null,
|
|
24
|
+
singleValidation: ValidationFunction<string>,
|
|
25
|
+
multipleValidation: readonly [ValidationFunction, ValidationFunction<number>],
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
type BaseArrayNormalizedField = {
|
|
29
|
+
type: 'array',
|
|
30
|
+
fields: {
|
|
31
|
+
noValidation: null,
|
|
32
|
+
singleValidation: ValidationFunction<string>,
|
|
33
|
+
multipleValidation: readonly [ValidationFunction, ValidationFunction<number>],
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
type BasicNormalizedField<T> = {
|
|
37
|
+
type: 'basic',
|
|
38
|
+
validate: T
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
{
|
|
42
|
+
const noValidationNormalizedField = normalize(noValidation)
|
|
43
|
+
expectNotAny(noValidationNormalizedField)
|
|
44
|
+
expectNotNever(noValidationNormalizedField)
|
|
45
|
+
expectAssignable<
|
|
46
|
+
BasicNormalizedField<null>,
|
|
47
|
+
Prepared<typeof noValidationNormalizedField>
|
|
48
|
+
>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
{
|
|
52
|
+
const multipleValidationNormalizedField = normalize(multipleValidation)
|
|
53
|
+
expectNotAny(multipleValidationNormalizedField)
|
|
54
|
+
expectNotNever(multipleValidationNormalizedField)
|
|
55
|
+
expectAssignable<
|
|
56
|
+
BasicNormalizedField<ValidationFunction<number>>,
|
|
57
|
+
Prepared<typeof multipleValidationNormalizedField>
|
|
58
|
+
>
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
{
|
|
62
|
+
const singleValidationNormalizedField = normalize(singleValidation)
|
|
63
|
+
expectNotAny(singleValidationNormalizedField)
|
|
64
|
+
expectNotNever(singleValidationNormalizedField)
|
|
65
|
+
expectAssignable<
|
|
66
|
+
BasicNormalizedField<ValidationFunction<string>>,
|
|
67
|
+
Prepared<typeof singleValidationNormalizedField>
|
|
68
|
+
>
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
{
|
|
72
|
+
const simpleObjectNormalizedField = normalize(simpleObjectSchema)
|
|
73
|
+
expectNotAny(simpleObjectNormalizedField)
|
|
74
|
+
expectNotNever(simpleObjectNormalizedField)
|
|
75
|
+
expectAssignable<
|
|
76
|
+
BaseObjectNormalizedField & { validate: null },
|
|
77
|
+
Prepared<typeof simpleObjectNormalizedField>
|
|
78
|
+
>
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
{
|
|
82
|
+
const simpleObjectWithValidationNormalizedField = normalize(simpleObjectWithValidationSchema)
|
|
83
|
+
expectNotAny(simpleObjectWithValidationNormalizedField)
|
|
84
|
+
expectNotNever(simpleObjectWithValidationNormalizedField)
|
|
85
|
+
expectAssignable<
|
|
86
|
+
BaseObjectNormalizedField & {
|
|
87
|
+
validate: ValidationFunction<{
|
|
88
|
+
noValidation: unknown,
|
|
89
|
+
singleValidation: string,
|
|
90
|
+
multipleValidation: number,
|
|
91
|
+
}>
|
|
92
|
+
},
|
|
93
|
+
Prepared<typeof simpleObjectWithValidationNormalizedField>
|
|
94
|
+
>
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
{
|
|
98
|
+
const simpleArrayNormalizedField = normalize(simpleArraySchema)
|
|
99
|
+
expectNotAny(simpleArrayNormalizedField)
|
|
100
|
+
expectNotNever(simpleArrayNormalizedField)
|
|
101
|
+
expectAssignable<
|
|
102
|
+
BaseArrayNormalizedField & { validate: null },
|
|
103
|
+
Prepared<typeof simpleArrayNormalizedField>
|
|
104
|
+
>
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
{
|
|
108
|
+
const simpleArrayWithValidationNormalizedField = normalize(simpleArrayWithValidationSchema)
|
|
109
|
+
expectNotAny(simpleArrayWithValidationNormalizedField)
|
|
110
|
+
expectNotNever(simpleArrayWithValidationNormalizedField)
|
|
111
|
+
expectAssignable<
|
|
112
|
+
BaseArrayNormalizedField & {
|
|
113
|
+
validate: ValidationFunction<{
|
|
114
|
+
noValidation: unknown,
|
|
115
|
+
singleValidation: string,
|
|
116
|
+
multipleValidation: number,
|
|
117
|
+
}[]>
|
|
118
|
+
},
|
|
119
|
+
Prepared<typeof simpleArrayWithValidationNormalizedField>
|
|
120
|
+
>
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function validate<T>(value: T) {
|
|
124
|
+
return value === 'failure' && { id: 'error' }
|
|
125
|
+
}
|
package/src/schema.js
CHANGED
|
@@ -1,16 +1,40 @@
|
|
|
1
|
+
/** @import { Expand, FieldInput, Validate } from './types.ts' */
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
|
-
* @template A
|
|
3
|
-
* @
|
|
4
|
-
*
|
|
4
|
+
* @template {Validate<FieldInput.ObjectToValue<B>>} const A
|
|
5
|
+
* @template {FieldInput.Object} const B
|
|
6
|
+
*
|
|
7
|
+
* @overload
|
|
8
|
+
* @arg {A} fieldsOrValidate
|
|
9
|
+
* @arg {B} fields
|
|
10
|
+
* @returns {ReturnType<typeof body<'object', A, B>>}
|
|
11
|
+
*
|
|
12
|
+
* @overload
|
|
13
|
+
* @arg {B} fieldsOrValidate
|
|
14
|
+
* @returns {ReturnType<typeof body<'object', B, undefined>>}
|
|
15
|
+
*
|
|
16
|
+
* @arg {A | B} fieldsOrValidate
|
|
17
|
+
* @arg {B} [fields]
|
|
5
18
|
*/
|
|
6
19
|
export function object(fieldsOrValidate, fields) {
|
|
7
20
|
return body('object', fieldsOrValidate, fields)
|
|
8
21
|
}
|
|
9
22
|
|
|
10
23
|
/**
|
|
11
|
-
* @template A
|
|
12
|
-
* @
|
|
13
|
-
*
|
|
24
|
+
* @template {Validate<Expand<FieldInput.ArrayToValue<B>>>} const A
|
|
25
|
+
* @template {FieldInput.Array} const B
|
|
26
|
+
*
|
|
27
|
+
* @overload
|
|
28
|
+
* @arg {A} fieldsOrValidate
|
|
29
|
+
* @arg {B} fields
|
|
30
|
+
* @returns {ReturnType<typeof body<'array', A, B>>}
|
|
31
|
+
*
|
|
32
|
+
* @overload
|
|
33
|
+
* @arg {B} fieldsOrValidate
|
|
34
|
+
* @returns {ReturnType<typeof body<'array', B, undefined>>}
|
|
35
|
+
*
|
|
36
|
+
* @arg {A | B} fieldsOrValidate
|
|
37
|
+
* @arg {B} [fields]
|
|
14
38
|
*/
|
|
15
39
|
export function array(fieldsOrValidate, fields) {
|
|
16
40
|
return body('array', fieldsOrValidate, fields)
|
|
@@ -20,13 +44,17 @@ export function array(fieldsOrValidate, fields) {
|
|
|
20
44
|
* @template {string} T
|
|
21
45
|
* @template A, B
|
|
22
46
|
*
|
|
23
|
-
* @
|
|
24
|
-
* @
|
|
25
|
-
* @
|
|
47
|
+
* @arg {T} type
|
|
48
|
+
* @arg {A} fieldsOrValidate
|
|
49
|
+
* @arg {B} [fields]
|
|
26
50
|
*
|
|
27
|
-
* @returns {
|
|
51
|
+
* @returns {{ type: T } & (
|
|
52
|
+
* B extends undefined ? { fields: A } :
|
|
53
|
+
* B extends infer X ? { fields: X, validate: A } :
|
|
54
|
+
* never
|
|
55
|
+
* )}
|
|
28
56
|
*/
|
|
29
57
|
function body(type, fieldsOrValidate, fields) {
|
|
30
|
-
// @ts-
|
|
58
|
+
// @ts-expect-error - if you want to remove this @ts-ignore: good luck and please leave a comment afterwards *
|
|
31
59
|
return { type, fields: fields || fieldsOrValidate, validate: fields && fieldsOrValidate }
|
|
32
60
|
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { email, number, optional, required } from './validation'
|
|
2
|
+
import { array, object } from './schema'
|
|
3
|
+
import { asConst } from './type-helpers'
|
|
4
|
+
import type { InitialValue, Validate } from './types.ts'
|
|
5
|
+
import { expectAssignable, expectNotAny, expectNotNever, Prepared } from './type.test.helpers.ts'
|
|
6
|
+
|
|
7
|
+
const simpleObjectInput = asConst({
|
|
8
|
+
noValidation: optional,
|
|
9
|
+
singleValidation: email,
|
|
10
|
+
multipleValidation: [required, number],
|
|
11
|
+
})
|
|
12
|
+
type ObjectSchema<Fields> = {
|
|
13
|
+
type: 'object',
|
|
14
|
+
fields: Fields,
|
|
15
|
+
}
|
|
16
|
+
type ArraySchema<Fields> = {
|
|
17
|
+
type: 'array',
|
|
18
|
+
fields: Fields,
|
|
19
|
+
}
|
|
20
|
+
type SimpleObjectSchema = ObjectSchema<SimpleObjectFields>
|
|
21
|
+
|
|
22
|
+
type SimpleObjectFields = {
|
|
23
|
+
noValidation: Validate<unknown>,
|
|
24
|
+
singleValidation: Validate<string>,
|
|
25
|
+
multipleValidation: readonly [Validate<any>, Validate<number>],
|
|
26
|
+
}
|
|
27
|
+
type SimpleObjectValues = {
|
|
28
|
+
noValidation: unknown,
|
|
29
|
+
singleValidation: string,
|
|
30
|
+
multipleValidation: number,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
{
|
|
34
|
+
const simpleObjectSchema = object(simpleObjectInput)
|
|
35
|
+
expectNotAny(simpleObjectSchema)
|
|
36
|
+
expectAssignable<SimpleObjectSchema, Prepared<typeof simpleObjectSchema>>()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
{
|
|
40
|
+
const simpleObjectWithValidationSchema = object(
|
|
41
|
+
(value) => {
|
|
42
|
+
expectNotNever(value)
|
|
43
|
+
expectNotAny(value)
|
|
44
|
+
expectAssignable<SimpleObjectValues, Prepared<typeof value>>()
|
|
45
|
+
|
|
46
|
+
return validate(value)
|
|
47
|
+
},
|
|
48
|
+
simpleObjectInput
|
|
49
|
+
)
|
|
50
|
+
expectNotAny(simpleObjectWithValidationSchema)
|
|
51
|
+
expectAssignable<
|
|
52
|
+
SimpleObjectSchema & { validate: Validate<SimpleObjectValues> },
|
|
53
|
+
Prepared<typeof simpleObjectWithValidationSchema>
|
|
54
|
+
>
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const nestedObjectInput = { nested: object(simpleObjectInput) }
|
|
58
|
+
type NestedObjectSchema = ObjectSchema<{ nested: SimpleObjectSchema }>
|
|
59
|
+
{
|
|
60
|
+
const nestedObjectSchema = object(nestedObjectInput)
|
|
61
|
+
expectNotAny(nestedObjectSchema)
|
|
62
|
+
expectAssignable<NestedObjectSchema, Prepared<typeof nestedObjectSchema>>
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
{
|
|
66
|
+
const nestedObjectWithValidationSchema = object(
|
|
67
|
+
value => {
|
|
68
|
+
expectNotAny(value)
|
|
69
|
+
expectNotNever(value)
|
|
70
|
+
expectAssignable<{ nested: SimpleObjectValues }, Prepared<typeof value>>
|
|
71
|
+
|
|
72
|
+
return validate(value)
|
|
73
|
+
},
|
|
74
|
+
nestedObjectInput
|
|
75
|
+
)
|
|
76
|
+
expectNotAny(nestedObjectWithValidationSchema)
|
|
77
|
+
expectAssignable<
|
|
78
|
+
NestedObjectSchema & { validate: Validate<{ nested: SimpleObjectValues }> },
|
|
79
|
+
Prepared<typeof nestedObjectWithValidationSchema>
|
|
80
|
+
>
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
{
|
|
84
|
+
const simpleArraySchema = array(simpleObjectInput)
|
|
85
|
+
expectNotAny(simpleArraySchema)
|
|
86
|
+
expectAssignable<ArraySchema<SimpleObjectFields>, Prepared<typeof simpleArraySchema>>
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
{
|
|
90
|
+
const simpleArrayWithValidationSchema = array(
|
|
91
|
+
value => {
|
|
92
|
+
expectNotAny(value)
|
|
93
|
+
expectNotNever(value)
|
|
94
|
+
expectAssignable<SimpleObjectValues[], Prepared<typeof value>>
|
|
95
|
+
|
|
96
|
+
return validate(value)
|
|
97
|
+
},
|
|
98
|
+
simpleObjectInput
|
|
99
|
+
)
|
|
100
|
+
expectNotAny(simpleArrayWithValidationSchema)
|
|
101
|
+
expectAssignable<
|
|
102
|
+
ArraySchema<SimpleObjectFields> & { validate: Validate<SimpleObjectValues[]> },
|
|
103
|
+
Prepared<typeof simpleArrayWithValidationSchema>
|
|
104
|
+
>
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const inputA = asConst({ type: validate<'a'>, a: object(simpleObjectInput) })
|
|
108
|
+
const inputB = asConst({ type: validate<'b'>, b: object(simpleObjectInput) })
|
|
109
|
+
type HeterogeneousInitialValues = InitialValue<typeof inputA | typeof inputB>
|
|
110
|
+
|
|
111
|
+
{
|
|
112
|
+
type InitialValues = InitialValue<typeof inputA | typeof inputB>
|
|
113
|
+
|
|
114
|
+
const heterogeneousArraySchema = array(
|
|
115
|
+
(initialValue: InitialValues) =>
|
|
116
|
+
initialValue.type === 'a' ? inputA : inputB
|
|
117
|
+
)
|
|
118
|
+
expectNotAny(heterogeneousArraySchema)
|
|
119
|
+
expectAssignable<
|
|
120
|
+
ArraySchema<(initialValues: InitialValues) =>
|
|
121
|
+
{ type: Validate<'a'>, a: SimpleObjectSchema } |
|
|
122
|
+
{ type: Validate<'b'>, b: SimpleObjectSchema }
|
|
123
|
+
>,
|
|
124
|
+
Prepared<typeof heterogeneousArraySchema>
|
|
125
|
+
>
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
{
|
|
129
|
+
const heterogeneousArrayWithValidationSchema = array(
|
|
130
|
+
value => {
|
|
131
|
+
expectNotAny(value)
|
|
132
|
+
expectNotNever(value)
|
|
133
|
+
expectAssignable<
|
|
134
|
+
({ type: 'a', a: SimpleObjectValues } | { type: 'b', b: SimpleObjectValues })[],
|
|
135
|
+
Prepared<typeof value>
|
|
136
|
+
>
|
|
137
|
+
|
|
138
|
+
return validate(value)
|
|
139
|
+
},
|
|
140
|
+
(initialValue: HeterogeneousInitialValues) =>
|
|
141
|
+
initialValue.type === 'a' ? inputA :
|
|
142
|
+
initialValue.type === 'b' ? inputB :
|
|
143
|
+
throwError(`Unknown type: '${initialValue.type}'`)
|
|
144
|
+
)
|
|
145
|
+
expectNotAny(heterogeneousArrayWithValidationSchema)
|
|
146
|
+
expectAssignable<
|
|
147
|
+
ArraySchema<(initialValues: HeterogeneousInitialValues) =>
|
|
148
|
+
{ type: Validate<'a'>, a: SimpleObjectSchema } |
|
|
149
|
+
{ type: Validate<'b'>, b: SimpleObjectSchema }
|
|
150
|
+
> & { validate: Validate<({ type: 'a', a: SimpleObjectValues } | { type: 'b', b: SimpleObjectValues })[]> },
|
|
151
|
+
Prepared<typeof heterogeneousArrayWithValidationSchema>
|
|
152
|
+
>
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function validate<T>(value: T) {
|
|
156
|
+
return value === 'failure' && { id: 'error' }
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function throwError(m: string): never { throw new Error(m) }
|