@hookform/resolvers 2.0.0-rc.1 → 2.0.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.
Files changed (144) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +115 -18
  3. package/dist/index.d.ts +1 -0
  4. package/dist/resolvers.js +2 -0
  5. package/dist/resolvers.js.map +1 -0
  6. package/dist/resolvers.mjs +2 -0
  7. package/dist/resolvers.modern.js +2 -0
  8. package/dist/resolvers.modern.js.map +1 -0
  9. package/dist/resolvers.module.js +2 -0
  10. package/dist/resolvers.module.js.map +1 -0
  11. package/dist/resolvers.umd.js +2 -0
  12. package/dist/resolvers.umd.js.map +1 -0
  13. package/dist/toNestError.d.ts +2 -0
  14. package/joi/dist/index.d.ts +2 -0
  15. package/joi/dist/joi.d.ts +2 -0
  16. package/joi/dist/joi.js +2 -0
  17. package/joi/dist/joi.js.map +1 -0
  18. package/joi/dist/joi.mjs +2 -0
  19. package/joi/dist/joi.modern.js +2 -0
  20. package/joi/dist/joi.modern.js.map +1 -0
  21. package/joi/dist/joi.module.js +2 -0
  22. package/joi/dist/joi.module.js.map +1 -0
  23. package/joi/dist/joi.umd.js +2 -0
  24. package/joi/dist/joi.umd.js.map +1 -0
  25. package/joi/dist/types.d.ts +5 -0
  26. package/joi/package.json +17 -0
  27. package/joi/src/__tests__/Form.tsx +61 -0
  28. package/joi/src/__tests__/__fixtures__/data.ts +76 -0
  29. package/joi/src/__tests__/__snapshots__/joi.ts.snap +283 -0
  30. package/joi/src/__tests__/joi.ts +89 -0
  31. package/joi/src/index.ts +2 -0
  32. package/joi/src/joi.ts +63 -0
  33. package/joi/src/types.ts +17 -0
  34. package/package.json +111 -55
  35. package/superstruct/dist/index.d.ts +2 -0
  36. package/superstruct/dist/superstruct.d.ts +2 -0
  37. package/superstruct/dist/superstruct.js +2 -0
  38. package/superstruct/dist/superstruct.js.map +1 -0
  39. package/superstruct/dist/superstruct.mjs +2 -0
  40. package/superstruct/dist/superstruct.modern.js +2 -0
  41. package/superstruct/dist/superstruct.modern.js.map +1 -0
  42. package/superstruct/dist/superstruct.module.js +2 -0
  43. package/superstruct/dist/superstruct.module.js.map +1 -0
  44. package/superstruct/dist/superstruct.umd.js +2 -0
  45. package/superstruct/dist/superstruct.umd.js.map +1 -0
  46. package/superstruct/dist/types.d.ts +5 -0
  47. package/superstruct/package.json +18 -0
  48. package/superstruct/src/__tests__/Form.tsx +62 -0
  49. package/superstruct/src/__tests__/__fixtures__/data.ts +73 -0
  50. package/superstruct/src/__tests__/__snapshots__/superstruct.ts.snap +64 -0
  51. package/superstruct/src/__tests__/superstruct.ts +20 -0
  52. package/superstruct/src/index.ts +2 -0
  53. package/superstruct/src/superstruct.ts +30 -0
  54. package/superstruct/src/types.ts +18 -0
  55. package/vest/dist/index.d.ts +1 -0
  56. package/vest/dist/vest.d.ts +2 -0
  57. package/vest/dist/vest.js +2 -0
  58. package/vest/dist/vest.js.map +1 -0
  59. package/vest/dist/vest.mjs +2 -0
  60. package/vest/dist/vest.modern.js +2 -0
  61. package/vest/dist/vest.modern.js.map +1 -0
  62. package/vest/dist/vest.module.js +2 -0
  63. package/vest/dist/vest.module.js.map +1 -0
  64. package/vest/dist/vest.umd.js +2 -0
  65. package/vest/dist/vest.umd.js.map +1 -0
  66. package/vest/package.json +18 -0
  67. package/vest/src/__tests__/Form.tsx +64 -0
  68. package/vest/src/__tests__/__fixtures__/data.ts +67 -0
  69. package/vest/src/__tests__/__snapshots__/vest.ts.snap +141 -0
  70. package/vest/src/__tests__/vest.ts +65 -0
  71. package/vest/src/index.ts +1 -0
  72. package/vest/src/types.ts +21 -0
  73. package/vest/src/vest.ts +45 -0
  74. package/yup/dist/index.d.ts +2 -0
  75. package/yup/dist/types.d.ts +8 -0
  76. package/yup/dist/yup.d.ts +2 -0
  77. package/yup/dist/yup.js +2 -0
  78. package/yup/dist/yup.js.map +1 -0
  79. package/yup/dist/yup.mjs +2 -0
  80. package/yup/dist/yup.modern.js +2 -0
  81. package/yup/dist/yup.modern.js.map +1 -0
  82. package/yup/dist/yup.module.js +2 -0
  83. package/yup/dist/yup.module.js.map +1 -0
  84. package/yup/dist/yup.umd.js +2 -0
  85. package/yup/dist/yup.umd.js.map +1 -0
  86. package/yup/package.json +17 -0
  87. package/yup/src/__tests__/Form.tsx +54 -0
  88. package/yup/src/__tests__/__fixtures__/data.ts +65 -0
  89. package/yup/src/__tests__/__snapshots__/yup.ts.snap +221 -0
  90. package/yup/src/__tests__/yup.ts +150 -0
  91. package/yup/src/index.ts +2 -0
  92. package/yup/src/types.ts +22 -0
  93. package/yup/src/yup.ts +65 -0
  94. package/zod/dist/index.d.ts +2 -0
  95. package/zod/dist/types.d.ts +6 -0
  96. package/zod/dist/zod.d.ts +2 -0
  97. package/zod/dist/zod.js +2 -0
  98. package/zod/dist/zod.js.map +1 -0
  99. package/zod/dist/zod.mjs +2 -0
  100. package/zod/dist/zod.modern.js +2 -0
  101. package/zod/dist/zod.modern.js.map +1 -0
  102. package/zod/dist/zod.module.js +2 -0
  103. package/zod/dist/zod.module.js.map +1 -0
  104. package/zod/dist/zod.umd.js +2 -0
  105. package/zod/dist/zod.umd.js.map +1 -0
  106. package/zod/package.json +17 -0
  107. package/zod/src/__tests__/Form.tsx +54 -0
  108. package/zod/src/__tests__/__fixtures__/data.ts +68 -0
  109. package/zod/src/__tests__/__snapshots__/zod.ts.snap +355 -0
  110. package/zod/src/__tests__/zod.ts +71 -0
  111. package/zod/src/index.ts +2 -0
  112. package/zod/src/types.ts +18 -0
  113. package/zod/src/zod.ts +65 -0
  114. package/dist/ie11/joi.d.ts +0 -3
  115. package/dist/ie11/joi.js +0 -104
  116. package/dist/ie11/joi.js.map +0 -1
  117. package/dist/ie11/superstruct.d.ts +0 -3
  118. package/dist/ie11/superstruct.js +0 -95
  119. package/dist/ie11/superstruct.js.map +0 -1
  120. package/dist/ie11/utils/convertArrayToPathName.d.ts +0 -2
  121. package/dist/ie11/utils/convertArrayToPathName.js +0 -12
  122. package/dist/ie11/utils/convertArrayToPathName.js.map +0 -1
  123. package/dist/ie11/yup.d.ts +0 -3
  124. package/dist/ie11/yup.js +0 -132
  125. package/dist/ie11/yup.js.map +0 -1
  126. package/dist/joi.d.ts +0 -3
  127. package/dist/joi.js +0 -41
  128. package/dist/joi.js.map +0 -1
  129. package/dist/superstruct.d.ts +0 -3
  130. package/dist/superstruct.js +0 -39
  131. package/dist/superstruct.js.map +0 -1
  132. package/dist/umd/index.js +0 -2
  133. package/dist/umd/index.js.map +0 -1
  134. package/dist/utils/convertArrayToPathName.d.ts +0 -2
  135. package/dist/utils/convertArrayToPathName.js +0 -8
  136. package/dist/utils/convertArrayToPathName.js.map +0 -1
  137. package/dist/yup.d.ts +0 -3
  138. package/dist/yup.js +0 -49
  139. package/dist/yup.js.map +0 -1
  140. package/joi.d.ts +0 -1
  141. package/joi.js +0 -1
  142. package/superstruct.js +0 -1
  143. package/yup.d.ts +0 -1
  144. package/yup.js +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"superstruct.js","sources":["../src/superstruct.ts"],"sourcesContent":["import { FieldError } from 'react-hook-form';\nimport { toNestError } from '@hookform/resolvers';\n\nimport { StructError, validate } from 'superstruct';\nimport { Resolver } from './types';\n\nconst parseErrorSchema = (error: StructError) =>\n error.failures().reduce<Record<string, FieldError>>(\n (previous, error) =>\n (previous[error.path.join('.')] = {\n message: error.message,\n type: error.type,\n }) && previous,\n {},\n );\n\nexport const superstructResolver: Resolver = (schema, resolverOptions) => (\n values,\n _,\n options,\n) => {\n const result = validate(values, schema, resolverOptions);\n\n return {\n values: result[1] || {},\n errors: result[0]\n ? toNestError(parseErrorSchema(result[0]), options.fields)\n : {},\n };\n};\n"],"names":["schema","resolverOptions","values","_","options","error","result","validate","errors","toNestError","failures","reduce","previous","path","join","message","type","fields"],"mappings":"0FAgB6C,SAACA,EAAQC,mBACpDC,EACAC,EACAC,GAEA,IAfwBC,EAelBC,EAASC,WAASL,EAAQF,EAAQC,GAExC,MAAO,CACLC,OAAQI,EAAO,IAAM,GACrBE,OAAQF,EAAO,GACXG,eApBkBJ,EAoBWC,EAAO,GAnB1CD,EAAMK,WAAWC,OACf,SAACC,EAAUP,UACRO,EAASP,EAAMQ,KAAKC,KAAK,MAAQ,CAChCC,QAASV,EAAMU,QACfC,KAAMX,EAAMW,QACRJ,GACR,KAa6CR,EAAQa,QACjD"}
@@ -0,0 +1,2 @@
1
+ import{toNestError as r}from"@hookform/resolvers";import{validate as e}from"superstruct";var o=function(o,t){return function(s,u,n){var f,i=e(s,o,t);return{values:i[1]||{},errors:i[0]?r((f=i[0],f.failures().reduce(function(r,e){return(r[e.path.join(".")]={message:e.message,type:e.type})&&r},{})),n.fields):{}}}};export{o as superstructResolver};
2
+ //# sourceMappingURL=superstruct.module.js.map
@@ -0,0 +1,2 @@
1
+ import{toNestError as r}from"@hookform/resolvers";import{validate as e}from"superstruct";const s=(s,o)=>(t,m,p)=>{const a=e(t,s,o);return{values:a[1]||{},errors:a[0]?r((u=a[0],u.failures().reduce((r,e)=>(r[e.path.join(".")]={message:e.message,type:e.type})&&r,{})),p.fields):{}};var u};export{s as superstructResolver};
2
+ //# sourceMappingURL=superstruct.modern.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"superstruct.modern.js","sources":["../src/superstruct.ts"],"sourcesContent":["import { FieldError } from 'react-hook-form';\nimport { toNestError } from '@hookform/resolvers';\n\nimport { StructError, validate } from 'superstruct';\nimport { Resolver } from './types';\n\nconst parseErrorSchema = (error: StructError) =>\n error.failures().reduce<Record<string, FieldError>>(\n (previous, error) =>\n (previous[error.path.join('.')] = {\n message: error.message,\n type: error.type,\n }) && previous,\n {},\n );\n\nexport const superstructResolver: Resolver = (schema, resolverOptions) => (\n values,\n _,\n options,\n) => {\n const result = validate(values, schema, resolverOptions);\n\n return {\n values: result[1] || {},\n errors: result[0]\n ? toNestError(parseErrorSchema(result[0]), options.fields)\n : {},\n };\n};\n"],"names":["superstructResolver","schema","resolverOptions","values","_","options","result","validate","errors","toNestError","error","failures","reduce","previous","path","join","message","type","fields"],"mappings":"yFAMA,MAUaA,EAAgC,CAACC,EAAQC,IAAoB,CACxEC,EACAC,EACAC,KAEA,MAAMC,EAASC,EAASJ,EAAQF,EAAQC,GAExC,MAAO,CACLC,OAAQG,EAAO,IAAM,GACrBE,OAAQF,EAAO,GACXG,GApBkBC,EAoBWJ,EAAO,GAnB1CI,EAAMC,WAAWC,OACf,CAACC,EAAUH,KACRG,EAASH,EAAMI,KAAKC,KAAK,MAAQ,CAChCC,QAASN,EAAMM,QACfC,KAAMP,EAAMO,QACRJ,EACR,KAa6CR,EAAQa,QACjD,IArBkBR,IAAAA"}
@@ -0,0 +1,2 @@
1
+ import{toNestError as r}from"@hookform/resolvers";import{validate as e}from"superstruct";var o=function(o,t){return function(s,u,n){var f,i=e(s,o,t);return{values:i[1]||{},errors:i[0]?r((f=i[0],f.failures().reduce(function(r,e){return(r[e.path.join(".")]={message:e.message,type:e.type})&&r},{})),n.fields):{}}}};export{o as superstructResolver};
2
+ //# sourceMappingURL=superstruct.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"superstruct.module.js","sources":["../src/superstruct.ts"],"sourcesContent":["import { FieldError } from 'react-hook-form';\nimport { toNestError } from '@hookform/resolvers';\n\nimport { StructError, validate } from 'superstruct';\nimport { Resolver } from './types';\n\nconst parseErrorSchema = (error: StructError) =>\n error.failures().reduce<Record<string, FieldError>>(\n (previous, error) =>\n (previous[error.path.join('.')] = {\n message: error.message,\n type: error.type,\n }) && previous,\n {},\n );\n\nexport const superstructResolver: Resolver = (schema, resolverOptions) => (\n values,\n _,\n options,\n) => {\n const result = validate(values, schema, resolverOptions);\n\n return {\n values: result[1] || {},\n errors: result[0]\n ? toNestError(parseErrorSchema(result[0]), options.fields)\n : {},\n };\n};\n"],"names":["superstructResolver","schema","resolverOptions","values","_","options","error","result","validate","errors","toNestError","failures","reduce","previous","path","join","message","type","fields"],"mappings":"yFAMA,IAUaA,EAAgC,SAACC,EAAQC,mBACpDC,EACAC,EACAC,GAEA,IAfwBC,EAelBC,EAASC,EAASL,EAAQF,EAAQC,GAExC,MAAO,CACLC,OAAQI,EAAO,IAAM,GACrBE,OAAQF,EAAO,GACXG,GApBkBJ,EAoBWC,EAAO,GAnB1CD,EAAMK,WAAWC,OACf,SAACC,EAAUP,UACRO,EAASP,EAAMQ,KAAKC,KAAK,MAAQ,CAChCC,QAASV,EAAMU,QACfC,KAAMX,EAAMW,QACRJ,GACR,KAa6CR,EAAQa,QACjD"}
@@ -0,0 +1,2 @@
1
+ !function(e,r){"object"==typeof exports&&"undefined"!=typeof module?r(exports,require("@hookform/resolvers"),require("superstruct")):"function"==typeof define&&define.amd?define(["exports","@hookform/resolvers","superstruct"],r):r((e||self).hookformResolversSuperstruct={},e.hookformResolvers,e.superstruct)}(this,function(e,r,o){e.superstructResolver=function(e,s){return function(t,u,n){var f,i=o.validate(t,e,s);return{values:i[1]||{},errors:i[0]?r.toNestError((f=i[0],f.failures().reduce(function(e,r){return(e[r.path.join(".")]={message:r.message,type:r.type})&&e},{})),n.fields):{}}}}});
2
+ //# sourceMappingURL=superstruct.umd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"superstruct.umd.js","sources":["../src/superstruct.ts"],"sourcesContent":["import { FieldError } from 'react-hook-form';\nimport { toNestError } from '@hookform/resolvers';\n\nimport { StructError, validate } from 'superstruct';\nimport { Resolver } from './types';\n\nconst parseErrorSchema = (error: StructError) =>\n error.failures().reduce<Record<string, FieldError>>(\n (previous, error) =>\n (previous[error.path.join('.')] = {\n message: error.message,\n type: error.type,\n }) && previous,\n {},\n );\n\nexport const superstructResolver: Resolver = (schema, resolverOptions) => (\n values,\n _,\n options,\n) => {\n const result = validate(values, schema, resolverOptions);\n\n return {\n values: result[1] || {},\n errors: result[0]\n ? toNestError(parseErrorSchema(result[0]), options.fields)\n : {},\n };\n};\n"],"names":["schema","resolverOptions","values","_","options","error","result","validate","errors","toNestError","failures","reduce","previous","path","join","message","type","fields"],"mappings":"4YAgB6C,SAACA,EAAQC,mBACpDC,EACAC,EACAC,GAEA,IAfwBC,EAelBC,EAASC,WAASL,EAAQF,EAAQC,GAExC,MAAO,CACLC,OAAQI,EAAO,IAAM,GACrBE,OAAQF,EAAO,GACXG,eApBkBJ,EAoBWC,EAAO,GAnB1CD,EAAMK,WAAWC,OACf,SAACC,EAAUP,UACRO,EAASP,EAAMQ,KAAKC,KAAK,MAAQ,CAChCC,QAASV,EAAMU,QACfC,KAAMX,EAAMW,QACRJ,GACR,KAa6CR,EAAQa,QACjD"}
@@ -0,0 +1,5 @@
1
+ import { FieldValues, ResolverOptions, ResolverResult, UnpackNestedValue } from 'react-hook-form';
2
+ import { validate, Struct } from 'superstruct';
3
+ declare type Options = Parameters<typeof validate>[2];
4
+ export declare type Resolver = <T extends Struct<any, any>>(schema: T, factoryOtions?: Options) => <TFieldValues extends FieldValues, TContext>(values: UnpackNestedValue<TFieldValues>, context: TContext | undefined, options: ResolverOptions<TFieldValues>) => ResolverResult<TFieldValues>;
5
+ export {};
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "superstruct",
3
+ "amdName": "hookformResolversSuperstruct",
4
+ "version": "1.0.0",
5
+ "private": true,
6
+ "description": "React Hook Form validation resolver: superstruct",
7
+ "main": "dist/superstruct.js",
8
+ "module": "dist/superstruct.module.js",
9
+ "umd:main": "dist/superstruct.umd.js",
10
+ "source": "src/index.ts",
11
+ "types": "dist/index.d.ts",
12
+ "license": "MIT",
13
+ "peerDependencies": {
14
+ "react-hook-form": ">=6.6.0",
15
+ "@hookform/resolvers": "^1.0.0",
16
+ "superstruct": ">=0.12.0"
17
+ }
18
+ }
@@ -0,0 +1,62 @@
1
+ import React from 'react';
2
+ import { render, screen, act } from '@testing-library/react';
3
+ import user from '@testing-library/user-event';
4
+ import { useForm } from 'react-hook-form';
5
+ import { object, string, Infer, size } from 'superstruct';
6
+ import { superstructResolver } from '..';
7
+
8
+ const schema = object({
9
+ username: size(string(), 2),
10
+ password: size(string(), 6),
11
+ });
12
+
13
+ type FormData = Infer<typeof schema>;
14
+
15
+ interface Props {
16
+ onSubmit: (data: FormData) => void;
17
+ }
18
+
19
+ function TestComponent({ onSubmit }: Props) {
20
+ const {
21
+ register,
22
+ formState: { errors },
23
+ handleSubmit,
24
+ } = useForm<FormData>({
25
+ resolver: superstructResolver(schema), // Useful to check TypeScript regressions
26
+ });
27
+
28
+ return (
29
+ <form onSubmit={handleSubmit(onSubmit)}>
30
+ <input {...register('username')} />
31
+ {errors.username && <span role="alert">{errors.username.message}</span>}
32
+
33
+ <input {...register('password')} />
34
+ {errors.password && <span role="alert">{errors.password.message}</span>}
35
+
36
+ <button type="submit">submit</button>
37
+ </form>
38
+ );
39
+ }
40
+
41
+ test("form's validation with Superstruct and TypeScript's integration", async () => {
42
+ const handleSubmit = jest.fn();
43
+ render(<TestComponent onSubmit={handleSubmit} />);
44
+
45
+ expect(screen.queryAllByRole(/alert/i)).toHaveLength(0);
46
+
47
+ await act(async () => {
48
+ user.click(screen.getByText(/submit/i));
49
+ });
50
+
51
+ expect(
52
+ screen.getByText(
53
+ /Expected a string with a length of `2` but received one with a length of `0`/i,
54
+ ),
55
+ ).toBeInTheDocument();
56
+ expect(
57
+ screen.getByText(
58
+ /Expected a string with a length of `6` but received one with a length of `0`/i,
59
+ ),
60
+ ).toBeInTheDocument();
61
+ expect(handleSubmit).not.toHaveBeenCalled();
62
+ });
@@ -0,0 +1,73 @@
1
+ import { Field, InternalFieldName } from 'react-hook-form';
2
+ import {
3
+ object,
4
+ number,
5
+ string,
6
+ optional,
7
+ pattern,
8
+ size,
9
+ union,
10
+ min,
11
+ max,
12
+ Infer,
13
+ define,
14
+ array,
15
+ boolean,
16
+ } from 'superstruct';
17
+
18
+ const Password = define('Password', (value, ctx) =>
19
+ value === ctx.branch[0].password);
20
+
21
+ export const schema = object({
22
+ username: size(pattern(string(), /^\w+$/), 3, 30),
23
+ password: pattern(string(), /^[a-zA-Z0-9]{3,30}/),
24
+ repeatPassword: Password,
25
+ accessToken: optional(union([string(), number()])),
26
+ birthYear: optional(max(min(number(), 1900), 2013)),
27
+ email: optional(pattern(string(), /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/)),
28
+ tags: array(string()),
29
+ enabled: boolean(),
30
+ like: optional(array(object({ id: number(), name: size(string(), 4) }))),
31
+ });
32
+
33
+ export const validData: Infer<typeof schema> = {
34
+ username: 'Doe',
35
+ password: 'Password123',
36
+ repeatPassword: 'Password123',
37
+ birthYear: 2000,
38
+ email: 'john@doe.com',
39
+ tags: ['tag1', 'tag2'],
40
+ enabled: true,
41
+ like: [
42
+ {
43
+ id: 1,
44
+ name: 'name',
45
+ },
46
+ ],
47
+ };
48
+
49
+ export const invalidData = {
50
+ password: '___',
51
+ email: '',
52
+ birthYear: 'birthYear',
53
+ like: [{ id: 'z' }],
54
+ };
55
+
56
+ export const fields: Record<InternalFieldName, Field['_f']> = {
57
+ username: {
58
+ ref: { name: 'username' },
59
+ name: 'username',
60
+ },
61
+ password: {
62
+ ref: { name: 'password' },
63
+ name: 'password',
64
+ },
65
+ email: {
66
+ ref: { name: 'email' },
67
+ name: 'email',
68
+ },
69
+ birthday: {
70
+ ref: { name: 'birthday' },
71
+ name: 'birthday',
72
+ },
73
+ };
@@ -0,0 +1,64 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`superstructResolver should return a single error from superstructResolver when validation fails 1`] = `
4
+ Object {
5
+ "errors": Object {
6
+ "birthYear": Object {
7
+ "message": "Expected a number, but received: \\"birthYear\\"",
8
+ "ref": undefined,
9
+ "type": "number",
10
+ },
11
+ "email": Object {
12
+ "message": "Expected a string matching \`/^[\\\\w-\\\\.]+@([\\\\w-]+\\\\.)+[\\\\w-]{2,4}$/\` but received \\"\\"",
13
+ "ref": Object {
14
+ "name": "email",
15
+ },
16
+ "type": "string",
17
+ },
18
+ "enabled": Object {
19
+ "message": "Expected a value of type \`boolean\`, but received: \`undefined\`",
20
+ "ref": undefined,
21
+ "type": "boolean",
22
+ },
23
+ "like": Array [
24
+ Object {
25
+ "id": Object {
26
+ "message": "Expected a number, but received: \\"z\\"",
27
+ "ref": undefined,
28
+ "type": "number",
29
+ },
30
+ "name": Object {
31
+ "message": "Expected a string, but received: undefined",
32
+ "ref": undefined,
33
+ "type": "string",
34
+ },
35
+ },
36
+ ],
37
+ "password": Object {
38
+ "message": "Expected a string matching \`/^[a-zA-Z0-9]{3,30}/\` but received \\"___\\"",
39
+ "ref": Object {
40
+ "name": "password",
41
+ },
42
+ "type": "string",
43
+ },
44
+ "repeatPassword": Object {
45
+ "message": "Expected a value of type \`Password\`, but received: \`undefined\`",
46
+ "ref": undefined,
47
+ "type": "Password",
48
+ },
49
+ "tags": Object {
50
+ "message": "Expected an array value, but received: undefined",
51
+ "ref": undefined,
52
+ "type": "array",
53
+ },
54
+ "username": Object {
55
+ "message": "Expected a string, but received: undefined",
56
+ "ref": Object {
57
+ "name": "username",
58
+ },
59
+ "type": "string",
60
+ },
61
+ },
62
+ "values": Object {},
63
+ }
64
+ `;
@@ -0,0 +1,20 @@
1
+ import { superstructResolver } from '..';
2
+ import { invalidData, schema, validData, fields } from './__fixtures__/data';
3
+
4
+ describe('superstructResolver', () => {
5
+ it('should return values from superstructResolver when validation pass', async () => {
6
+ const result = await superstructResolver(schema)(validData, undefined, {
7
+ fields,
8
+ });
9
+
10
+ expect(result).toEqual({ errors: {}, values: validData });
11
+ });
12
+
13
+ it('should return a single error from superstructResolver when validation fails', async () => {
14
+ const result = await superstructResolver(schema)(invalidData, undefined, {
15
+ fields,
16
+ });
17
+
18
+ expect(result).toMatchSnapshot();
19
+ });
20
+ });
@@ -0,0 +1,2 @@
1
+ export * from './superstruct';
2
+ export * from './types';
@@ -0,0 +1,30 @@
1
+ import { FieldError } from 'react-hook-form';
2
+ import { toNestError } from '@hookform/resolvers';
3
+
4
+ import { StructError, validate } from 'superstruct';
5
+ import { Resolver } from './types';
6
+
7
+ const parseErrorSchema = (error: StructError) =>
8
+ error.failures().reduce<Record<string, FieldError>>(
9
+ (previous, error) =>
10
+ (previous[error.path.join('.')] = {
11
+ message: error.message,
12
+ type: error.type,
13
+ }) && previous,
14
+ {},
15
+ );
16
+
17
+ export const superstructResolver: Resolver = (schema, resolverOptions) => (
18
+ values,
19
+ _,
20
+ options,
21
+ ) => {
22
+ const result = validate(values, schema, resolverOptions);
23
+
24
+ return {
25
+ values: result[1] || {},
26
+ errors: result[0]
27
+ ? toNestError(parseErrorSchema(result[0]), options.fields)
28
+ : {},
29
+ };
30
+ };
@@ -0,0 +1,18 @@
1
+ import {
2
+ FieldValues,
3
+ ResolverOptions,
4
+ ResolverResult,
5
+ UnpackNestedValue,
6
+ } from 'react-hook-form';
7
+ import { validate, Struct } from 'superstruct';
8
+
9
+ type Options = Parameters<typeof validate>[2];
10
+
11
+ export type Resolver = <T extends Struct<any, any>>(
12
+ schema: T,
13
+ factoryOtions?: Options,
14
+ ) => <TFieldValues extends FieldValues, TContext>(
15
+ values: UnpackNestedValue<TFieldValues>,
16
+ context: TContext | undefined,
17
+ options: ResolverOptions<TFieldValues>,
18
+ ) => ResolverResult<TFieldValues>;
@@ -0,0 +1 @@
1
+ export * from './vest';
@@ -0,0 +1,2 @@
1
+ import type { Resolver } from './types';
2
+ export declare const vestResolver: Resolver;
@@ -0,0 +1,2 @@
1
+ var r=require("@hookform/resolvers");function e(r){return r&&"object"==typeof r&&"default"in r?r:{default:r}}var t=e(require("vest/promisify")),o=function(r,e){var t={};for(var o in r)t[o]||(t[o]={message:r[o][0],type:""}),e&&(t[o].types=r[o].reduce(function(r,e,t){return(r[t]=e)&&r},{}));return t};exports.vestResolver=function(e,s,n){return void 0===n&&(n={}),function(s,u,i){try{var a=function(e){return e.hasErrors()?{values:{},errors:r.toNestError(o(e.getErrors(),"all"===i.criteriaMode),i.fields)}:{values:s,errors:{}}};return Promise.resolve("sync"===n.mode?a(e(s)):Promise.resolve(t.default(e)(s)).then(a))}catch(r){return Promise.reject(r)}}};
2
+ //# sourceMappingURL=vest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vest.js","sources":["../src/vest.ts"],"sourcesContent":["import { toNestError } from '@hookform/resolvers';\nimport { FieldError } from 'react-hook-form';\nimport promisify from 'vest/promisify';\nimport type { VestErrors, Resolver } from './types';\n\nconst parseErrorSchema = (\n vestError: VestErrors,\n validateAllFieldCriteria: boolean,\n) => {\n const errors: Record<string, FieldError> = {};\n for (const path in vestError) {\n if (!errors[path]) {\n errors[path] = { message: vestError[path][0], type: '' };\n }\n\n if (validateAllFieldCriteria) {\n errors[path].types = vestError[path].reduce<Record<number, string>>(\n (acc, message, index) => (acc[index] = message) && acc,\n {},\n );\n }\n }\n return errors;\n};\n\nexport const vestResolver: Resolver = (\n schema,\n _,\n resolverOptions = {},\n) => async (values, _context, options) => {\n const result =\n resolverOptions.mode === 'sync'\n ? schema(values)\n : await promisify(schema)(values);\n\n return result.hasErrors()\n ? {\n values: {},\n errors: toNestError(\n parseErrorSchema(result.getErrors(), options.criteriaMode === 'all'),\n options.fields,\n ),\n }\n : { values, errors: {} };\n};\n"],"names":["parseErrorSchema","vestError","validateAllFieldCriteria","errors","path","message","type","types","reduce","acc","index","schema","_","resolverOptions","values","_context","options","result","hasErrors","toNestError","getErrors","criteriaMode","fields","mode","promisify"],"mappings":"gJAKMA,EAAmB,SACvBC,EACAC,GAEA,IAAMC,EAAqC,GAC3C,IAAK,IAAMC,KAAQH,EACZE,EAAOC,KACVD,EAAOC,GAAQ,CAAEC,QAASJ,EAAUG,GAAM,GAAIE,KAAM,KAGlDJ,IACFC,EAAOC,GAAMG,MAAQN,EAAUG,GAAMI,OACnC,SAACC,EAAKJ,EAASK,UAAWD,EAAIC,GAASL,IAAYI,GACnD,KAIN,OAAON,wBAG6B,SACpCQ,EACAC,EACAC,mBAAAA,IAAAA,EAAkB,aACRC,EAAQC,EAAUC,sBACtBC,GAKN,OAAOA,EAAOC,YACV,CACEJ,OAAQ,GACRX,OAAQgB,cACNnB,EAAiBiB,EAAOG,YAAsC,QAAzBJ,EAAQK,cAC7CL,EAAQM,SAGZ,CAAER,OAAAA,EAAQX,OAAQ,4BAZK,SAAzBU,EAAgBU,OACZZ,EAAOG,oBACDU,UAAUb,EAAVa,CAAkBV,aARM"}
@@ -0,0 +1,2 @@
1
+ import{toNestError as r}from"@hookform/resolvers";import e from"vest/promisify";var o=function(r,e){var o={};for(var t in r)o[t]||(o[t]={message:r[t][0],type:""}),e&&(o[t].types=r[t].reduce(function(r,e,o){return(r[o]=e)&&r},{}));return o},t=function(t,s,n){return void 0===n&&(n={}),function(s,i,u){try{var a=function(e){return e.hasErrors()?{values:{},errors:r(o(e.getErrors(),"all"===u.criteriaMode),u.fields)}:{values:s,errors:{}}};return Promise.resolve("sync"===n.mode?a(t(s)):Promise.resolve(e(t)(s)).then(a))}catch(r){return Promise.reject(r)}}};export{t as vestResolver};
2
+ //# sourceMappingURL=vest.module.js.map
@@ -0,0 +1,2 @@
1
+ import{toNestError as r}from"@hookform/resolvers";import e from"vest/promisify";const o=(r,e)=>{const o={};for(const s in r)o[s]||(o[s]={message:r[s][0],type:""}),e&&(o[s].types=r[s].reduce((r,e,o)=>(r[o]=e)&&r,{}));return o},s=(s,t,a={})=>async(t,i,n)=>{const c="sync"===a.mode?s(t):await e(s)(t);return c.hasErrors()?{values:{},errors:r(o(c.getErrors(),"all"===n.criteriaMode),n.fields)}:{values:t,errors:{}}};export{s as vestResolver};
2
+ //# sourceMappingURL=vest.modern.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vest.modern.js","sources":["../src/vest.ts"],"sourcesContent":["import { toNestError } from '@hookform/resolvers';\nimport { FieldError } from 'react-hook-form';\nimport promisify from 'vest/promisify';\nimport type { VestErrors, Resolver } from './types';\n\nconst parseErrorSchema = (\n vestError: VestErrors,\n validateAllFieldCriteria: boolean,\n) => {\n const errors: Record<string, FieldError> = {};\n for (const path in vestError) {\n if (!errors[path]) {\n errors[path] = { message: vestError[path][0], type: '' };\n }\n\n if (validateAllFieldCriteria) {\n errors[path].types = vestError[path].reduce<Record<number, string>>(\n (acc, message, index) => (acc[index] = message) && acc,\n {},\n );\n }\n }\n return errors;\n};\n\nexport const vestResolver: Resolver = (\n schema,\n _,\n resolverOptions = {},\n) => async (values, _context, options) => {\n const result =\n resolverOptions.mode === 'sync'\n ? schema(values)\n : await promisify(schema)(values);\n\n return result.hasErrors()\n ? {\n values: {},\n errors: toNestError(\n parseErrorSchema(result.getErrors(), options.criteriaMode === 'all'),\n options.fields,\n ),\n }\n : { values, errors: {} };\n};\n"],"names":["parseErrorSchema","vestError","validateAllFieldCriteria","errors","path","message","type","types","reduce","acc","index","vestResolver","schema","_","resolverOptions","async","values","_context","options","result","mode","promisify","hasErrors","toNestError","getErrors","criteriaMode","fields"],"mappings":"gFAKA,MAAMA,EAAmB,CACvBC,EACAC,KAEA,MAAMC,EAAqC,GAC3C,IAAK,MAAMC,KAAQH,EACZE,EAAOC,KACVD,EAAOC,GAAQ,CAAEC,QAASJ,EAAUG,GAAM,GAAIE,KAAM,KAGlDJ,IACFC,EAAOC,GAAMG,MAAQN,EAAUG,GAAMI,OACnC,CAACC,EAAKJ,EAASK,KAAWD,EAAIC,GAASL,IAAYI,EACnD,KAIN,OAAON,GAGIQ,EAAyB,CACpCC,EACAC,EACAC,EAAkB,KACfC,MAAOC,EAAQC,EAAUC,KAC5B,MAAMC,EACqB,SAAzBL,EAAgBM,KACZR,EAAOI,SACDK,EAAUT,EAAVS,CAAkBL,GAE9B,OAAOG,EAAOG,YACV,CACEN,OAAQ,GACRb,OAAQoB,EACNvB,EAAiBmB,EAAOK,YAAsC,QAAzBN,EAAQO,cAC7CP,EAAQQ,SAGZ,CAAEV,OAAAA,EAAQb,OAAQ"}
@@ -0,0 +1,2 @@
1
+ import{toNestError as r}from"@hookform/resolvers";import e from"vest/promisify";var o=function(r,e){var o={};for(var t in r)o[t]||(o[t]={message:r[t][0],type:""}),e&&(o[t].types=r[t].reduce(function(r,e,o){return(r[o]=e)&&r},{}));return o},t=function(t,s,n){return void 0===n&&(n={}),function(s,i,u){try{var a=function(e){return e.hasErrors()?{values:{},errors:r(o(e.getErrors(),"all"===u.criteriaMode),u.fields)}:{values:s,errors:{}}};return Promise.resolve("sync"===n.mode?a(t(s)):Promise.resolve(e(t)(s)).then(a))}catch(r){return Promise.reject(r)}}};export{t as vestResolver};
2
+ //# sourceMappingURL=vest.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vest.module.js","sources":["../src/vest.ts"],"sourcesContent":["import { toNestError } from '@hookform/resolvers';\nimport { FieldError } from 'react-hook-form';\nimport promisify from 'vest/promisify';\nimport type { VestErrors, Resolver } from './types';\n\nconst parseErrorSchema = (\n vestError: VestErrors,\n validateAllFieldCriteria: boolean,\n) => {\n const errors: Record<string, FieldError> = {};\n for (const path in vestError) {\n if (!errors[path]) {\n errors[path] = { message: vestError[path][0], type: '' };\n }\n\n if (validateAllFieldCriteria) {\n errors[path].types = vestError[path].reduce<Record<number, string>>(\n (acc, message, index) => (acc[index] = message) && acc,\n {},\n );\n }\n }\n return errors;\n};\n\nexport const vestResolver: Resolver = (\n schema,\n _,\n resolverOptions = {},\n) => async (values, _context, options) => {\n const result =\n resolverOptions.mode === 'sync'\n ? schema(values)\n : await promisify(schema)(values);\n\n return result.hasErrors()\n ? {\n values: {},\n errors: toNestError(\n parseErrorSchema(result.getErrors(), options.criteriaMode === 'all'),\n options.fields,\n ),\n }\n : { values, errors: {} };\n};\n"],"names":["parseErrorSchema","vestError","validateAllFieldCriteria","errors","path","message","type","types","reduce","acc","index","vestResolver","schema","_","resolverOptions","values","_context","options","result","hasErrors","toNestError","getErrors","criteriaMode","fields","mode","promisify"],"mappings":"gFAKA,IAAMA,EAAmB,SACvBC,EACAC,GAEA,IAAMC,EAAqC,GAC3C,IAAK,IAAMC,KAAQH,EACZE,EAAOC,KACVD,EAAOC,GAAQ,CAAEC,QAASJ,EAAUG,GAAM,GAAIE,KAAM,KAGlDJ,IACFC,EAAOC,GAAMG,MAAQN,EAAUG,GAAMI,OACnC,SAACC,EAAKJ,EAASK,UAAWD,EAAIC,GAASL,IAAYI,GACnD,KAIN,OAAON,GAGIQ,EAAyB,SACpCC,EACAC,EACAC,mBAAAA,IAAAA,EAAkB,aACRC,EAAQC,EAAUC,sBACtBC,GAKN,OAAOA,EAAOC,YACV,CACEJ,OAAQ,GACRZ,OAAQiB,EACNpB,EAAiBkB,EAAOG,YAAsC,QAAzBJ,EAAQK,cAC7CL,EAAQM,SAGZ,CAAER,OAAAA,EAAQZ,OAAQ,4BAZK,SAAzBW,EAAgBU,OACZZ,EAAOG,oBACDU,EAAUb,EAAVa,CAAkBV,aARM"}
@@ -0,0 +1,2 @@
1
+ !function(e,r){"object"==typeof exports&&"undefined"!=typeof module?r(exports,require("@hookform/resolvers"),require("vest/promisify")):"function"==typeof define&&define.amd?define(["exports","@hookform/resolvers","vest/promisify"],r):r((e||self).hookformResolversVest={},e.hookformResolvers,e.promisify)}(this,function(e,r,o){function t(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var s=t(o),n=function(e,r){var o={};for(var t in e)o[t]||(o[t]={message:e[t][0],type:""}),r&&(o[t].types=e[t].reduce(function(e,r,o){return(e[o]=r)&&e},{}));return o};e.vestResolver=function(e,o,t){return void 0===t&&(t={}),function(o,i,f){try{var u=function(e){return e.hasErrors()?{values:{},errors:r.toNestError(n(e.getErrors(),"all"===f.criteriaMode),f.fields)}:{values:o,errors:{}}};return Promise.resolve("sync"===t.mode?u(e(o)):Promise.resolve(s.default(e)(o)).then(u))}catch(e){return Promise.reject(e)}}}});
2
+ //# sourceMappingURL=vest.umd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vest.umd.js","sources":["../src/vest.ts"],"sourcesContent":["import { toNestError } from '@hookform/resolvers';\nimport { FieldError } from 'react-hook-form';\nimport promisify from 'vest/promisify';\nimport type { VestErrors, Resolver } from './types';\n\nconst parseErrorSchema = (\n vestError: VestErrors,\n validateAllFieldCriteria: boolean,\n) => {\n const errors: Record<string, FieldError> = {};\n for (const path in vestError) {\n if (!errors[path]) {\n errors[path] = { message: vestError[path][0], type: '' };\n }\n\n if (validateAllFieldCriteria) {\n errors[path].types = vestError[path].reduce<Record<number, string>>(\n (acc, message, index) => (acc[index] = message) && acc,\n {},\n );\n }\n }\n return errors;\n};\n\nexport const vestResolver: Resolver = (\n schema,\n _,\n resolverOptions = {},\n) => async (values, _context, options) => {\n const result =\n resolverOptions.mode === 'sync'\n ? schema(values)\n : await promisify(schema)(values);\n\n return result.hasErrors()\n ? {\n values: {},\n errors: toNestError(\n parseErrorSchema(result.getErrors(), options.criteriaMode === 'all'),\n options.fields,\n ),\n }\n : { values, errors: {} };\n};\n"],"names":["parseErrorSchema","vestError","validateAllFieldCriteria","errors","path","message","type","types","reduce","acc","index","schema","_","resolverOptions","values","_context","options","result","hasErrors","toNestError","getErrors","criteriaMode","fields","mode","promisify"],"mappings":"scAKMA,EAAmB,SACvBC,EACAC,GAEA,IAAMC,EAAqC,GAC3C,IAAK,IAAMC,KAAQH,EACZE,EAAOC,KACVD,EAAOC,GAAQ,CAAEC,QAASJ,EAAUG,GAAM,GAAIE,KAAM,KAGlDJ,IACFC,EAAOC,GAAMG,MAAQN,EAAUG,GAAMI,OACnC,SAACC,EAAKJ,EAASK,UAAWD,EAAIC,GAASL,IAAYI,GACnD,KAIN,OAAON,kBAG6B,SACpCQ,EACAC,EACAC,mBAAAA,IAAAA,EAAkB,aACRC,EAAQC,EAAUC,sBACtBC,GAKN,OAAOA,EAAOC,YACV,CACEJ,OAAQ,GACRX,OAAQgB,cACNnB,EAAiBiB,EAAOG,YAAsC,QAAzBJ,EAAQK,cAC7CL,EAAQM,SAGZ,CAAER,OAAAA,EAAQX,OAAQ,4BAZK,SAAzBU,EAAgBU,OACZZ,EAAOG,oBACDU,UAAUb,EAAVa,CAAkBV,aARM"}
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "vest",
3
+ "amdName": "hookformResolversVest",
4
+ "version": "1.0.0",
5
+ "private": true,
6
+ "description": "React Hook Form validation resolver: vest",
7
+ "main": "dist/vest.js",
8
+ "module": "dist/vest.module.js",
9
+ "umd:main": "dist/vest.umd.js",
10
+ "source": "src/index.ts",
11
+ "types": "dist/index.d.ts",
12
+ "license": "MIT",
13
+ "peerDependencies": {
14
+ "react-hook-form": ">=6.6.0",
15
+ "@hookform/resolvers": ">=2.0.0",
16
+ "vest": ">=3.0.0"
17
+ }
18
+ }
@@ -0,0 +1,64 @@
1
+ import React from 'react';
2
+ import { render, screen, act } from '@testing-library/react';
3
+ import user from '@testing-library/user-event';
4
+ import { useForm } from 'react-hook-form';
5
+ import vest from 'vest';
6
+ import { vestResolver } from '..';
7
+
8
+ interface FormData {
9
+ username: string;
10
+ password: string;
11
+ }
12
+
13
+ const validationSuite = vest.create('form', (data: FormData) => {
14
+ vest.test('username', 'Username is required', () => {
15
+ vest.enforce(data.username).isNotEmpty();
16
+ });
17
+
18
+ vest.test('password', 'Password must contain a symbol', () => {
19
+ vest.enforce(data.password).matches(/[^A-Za-z0-9]/);
20
+ });
21
+ });
22
+
23
+ interface Props {
24
+ onSubmit: (data: FormData) => void;
25
+ }
26
+
27
+ function TestComponent({ onSubmit }: Props) {
28
+ const {
29
+ register,
30
+ formState: { errors },
31
+ handleSubmit,
32
+ } = useForm<FormData>({
33
+ resolver: vestResolver(validationSuite), // Useful to check TypeScript regressions
34
+ });
35
+
36
+ return (
37
+ <form onSubmit={handleSubmit(onSubmit)}>
38
+ <input {...register('username')} />
39
+ {errors.username && <span role="alert">{errors.username.message}</span>}
40
+
41
+ <input {...register('password')} />
42
+ {errors.password && <span role="alert">{errors.password.message}</span>}
43
+
44
+ <button type="submit">submit</button>
45
+ </form>
46
+ );
47
+ }
48
+
49
+ test("form's validation with Vest and TypeScript's integration", async () => {
50
+ const handleSubmit = jest.fn();
51
+ render(<TestComponent onSubmit={handleSubmit} />);
52
+
53
+ expect(screen.queryAllByRole(/alert/i)).toHaveLength(0);
54
+
55
+ await act(async () => {
56
+ user.click(screen.getByText(/submit/i));
57
+ });
58
+
59
+ expect(screen.getByText(/Username is required/i)).toBeInTheDocument();
60
+ expect(
61
+ screen.getByText(/Password must contain a symbol/i),
62
+ ).toBeInTheDocument();
63
+ expect(handleSubmit).not.toHaveBeenCalled();
64
+ });
@@ -0,0 +1,67 @@
1
+ import { Field, InternalFieldName } from 'react-hook-form';
2
+ import * as vest from 'vest';
3
+
4
+ export const validationSuite = vest.create('form', (data: any = {}) => {
5
+ vest.test('username', 'Username is required', () => {
6
+ vest.enforce(data.username).isNotEmpty();
7
+ });
8
+
9
+ vest.test('username', 'Must be longer than 3 chars', () => {
10
+ vest.enforce(data.username).longerThan(3);
11
+ });
12
+
13
+ vest.test('deepObject.data', 'deepObject.data is required', () => {
14
+ vest.enforce(data.deepObject.data).isNotEmpty();
15
+ });
16
+
17
+ vest.test('password', 'Password is required', () => {
18
+ vest.enforce(data.password).isNotEmpty();
19
+ });
20
+
21
+ vest.test('password', 'Password must be at least 5 chars', () => {
22
+ vest.enforce(data.password).longerThanOrEquals(5);
23
+ });
24
+
25
+ vest.test('password', 'Password must contain a digit', () => {
26
+ vest.enforce(data.password).matches(/[0-9]/);
27
+ });
28
+
29
+ vest.test('password', 'Password must contain a symbol', () => {
30
+ vest.enforce(data.password).matches(/[^A-Za-z0-9]/);
31
+ });
32
+ });
33
+
34
+ export const validData = {
35
+ username: 'asdda',
36
+ password: 'asddfg123!',
37
+ deepObject: {
38
+ data: 'test',
39
+ },
40
+ };
41
+
42
+ export const invalidData = {
43
+ username: '',
44
+ password: 'a',
45
+ deepObject: {
46
+ data: '',
47
+ },
48
+ };
49
+
50
+ export const fields: Record<InternalFieldName, Field['_f']> = {
51
+ username: {
52
+ ref: { name: 'username' },
53
+ name: 'username',
54
+ },
55
+ password: {
56
+ ref: { name: 'password' },
57
+ name: 'password',
58
+ },
59
+ email: {
60
+ ref: { name: 'email' },
61
+ name: 'email',
62
+ },
63
+ birthday: {
64
+ ref: { name: 'birthday' },
65
+ name: 'birthday',
66
+ },
67
+ };