Skip to content

Commit 326f110

Browse files
authored
Merge pull request #667 from MH4GF/support-valibot-2
2 parents 6108482 + b9cb35b commit 326f110

File tree

10 files changed

+1503
-2
lines changed

10 files changed

+1503
-2
lines changed

codegen.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,26 @@ generates:
8181
email: email
8282
scalars:
8383
ID: string
84+
example/valibot/schemas.ts:
85+
plugins:
86+
- ./dist/main/index.js:
87+
schema: valibot
88+
importFrom: ../types
89+
withObjectType: true
90+
directives:
91+
# Write directives like
92+
#
93+
# directive:
94+
# arg1: schemaApi
95+
# arg2: ["schemaApi2", "Hello $1"]
96+
#
97+
# See more examples in `./tests/directive.spec.ts`
98+
# https://github.com/Code-Hex/graphql-codegen-typescript-validation-schema/blob/main/tests/directive.spec.ts
99+
constraint:
100+
minLength: minLength
101+
# Replace $1 with specified `startsWith` argument value of the constraint directive
102+
startsWith: [regex, /^$1/, message]
103+
format:
104+
email: email
105+
scalars:
106+
ID: string

example/valibot/schemas.ts

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import * as v from 'valibot'
2+
import { Admin, AttributeInput, ButtonComponentType, ComponentInput, DropDownComponentInput, EventArgumentInput, EventInput, EventOptionType, Guest, HttpInput, HttpMethod, LayoutInput, MyType, MyTypeFooArgs, Namer, PageInput, PageType, User } from '../types'
3+
4+
export const ButtonComponentTypeSchema = v.enum_(ButtonComponentType);
5+
6+
export const EventOptionTypeSchema = v.enum_(EventOptionType);
7+
8+
export const HttpMethodSchema = v.enum_(HttpMethod);
9+
10+
export const PageTypeSchema = v.enum_(PageType);
11+
12+
export function AdminSchema(): v.GenericSchema<Admin> {
13+
return v.object({
14+
__typename: v.optional(v.literal('Admin')),
15+
lastModifiedAt: v.nullish(v.any())
16+
})
17+
}
18+
19+
export function AttributeInputSchema(): v.GenericSchema<AttributeInput> {
20+
return v.object({
21+
key: v.nullish(v.string()),
22+
val: v.nullish(v.string())
23+
})
24+
}
25+
26+
export function ComponentInputSchema(): v.GenericSchema<ComponentInput> {
27+
return v.object({
28+
child: v.lazy(() => v.nullish(ComponentInputSchema())),
29+
childrens: v.nullish(v.array(v.lazy(() => v.nullable(ComponentInputSchema())))),
30+
event: v.lazy(() => v.nullish(EventInputSchema())),
31+
name: v.string(),
32+
type: ButtonComponentTypeSchema
33+
})
34+
}
35+
36+
export function DropDownComponentInputSchema(): v.GenericSchema<DropDownComponentInput> {
37+
return v.object({
38+
dropdownComponent: v.lazy(() => v.nullish(ComponentInputSchema())),
39+
getEvent: v.lazy(() => EventInputSchema())
40+
})
41+
}
42+
43+
export function EventArgumentInputSchema(): v.GenericSchema<EventArgumentInput> {
44+
return v.object({
45+
name: v.pipe(v.string(), v.minLength(5)),
46+
value: v.pipe(v.string(), v.regex(/^foo/, "message"))
47+
})
48+
}
49+
50+
export function EventInputSchema(): v.GenericSchema<EventInput> {
51+
return v.object({
52+
arguments: v.array(v.lazy(() => EventArgumentInputSchema())),
53+
options: v.nullish(v.array(EventOptionTypeSchema))
54+
})
55+
}
56+
57+
export function GuestSchema(): v.GenericSchema<Guest> {
58+
return v.object({
59+
__typename: v.optional(v.literal('Guest')),
60+
lastLoggedIn: v.nullish(v.any())
61+
})
62+
}
63+
64+
export function HttpInputSchema(): v.GenericSchema<HttpInput> {
65+
return v.object({
66+
method: v.nullish(HttpMethodSchema),
67+
url: v.any()
68+
})
69+
}
70+
71+
export function LayoutInputSchema(): v.GenericSchema<LayoutInput> {
72+
return v.object({
73+
dropdown: v.lazy(() => v.nullish(DropDownComponentInputSchema()))
74+
})
75+
}
76+
77+
export function MyTypeSchema(): v.GenericSchema<MyType> {
78+
return v.object({
79+
__typename: v.optional(v.literal('MyType')),
80+
foo: v.nullish(v.string())
81+
})
82+
}
83+
84+
export function MyTypeFooArgsSchema(): v.GenericSchema<MyTypeFooArgs> {
85+
return v.object({
86+
a: v.nullish(v.string()),
87+
b: v.number(),
88+
c: v.nullish(v.boolean()),
89+
d: v.number()
90+
})
91+
}
92+
93+
export function NamerSchema(): v.GenericSchema<Namer> {
94+
return v.object({
95+
name: v.nullish(v.string())
96+
})
97+
}
98+
99+
export function PageInputSchema(): v.GenericSchema<PageInput> {
100+
return v.object({
101+
attributes: v.nullish(v.array(v.lazy(() => AttributeInputSchema()))),
102+
date: v.nullish(v.any()),
103+
height: v.number(),
104+
id: v.string(),
105+
layout: v.lazy(() => LayoutInputSchema()),
106+
pageType: PageTypeSchema,
107+
postIDs: v.nullish(v.array(v.string())),
108+
show: v.boolean(),
109+
tags: v.nullish(v.array(v.nullable(v.string()))),
110+
title: v.string(),
111+
width: v.number()
112+
})
113+
}
114+
115+
export function UserSchema(): v.GenericSchema<User> {
116+
return v.object({
117+
__typename: v.optional(v.literal('User')),
118+
createdAt: v.nullish(v.any()),
119+
email: v.nullish(v.string()),
120+
id: v.nullish(v.string()),
121+
kind: v.nullish(UserKindSchema()),
122+
name: v.nullish(v.string()),
123+
password: v.nullish(v.string()),
124+
updatedAt: v.nullish(v.any())
125+
})
126+
}
127+
128+
export function UserKindSchema() {
129+
return v.union([AdminSchema(), GuestSchema()])
130+
}

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"type-check:yup": "tsc --strict --skipLibCheck --noEmit example/yup/schemas.ts",
4848
"type-check:zod": "tsc --strict --skipLibCheck --noEmit example/zod/schemas.ts",
4949
"type-check:myzod": "tsc --strict --skipLibCheck --noEmit example/myzod/schemas.ts",
50+
"type-check:valibot": "tsc --strict --skipLibCheck --noEmit example/valibot/schemas.ts",
5051
"test": "vitest run",
5152
"build": "run-p build:*",
5253
"build:main": "tsc -p tsconfig.main.json",
@@ -82,6 +83,7 @@
8283
"ts-dedent": "^2.2.0",
8384
"ts-jest": "29.1.4",
8485
"typescript": "5.4.5",
86+
"valibot": "0.31.0-rc.6",
8587
"vitest": "^1.0.0",
8688
"yup": "1.4.0",
8789
"zod": "3.23.8"

pnpm-lock.yaml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { TypeScriptPluginConfig } from '@graphql-codegen/typescript';
22

3-
export type ValidationSchema = 'yup' | 'zod' | 'myzod';
3+
export type ValidationSchema = 'yup' | 'zod' | 'myzod' | 'valibot';
44
export type ValidationSchemaExportType = 'function' | 'const';
55

66
export interface DirectiveConfig {

src/directive.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,39 @@ export function buildApi(config: FormattedDirectiveConfig, directives: ReadonlyA
120120
.join('')
121121
}
122122

123+
// This function generates `[v.minLength(100), v.email()]`
124+
// NOTE: valibot's API is not a method chain, so it is prepared separately from buildApi.
125+
//
126+
// config
127+
// {
128+
// 'constraint': {
129+
// 'minLength': ['minLength', '$1'],
130+
// 'format': {
131+
// 'uri': ['url', '$2'],
132+
// 'email': ['email', '$2'],
133+
// }
134+
// }
135+
// }
136+
//
137+
// GraphQL schema
138+
// ```graphql
139+
// input ExampleInput {
140+
// email: String! @required(msg: "message") @constraint(minLength: 100, format: "email")
141+
// }
142+
// ```
143+
//
144+
// FIXME: v.required() is not supported yet. v.required() is classified as `Methods` and must wrap the schema. ex) `v.required(v.object({...}))`
145+
export function buildApiForValibot(config: FormattedDirectiveConfig, directives: ReadonlyArray<ConstDirectiveNode>): string[] {
146+
return directives
147+
.filter(directive => config[directive.name.value] !== undefined)
148+
.map((directive) => {
149+
const directiveName = directive.name.value;
150+
const argsConfig = config[directiveName];
151+
const apis = _buildApiFromDirectiveArguments(argsConfig, directive.arguments ?? []);
152+
return apis.map(api => `v${api}`);
153+
}).flat()
154+
}
155+
123156
function buildApiSchema(validationSchema: string[] | undefined, argValue: ConstValueNode): string {
124157
if (!validationSchema)
125158
return '';
@@ -133,6 +166,10 @@ function buildApiSchema(validationSchema: string[] | undefined, argValue: ConstV
133166
}
134167

135168
function buildApiFromDirectiveArguments(config: FormattedDirectiveArguments, args: ReadonlyArray<ConstArgumentNode>): string {
169+
return _buildApiFromDirectiveArguments(config, args).join('');
170+
}
171+
172+
function _buildApiFromDirectiveArguments(config: FormattedDirectiveArguments, args: ReadonlyArray<ConstArgumentNode>): string[] {
136173
return args
137174
.map((arg) => {
138175
const argName = arg.name.value;
@@ -142,7 +179,6 @@ function buildApiFromDirectiveArguments(config: FormattedDirectiveArguments, arg
142179

143180
return buildApiSchema(validationSchema, arg.value);
144181
})
145-
.join('');
146182
}
147183

148184
function buildApiFromDirectiveObjectArguments(config: FormattedDirectiveObjectArguments, argValue: ConstValueNode): string {

src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { MyZodSchemaVisitor } from './myzod/index';
99
import type { SchemaVisitor } from './types';
1010
import { YupSchemaVisitor } from './yup/index';
1111
import { ZodSchemaVisitor } from './zod/index';
12+
import { ValibotSchemaVisitor } from './valibot';
1213

1314
export const plugin: PluginFunction<ValidationSchemaPluginConfig, Types.ComplexPluginOutput> = (
1415
schema: GraphQLSchema,
@@ -33,6 +34,8 @@ function schemaVisitor(schema: GraphQLSchema, config: ValidationSchemaPluginConf
3334
return new ZodSchemaVisitor(schema, config);
3435
else if (config?.schema === 'myzod')
3536
return new MyZodSchemaVisitor(schema, config);
37+
else if (config?.schema === 'valibot')
38+
return new ValibotSchemaVisitor(schema, config);
3639

3740
return new YupSchemaVisitor(schema, config);
3841
}

0 commit comments

Comments
 (0)