diff --git a/README.md b/README.md index 26aa6ff6..58ea9d21 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ - [x] support [yup](https://github.com/jquense/yup) - [x] support [zod](https://github.com/colinhacks/zod) +- [x] support [myzod](https://github.com/davidmdm/myzod) ## Quick Start @@ -29,9 +30,9 @@ generates: schema: yup # or zod ``` -You can check [example directory](https://github.com/Code-Hex/graphql-codegen-typescript-validation-schema/tree/main/example) if you want to see more complex config example or how is generated some files. +You can check [example](https://github.com/Code-Hex/graphql-codegen-typescript-validation-schema/tree/main/example) directory if you want to see more complex config example or how is generated some files. -...And I wrote some tips in there. +The Q&A for each schema is written in the README in the respective example directory. ## Config API Reference @@ -41,7 +42,7 @@ type: `ValidationSchema` default: `'yup'` Specify generete validation schema you want. -You can specify `yup` or `zod`. +You can specify `yup` or `zod` or `myzod`. ```yml generates: @@ -207,3 +208,7 @@ export function ExampleInputSchema(): z.ZodSchema { }) } ``` + +#### other schema + +Please see [example](https://github.com/Code-Hex/graphql-codegen-typescript-validation-schema/tree/main/example) directory. \ No newline at end of file diff --git a/codegen.yml b/codegen.yml index 6ccd8a11..f56baf9b 100644 --- a/codegen.yml +++ b/codegen.yml @@ -57,3 +57,15 @@ generates: startsWith: ['regex', '/^$1/', 'message'] format: email: email + example/myzod/schemas.ts: + plugins: + - ./dist/main/index.js: + schema: myzod + importFrom: ../types + directives: + constraint: + minLength: min + # Replace $1 with specified `startsWith` argument value of the constraint directive + startsWith: ['pattern', '/^$1/'] + format: + email: email diff --git a/example/myzod/README.md b/example/myzod/README.md new file mode 100644 index 00000000..b055c323 --- /dev/null +++ b/example/myzod/README.md @@ -0,0 +1,7 @@ +# Tips for myzod schema + +## How to overwrite generated schema? + +Basically, I think [it does not support overwrite schema](https://github.com/davidmdm/myzod/issues/51) in myzod. However, [`and`](https://github.com/davidmdm/myzod#typeand) and [`or`](https://github.com/davidmdm/myzod#typeor) may helps you. + +See also: https://github.com/Code-Hex/graphql-codegen-typescript-validation-schema/issues/25#issuecomment-1086532098 \ No newline at end of file diff --git a/example/myzod/schemas.ts b/example/myzod/schemas.ts new file mode 100644 index 00000000..b953fa98 --- /dev/null +++ b/example/myzod/schemas.ts @@ -0,0 +1,79 @@ +import * as myzod from 'myzod' +import { AttributeInput, ButtonComponentType, ComponentInput, DropDownComponentInput, EventArgumentInput, EventInput, EventOptionType, HttpInput, HttpMethod, LayoutInput, PageInput, PageType } from '../types' + +export const definedNonNullAnySchema = myzod.object({}); + +export function AttributeInputSchema(): myzod.Type { + return myzod.object({ + key: myzod.string().optional().nullable(), + val: myzod.string().optional().nullable() + }) +} + +export const ButtonComponentTypeSchema = myzod.enum(ButtonComponentType); + +export function ComponentInputSchema(): myzod.Type { + return myzod.object({ + child: myzod.lazy(() => ComponentInputSchema().optional().nullable()), + childrens: myzod.array(myzod.lazy(() => ComponentInputSchema().nullable())).optional().nullable(), + event: myzod.lazy(() => EventInputSchema().optional().nullable()), + name: myzod.string(), + type: ButtonComponentTypeSchema + }) +} + +export function DropDownComponentInputSchema(): myzod.Type { + return myzod.object({ + dropdownComponent: myzod.lazy(() => ComponentInputSchema().optional().nullable()), + getEvent: myzod.lazy(() => EventInputSchema()) + }) +} + +export function EventArgumentInputSchema(): myzod.Type { + return myzod.object({ + name: myzod.string().min(5), + value: myzod.string().pattern(/^foo/) + }) +} + +export function EventInputSchema(): myzod.Type { + return myzod.object({ + arguments: myzod.array(myzod.lazy(() => EventArgumentInputSchema())), + options: myzod.array(EventOptionTypeSchema).optional().nullable() + }) +} + +export const EventOptionTypeSchema = myzod.enum(EventOptionType); + +export function HttpInputSchema(): myzod.Type { + return myzod.object({ + method: HttpMethodSchema.optional().nullable(), + url: definedNonNullAnySchema + }) +} + +export const HttpMethodSchema = myzod.enum(HttpMethod); + +export function LayoutInputSchema(): myzod.Type { + return myzod.object({ + dropdown: myzod.lazy(() => DropDownComponentInputSchema().optional().nullable()) + }) +} + +export function PageInputSchema(): myzod.Type { + return myzod.object({ + attributes: myzod.array(myzod.lazy(() => AttributeInputSchema())).optional().nullable(), + date: definedNonNullAnySchema.optional().nullable(), + height: myzod.number(), + id: myzod.string(), + layout: myzod.lazy(() => LayoutInputSchema()), + pageType: PageTypeSchema, + postIDs: myzod.array(myzod.string()).optional().nullable(), + show: myzod.boolean(), + tags: myzod.array(myzod.string().nullable()).optional().nullable(), + title: myzod.string(), + width: myzod.number() + }) +} + +export const PageTypeSchema = myzod.enum(PageType); diff --git a/src/myzod/index.ts b/src/myzod/index.ts index 3612128e..e3bd7f97 100644 --- a/src/myzod/index.ts +++ b/src/myzod/index.ts @@ -12,7 +12,7 @@ import { DeclarationBlock, indent } from '@graphql-codegen/visitor-plugin-common import { TsVisitor } from '@graphql-codegen/typescript'; import { buildApi, formatDirectiveConfig } from '../directive'; -const importZod = `import myzod from 'myzod'`; +const importZod = `import * as myzod from 'myzod'`; const anySchema = `definedNonNullAnySchema`; export const MyZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSchemaPluginConfig) => { @@ -30,32 +30,8 @@ export const MyZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSche initialEmit: (): string => '\n' + [ - /* - * MyZod allows you to create typed objects with `myzod.Type` - * See https://www.npmjs.com/package/myzod#lazy - new DeclarationBlock({}) - .asKind('type') - .withName('Properties') - .withContent(['Required<{', ' [K in keyof T]: z.ZodType;', '}>'].join('\n')).string, - */ - /* - * MyZod allows empty object hence no need for these hacks - * See https://www.npmjs.com/package/myzod#object - // Unfortunately, zod doesn’t provide non-null defined any schema. - // This is a temporary hack until it is fixed. - // see: https://github.com/colinhacks/zod/issues/884 - new DeclarationBlock({}).asKind('type').withName('definedNonNullAny').withContent('{}').string, - new DeclarationBlock({}) - .export() - .asKind('const') - .withName(`isDefinedNonNullAny`) - .withContent(`(v: any): v is definedNonNullAny => v !== undefined && v !== null`).string, - new DeclarationBlock({}) - .export() - .asKind('const') - .withName(`${anySchema}`) - .withContent(`z.any().refine((v) => isDefinedNonNullAny(v))`).string, - */ + new DeclarationBlock({}).export().asKind('const').withName(`${anySchema}`).withContent(`myzod.object({})`) + .string, ].join('\n'), InputObjectTypeDefinition: (node: InputObjectTypeDefinitionNode) => { const name = tsVisitor.convertName(node.name.value); @@ -67,9 +43,9 @@ export const MyZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSche return new DeclarationBlock({}) .export() - .asKind('const') - .withName(`${name}Schema: myzod.Type<${name}>`) //TODO: Test this - .withBlock([indent(`myzod.object({`), shape, indent('})')].join('\n')).string; + .asKind('function') + .withName(`${name}Schema(): myzod.Type<${name}>`) + .withBlock([indent(`return myzod.object({`), shape, indent('})')].join('\n')).string; }, EnumTypeDefinition: (node: EnumTypeDefinitionNode) => { const enumname = tsVisitor.convertName(node.name.value); @@ -177,7 +153,7 @@ const generateNameNodeMyZodSchema = ( return `${enumName}Schema`; } - return zod4Scalar(config, tsVisitor, node.value); + return myzod4Scalar(config, tsVisitor, node.value); }; const maybeLazy = (type: TypeNode, schema: string): string => { @@ -187,7 +163,7 @@ const maybeLazy = (type: TypeNode, schema: string): string => { return schema; }; -const zod4Scalar = (config: ValidationSchemaPluginConfig, tsVisitor: TsVisitor, scalarName: string): string => { +const myzod4Scalar = (config: ValidationSchemaPluginConfig, tsVisitor: TsVisitor, scalarName: string): string => { if (config.scalarSchemas?.[scalarName]) { return config.scalarSchemas[scalarName]; } diff --git a/tests/myzod.spec.ts b/tests/myzod.spec.ts index b9af0d9f..07b57b89 100644 --- a/tests/myzod.spec.ts +++ b/tests/myzod.spec.ts @@ -15,7 +15,7 @@ describe('myzod', () => { } `, [ - 'export const PrimitiveInputSchema: myzod.Type', + 'export function PrimitiveInputSchema(): myzod.Type {', 'a: myzod.string()', 'b: myzod.string()', 'c: myzod.boolean()', @@ -36,7 +36,7 @@ describe('myzod', () => { } `, [ - 'export const PrimitiveInputSchema: myzod.Type', + 'export function PrimitiveInputSchema(): myzod.Type {', // alphabet order 'a: myzod.string().optional().nullable(),', 'b: myzod.string().optional().nullable(),', @@ -58,7 +58,7 @@ describe('myzod', () => { } `, [ - 'export const ArrayInputSchema: myzod.Type', + 'export function ArrayInputSchema(): myzod.Type {', 'a: myzod.array(myzod.string().nullable()).optional().nullable(),', 'b: myzod.array(myzod.string()).optional().nullable(),', 'c: myzod.array(myzod.string()),', @@ -81,11 +81,11 @@ describe('myzod', () => { } `, [ - 'export const AInputSchema: myzod.Type', + 'export function AInputSchema(): myzod.Type {', 'b: myzod.lazy(() => BInputSchema())', - 'export const BInputSchema: myzod.Type', + 'export function BInputSchema(): myzod.Type {', 'c: myzod.lazy(() => CInputSchema())', - 'export const CInputSchema: myzod.Type', + 'export function CInputSchema(): myzod.Type {', 'a: myzod.lazy(() => AInputSchema())', ], ], @@ -98,7 +98,7 @@ describe('myzod', () => { } `, [ - 'export const NestedInputSchema: myzod.Type', + 'export function NestedInputSchema(): myzod.Type {', 'child: myzod.lazy(() => NestedInputSchema().optional().nullable()),', 'childrens: myzod.array(myzod.lazy(() => NestedInputSchema().nullable())).optional().nullable()', ], @@ -116,7 +116,7 @@ describe('myzod', () => { `, [ 'export const PageTypeSchema = myzod.enum(PageType)', - 'export const PageInputSchema: myzod.Type', + 'export function PageInputSchema(): myzod.Type {', 'pageType: PageTypeSchema', ], ], @@ -136,7 +136,7 @@ describe('myzod', () => { scalar URL # unknown scalar, should be any (definedNonNullAnySchema) `, [ - 'export const HttpInputSchema: myzod.Type', + 'export function HttpInputSchema(): myzod.Type {', 'export const HttpMethodSchema = myzod.enum(HttpMethod)', 'method: HttpMethodSchema', 'url: definedNonNullAnySchema', @@ -145,7 +145,7 @@ describe('myzod', () => { ])('%s', async (_, textSchema, wantContains) => { const schema = buildSchema(textSchema); const result = await plugin(schema, [], { schema: 'myzod' }, {}); - expect(result.prepend).toContain("import myzod from 'myzod'"); + expect(result.prepend).toContain("import * as myzod from 'myzod'"); for (const wantContain of wantContains) { expect(result.content).toContain(wantContain); @@ -236,7 +236,7 @@ describe('myzod', () => { {} ); const wantContains = [ - 'export const PrimitiveInputSchema: myzod.Type', + 'export function PrimitiveInputSchema(): myzod.Type {', 'a: myzod.string().min(1),', 'b: myzod.string().min(1),', 'c: myzod.boolean(),', @@ -271,7 +271,7 @@ describe('myzod', () => { {} ); const wantContains = [ - 'export const ScalarsInputSchema: myzod.Type', + 'export function ScalarsInputSchema(): myzod.Type {', 'date: myzod.date(),', 'email: myzod.string()', // TODO: Test implementation 'str: myzod.string()', @@ -304,7 +304,7 @@ describe('myzod', () => { {} ); const wantContains = [ - 'export const UserCreateInputSchema: myzod.Type', + 'export function UserCreateInputSchema(): myzod.Type {', 'profile: myzod.string().min(1, "Please input more than 1").max(5000, "Please input less than 5000").optional().nullable()', ]; for (const wantContain of wantContains) { @@ -334,7 +334,7 @@ describe('myzod', () => { {} ); const wantContains = [ - 'export const UserCreateInputSchema: myzod.Type', + 'export function UserCreateInputSchema(): myzod.Type {', 'profile: myzod.string().min(1, "Please input more than 1").max(5000, "Please input less than 5000")', ]; for (const wantContain of wantContains) { @@ -364,7 +364,7 @@ describe('myzod', () => { {} ); const wantContains = [ - 'export const UserCreateInputSchema: myzod.Type', + 'export function UserCreateInputSchema(): myzod.Type {', 'profile: myzod.array(myzod.string().nullable()).min(1, "Please input more than 1").max(5000, "Please input less than 5000").optional().nullable()', ]; for (const wantContain of wantContains) {