Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit ee78cf3

Browse files
authoredJun 8, 2023
Merge pull request #389 from Code-Hex/add/sort-deps
feat: add `validationSchemaExportType` to config w/ resolve deps
2 parents 7a7e9ed + 9f1e2e0 commit ee78cf3

File tree

16 files changed

+1258
-438
lines changed

16 files changed

+1258
-438
lines changed
 

‎codegen.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ generates:
1313
schema: yup
1414
importFrom: ../types
1515
withObjectType: true
16+
validationSchemaExportType: const
1617
directives:
1718
required:
1819
msg: required
@@ -49,6 +50,7 @@ generates:
4950
schema: zod
5051
importFrom: ../types
5152
withObjectType: true
53+
validationSchemaExportType: const
5254
directives:
5355
# Write directives like
5456
#
@@ -72,6 +74,7 @@ generates:
7274
schema: myzod
7375
importFrom: ../types
7476
withObjectType: true
77+
validationSchemaExportType: const
7578
directives:
7679
constraint:
7780
minLength: min

‎example/myzod/schemas.ts

Lines changed: 51 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,110 +1,86 @@
11
import * as myzod from 'myzod'
2-
import { Admin, AttributeInput, ButtonComponentType, ComponentInput, DropDownComponentInput, EventArgumentInput, EventInput, EventOptionType, Guest, HttpInput, HttpMethod, LayoutInput, PageInput, PageType, User } from '../types'
2+
import { PageType, HttpMethod, HttpInput, EventOptionType, EventArgumentInput, EventInput, ComponentInput, DropDownComponentInput, LayoutInput, ButtonComponentType, AttributeInput, PageInput, Guest, Admin, User } from '../types'
33

44
export const definedNonNullAnySchema = myzod.object({});
55

6-
export function AdminSchema(): myzod.Type<Admin> {
7-
return myzod.object({
8-
__typename: myzod.literal('Admin').optional(),
9-
lastModifiedAt: definedNonNullAnySchema.optional().nullable()
10-
})
11-
}
6+
export const PageTypeSchema = myzod.enum(PageType);
127

13-
export function AttributeInputSchema(): myzod.Type<AttributeInput> {
14-
return myzod.object({
15-
key: myzod.string().optional().nullable(),
16-
val: myzod.string().optional().nullable()
17-
})
18-
}
8+
export const HttpMethodSchema = myzod.enum(HttpMethod);
9+
10+
export const EventOptionTypeSchema = myzod.enum(EventOptionType);
1911

2012
export const ButtonComponentTypeSchema = myzod.enum(ButtonComponentType);
2113

22-
export function ComponentInputSchema(): myzod.Type<ComponentInput> {
23-
return myzod.object({
24-
child: myzod.lazy(() => ComponentInputSchema().optional().nullable()),
25-
childrens: myzod.array(myzod.lazy(() => ComponentInputSchema().nullable())).optional().nullable(),
26-
event: myzod.lazy(() => EventInputSchema().optional().nullable()),
27-
name: myzod.string(),
28-
type: ButtonComponentTypeSchema
29-
})
30-
}
31-
32-
export function DropDownComponentInputSchema(): myzod.Type<DropDownComponentInput> {
33-
return myzod.object({
34-
dropdownComponent: myzod.lazy(() => ComponentInputSchema().optional().nullable()),
35-
getEvent: myzod.lazy(() => EventInputSchema())
36-
})
37-
}
38-
39-
export function EventArgumentInputSchema(): myzod.Type<EventArgumentInput> {
40-
return myzod.object({
14+
export const HttpInputSchema: myzod.Type<HttpInput> = myzod.object({
15+
method: HttpMethodSchema.optional().nullable(),
16+
url: definedNonNullAnySchema
17+
});
18+
19+
export const EventArgumentInputSchema: myzod.Type<EventArgumentInput> = myzod.object({
4120
name: myzod.string().min(5),
4221
value: myzod.string().pattern(/^foo/)
43-
})
44-
}
22+
});
4523

46-
export function EventInputSchema(): myzod.Type<EventInput> {
47-
return myzod.object({
48-
arguments: myzod.array(myzod.lazy(() => EventArgumentInputSchema())),
24+
export const EventInputSchema: myzod.Type<EventInput> = myzod.object({
25+
arguments: myzod.array(myzod.lazy(() => EventArgumentInputSchema)),
4926
options: myzod.array(EventOptionTypeSchema).optional().nullable()
50-
})
51-
}
52-
53-
export const EventOptionTypeSchema = myzod.enum(EventOptionType);
27+
});
5428

55-
export function GuestSchema(): myzod.Type<Guest> {
56-
return myzod.object({
57-
__typename: myzod.literal('Guest').optional(),
58-
lastLoggedIn: definedNonNullAnySchema.optional().nullable()
59-
})
60-
}
29+
export const ComponentInputSchema: myzod.Type<ComponentInput> = myzod.object({
30+
child: myzod.lazy(() => ComponentInputSchema.optional().nullable()),
31+
childrens: myzod.array(myzod.lazy(() => ComponentInputSchema.nullable())).optional().nullable(),
32+
event: myzod.lazy(() => EventInputSchema.optional().nullable()),
33+
name: myzod.string(),
34+
type: ButtonComponentTypeSchema
35+
});
6136

62-
export function HttpInputSchema(): myzod.Type<HttpInput> {
63-
return myzod.object({
64-
method: HttpMethodSchema.optional().nullable(),
65-
url: definedNonNullAnySchema
66-
})
67-
}
37+
export const DropDownComponentInputSchema: myzod.Type<DropDownComponentInput> = myzod.object({
38+
dropdownComponent: myzod.lazy(() => ComponentInputSchema.optional().nullable()),
39+
getEvent: myzod.lazy(() => EventInputSchema)
40+
});
6841

69-
export const HttpMethodSchema = myzod.enum(HttpMethod);
42+
export const LayoutInputSchema: myzod.Type<LayoutInput> = myzod.object({
43+
dropdown: myzod.lazy(() => DropDownComponentInputSchema.optional().nullable())
44+
});
7045

71-
export function LayoutInputSchema(): myzod.Type<LayoutInput> {
72-
return myzod.object({
73-
dropdown: myzod.lazy(() => DropDownComponentInputSchema().optional().nullable())
74-
})
75-
}
46+
export const AttributeInputSchema: myzod.Type<AttributeInput> = myzod.object({
47+
key: myzod.string().optional().nullable(),
48+
val: myzod.string().optional().nullable()
49+
});
7650

77-
export function PageInputSchema(): myzod.Type<PageInput> {
78-
return myzod.object({
79-
attributes: myzod.array(myzod.lazy(() => AttributeInputSchema())).optional().nullable(),
51+
export const PageInputSchema: myzod.Type<PageInput> = myzod.object({
52+
attributes: myzod.array(myzod.lazy(() => AttributeInputSchema)).optional().nullable(),
8053
date: definedNonNullAnySchema.optional().nullable(),
8154
height: myzod.number(),
8255
id: myzod.string(),
83-
layout: myzod.lazy(() => LayoutInputSchema()),
56+
layout: myzod.lazy(() => LayoutInputSchema),
8457
pageType: PageTypeSchema,
8558
postIDs: myzod.array(myzod.string()).optional().nullable(),
8659
show: myzod.boolean(),
8760
tags: myzod.array(myzod.string().nullable()).optional().nullable(),
8861
title: myzod.string(),
8962
width: myzod.number()
90-
})
91-
}
63+
});
9264

93-
export const PageTypeSchema = myzod.enum(PageType);
65+
export const GuestSchema: myzod.Type<Guest> = myzod.object({
66+
__typename: myzod.literal('Guest').optional(),
67+
lastLoggedIn: definedNonNullAnySchema.optional().nullable()
68+
});
69+
70+
export const AdminSchema: myzod.Type<Admin> = myzod.object({
71+
__typename: myzod.literal('Admin').optional(),
72+
lastModifiedAt: definedNonNullAnySchema.optional().nullable()
73+
});
9474

95-
export function UserSchema(): myzod.Type<User> {
96-
return myzod.object({
75+
export const UserKindSchema = myzod.union([AdminSchema, GuestSchema]);
76+
77+
export const UserSchema: myzod.Type<User> = myzod.object({
9778
__typename: myzod.literal('User').optional(),
9879
createdAt: definedNonNullAnySchema.optional().nullable(),
9980
email: myzod.string().optional().nullable(),
10081
id: myzod.string().optional().nullable(),
101-
kind: UserKindSchema().optional().nullable(),
82+
kind: UserKindSchema.optional().nullable(),
10283
name: myzod.string().optional().nullable(),
10384
password: myzod.string().optional().nullable(),
10485
updatedAt: definedNonNullAnySchema.optional().nullable()
105-
})
106-
}
107-
108-
export function UserKindSchema() {
109-
return myzod.union([AdminSchema(), GuestSchema()])
110-
}
86+
});

‎example/yup/schemas.ts

Lines changed: 53 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,114 +1,90 @@
11
import * as yup from 'yup'
2-
import { Admin, AttributeInput, ButtonComponentType, ComponentInput, DropDownComponentInput, EventArgumentInput, EventInput, EventOptionType, Guest, HttpInput, HttpMethod, LayoutInput, PageInput, PageType, User, UserKind } from '../types'
2+
import { PageType, HttpMethod, HttpInput, EventOptionType, EventArgumentInput, EventInput, ComponentInput, DropDownComponentInput, LayoutInput, ButtonComponentType, AttributeInput, PageInput, Guest, Admin, UserKind, User } from '../types'
33

4-
function union<T extends {}>(...schemas: ReadonlyArray<yup.Schema<T>>): yup.MixedSchema<T> {
5-
return yup.mixed<T>().test({
6-
test: (value) => schemas.some((schema) => schema.isValidSync(value))
7-
}).defined()
8-
}
4+
export const PageTypeSchema = yup.string<PageType>().oneOf([PageType.BasicAuth, PageType.Lp, PageType.Restricted, PageType.Service]).defined();
95

10-
export function AdminSchema(): yup.ObjectSchema<Admin> {
11-
return yup.object({
12-
__typename: yup.string<'Admin'>().optional(),
13-
lastModifiedAt: yup.mixed().nullable().optional()
14-
})
15-
}
6+
export const HttpMethodSchema = yup.string<HttpMethod>().oneOf([HttpMethod.Get, HttpMethod.Post]).defined();
167

17-
export function AttributeInputSchema(): yup.ObjectSchema<AttributeInput> {
18-
return yup.object({
19-
key: yup.string().defined().nullable().optional(),
20-
val: yup.string().defined().nullable().optional()
21-
})
22-
}
8+
export const EventOptionTypeSchema = yup.string<EventOptionType>().oneOf([EventOptionType.Reload, EventOptionType.Retry]).defined();
239

2410
export const ButtonComponentTypeSchema = yup.string<ButtonComponentType>().oneOf([ButtonComponentType.Button, ButtonComponentType.Submit]).defined();
2511

26-
export function ComponentInputSchema(): yup.ObjectSchema<ComponentInput> {
27-
return yup.object({
28-
child: yup.lazy(() => ComponentInputSchema()).optional(),
29-
childrens: yup.array(yup.lazy(() => ComponentInputSchema())).defined().nullable().optional(),
30-
event: yup.lazy(() => EventInputSchema()).optional(),
31-
name: yup.string().defined().nonNullable(),
32-
type: ButtonComponentTypeSchema.nonNullable()
33-
})
12+
function union<T extends {}>(...schemas: ReadonlyArray<yup.Schema<T>>): yup.MixedSchema<T> {
13+
return yup.mixed<T>().test({
14+
test: (value) => schemas.some((schema) => schema.isValidSync(value))
15+
}).defined()
3416
}
3517

36-
export function DropDownComponentInputSchema(): yup.ObjectSchema<DropDownComponentInput> {
37-
return yup.object({
38-
dropdownComponent: yup.lazy(() => ComponentInputSchema()).optional(),
39-
getEvent: yup.lazy(() => EventInputSchema().nonNullable())
40-
})
41-
}
18+
export const HttpInputSchema: yup.ObjectSchema<HttpInput> = yup.object({
19+
method: HttpMethodSchema.nullable().optional(),
20+
url: yup.mixed().nonNullable()
21+
});
4222

43-
export function EventArgumentInputSchema(): yup.ObjectSchema<EventArgumentInput> {
44-
return yup.object({
23+
export const EventArgumentInputSchema: yup.ObjectSchema<EventArgumentInput> = yup.object({
4524
name: yup.string().defined().nonNullable().min(5),
4625
value: yup.string().defined().nonNullable().matches(/^foo/)
47-
})
48-
}
26+
});
4927

50-
export function EventInputSchema(): yup.ObjectSchema<EventInput> {
51-
return yup.object({
52-
arguments: yup.array(yup.lazy(() => EventArgumentInputSchema().nonNullable())).defined(),
28+
export const EventInputSchema: yup.ObjectSchema<EventInput> = yup.object({
29+
arguments: yup.array(yup.lazy(() => EventArgumentInputSchema.nonNullable())).defined(),
5330
options: yup.array(EventOptionTypeSchema.nonNullable()).defined().nullable().optional()
54-
})
55-
}
31+
});
5632

57-
export const EventOptionTypeSchema = yup.string<EventOptionType>().oneOf([EventOptionType.Reload, EventOptionType.Retry]).defined();
33+
export const ComponentInputSchema: yup.ObjectSchema<ComponentInput> = yup.object({
34+
child: yup.lazy(() => ComponentInputSchema).optional(),
35+
childrens: yup.array(yup.lazy(() => ComponentInputSchema)).defined().nullable().optional(),
36+
event: yup.lazy(() => EventInputSchema).optional(),
37+
name: yup.string().defined().nonNullable(),
38+
type: ButtonComponentTypeSchema.nonNullable()
39+
});
5840

59-
export function GuestSchema(): yup.ObjectSchema<Guest> {
60-
return yup.object({
61-
__typename: yup.string<'Guest'>().optional(),
62-
lastLoggedIn: yup.mixed().nullable().optional()
63-
})
64-
}
41+
export const DropDownComponentInputSchema: yup.ObjectSchema<DropDownComponentInput> = yup.object({
42+
dropdownComponent: yup.lazy(() => ComponentInputSchema).optional(),
43+
getEvent: yup.lazy(() => EventInputSchema.nonNullable())
44+
});
6545

66-
export function HttpInputSchema(): yup.ObjectSchema<HttpInput> {
67-
return yup.object({
68-
method: HttpMethodSchema.nullable().optional(),
69-
url: yup.mixed().nonNullable()
70-
})
71-
}
72-
73-
export const HttpMethodSchema = yup.string<HttpMethod>().oneOf([HttpMethod.Get, HttpMethod.Post]).defined();
46+
export const LayoutInputSchema: yup.ObjectSchema<LayoutInput> = yup.object({
47+
dropdown: yup.lazy(() => DropDownComponentInputSchema).optional()
48+
});
7449

75-
export function LayoutInputSchema(): yup.ObjectSchema<LayoutInput> {
76-
return yup.object({
77-
dropdown: yup.lazy(() => DropDownComponentInputSchema()).optional()
78-
})
79-
}
50+
export const AttributeInputSchema: yup.ObjectSchema<AttributeInput> = yup.object({
51+
key: yup.string().defined().nullable().optional(),
52+
val: yup.string().defined().nullable().optional()
53+
});
8054

81-
export function PageInputSchema(): yup.ObjectSchema<PageInput> {
82-
return yup.object({
83-
attributes: yup.array(yup.lazy(() => AttributeInputSchema().nonNullable())).defined().nullable().optional(),
55+
export const PageInputSchema: yup.ObjectSchema<PageInput> = yup.object({
56+
attributes: yup.array(yup.lazy(() => AttributeInputSchema.nonNullable())).defined().nullable().optional(),
8457
date: yup.mixed().nullable().optional(),
8558
height: yup.number().defined().nonNullable(),
8659
id: yup.string().defined().nonNullable(),
87-
layout: yup.lazy(() => LayoutInputSchema().nonNullable()),
60+
layout: yup.lazy(() => LayoutInputSchema.nonNullable()),
8861
pageType: PageTypeSchema.nonNullable(),
8962
postIDs: yup.array(yup.string().defined().nonNullable()).defined().nullable().optional(),
9063
show: yup.boolean().defined().nonNullable(),
9164
tags: yup.array(yup.string().defined().nullable()).defined().nullable().optional(),
9265
title: yup.string().defined().nonNullable(),
9366
width: yup.number().defined().nonNullable()
94-
})
95-
}
67+
});
9668

97-
export const PageTypeSchema = yup.string<PageType>().oneOf([PageType.BasicAuth, PageType.Lp, PageType.Restricted, PageType.Service]).defined();
69+
export const GuestSchema: yup.ObjectSchema<Guest> = yup.object({
70+
__typename: yup.string<'Guest'>().optional(),
71+
lastLoggedIn: yup.mixed().nullable().optional()
72+
});
73+
74+
export const AdminSchema: yup.ObjectSchema<Admin> = yup.object({
75+
__typename: yup.string<'Admin'>().optional(),
76+
lastModifiedAt: yup.mixed().nullable().optional()
77+
});
78+
79+
export const UserKindSchema: yup.MixedSchema<UserKind> = union<UserKind>(AdminSchema, GuestSchema);
9880

99-
export function UserSchema(): yup.ObjectSchema<User> {
100-
return yup.object({
81+
export const UserSchema: yup.ObjectSchema<User> = yup.object({
10182
__typename: yup.string<'User'>().optional(),
10283
createdAt: yup.mixed().nullable().optional(),
10384
email: yup.string().defined().nullable().optional(),
10485
id: yup.string().defined().nullable().optional(),
105-
kind: UserKindSchema().nullable().optional(),
86+
kind: UserKindSchema.nullable().optional(),
10687
name: yup.string().defined().nullable().optional(),
10788
password: yup.string().defined().nullable().optional(),
10889
updatedAt: yup.mixed().nullable().optional()
109-
})
110-
}
111-
112-
export function UserKindSchema(): yup.MixedSchema<UserKind> {
113-
return union<UserKind>(AdminSchema(), GuestSchema())
114-
}
90+
});

‎example/zod/schemas.ts

Lines changed: 51 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { z } from 'zod'
2-
import { Admin, AttributeInput, ButtonComponentType, ComponentInput, DropDownComponentInput, EventArgumentInput, EventInput, EventOptionType, Guest, HttpInput, HttpMethod, LayoutInput, PageInput, PageType, User } from '../types'
2+
import { PageType, HttpMethod, HttpInput, EventOptionType, EventArgumentInput, EventInput, ComponentInput, DropDownComponentInput, LayoutInput, ButtonComponentType, AttributeInput, PageInput, Guest, Admin, User } from '../types'
33

44
type Properties<T> = Required<{
55
[K in keyof T]: z.ZodType<T[K], any, T[K]>;
@@ -11,108 +11,84 @@ export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== und
1111

1212
export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v));
1313

14-
export function AdminSchema(): z.ZodObject<Properties<Admin>> {
15-
return z.object<Properties<Admin>>({
16-
__typename: z.literal('Admin').optional(),
17-
lastModifiedAt: definedNonNullAnySchema.nullish()
18-
})
19-
}
14+
export const PageTypeSchema = z.nativeEnum(PageType);
2015

21-
export function AttributeInputSchema(): z.ZodObject<Properties<AttributeInput>> {
22-
return z.object<Properties<AttributeInput>>({
23-
key: z.string().nullish(),
24-
val: z.string().nullish()
25-
})
26-
}
16+
export const HttpMethodSchema = z.nativeEnum(HttpMethod);
17+
18+
export const EventOptionTypeSchema = z.nativeEnum(EventOptionType);
2719

2820
export const ButtonComponentTypeSchema = z.nativeEnum(ButtonComponentType);
2921

30-
export function ComponentInputSchema(): z.ZodObject<Properties<ComponentInput>> {
31-
return z.object<Properties<ComponentInput>>({
32-
child: z.lazy(() => ComponentInputSchema().nullish()),
33-
childrens: z.array(z.lazy(() => ComponentInputSchema().nullable())).nullish(),
34-
event: z.lazy(() => EventInputSchema().nullish()),
35-
name: z.string(),
36-
type: ButtonComponentTypeSchema
37-
})
38-
}
39-
40-
export function DropDownComponentInputSchema(): z.ZodObject<Properties<DropDownComponentInput>> {
41-
return z.object<Properties<DropDownComponentInput>>({
42-
dropdownComponent: z.lazy(() => ComponentInputSchema().nullish()),
43-
getEvent: z.lazy(() => EventInputSchema())
44-
})
45-
}
46-
47-
export function EventArgumentInputSchema(): z.ZodObject<Properties<EventArgumentInput>> {
48-
return z.object<Properties<EventArgumentInput>>({
22+
export const HttpInputSchema: z.ZodObject<Properties<HttpInput>> = z.object({
23+
method: HttpMethodSchema.nullish(),
24+
url: definedNonNullAnySchema
25+
});
26+
27+
export const EventArgumentInputSchema: z.ZodObject<Properties<EventArgumentInput>> = z.object({
4928
name: z.string().min(5),
5029
value: z.string().regex(/^foo/, "message")
51-
})
52-
}
30+
});
5331

54-
export function EventInputSchema(): z.ZodObject<Properties<EventInput>> {
55-
return z.object<Properties<EventInput>>({
56-
arguments: z.array(z.lazy(() => EventArgumentInputSchema())),
32+
export const EventInputSchema: z.ZodObject<Properties<EventInput>> = z.object({
33+
arguments: z.array(z.lazy(() => EventArgumentInputSchema)),
5734
options: z.array(EventOptionTypeSchema).nullish()
58-
})
59-
}
60-
61-
export const EventOptionTypeSchema = z.nativeEnum(EventOptionType);
35+
});
6236

63-
export function GuestSchema(): z.ZodObject<Properties<Guest>> {
64-
return z.object<Properties<Guest>>({
65-
__typename: z.literal('Guest').optional(),
66-
lastLoggedIn: definedNonNullAnySchema.nullish()
67-
})
68-
}
37+
export const ComponentInputSchema: z.ZodObject<Properties<ComponentInput>> = z.object({
38+
child: z.lazy(() => ComponentInputSchema.nullish()),
39+
childrens: z.array(z.lazy(() => ComponentInputSchema.nullable())).nullish(),
40+
event: z.lazy(() => EventInputSchema.nullish()),
41+
name: z.string(),
42+
type: ButtonComponentTypeSchema
43+
});
6944

70-
export function HttpInputSchema(): z.ZodObject<Properties<HttpInput>> {
71-
return z.object<Properties<HttpInput>>({
72-
method: HttpMethodSchema.nullish(),
73-
url: definedNonNullAnySchema
74-
})
75-
}
45+
export const DropDownComponentInputSchema: z.ZodObject<Properties<DropDownComponentInput>> = z.object({
46+
dropdownComponent: z.lazy(() => ComponentInputSchema.nullish()),
47+
getEvent: z.lazy(() => EventInputSchema)
48+
});
7649

77-
export const HttpMethodSchema = z.nativeEnum(HttpMethod);
50+
export const LayoutInputSchema: z.ZodObject<Properties<LayoutInput>> = z.object({
51+
dropdown: z.lazy(() => DropDownComponentInputSchema.nullish())
52+
});
7853

79-
export function LayoutInputSchema(): z.ZodObject<Properties<LayoutInput>> {
80-
return z.object<Properties<LayoutInput>>({
81-
dropdown: z.lazy(() => DropDownComponentInputSchema().nullish())
82-
})
83-
}
54+
export const AttributeInputSchema: z.ZodObject<Properties<AttributeInput>> = z.object({
55+
key: z.string().nullish(),
56+
val: z.string().nullish()
57+
});
8458

85-
export function PageInputSchema(): z.ZodObject<Properties<PageInput>> {
86-
return z.object<Properties<PageInput>>({
87-
attributes: z.array(z.lazy(() => AttributeInputSchema())).nullish(),
59+
export const PageInputSchema: z.ZodObject<Properties<PageInput>> = z.object({
60+
attributes: z.array(z.lazy(() => AttributeInputSchema)).nullish(),
8861
date: definedNonNullAnySchema.nullish(),
8962
height: z.number(),
9063
id: z.string(),
91-
layout: z.lazy(() => LayoutInputSchema()),
64+
layout: z.lazy(() => LayoutInputSchema),
9265
pageType: PageTypeSchema,
9366
postIDs: z.array(z.string()).nullish(),
9467
show: z.boolean(),
9568
tags: z.array(z.string().nullable()).nullish(),
9669
title: z.string(),
9770
width: z.number()
98-
})
99-
}
71+
});
10072

101-
export const PageTypeSchema = z.nativeEnum(PageType);
73+
export const GuestSchema: z.ZodObject<Properties<Guest>> = z.object({
74+
__typename: z.literal('Guest').optional(),
75+
lastLoggedIn: definedNonNullAnySchema.nullish()
76+
});
77+
78+
export const AdminSchema: z.ZodObject<Properties<Admin>> = z.object({
79+
__typename: z.literal('Admin').optional(),
80+
lastModifiedAt: definedNonNullAnySchema.nullish()
81+
});
10282

103-
export function UserSchema(): z.ZodObject<Properties<User>> {
104-
return z.object<Properties<User>>({
83+
export const UserKindSchema = z.union([AdminSchema, GuestSchema]);
84+
85+
export const UserSchema: z.ZodObject<Properties<User>> = z.object({
10586
__typename: z.literal('User').optional(),
10687
createdAt: definedNonNullAnySchema.nullish(),
10788
email: z.string().nullish(),
10889
id: z.string().nullish(),
109-
kind: UserKindSchema().nullish(),
90+
kind: UserKindSchema.nullish(),
11091
name: z.string().nullish(),
11192
password: z.string().nullish(),
11293
updatedAt: definedNonNullAnySchema.nullish()
113-
})
114-
}
115-
116-
export function UserKindSchema() {
117-
return z.union([AdminSchema(), GuestSchema()])
118-
}
94+
});

‎package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"@graphql-codegen/cli": "4.0.1",
5151
"@graphql-codegen/typescript": "^4.0.0",
5252
"@tsconfig/recommended": "1.0.2",
53+
"@types/graphlib": "^2.1.8",
5354
"@types/jest": "29.5.2",
5455
"@types/node": "^20.1.3",
5556
"@typescript-eslint/eslint-plugin": "5.59.9",
@@ -59,6 +60,7 @@
5960
"myzod": "1.10.0",
6061
"npm-run-all": "4.1.5",
6162
"prettier": "2.8.8",
63+
"ts-dedent": "^2.2.0",
6264
"ts-jest": "29.1.0",
6365
"typescript": "5.1.3",
6466
"yup": "1.2.0",
@@ -69,6 +71,7 @@
6971
"@graphql-codegen/schema-ast": "4.0.0",
7072
"@graphql-codegen/visitor-plugin-common": "^4.0.0",
7173
"@graphql-tools/utils": "^10.0.0",
74+
"graphlib": "^2.1.8",
7275
"graphql": "^16.6.0"
7376
},
7477
"peerDependencies": {

‎src/config.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { TypeScriptPluginConfig } from '@graphql-codegen/typescript';
22

33
export type ValidationSchema = 'yup' | 'zod' | 'myzod';
4+
export type ValidationSchemaExportType = 'function' | 'const';
45

56
export interface DirectiveConfig {
67
[directive: string]: {
@@ -234,4 +235,20 @@ export interface ValidationSchemaPluginConfig extends TypeScriptPluginConfig {
234235
* ```
235236
*/
236237
directives?: DirectiveConfig;
238+
/**
239+
* @description Specify validation schema export type
240+
* @default function
241+
*
242+
* @exampleMarkdown
243+
* ```yml
244+
* generates:
245+
* path/to/file.ts:
246+
* plugins:
247+
* - typescript
248+
* - graphql-codegen-validation-schema
249+
* config:
250+
* validationSchemaExportType: const
251+
* ```
252+
*/
253+
validationSchemaExportType?: ValidationSchemaExportType;
237254
}

‎src/graphql.ts

Lines changed: 150 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,17 @@
1-
import { ListTypeNode, NonNullTypeNode, NamedTypeNode, TypeNode, ObjectTypeDefinitionNode } from 'graphql';
1+
import {
2+
ListTypeNode,
3+
NonNullTypeNode,
4+
NamedTypeNode,
5+
TypeNode,
6+
ObjectTypeDefinitionNode,
7+
visit,
8+
DocumentNode,
9+
DefinitionNode,
10+
NameNode,
11+
ASTNode,
12+
GraphQLSchema,
13+
} from 'graphql';
14+
import { Graph } from 'graphlib';
215

316
export const isListType = (typ?: TypeNode): typ is ListTypeNode => typ?.kind === 'ListType';
417
export const isNonNullType = (typ?: TypeNode): typ is NonNullTypeNode => typ?.kind === 'NonNullType';
@@ -20,3 +33,139 @@ export const ObjectTypeDefinitionBuilder = (
2033
return callback(node);
2134
};
2235
};
36+
37+
export const topologicalSortAST = (schema: GraphQLSchema, ast: DocumentNode): DocumentNode => {
38+
const dependencyGraph = new Graph();
39+
const targetKinds = [
40+
'ObjectTypeDefinition',
41+
'InputObjectTypeDefinition',
42+
'EnumTypeDefinition',
43+
'UnionTypeDefinition',
44+
'ScalarTypeDefinition',
45+
];
46+
47+
visit<DocumentNode>(ast, {
48+
enter: node => {
49+
switch (node.kind) {
50+
case 'ObjectTypeDefinition':
51+
case 'InputObjectTypeDefinition': {
52+
const typeName = node.name.value;
53+
dependencyGraph.setNode(typeName);
54+
55+
if (node.fields) {
56+
node.fields.forEach(field => {
57+
if (field.type.kind === 'NamedType') {
58+
const dependency = field.type.name.value;
59+
const typ = schema.getType(dependency);
60+
if (typ?.astNode?.kind === undefined || !targetKinds.includes(typ.astNode.kind)) {
61+
return;
62+
}
63+
if (!dependencyGraph.hasNode(dependency)) {
64+
dependencyGraph.setNode(dependency);
65+
}
66+
dependencyGraph.setEdge(typeName, dependency);
67+
}
68+
});
69+
}
70+
break;
71+
}
72+
case 'ScalarTypeDefinition':
73+
case 'EnumTypeDefinition': {
74+
dependencyGraph.setNode(node.name.value);
75+
break;
76+
}
77+
case 'UnionTypeDefinition': {
78+
const dependency = node.name.value;
79+
if (!dependencyGraph.hasNode(dependency)) {
80+
dependencyGraph.setNode(dependency);
81+
}
82+
node.types?.forEach(type => {
83+
const dependency = type.name.value;
84+
const typ = schema.getType(dependency);
85+
if (typ?.astNode?.kind === undefined || !targetKinds.includes(typ.astNode.kind)) {
86+
return;
87+
}
88+
dependencyGraph.setEdge(node.name.value, dependency);
89+
});
90+
break;
91+
}
92+
default:
93+
break;
94+
}
95+
},
96+
});
97+
98+
const sorted = topsort(dependencyGraph);
99+
100+
// Create a map of definitions for quick access, using the definition's name as the key.
101+
const definitionsMap: Map<string, DefinitionNode> = new Map();
102+
103+
// SCHEMA_DEFINITION does not have name.
104+
// https://spec.graphql.org/October2021/#sec-Schema
105+
const astDefinitions = ast.definitions.filter(def => def.kind !== 'SchemaDefinition');
106+
107+
astDefinitions.forEach(definition => {
108+
if (hasNameField(definition) && definition.name) {
109+
definitionsMap.set(definition.name.value, definition);
110+
}
111+
});
112+
113+
// Two arrays to store sorted and not sorted definitions.
114+
const sortedDefinitions: DefinitionNode[] = [];
115+
const notSortedDefinitions: DefinitionNode[] = [];
116+
117+
// Iterate over sorted type names and retrieve their corresponding definitions.
118+
sorted.forEach(sortedType => {
119+
const definition = definitionsMap.get(sortedType);
120+
if (definition) {
121+
sortedDefinitions.push(definition);
122+
definitionsMap.delete(sortedType);
123+
}
124+
});
125+
126+
// Definitions that are left in the map were not included in sorted list
127+
// Add them to notSortedDefinitions.
128+
definitionsMap.forEach(definition => notSortedDefinitions.push(definition));
129+
130+
const newDefinitions = [...sortedDefinitions, ...notSortedDefinitions];
131+
132+
if (newDefinitions.length !== astDefinitions.length) {
133+
throw new Error(
134+
`unexpected definition length after sorted: want ${astDefinitions.length} but got ${newDefinitions.length}`
135+
);
136+
}
137+
138+
return {
139+
...ast,
140+
definitions: newDefinitions as ReadonlyArray<DefinitionNode>,
141+
};
142+
};
143+
144+
const hasNameField = (node: ASTNode): node is DefinitionNode & { name?: NameNode } => {
145+
return 'name' in node;
146+
};
147+
148+
// Re-implemented w/o CycleException version
149+
// https://github.com/dagrejs/graphlib/blob/8d27cb89029081c72eb89dde652602805bdd0a34/lib/alg/topsort.js
150+
export const topsort = (g: Graph): string[] => {
151+
const visited: Record<string, boolean> = {};
152+
const stack: Record<string, boolean> = {};
153+
const results: any[] = [];
154+
155+
function visit(node: string) {
156+
if (!(node in visited)) {
157+
stack[node] = true;
158+
visited[node] = true;
159+
const predecessors = g.predecessors(node);
160+
if (Array.isArray(predecessors)) {
161+
predecessors.forEach(node => visit(node));
162+
}
163+
delete stack[node];
164+
results.push(node);
165+
}
166+
}
167+
168+
g.sinks().forEach(node => visit(node));
169+
170+
return results.reverse();
171+
};

‎src/index.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ import { ValidationSchemaPluginConfig } from './config';
66
import { PluginFunction, Types } from '@graphql-codegen/plugin-helpers';
77
import { GraphQLSchema, visit } from 'graphql';
88
import { SchemaVisitor } from './types';
9+
import { topologicalSortAST } from './graphql';
910

1011
export const plugin: PluginFunction<ValidationSchemaPluginConfig, Types.ComplexPluginOutput> = (
1112
schema: GraphQLSchema,
1213
_documents: Types.DocumentFile[],
1314
config: ValidationSchemaPluginConfig
1415
): Types.ComplexPluginOutput => {
15-
const { schema: _schema, ast } = transformSchemaAST(schema, config);
16+
const { schema: _schema, ast } = _transformSchemaAST(schema, config);
1617
const { buildImports, initialEmit, ...visitor } = schemaVisitor(_schema, config);
1718

1819
const result = visit(ast, visitor);
@@ -35,3 +36,19 @@ const schemaVisitor = (schema: GraphQLSchema, config: ValidationSchemaPluginConf
3536
}
3637
return YupSchemaVisitor(schema, config);
3738
};
39+
40+
const _transformSchemaAST = (schema: GraphQLSchema, config: ValidationSchemaPluginConfig) => {
41+
const { schema: _schema, ast } = transformSchemaAST(schema, config);
42+
// This affects the performance of code generation, so it is
43+
// enabled only when this option is selected.
44+
if (config.validationSchemaExportType === 'const') {
45+
return {
46+
schema: _schema,
47+
ast: topologicalSortAST(_schema, ast),
48+
};
49+
}
50+
return {
51+
schema: _schema,
52+
ast,
53+
};
54+
};

‎src/myzod/index.ts

Lines changed: 109 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
1-
import { isInput, isNonNullType, isListType, isNamedType, ObjectTypeDefinitionBuilder } from './../graphql';
2-
import { ValidationSchemaPluginConfig } from '../config';
1+
import { DeclarationBlock, indent } from '@graphql-codegen/visitor-plugin-common';
32
import {
4-
InputValueDefinitionNode,
5-
NameNode,
6-
TypeNode,
3+
EnumTypeDefinitionNode,
4+
FieldDefinitionNode,
75
GraphQLSchema,
86
InputObjectTypeDefinitionNode,
7+
InputValueDefinitionNode,
8+
NameNode,
99
ObjectTypeDefinitionNode,
10-
EnumTypeDefinitionNode,
11-
FieldDefinitionNode,
10+
TypeNode,
1211
UnionTypeDefinitionNode,
1312
} from 'graphql';
14-
import { DeclarationBlock, indent } from '@graphql-codegen/visitor-plugin-common';
15-
import { Visitor } from '../visitor';
13+
import { ValidationSchemaPluginConfig } from '../config';
1614
import { buildApi, formatDirectiveConfig } from '../directive';
1715
import { SchemaVisitor } from '../types';
16+
import { Visitor } from '../visitor';
17+
import { ObjectTypeDefinitionBuilder, isInput, isListType, isNamedType, isNonNullType } from './../graphql';
1818

1919
const importZod = `import * as myzod from 'myzod'`;
2020
const anySchema = `definedNonNullAnySchema`;
2121

2222
export const MyZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSchemaPluginConfig): SchemaVisitor => {
2323
const importTypes: string[] = [];
24+
const enumDeclarations: string[] = [];
2425

2526
return {
2627
buildImports: (): string[] => {
@@ -37,6 +38,7 @@ export const MyZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSche
3738
[
3839
new DeclarationBlock({}).export().asKind('const').withName(`${anySchema}`).withContent(`myzod.object({})`)
3940
.string,
41+
...enumDeclarations,
4042
].join('\n'),
4143
InputObjectTypeDefinition: {
4244
leave: (node: InputObjectTypeDefinitionNode) => {
@@ -46,11 +48,22 @@ export const MyZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSche
4648

4749
const shape = node.fields?.map(field => generateFieldMyZodSchema(config, visitor, field, 2)).join(',\n');
4850

49-
return new DeclarationBlock({})
50-
.export()
51-
.asKind('function')
52-
.withName(`${name}Schema(): myzod.Type<${name}>`)
53-
.withBlock([indent(`return myzod.object({`), shape, indent('})')].join('\n')).string;
51+
switch (config.validationSchemaExportType) {
52+
case 'const':
53+
return new DeclarationBlock({})
54+
.export()
55+
.asKind('const')
56+
.withName(`${name}Schema: myzod.Type<${name}>`)
57+
.withContent(['myzod.object({', shape, '})'].join('\n')).string;
58+
59+
case 'function':
60+
default:
61+
return new DeclarationBlock({})
62+
.export()
63+
.asKind('function')
64+
.withName(`${name}Schema(): myzod.Type<${name}>`)
65+
.withBlock([indent(`return myzod.object({`), shape, indent('})')].join('\n')).string;
66+
}
5467
},
5568
},
5669
ObjectTypeDefinition: {
@@ -61,18 +74,36 @@ export const MyZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSche
6174

6275
const shape = node.fields?.map(field => generateFieldMyZodSchema(config, visitor, field, 2)).join(',\n');
6376

64-
return new DeclarationBlock({})
65-
.export()
66-
.asKind('function')
67-
.withName(`${name}Schema(): myzod.Type<${name}>`)
68-
.withBlock(
69-
[
70-
indent(`return myzod.object({`),
71-
indent(`__typename: myzod.literal('${node.name.value}').optional(),`, 2),
72-
shape,
73-
indent('})'),
74-
].join('\n')
75-
).string;
77+
switch (config.validationSchemaExportType) {
78+
case 'const':
79+
return new DeclarationBlock({})
80+
.export()
81+
.asKind('const')
82+
.withName(`${name}Schema: myzod.Type<${name}>`)
83+
.withContent(
84+
[
85+
`myzod.object({`,
86+
indent(`__typename: myzod.literal('${node.name.value}').optional(),`, 2),
87+
shape,
88+
'})',
89+
].join('\n')
90+
).string;
91+
92+
case 'function':
93+
default:
94+
return new DeclarationBlock({})
95+
.export()
96+
.asKind('function')
97+
.withName(`${name}Schema(): myzod.Type<${name}>`)
98+
.withBlock(
99+
[
100+
indent(`return myzod.object({`),
101+
indent(`__typename: myzod.literal('${node.name.value}').optional(),`, 2),
102+
shape,
103+
indent('})'),
104+
].join('\n')
105+
).string;
106+
}
76107
}),
77108
},
78109
EnumTypeDefinition: {
@@ -81,20 +112,22 @@ export const MyZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSche
81112
const enumname = visitor.convertName(node.name.value);
82113
importTypes.push(enumname);
83114
// z.enum are basically myzod.literals
84-
if (config.enumsAsTypes) {
85-
return new DeclarationBlock({})
86-
.export()
87-
.asKind('type')
88-
.withName(`${enumname}Schema`)
89-
.withContent(`myzod.literals(${node.values?.map(enumOption => `'${enumOption.name.value}'`).join(', ')})`)
90-
.string;
91-
}
92-
93-
return new DeclarationBlock({})
94-
.export()
95-
.asKind('const')
96-
.withName(`${enumname}Schema`)
97-
.withContent(`myzod.enum(${enumname})`).string;
115+
// hoist enum declarations
116+
enumDeclarations.push(
117+
config.enumsAsTypes
118+
? new DeclarationBlock({})
119+
.export()
120+
.asKind('type')
121+
.withName(`${enumname}Schema`)
122+
.withContent(
123+
`myzod.literals(${node.values?.map(enumOption => `'${enumOption.name.value}'`).join(', ')})`
124+
).string
125+
: new DeclarationBlock({})
126+
.export()
127+
.asKind('const')
128+
.withName(`${enumname}Schema`)
129+
.withContent(`myzod.enum(${enumname})`).string
130+
);
98131
},
99132
},
100133
UnionTypeDefinition: {
@@ -111,16 +144,31 @@ export const MyZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSche
111144
if (typ?.astNode?.kind === 'EnumTypeDefinition') {
112145
return `${element}Schema`;
113146
}
114-
return `${element}Schema()`;
147+
switch (config.validationSchemaExportType) {
148+
case 'const':
149+
return `${element}Schema`;
150+
case 'function':
151+
default:
152+
return `${element}Schema()`;
153+
}
115154
})
116155
.join(', ');
117156
const unionElementsCount = node.types?.length ?? 0;
118157

119-
const union =
120-
unionElementsCount > 1 ? indent(`return myzod.union([${unionElements}])`) : indent(`return ${unionElements}`);
158+
const union = unionElementsCount > 1 ? `myzod.union([${unionElements}])` : unionElements;
121159

122-
return new DeclarationBlock({}).export().asKind('function').withName(`${unionName}Schema()`).withBlock(union)
123-
.string;
160+
switch (config.validationSchemaExportType) {
161+
case 'const':
162+
return new DeclarationBlock({}).export().asKind('const').withName(`${unionName}Schema`).withContent(union)
163+
.string;
164+
case 'function':
165+
default:
166+
return new DeclarationBlock({})
167+
.export()
168+
.asKind('function')
169+
.withName(`${unionName}Schema()`)
170+
.withBlock(indent(`return ${union}`)).string;
171+
}
124172
},
125173
},
126174
};
@@ -197,27 +245,23 @@ const generateNameNodeMyZodSchema = (
197245
): string => {
198246
const converter = visitor.getNameNodeConverter(node);
199247

200-
if (converter?.targetKind === 'InputObjectTypeDefinition') {
201-
const name = converter.convertName();
202-
return `${name}Schema()`;
203-
}
204-
205-
if (converter?.targetKind === 'ObjectTypeDefinition') {
206-
const name = converter.convertName();
207-
return `${name}Schema()`;
208-
}
209-
210-
if (converter?.targetKind === 'EnumTypeDefinition') {
211-
const name = converter.convertName();
212-
return `${name}Schema`;
213-
}
214-
215-
if (converter?.targetKind === 'UnionTypeDefinition') {
216-
const name = converter.convertName();
217-
return `${name}Schema()`;
248+
switch (converter?.targetKind) {
249+
case 'InputObjectTypeDefinition':
250+
case 'ObjectTypeDefinition':
251+
case 'UnionTypeDefinition':
252+
// using switch-case rather than if-else to allow for future expansion
253+
switch (config.validationSchemaExportType) {
254+
case 'const':
255+
return `${converter.convertName()}Schema`;
256+
case 'function':
257+
default:
258+
return `${converter.convertName()}Schema()`;
259+
}
260+
case 'EnumTypeDefinition':
261+
return `${converter.convertName()}Schema`;
262+
default:
263+
return myzod4Scalar(config, visitor, node.value);
218264
}
219-
220-
return myzod4Scalar(config, visitor, node.value);
221265
};
222266

223267
const maybeLazy = (type: TypeNode, schema: string): string => {

‎src/yup/index.ts

Lines changed: 122 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
1-
import { isInput, isNonNullType, isListType, isNamedType, ObjectTypeDefinitionBuilder } from './../graphql';
2-
import { ValidationSchemaPluginConfig } from '../config';
1+
import { DeclarationBlock, indent } from '@graphql-codegen/visitor-plugin-common';
32
import {
4-
InputValueDefinitionNode,
5-
NameNode,
6-
TypeNode,
3+
EnumTypeDefinitionNode,
4+
FieldDefinitionNode,
75
GraphQLSchema,
86
InputObjectTypeDefinitionNode,
9-
EnumTypeDefinitionNode,
7+
InputValueDefinitionNode,
8+
NameNode,
109
ObjectTypeDefinitionNode,
11-
FieldDefinitionNode,
10+
TypeNode,
1211
UnionTypeDefinitionNode,
1312
} from 'graphql';
14-
import { DeclarationBlock, indent } from '@graphql-codegen/visitor-plugin-common';
15-
import { Visitor } from '../visitor';
13+
import { ValidationSchemaPluginConfig } from '../config';
1614
import { buildApi, formatDirectiveConfig } from '../directive';
1715
import { SchemaVisitor } from '../types';
16+
import { Visitor } from '../visitor';
17+
import { ObjectTypeDefinitionBuilder, isInput, isListType, isNamedType, isNonNullType } from './../graphql';
1818

1919
const importYup = `import * as yup from 'yup'`;
2020

2121
export const YupSchemaVisitor = (schema: GraphQLSchema, config: ValidationSchemaPluginConfig): SchemaVisitor => {
2222
const importTypes: string[] = [];
23+
const enumDeclarations: string[] = [];
2324

2425
return {
2526
buildImports: (): string[] => {
@@ -32,8 +33,10 @@ export const YupSchemaVisitor = (schema: GraphQLSchema, config: ValidationSchema
3233
return [importYup];
3334
},
3435
initialEmit: (): string => {
35-
if (!config.withObjectType) return '';
36+
if (!config.withObjectType) return '\n' + enumDeclarations.join('\n');
3637
return (
38+
'\n' +
39+
enumDeclarations.join('\n') +
3740
'\n' +
3841
new DeclarationBlock({})
3942
.asKind('function')
@@ -60,11 +63,22 @@ export const YupSchemaVisitor = (schema: GraphQLSchema, config: ValidationSchema
6063
})
6164
.join(',\n');
6265

63-
return new DeclarationBlock({})
64-
.export()
65-
.asKind('function')
66-
.withName(`${name}Schema(): yup.ObjectSchema<${name}>`)
67-
.withBlock([indent(`return yup.object({`), shape, indent('})')].join('\n')).string;
66+
switch (config.validationSchemaExportType) {
67+
case 'const':
68+
return new DeclarationBlock({})
69+
.export()
70+
.asKind('const')
71+
.withName(`${name}Schema: yup.ObjectSchema<${name}>`)
72+
.withContent(['yup.object({', shape, '})'].join('\n')).string;
73+
74+
case 'function':
75+
default:
76+
return new DeclarationBlock({})
77+
.export()
78+
.asKind('function')
79+
.withName(`${name}Schema(): yup.ObjectSchema<${name}>`)
80+
.withBlock([indent(`return yup.object({`), shape, indent('})')].join('\n')).string;
81+
}
6882
},
6983
},
7084
ObjectTypeDefinition: {
@@ -80,18 +94,36 @@ export const YupSchemaVisitor = (schema: GraphQLSchema, config: ValidationSchema
8094
})
8195
.join(',\n');
8296

83-
return new DeclarationBlock({})
84-
.export()
85-
.asKind('function')
86-
.withName(`${name}Schema(): yup.ObjectSchema<${name}>`)
87-
.withBlock(
88-
[
89-
indent(`return yup.object({`),
90-
indent(`__typename: yup.string<'${node.name.value}'>().optional(),`, 2),
91-
shape,
92-
indent('})'),
93-
].join('\n')
94-
).string;
97+
switch (config.validationSchemaExportType) {
98+
case 'const':
99+
return new DeclarationBlock({})
100+
.export()
101+
.asKind('const')
102+
.withName(`${name}Schema: yup.ObjectSchema<${name}>`)
103+
.withContent(
104+
[
105+
`yup.object({`,
106+
indent(`__typename: yup.string<'${node.name.value}'>().optional(),`, 2),
107+
shape,
108+
'})',
109+
].join('\n')
110+
).string;
111+
112+
case 'function':
113+
default:
114+
return new DeclarationBlock({})
115+
.export()
116+
.asKind('function')
117+
.withName(`${name}Schema(): yup.ObjectSchema<${name}>`)
118+
.withBlock(
119+
[
120+
indent(`return yup.object({`),
121+
indent(`__typename: yup.string<'${node.name.value}'>().optional(),`, 2),
122+
shape,
123+
indent('})'),
124+
].join('\n')
125+
).string;
126+
}
95127
}),
96128
},
97129
EnumTypeDefinition: {
@@ -100,30 +132,35 @@ export const YupSchemaVisitor = (schema: GraphQLSchema, config: ValidationSchema
100132
const enumname = visitor.convertName(node.name.value);
101133
importTypes.push(enumname);
102134

135+
// hoise enum declarations
103136
if (config.enumsAsTypes) {
104137
const enums = node.values?.map(enumOption => `'${enumOption.name.value}'`);
105138

106-
return new DeclarationBlock({})
107-
.export()
108-
.asKind('const')
109-
.withName(`${enumname}Schema`)
110-
.withContent(`yup.string().oneOf([${enums?.join(', ')}]).defined()`).string;
139+
enumDeclarations.push(
140+
new DeclarationBlock({})
141+
.export()
142+
.asKind('const')
143+
.withName(`${enumname}Schema`)
144+
.withContent(`yup.string().oneOf([${enums?.join(', ')}]).defined()`).string
145+
);
146+
} else {
147+
const values = node.values
148+
?.map(
149+
enumOption =>
150+
`${enumname}.${visitor.convertName(enumOption.name, {
151+
useTypesPrefix: false,
152+
transformUnderscore: true,
153+
})}`
154+
)
155+
.join(', ');
156+
enumDeclarations.push(
157+
new DeclarationBlock({})
158+
.export()
159+
.asKind('const')
160+
.withName(`${enumname}Schema`)
161+
.withContent(`yup.string<${enumname}>().oneOf([${values}]).defined()`).string
162+
);
111163
}
112-
113-
const values = node.values
114-
?.map(
115-
enumOption =>
116-
`${enumname}.${visitor.convertName(enumOption.name, {
117-
useTypesPrefix: false,
118-
transformUnderscore: true,
119-
})}`
120-
)
121-
.join(', ');
122-
return new DeclarationBlock({})
123-
.export()
124-
.asKind('const')
125-
.withName(`${enumname}Schema`)
126-
.withContent(`yup.string<${enumname}>().oneOf([${values}]).defined()`).string;
127164
},
128165
},
129166
UnionTypeDefinition: {
@@ -141,16 +178,31 @@ export const YupSchemaVisitor = (schema: GraphQLSchema, config: ValidationSchema
141178
if (typ?.astNode?.kind === 'EnumTypeDefinition') {
142179
return `${element}Schema`;
143180
}
144-
return `${element}Schema()`;
181+
switch (config.validationSchemaExportType) {
182+
case 'const':
183+
return `${element}Schema`;
184+
case 'function':
185+
default:
186+
return `${element}Schema()`;
187+
}
145188
})
146189
.join(', ');
147-
const union = indent(`return union<${unionName}>(${unionElements})`);
148190

149-
return new DeclarationBlock({})
150-
.export()
151-
.asKind('function')
152-
.withName(`${unionName}Schema(): yup.MixedSchema<${unionName}>`)
153-
.withBlock(union).string;
191+
switch (config.validationSchemaExportType) {
192+
case 'const':
193+
return new DeclarationBlock({})
194+
.export()
195+
.asKind('const')
196+
.withName(`${unionName}Schema: yup.MixedSchema<${unionName}>`)
197+
.withContent(`union<${unionName}>(${unionElements})`).string;
198+
case 'function':
199+
default:
200+
return new DeclarationBlock({})
201+
.export()
202+
.asKind('function')
203+
.withName(`${unionName}Schema(): yup.MixedSchema<${unionName}>`)
204+
.withBlock(indent(`return union<${unionName}>(${unionElements})`)).string;
205+
}
154206
},
155207
},
156208
// ScalarTypeDefinition: (node) => {
@@ -228,28 +280,23 @@ const generateFieldTypeYupSchema = (
228280
const generateNameNodeYupSchema = (config: ValidationSchemaPluginConfig, visitor: Visitor, node: NameNode): string => {
229281
const converter = visitor.getNameNodeConverter(node);
230282

231-
if (converter?.targetKind === 'InputObjectTypeDefinition') {
232-
const name = converter.convertName();
233-
return `${name}Schema()`;
234-
}
235-
236-
if (converter?.targetKind === 'ObjectTypeDefinition') {
237-
const name = converter.convertName();
238-
return `${name}Schema()`;
239-
}
240-
241-
if (converter?.targetKind === 'EnumTypeDefinition') {
242-
const name = converter.convertName();
243-
return `${name}Schema`;
244-
}
245-
246-
if (converter?.targetKind === 'UnionTypeDefinition') {
247-
const name = converter.convertName();
248-
return `${name}Schema()`;
283+
switch (converter?.targetKind) {
284+
case 'InputObjectTypeDefinition':
285+
case 'ObjectTypeDefinition':
286+
case 'UnionTypeDefinition':
287+
// using switch-case rather than if-else to allow for future expansion
288+
switch (config.validationSchemaExportType) {
289+
case 'const':
290+
return `${converter.convertName()}Schema`;
291+
case 'function':
292+
default:
293+
return `${converter.convertName()}Schema()`;
294+
}
295+
case 'EnumTypeDefinition':
296+
return `${converter.convertName()}Schema`;
297+
default:
298+
return yup4Scalar(config, visitor, node.value);
249299
}
250-
251-
const primitive = yup4Scalar(config, visitor, node.value);
252-
return primitive;
253300
};
254301

255302
const maybeLazy = (type: TypeNode, schema: string): string => {

‎src/zod/index.ts

Lines changed: 105 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
1-
import { isInput, isNonNullType, isListType, isNamedType, ObjectTypeDefinitionBuilder } from './../graphql';
2-
import { ValidationSchemaPluginConfig } from '../config';
1+
import { DeclarationBlock, indent } from '@graphql-codegen/visitor-plugin-common';
32
import {
4-
InputValueDefinitionNode,
5-
NameNode,
6-
TypeNode,
3+
EnumTypeDefinitionNode,
4+
FieldDefinitionNode,
75
GraphQLSchema,
86
InputObjectTypeDefinitionNode,
7+
InputValueDefinitionNode,
8+
NameNode,
99
ObjectTypeDefinitionNode,
10-
EnumTypeDefinitionNode,
10+
TypeNode,
1111
UnionTypeDefinitionNode,
12-
FieldDefinitionNode,
1312
} from 'graphql';
14-
import { DeclarationBlock, indent } from '@graphql-codegen/visitor-plugin-common';
15-
import { Visitor } from '../visitor';
13+
import { ValidationSchemaPluginConfig } from '../config';
1614
import { buildApi, formatDirectiveConfig } from '../directive';
1715
import { SchemaVisitor } from '../types';
16+
import { Visitor } from '../visitor';
17+
import { ObjectTypeDefinitionBuilder, isInput, isListType, isNamedType, isNonNullType } from './../graphql';
1818

1919
const importZod = `import { z } from 'zod'`;
2020
const anySchema = `definedNonNullAnySchema`;
2121

2222
export const ZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSchemaPluginConfig): SchemaVisitor => {
2323
const importTypes: string[] = [];
24+
const enumDeclarations: string[] = [];
2425

2526
return {
2627
buildImports: (): string[] => {
@@ -53,6 +54,7 @@ export const ZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSchema
5354
.asKind('const')
5455
.withName(`${anySchema}`)
5556
.withContent(`z.any().refine((v) => isDefinedNonNullAny(v))`).string,
57+
...enumDeclarations,
5658
].join('\n'),
5759
InputObjectTypeDefinition: {
5860
leave: (node: InputObjectTypeDefinitionNode) => {
@@ -62,11 +64,22 @@ export const ZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSchema
6264

6365
const shape = node.fields?.map(field => generateFieldZodSchema(config, visitor, field, 2)).join(',\n');
6466

65-
return new DeclarationBlock({})
66-
.export()
67-
.asKind('function')
68-
.withName(`${name}Schema(): z.ZodObject<Properties<${name}>>`)
69-
.withBlock([indent(`return z.object<Properties<${name}>>({`), shape, indent('})')].join('\n')).string;
67+
switch (config.validationSchemaExportType) {
68+
case 'const':
69+
return new DeclarationBlock({})
70+
.export()
71+
.asKind('const')
72+
.withName(`${name}Schema: z.ZodObject<Properties<${name}>>`)
73+
.withContent(['z.object({', shape, '})'].join('\n')).string;
74+
75+
case 'function':
76+
default:
77+
return new DeclarationBlock({})
78+
.export()
79+
.asKind('function')
80+
.withName(`${name}Schema(): z.ZodObject<Properties<${name}>>`)
81+
.withBlock([indent(`return z.object({`), shape, indent('})')].join('\n')).string;
82+
}
7083
},
7184
},
7285
ObjectTypeDefinition: {
@@ -77,18 +90,33 @@ export const ZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSchema
7790

7891
const shape = node.fields?.map(field => generateFieldZodSchema(config, visitor, field, 2)).join(',\n');
7992

80-
return new DeclarationBlock({})
81-
.export()
82-
.asKind('function')
83-
.withName(`${name}Schema(): z.ZodObject<Properties<${name}>>`)
84-
.withBlock(
85-
[
86-
indent(`return z.object<Properties<${name}>>({`),
87-
indent(`__typename: z.literal('${node.name.value}').optional(),`, 2),
88-
shape,
89-
indent('})'),
90-
].join('\n')
91-
).string;
93+
switch (config.validationSchemaExportType) {
94+
case 'const':
95+
return new DeclarationBlock({})
96+
.export()
97+
.asKind('const')
98+
.withName(`${name}Schema: z.ZodObject<Properties<${name}>>`)
99+
.withContent(
100+
[`z.object({`, indent(`__typename: z.literal('${node.name.value}').optional(),`, 2), shape, '})'].join(
101+
'\n'
102+
)
103+
).string;
104+
105+
case 'function':
106+
default:
107+
return new DeclarationBlock({})
108+
.export()
109+
.asKind('function')
110+
.withName(`${name}Schema(): z.ZodObject<Properties<${name}>>`)
111+
.withBlock(
112+
[
113+
indent(`return z.object({`),
114+
indent(`__typename: z.literal('${node.name.value}').optional(),`, 2),
115+
shape,
116+
indent('})'),
117+
].join('\n')
118+
).string;
119+
}
92120
}),
93121
},
94122
EnumTypeDefinition: {
@@ -97,19 +125,21 @@ export const ZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSchema
97125
const enumname = visitor.convertName(node.name.value);
98126
importTypes.push(enumname);
99127

100-
if (config.enumsAsTypes) {
101-
return new DeclarationBlock({})
102-
.export()
103-
.asKind('const')
104-
.withName(`${enumname}Schema`)
105-
.withContent(`z.enum([${node.values?.map(enumOption => `'${enumOption.name.value}'`).join(', ')}])`).string;
106-
}
107-
108-
return new DeclarationBlock({})
109-
.export()
110-
.asKind('const')
111-
.withName(`${enumname}Schema`)
112-
.withContent(`z.nativeEnum(${enumname})`).string;
128+
// hoist enum declarations
129+
enumDeclarations.push(
130+
config.enumsAsTypes
131+
? new DeclarationBlock({})
132+
.export()
133+
.asKind('const')
134+
.withName(`${enumname}Schema`)
135+
.withContent(`z.enum([${node.values?.map(enumOption => `'${enumOption.name.value}'`).join(', ')}])`)
136+
.string
137+
: new DeclarationBlock({})
138+
.export()
139+
.asKind('const')
140+
.withName(`${enumname}Schema`)
141+
.withContent(`z.nativeEnum(${enumname})`).string
142+
);
113143
},
114144
},
115145
UnionTypeDefinition: {
@@ -124,16 +154,31 @@ export const ZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSchema
124154
if (typ?.astNode?.kind === 'EnumTypeDefinition') {
125155
return `${element}Schema`;
126156
}
127-
return `${element}Schema()`;
157+
switch (config.validationSchemaExportType) {
158+
case 'const':
159+
return `${element}Schema`;
160+
case 'function':
161+
default:
162+
return `${element}Schema()`;
163+
}
128164
})
129165
.join(', ');
130166
const unionElementsCount = node.types.length ?? 0;
131167

132-
const union =
133-
unionElementsCount > 1 ? indent(`return z.union([${unionElements}])`) : indent(`return ${unionElements}`);
168+
const union = unionElementsCount > 1 ? `z.union([${unionElements}])` : unionElements;
134169

135-
return new DeclarationBlock({}).export().asKind('function').withName(`${unionName}Schema()`).withBlock(union)
136-
.string;
170+
switch (config.validationSchemaExportType) {
171+
case 'const':
172+
return new DeclarationBlock({}).export().asKind('const').withName(`${unionName}Schema`).withContent(union)
173+
.string;
174+
case 'function':
175+
default:
176+
return new DeclarationBlock({})
177+
.export()
178+
.asKind('function')
179+
.withName(`${unionName}Schema()`)
180+
.withBlock(indent(`return ${union}`)).string;
181+
}
137182
},
138183
},
139184
};
@@ -206,27 +251,23 @@ const applyDirectives = (
206251
const generateNameNodeZodSchema = (config: ValidationSchemaPluginConfig, visitor: Visitor, node: NameNode): string => {
207252
const converter = visitor.getNameNodeConverter(node);
208253

209-
if (converter?.targetKind === 'InputObjectTypeDefinition') {
210-
const name = converter.convertName();
211-
return `${name}Schema()`;
212-
}
213-
214-
if (converter?.targetKind === 'ObjectTypeDefinition') {
215-
const name = converter.convertName();
216-
return `${name}Schema()`;
217-
}
218-
219-
if (converter?.targetKind === 'EnumTypeDefinition') {
220-
const name = converter.convertName();
221-
return `${name}Schema`;
222-
}
223-
224-
if (converter?.targetKind === 'UnionTypeDefinition') {
225-
const name = converter.convertName();
226-
return `${name}Schema()`;
254+
switch (converter?.targetKind) {
255+
case 'InputObjectTypeDefinition':
256+
case 'ObjectTypeDefinition':
257+
case 'UnionTypeDefinition':
258+
// using switch-case rather than if-else to allow for future expansion
259+
switch (config.validationSchemaExportType) {
260+
case 'const':
261+
return `${converter.convertName()}Schema`;
262+
case 'function':
263+
default:
264+
return `${converter.convertName()}Schema()`;
265+
}
266+
case 'EnumTypeDefinition':
267+
return `${converter.convertName()}Schema`;
268+
default:
269+
return zod4Scalar(config, visitor, node.value);
227270
}
228-
229-
return zod4Scalar(config, visitor, node.value);
230271
};
231272

232273
const maybeLazy = (type: TypeNode, schema: string): string => {

‎tests/graphql.spec.ts

Lines changed: 195 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
import { Kind, ObjectTypeDefinitionNode } from 'graphql';
2-
import { ObjectTypeDefinitionBuilder } from '../src/graphql';
1+
import { Kind, ObjectTypeDefinitionNode, buildSchema, parse, print } from 'graphql';
2+
import { ObjectTypeDefinitionBuilder, topsort, topologicalSortAST } from '../src/graphql';
3+
import { Graph } from 'graphlib';
4+
import dedent from 'ts-dedent';
35

46
describe('graphql', () => {
57
describe('ObjectTypeDefinitionBuilder', () => {
@@ -44,3 +46,194 @@ describe('graphql', () => {
4446
});
4547
});
4648
});
49+
50+
describe('topsort', () => {
51+
it('should correctly sort nodes based on their dependencies', () => {
52+
const g = new Graph();
53+
54+
// Setting up the graph
55+
g.setNode('A');
56+
g.setNode('B');
57+
g.setNode('C');
58+
g.setEdge('A', 'B');
59+
g.setEdge('B', 'C');
60+
61+
const sortedNodes = topsort(g);
62+
expect(sortedNodes).toEqual(['C', 'B', 'A']);
63+
});
64+
65+
it('should correctly handle graph with no edges', () => {
66+
const g = new Graph();
67+
68+
// Setting up the graph
69+
g.setNode('A');
70+
g.setNode('B');
71+
g.setNode('C');
72+
73+
const sortedNodes = topsort(g);
74+
const isCorrectOrder = ['A', 'B', 'C'].every(node => sortedNodes.includes(node));
75+
expect(isCorrectOrder).toBe(true);
76+
});
77+
78+
it('should correctly handle an empty graph', () => {
79+
const g = new Graph();
80+
81+
const sortedNodes = topsort(g);
82+
expect(sortedNodes).toEqual([]);
83+
});
84+
85+
it('should correctly handle graph with multiple dependencies', () => {
86+
const g = new Graph();
87+
88+
// Setting up the graph
89+
g.setNode('A');
90+
g.setNode('B');
91+
g.setNode('C');
92+
g.setNode('D');
93+
g.setEdge('A', 'B');
94+
g.setEdge('A', 'C');
95+
g.setEdge('B', 'D');
96+
g.setEdge('C', 'D');
97+
98+
const sortedNodes = topsort(g);
99+
expect(sortedNodes).toEqual(['D', 'C', 'B', 'A']);
100+
});
101+
});
102+
103+
describe('topologicalSortAST', () => {
104+
const getSortedSchema = (schema: string): string => {
105+
const ast = parse(schema);
106+
const sortedAst = topologicalSortAST(buildSchema(schema), ast);
107+
return print(sortedAst);
108+
};
109+
110+
it('should correctly sort nodes based on their input type dependencies', () => {
111+
const schema = /* GraphQL */ `
112+
input A {
113+
b: B
114+
}
115+
116+
input B {
117+
c: C
118+
}
119+
120+
input C {
121+
d: String
122+
}
123+
`;
124+
125+
const sortedSchema = getSortedSchema(schema);
126+
127+
const expectedSortedSchema = dedent/* GraphQL */ `
128+
input C {
129+
d: String
130+
}
131+
132+
input B {
133+
c: C
134+
}
135+
136+
input A {
137+
b: B
138+
}
139+
`;
140+
141+
expect(sortedSchema).toBe(expectedSortedSchema);
142+
});
143+
144+
it('should correctly sort nodes based on their objet type dependencies', () => {
145+
const schema = /* GraphQL */ `
146+
type D {
147+
e: UserKind
148+
}
149+
150+
union UserKind = A | B
151+
152+
type A {
153+
b: B
154+
}
155+
156+
type B {
157+
c: C
158+
}
159+
160+
type C {
161+
d: String
162+
}
163+
`;
164+
165+
const sortedSchema = getSortedSchema(schema);
166+
167+
const expectedSortedSchema = dedent/* GraphQL */ `
168+
type C {
169+
d: String
170+
}
171+
172+
type B {
173+
c: C
174+
}
175+
176+
type A {
177+
b: B
178+
}
179+
180+
union UserKind = A | B
181+
182+
type D {
183+
e: UserKind
184+
}
185+
`;
186+
187+
expect(sortedSchema).toBe(expectedSortedSchema);
188+
});
189+
190+
it('should correctly handle schema with circular dependencies', () => {
191+
const schema = /* GraphQL */ `
192+
input A {
193+
b: B
194+
}
195+
196+
input B {
197+
a: A
198+
}
199+
`;
200+
const sortedSchema = getSortedSchema(schema);
201+
202+
const expectedSortedSchema = dedent/* GraphQL */ `
203+
input A {
204+
b: B
205+
}
206+
207+
input B {
208+
a: A
209+
}
210+
`;
211+
212+
expect(sortedSchema).toBe(expectedSortedSchema);
213+
});
214+
215+
it('should correctly handle schema with self circular dependencies', () => {
216+
const schema = /* GraphQL */ `
217+
input A {
218+
a: A
219+
}
220+
221+
input B {
222+
b: B
223+
}
224+
`;
225+
const sortedSchema = getSortedSchema(schema);
226+
227+
const expectedSortedSchema = dedent/* GraphQL */ `
228+
input A {
229+
a: A
230+
}
231+
232+
input B {
233+
b: B
234+
}
235+
`;
236+
237+
expect(sortedSchema).toBe(expectedSortedSchema);
238+
});
239+
});

‎tests/myzod.spec.ts

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,43 @@ describe('myzod', () => {
733733
expect(result.content).toContain(wantContain);
734734
}
735735
});
736+
737+
it('generate union types with single element, export as const', async () => {
738+
const schema = buildSchema(/* GraphQL */ `
739+
type Square {
740+
size: Int
741+
}
742+
type Circle {
743+
radius: Int
744+
}
745+
union Shape = Circle | Square
746+
747+
type Geometry {
748+
shape: Shape
749+
}
750+
`);
751+
752+
const result = await plugin(
753+
schema,
754+
[],
755+
{
756+
schema: 'myzod',
757+
withObjectType: true,
758+
validationSchemaExportType: 'const',
759+
},
760+
{}
761+
);
762+
763+
const wantContains = [
764+
'export const GeometrySchema: myzod.Type<Geometry> = myzod.object({',
765+
"__typename: myzod.literal('Geometry').optional(),",
766+
'shape: ShapeSchema.optional().nullable()',
767+
'}',
768+
];
769+
for (const wantContain of wantContains) {
770+
expect(result.content).toContain(wantContain);
771+
}
772+
});
736773
});
737774

738775
it('properly generates custom directive values', async () => {
@@ -768,4 +805,88 @@ describe('myzod', () => {
768805
expect(result.content).toContain(wantContain);
769806
}
770807
});
808+
809+
it('exports as const instead of func', async () => {
810+
const schema = buildSchema(/* GraphQL */ `
811+
input Say {
812+
phrase: String!
813+
}
814+
`);
815+
const result = await plugin(
816+
schema,
817+
[],
818+
{
819+
schema: 'myzod',
820+
validationSchemaExportType: 'const',
821+
},
822+
{}
823+
);
824+
expect(result.content).toContain('export const SaySchema: myzod.Type<Say> = myzod.object({');
825+
});
826+
827+
it('generate both input & type, export as const', async () => {
828+
const schema = buildSchema(/* GraphQL */ `
829+
scalar Date
830+
scalar Email
831+
input UserCreateInput {
832+
name: String!
833+
date: Date!
834+
email: Email!
835+
}
836+
type User {
837+
id: ID!
838+
name: String
839+
age: Int
840+
email: Email
841+
isMember: Boolean
842+
createdAt: Date!
843+
}
844+
type Mutation {
845+
_empty: String
846+
}
847+
type Query {
848+
_empty: String
849+
}
850+
type Subscription {
851+
_empty: String
852+
}
853+
`);
854+
const result = await plugin(
855+
schema,
856+
[],
857+
{
858+
schema: 'myzod',
859+
withObjectType: true,
860+
scalarSchemas: {
861+
Date: 'myzod.date()',
862+
Email: 'myzod.string().email()',
863+
},
864+
validationSchemaExportType: 'const',
865+
},
866+
{}
867+
);
868+
const wantContains = [
869+
// User Create Input
870+
'export const UserCreateInputSchema: myzod.Type<UserCreateInput> = myzod.object({',
871+
'name: myzod.string(),',
872+
'date: myzod.date(),',
873+
'email: myzod.string().email()',
874+
// User
875+
'export const UserSchema: myzod.Type<User> = myzod.object({',
876+
"__typename: myzod.literal('User').optional(),",
877+
'id: myzod.string(),',
878+
'name: myzod.string().optional().nullable(),',
879+
'age: myzod.number().optional().nullable(),',
880+
'email: myzod.string().email().optional().nullable(),',
881+
'isMember: myzod.boolean().optional().nullable(),',
882+
'createdAt: myzod.date()',
883+
];
884+
for (const wantContain of wantContains) {
885+
expect(result.content).toContain(wantContain);
886+
}
887+
888+
for (const wantNotContain of ['Query', 'Mutation', 'Subscription']) {
889+
expect(result.content).not.toContain(wantNotContain);
890+
}
891+
});
771892
});

‎tests/yup.spec.ts

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,43 @@ describe('yup', () => {
647647
expect(result.content).toContain(wantContain);
648648
}
649649
});
650+
651+
it('generate union types with single element, export as const', async () => {
652+
const schema = buildSchema(/* GraphQL */ `
653+
type Square {
654+
size: Int
655+
}
656+
type Circle {
657+
radius: Int
658+
}
659+
union Shape = Circle | Square
660+
661+
type Geometry {
662+
shape: Shape
663+
}
664+
`);
665+
666+
const result = await plugin(
667+
schema,
668+
[],
669+
{
670+
schema: 'yup',
671+
withObjectType: true,
672+
validationSchemaExportType: 'const',
673+
},
674+
{}
675+
);
676+
677+
const wantContains = [
678+
'export const GeometrySchema: yup.ObjectSchema<Geometry> = yup.object({',
679+
"__typename: yup.string<'Geometry'>().optional(),",
680+
'shape: ShapeSchema.nullable().optional()',
681+
'})',
682+
];
683+
for (const wantContain of wantContains) {
684+
expect(result.content).toContain(wantContain);
685+
}
686+
});
650687
});
651688

652689
it('properly generates custom directive values', async () => {
@@ -682,4 +719,88 @@ describe('yup', () => {
682719
expect(result.content).toContain(wantContain);
683720
}
684721
});
722+
723+
it('exports as const instead of func', async () => {
724+
const schema = buildSchema(/* GraphQL */ `
725+
input Say {
726+
phrase: String!
727+
}
728+
`);
729+
const result = await plugin(
730+
schema,
731+
[],
732+
{
733+
schema: 'yup',
734+
validationSchemaExportType: 'const',
735+
},
736+
{}
737+
);
738+
expect(result.content).toContain('export const SaySchema: yup.ObjectSchema<Say> = yup.object({');
739+
});
740+
741+
it('generate both input & type, export as const', async () => {
742+
const schema = buildSchema(/* GraphQL */ `
743+
scalar Date
744+
scalar Email
745+
input UserCreateInput {
746+
name: String!
747+
date: Date!
748+
email: Email!
749+
}
750+
type User {
751+
id: ID!
752+
name: String
753+
age: Int
754+
email: Email
755+
isMember: Boolean
756+
createdAt: Date!
757+
}
758+
type Mutation {
759+
_empty: String
760+
}
761+
type Query {
762+
_empty: String
763+
}
764+
type Subscription {
765+
_empty: String
766+
}
767+
`);
768+
const result = await plugin(
769+
schema,
770+
[],
771+
{
772+
schema: 'yup',
773+
withObjectType: true,
774+
scalarSchemas: {
775+
Date: 'yup.date()',
776+
Email: 'yup.string().email()',
777+
},
778+
validationSchemaExportType: 'const',
779+
},
780+
{}
781+
);
782+
const wantContains = [
783+
// User Create Input
784+
'export const UserCreateInputSchema: yup.ObjectSchema<UserCreateInput> = yup.object({',
785+
'name: yup.string().defined().nonNullable(),',
786+
'date: yup.date().defined().nonNullable(),',
787+
'email: yup.string().email().defined().nonNullable()',
788+
// User
789+
'export const UserSchema: yup.ObjectSchema<User> = yup.object({',
790+
"__typename: yup.string<'User'>().optional(),",
791+
'id: yup.string().defined().nonNullable(),',
792+
'name: yup.string().defined().nullable().optional(),',
793+
'age: yup.number().defined().nullable().optional(),',
794+
'email: yup.string().email().defined().nullable().optional(),',
795+
'isMember: yup.boolean().defined().nullable().optional(),',
796+
'createdAt: yup.date().defined().nonNullable()',
797+
];
798+
for (const wantContain of wantContains) {
799+
expect(result.content).toContain(wantContain);
800+
}
801+
802+
for (const wantNotContain of ['Query', 'Mutation', 'Subscription']) {
803+
expect(result.content).not.toContain(wantNotContain);
804+
}
805+
});
685806
});

‎tests/zod.spec.ts

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { buildSchema } from 'graphql';
22
import { plugin } from '../src/index';
3-
import { ScalarsMap } from '@graphql-codegen/visitor-plugin-common';
43

54
describe('zod', () => {
65
test.each([
@@ -802,6 +801,42 @@ describe('zod', () => {
802801
expect(result.content).toContain(wantContain);
803802
}
804803
});
804+
805+
it('generate union types with single element, export as const', async () => {
806+
const schema = buildSchema(/* GraphQL */ `
807+
type Square {
808+
size: Int
809+
}
810+
type Circle {
811+
radius: Int
812+
}
813+
union Shape = Circle | Square
814+
815+
type Geometry {
816+
shape: Shape
817+
}
818+
`);
819+
820+
const result = await plugin(
821+
schema,
822+
[],
823+
{
824+
schema: 'zod',
825+
withObjectType: true,
826+
validationSchemaExportType: 'const',
827+
},
828+
{}
829+
);
830+
831+
const wantContains = [
832+
'export const GeometrySchema: z.ZodObject<Properties<Geometry>> = z.object({',
833+
"__typename: z.literal('Geometry').optional(),",
834+
'shape: ShapeSchema.nullish()',
835+
];
836+
for (const wantContain of wantContains) {
837+
expect(result.content).toContain(wantContain);
838+
}
839+
});
805840
});
806841

807842
it('properly generates custom directive values', async () => {
@@ -837,4 +872,88 @@ describe('zod', () => {
837872
expect(result.content).toContain(wantContain);
838873
}
839874
});
875+
876+
it('exports as const instead of func', async () => {
877+
const schema = buildSchema(/* GraphQL */ `
878+
input Say {
879+
phrase: String!
880+
}
881+
`);
882+
const result = await plugin(
883+
schema,
884+
[],
885+
{
886+
schema: 'zod',
887+
validationSchemaExportType: 'const',
888+
},
889+
{}
890+
);
891+
expect(result.content).toContain('export const SaySchema: z.ZodObject<Properties<Say>> = z.object({');
892+
});
893+
894+
it('generate both input & type, export as const', async () => {
895+
const schema = buildSchema(/* GraphQL */ `
896+
scalar Date
897+
scalar Email
898+
input UserCreateInput {
899+
name: String!
900+
date: Date!
901+
email: Email!
902+
}
903+
type User {
904+
id: ID!
905+
name: String
906+
age: Int
907+
email: Email
908+
isMember: Boolean
909+
createdAt: Date!
910+
}
911+
type Mutation {
912+
_empty: String
913+
}
914+
type Query {
915+
_empty: String
916+
}
917+
type Subscription {
918+
_empty: String
919+
}
920+
`);
921+
const result = await plugin(
922+
schema,
923+
[],
924+
{
925+
schema: 'zod',
926+
withObjectType: true,
927+
scalarSchemas: {
928+
Date: 'z.date()',
929+
Email: 'z.string().email()',
930+
},
931+
validationSchemaExportType: 'const',
932+
},
933+
{}
934+
);
935+
const wantContains = [
936+
// User Create Input
937+
'export const UserCreateInputSchema: z.ZodObject<Properties<UserCreateInput>> = z.object({',
938+
'name: z.string(),',
939+
'date: z.date(),',
940+
'email: z.string().email()',
941+
// User
942+
'export const UserSchema: z.ZodObject<Properties<User>> = z.object({',
943+
"__typename: z.literal('User').optional()",
944+
'id: z.string(),',
945+
'name: z.string().nullish(),',
946+
'age: z.number().nullish(),',
947+
'isMember: z.boolean().nullish(),',
948+
'email: z.string().email().nullish(),',
949+
'createdAt: z.date()',
950+
];
951+
for (const wantContain of wantContains) {
952+
expect(result.content).toContain(wantContain);
953+
}
954+
955+
for (const wantNotContain of ['Query', 'Mutation', 'Subscription']) {
956+
expect(result.content).not.toContain(wantNotContain);
957+
}
958+
});
840959
});

‎yarn.lock

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1757,6 +1757,11 @@
17571757
dependencies:
17581758
"@types/node" "*"
17591759

1760+
"@types/graphlib@^2.1.8":
1761+
version "2.1.8"
1762+
resolved "https://registry.yarnpkg.com/@types/graphlib/-/graphlib-2.1.8.tgz#9edd607e4b863a33b8b78cb08385c0be6896008a"
1763+
integrity sha512-8nbbyD3zABRA9ePoBgAl2ym8cIwKQXTfv1gaIRTdY99yEOCaHfmjBeRp+BIemS8NtOqoWK7mfzWxjNrxLK3T5w==
1764+
17601765
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1":
17611766
version "2.0.4"
17621767
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44"
@@ -3199,6 +3204,13 @@ graphemer@^1.4.0:
31993204
resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6"
32003205
integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
32013206

3207+
graphlib@^2.1.8:
3208+
version "2.1.8"
3209+
resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.8.tgz#5761d414737870084c92ec7b5dbcb0592c9d35da"
3210+
integrity sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==
3211+
dependencies:
3212+
lodash "^4.17.15"
3213+
32023214
graphql-config@^5.0.2:
32033215
version "5.0.2"
32043216
resolved "https://registry.yarnpkg.com/graphql-config/-/graphql-config-5.0.2.tgz#7e962f94ccddcc2ee0aa71d75cf4491ec5092bdb"
@@ -4234,7 +4246,7 @@ lodash.merge@^4.6.2:
42344246
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
42354247
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
42364248

4237-
lodash@^4.17.20, lodash@^4.17.21, lodash@~4.17.0:
4249+
lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@~4.17.0:
42384250
version "4.17.21"
42394251
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
42404252
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -5356,13 +5368,18 @@ to-regex-range@^5.0.1:
53565368
toposort@^2.0.2:
53575369
version "2.0.2"
53585370
resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330"
5359-
integrity sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=
5371+
integrity sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==
53605372

53615373
tr46@~0.0.3:
53625374
version "0.0.3"
53635375
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
53645376
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
53655377

5378+
ts-dedent@^2.2.0:
5379+
version "2.2.0"
5380+
resolved "https://registry.yarnpkg.com/ts-dedent/-/ts-dedent-2.2.0.tgz#39e4bd297cd036292ae2394eb3412be63f563bb5"
5381+
integrity sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==
5382+
53665383
ts-jest@29.1.0:
53675384
version "29.1.0"
53685385
resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.0.tgz#4a9db4104a49b76d2b368ea775b6c9535c603891"

0 commit comments

Comments
 (0)
Please sign in to comment.