Skip to content

Commit 4e40368

Browse files
committed
feat(standardSchema): type inference
1 parent 0c137d3 commit 4e40368

File tree

3 files changed

+102
-7
lines changed

3 files changed

+102
-7
lines changed

standard-schema/src/__tests__/__fixtures__/data.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,15 @@ export const validData = {
5959
},
6060
],
6161
dateStr: '2020-01-01T00:00:00.000Z',
62-
} as any as z.infer<typeof schema>;
62+
} satisfies z.input<typeof schema>;
6363

6464
export const invalidData = {
6565
password: '___',
6666
email: '',
6767
birthYear: 'birthYear',
6868
like: [{ id: 'z' }],
6969
url: 'abc',
70-
} as any as z.infer<typeof schema>;
70+
} as unknown as z.input<typeof schema>;
7171

7272
export const fields: Record<InternalFieldName, Field['_f']> = {
7373
username: {

standard-schema/src/__tests__/standard-schema.ts

+71
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Resolver, SubmitHandler, useForm } from 'react-hook-form';
12
import { standardSchemaResolver } from '..';
23
import {
34
customSchema,
@@ -6,6 +7,7 @@ import {
67
schema,
78
validData,
89
} from './__fixtures__/data';
10+
import { z } from 'zod';
911

1012
const shouldUseNativeValidation = false;
1113

@@ -71,4 +73,73 @@ describe('standardSchemaResolver', () => {
7173

7274
expect(result).toMatchSnapshot();
7375
});
76+
77+
/**
78+
* Type inference tests
79+
*/
80+
it('should correctly infer the output type from a standardSchema schema', () => {
81+
const resolver = standardSchemaResolver(z.object({ id: z.number() }));
82+
83+
expectTypeOf(resolver).toEqualTypeOf<
84+
Resolver<{ id: number }, unknown, { id: number }>
85+
>();
86+
});
87+
88+
it('should correctly infer the output type from a standardSchema schema using a transform', () => {
89+
const resolver = standardSchemaResolver(
90+
z.object({ id: z.number().transform((val) => String(val)) }),
91+
);
92+
93+
expectTypeOf(resolver).toEqualTypeOf<
94+
Resolver<{ id: number }, unknown, { id: string }>
95+
>();
96+
});
97+
98+
it('should correctly infer the output type from a standardSchema schema when a different input type is specified', () => {
99+
const schema = z.object({ id: z.number() }).transform(({ id }) => {
100+
return { id: String(id) };
101+
});
102+
103+
const resolver = standardSchemaResolver<
104+
{ id: number },
105+
any,
106+
z.output<typeof schema>
107+
>(schema);
108+
109+
expectTypeOf(resolver).toEqualTypeOf<
110+
Resolver<{ id: number }, any, { id: string }>
111+
>();
112+
});
113+
114+
it('should correctly infer the output type from a standardSchema schema for the handleSubmit function in useForm', () => {
115+
const schema = z.object({ id: z.number() });
116+
117+
const form = useForm({
118+
resolver: standardSchemaResolver(schema),
119+
});
120+
121+
expectTypeOf(form.watch('id')).toEqualTypeOf<number>();
122+
123+
expectTypeOf(form.handleSubmit).parameter(0).toEqualTypeOf<
124+
SubmitHandler<{
125+
id: number;
126+
}>
127+
>();
128+
});
129+
130+
it('should correctly infer the output type from a standardSchema schema with a transform for the handleSubmit function in useForm', () => {
131+
const schema = z.object({ id: z.number().transform((val) => String(val)) });
132+
133+
const form = useForm({
134+
resolver: standardSchemaResolver(schema),
135+
});
136+
137+
expectTypeOf(form.watch('id')).toEqualTypeOf<number>();
138+
139+
expectTypeOf(form.handleSubmit).parameter(0).toEqualTypeOf<
140+
SubmitHandler<{
141+
id: string;
142+
}>
143+
>();
144+
});
74145
});

standard-schema/src/standard-schema.ts

+29-5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { StandardSchemaV1 } from '@standard-schema/spec';
33
import { getDotPath } from '@standard-schema/utils';
44
import { FieldError, FieldValues, Resolver } from 'react-hook-form';
55

6-
function parseIssues(
6+
function parseErrorSchema(
77
issues: readonly StandardSchemaV1.Issue[],
88
validateAllFieldCriteria: boolean,
99
) {
@@ -32,6 +32,28 @@ function parseIssues(
3232
return errors;
3333
}
3434

35+
export function standardSchemaResolver<
36+
Input extends FieldValues,
37+
Context,
38+
Output,
39+
>(
40+
schema: StandardSchemaV1<Input, Output>,
41+
resolverOptions?: {
42+
raw?: false;
43+
},
44+
): Resolver<Input, Context, Output>;
45+
46+
export function standardSchemaResolver<
47+
Input extends FieldValues,
48+
Context,
49+
Output,
50+
>(
51+
schema: StandardSchemaV1<Input, Output>,
52+
resolverOptions: {
53+
raw: true;
54+
},
55+
): Resolver<Input, Context, Input>;
56+
3557
/**
3658
* Creates a resolver for react-hook-form that validates data using a Standard Schema.
3759
*
@@ -53,21 +75,23 @@ function parseIssues(
5375
* ```
5476
*/
5577
export function standardSchemaResolver<
56-
Schema extends StandardSchemaV1<FieldValues>,
78+
Input extends FieldValues,
79+
Context,
80+
Output,
5781
>(
58-
schema: Schema,
82+
schema: StandardSchemaV1<Input, Output>,
5983
resolverOptions: {
6084
raw?: boolean;
6185
} = {},
62-
): Resolver<StandardSchemaV1.InferOutput<Schema>> {
86+
): Resolver<Input, Context, Output | Input> {
6387
return async (values, _, options) => {
6488
let result = schema['~standard'].validate(values);
6589
if (result instanceof Promise) {
6690
result = await result;
6791
}
6892

6993
if (result.issues) {
70-
const errors = parseIssues(
94+
const errors = parseErrorSchema(
7195
result.issues,
7296
!options.shouldUseNativeValidation && options.criteriaMode === 'all',
7397
);

0 commit comments

Comments
 (0)