From 9f22caf44ded04015db898f546d854e0b00e5a38 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Fri, 13 Sep 2024 08:50:10 -0700 Subject: [PATCH 1/2] feat: non-strict mode for zod plugin --- packages/schema/src/plugins/zod/generator.ts | 9 +- .../schema/src/plugins/zod/transformer.ts | 11 +- tests/integration/tests/plugins/zod.test.ts | 102 ++++++++++++++++++ 3 files changed, 116 insertions(+), 6 deletions(-) diff --git a/packages/schema/src/plugins/zod/generator.ts b/packages/schema/src/plugins/zod/generator.ts index fecf69ced..97196b2b6 100644 --- a/packages/schema/src/plugins/zod/generator.ts +++ b/packages/schema/src/plugins/zod/generator.ts @@ -322,7 +322,12 @@ export class ZodSchemaGenerator { writer.writeLine(`${field.name}: ${makeFieldSchema(field)},`); }); }); - writer.writeLine(');'); + + if (this.options.strict !== false) { + writer.writeLine(').strict();'); + } else { + writer.writeLine(');'); + } // relation fields @@ -463,7 +468,7 @@ export const ${upperCaseFirst(model.name)}PrismaCreateSchema = ${prismaCreateSch }) .join(',\n')} })`; - prismaUpdateSchema = this.makePartial(prismaUpdateSchema); + prismaUpdateSchema = this.makePassthrough(this.makePartial(prismaUpdateSchema)); writer.writeLine( ` /** diff --git a/packages/schema/src/plugins/zod/transformer.ts b/packages/schema/src/plugins/zod/transformer.ts index ca714f1ad..5d43bc9c4 100644 --- a/packages/schema/src/plugins/zod/transformer.ts +++ b/packages/schema/src/plugins/zod/transformer.ts @@ -291,7 +291,7 @@ export default class Transformer { prepareObjectSchema(zodObjectSchemaFields: string[], options: PluginOptions) { const objectSchema = `${this.generateExportObjectSchemaStatement( - this.addFinalWrappers({ zodStringFields: zodObjectSchemaFields }) + this.addFinalWrappers({ zodStringFields: zodObjectSchemaFields }, options.strict !== false) )}\n`; const prismaImportStatement = this.generateImportPrismaStatement(options); @@ -314,10 +314,13 @@ export default class Transformer { export const ${this.name}ObjectSchema: SchemaType = ${schema} as SchemaType;`; } - addFinalWrappers({ zodStringFields }: { zodStringFields: string[] }) { + addFinalWrappers({ zodStringFields }: { zodStringFields: string[] }, strict = true) { const fields = [...zodStringFields]; - - return this.wrapWithZodObject(fields) + '.strict()'; + let result = this.wrapWithZodObject(fields); + if (strict) { + result = `${result}.strict()`; + } + return result; } generateImportPrismaStatement(options: PluginOptions) { diff --git a/tests/integration/tests/plugins/zod.test.ts b/tests/integration/tests/plugins/zod.test.ts index 2b9d73c59..e30eaa27a 100644 --- a/tests/integration/tests/plugins/zod.test.ts +++ b/tests/integration/tests/plugins/zod.test.ts @@ -850,4 +850,106 @@ describe('Zod plugin tests', () => { ) ).rejects.toThrow('already exists and is not a directory'); }); + + it('is strict by default', async () => { + const { zodSchemas } = await loadSchema( + ` + datasource db { + provider = 'postgresql' + url = env('DATABASE_URL') + } + + generator js { + provider = 'prisma-client-js' + } + + plugin zod { + provider = "@core/zod" + } + + model User { + id Int @id @default(autoincrement()) + email String @unique @email @endsWith('@zenstack.dev') + password String + @@validate(length(password, 6, 20)) + } + `, + { addPrelude: false, pushDb: false } + ); + + const schemas = zodSchemas.models; + expect( + schemas.UserSchema.safeParse({ id: 1, email: 'abc@zenstack.dev', password: 'abc123' }).success + ).toBeTruthy(); + expect( + schemas.UserSchema.safeParse({ id: 1, email: 'abc@zenstack.dev', password: 'abc123', x: 1 }).success + ).toBeFalsy(); + + expect( + schemas.UserCreateSchema.safeParse({ email: 'abc@zenstack.dev', password: 'abc123' }).success + ).toBeTruthy(); + expect( + schemas.UserCreateSchema.safeParse({ email: 'abc@zenstack.dev', password: 'abc123', x: 1 }).success + ).toBeFalsy(); + + // Prisma create/update schema should always non-strict + expect( + schemas.UserPrismaCreateSchema.safeParse({ email: 'abc@zenstack.dev', password: 'abc123', x: 1 }).data.x + ).toBe(1); + expect(schemas.UserPrismaUpdateSchema.safeParse({ x: 1 }).data.x).toBe(1); + + expect( + zodSchemas.input.UserInputSchema.create.safeParse({ + data: { id: 1, email: 'abc@zenstack.dev', password: 'abc123' }, + }).success + ).toBeTruthy(); + expect( + zodSchemas.input.UserInputSchema.create.safeParse({ + data: { id: 1, email: 'abc@zenstack.dev', password: 'abc123', x: 1 }, + }).success + ).toBeFalsy(); + }); + + it('can work in non-strict mode', async () => { + const { zodSchemas } = await loadSchema( + ` + datasource db { + provider = 'postgresql' + url = env('DATABASE_URL') + } + + generator js { + provider = 'prisma-client-js' + } + + plugin zod { + provider = "@core/zod" + strict = false + } + + model User { + id Int @id @default(autoincrement()) + email String @unique @email @endsWith('@zenstack.dev') + password String + @@validate(length(password, 6, 20)) + } + `, + { addPrelude: false, pushDb: false } + ); + + const schemas = zodSchemas.models; + expect( + schemas.UserSchema.safeParse({ id: 1, email: 'abc@zenstack.dev', password: 'abc123', x: 1 }).success + ).toBeTruthy(); + + expect( + schemas.UserCreateSchema.safeParse({ email: 'abc@zenstack.dev', password: 'abc123', x: 1 }).success + ).toBeTruthy(); + + expect( + zodSchemas.input.UserInputSchema.create.safeParse({ + data: { id: 1, email: 'abc@zenstack.dev', password: 'abc123', x: 1 }, + }).success + ).toBeTruthy(); + }); }); From e9efe6ccc7bf6876f4fd8113263ad5f4943f2695 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Sat, 14 Sep 2024 21:22:57 -0700 Subject: [PATCH 2/2] update --- packages/schema/src/plugins/zod/generator.ts | 27 ++++- .../schema/src/plugins/zod/transformer.ts | 69 +++++++----- tests/integration/tests/plugins/zod.test.ts | 103 +++++++++++++++--- 3 files changed, 154 insertions(+), 45 deletions(-) diff --git a/packages/schema/src/plugins/zod/generator.ts b/packages/schema/src/plugins/zod/generator.ts index 97196b2b6..ad6dc1de6 100644 --- a/packages/schema/src/plugins/zod/generator.ts +++ b/packages/schema/src/plugins/zod/generator.ts @@ -1,4 +1,5 @@ import { + PluginError, PluginGlobalOptions, PluginOptions, RUNTIME_PACKAGE, @@ -54,6 +55,17 @@ export class ZodSchemaGenerator { ensureEmptyDir(output); Transformer.setOutputPath(output); + // options validation + if ( + this.options.mode && + (typeof this.options.mode !== 'string' || !['strip', 'strict', 'passthrough'].includes(this.options.mode)) + ) { + throw new PluginError( + name, + `Invalid mode option: "${this.options.mode}". Must be one of 'strip', 'strict', or 'passthrough'.` + ); + } + // calculate the models to be excluded const excludeModels = this.getExcludedModels(); @@ -323,10 +335,17 @@ export class ZodSchemaGenerator { }); }); - if (this.options.strict !== false) { - writer.writeLine(').strict();'); - } else { - writer.writeLine(');'); + switch (this.options.mode) { + case 'strip': + // zod strips by default + writer.writeLine(')'); + break; + case 'passthrough': + writer.writeLine(').passthrough();'); + break; + default: + writer.writeLine(').strict();'); + break; } // relation fields diff --git a/packages/schema/src/plugins/zod/transformer.ts b/packages/schema/src/plugins/zod/transformer.ts index 5d43bc9c4..b39a205d7 100644 --- a/packages/schema/src/plugins/zod/transformer.ts +++ b/packages/schema/src/plugins/zod/transformer.ts @@ -291,7 +291,7 @@ export default class Transformer { prepareObjectSchema(zodObjectSchemaFields: string[], options: PluginOptions) { const objectSchema = `${this.generateExportObjectSchemaStatement( - this.addFinalWrappers({ zodStringFields: zodObjectSchemaFields }, options.strict !== false) + this.wrapWithZodObject(zodObjectSchemaFields, options.mode as string) )}\n`; const prismaImportStatement = this.generateImportPrismaStatement(options); @@ -314,15 +314,6 @@ export default class Transformer { export const ${this.name}ObjectSchema: SchemaType = ${schema} as SchemaType;`; } - addFinalWrappers({ zodStringFields }: { zodStringFields: string[] }, strict = true) { - const fields = [...zodStringFields]; - let result = this.wrapWithZodObject(fields); - if (strict) { - result = `${result}.strict()`; - } - return result; - } - generateImportPrismaStatement(options: PluginOptions) { const prismaClientImportPath = computePrismaClientImport( path.resolve(Transformer.outputPath, './objects'), @@ -411,7 +402,7 @@ export const ${this.name}ObjectSchema: SchemaType = ${schema} as SchemaType;`; return wrapped; } - wrapWithZodObject(zodStringFields: string | string[]) { + wrapWithZodObject(zodStringFields: string | string[], mode = 'strict') { let wrapped = ''; wrapped += 'z.object({'; @@ -419,6 +410,18 @@ export const ${this.name}ObjectSchema: SchemaType = ${schema} as SchemaType;`; wrapped += ' ' + zodStringFields; wrapped += '\n'; wrapped += '})'; + + switch (mode) { + case 'strip': + // zod strips by default + break; + case 'passthrough': + wrapped += '.passthrough()'; + break; + default: + wrapped += '.strict()'; + break; + } return wrapped; } @@ -468,6 +471,7 @@ export const ${this.name}ObjectSchema: SchemaType = ${schema} as SchemaType;`; ]; let codeBody = ''; const operations: [string, string][] = []; + const mode = (options.mode as string) ?? 'strict'; // OrderByWithRelationInput's name is different when "fullTextSearch" is enabled const orderByWithRelationInput = this.inputObjectTypes @@ -480,7 +484,8 @@ export const ${this.name}ObjectSchema: SchemaType = ${schema} as SchemaType;`; imports.push( `import { ${modelName}WhereUniqueInputObjectSchema } from '../objects/${modelName}WhereUniqueInput.schema'` ); - codeBody += `findUnique: z.object({ ${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} where: ${modelName}WhereUniqueInputObjectSchema }),`; + const fields = `${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} where: ${modelName}WhereUniqueInputObjectSchema`; + codeBody += `findUnique: ${this.wrapWithZodObject(fields, mode)},`; operations.push(['findUnique', origModelName]); } @@ -491,7 +496,8 @@ export const ${this.name}ObjectSchema: SchemaType = ${schema} as SchemaType;`; `import { ${modelName}WhereUniqueInputObjectSchema } from '../objects/${modelName}WhereUniqueInput.schema'`, `import { ${modelName}ScalarFieldEnumSchema } from '../enums/${modelName}ScalarFieldEnum.schema'` ); - codeBody += `findFirst: z.object({ ${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} where: ${modelName}WhereInputObjectSchema.optional(), orderBy: z.union([${orderByWithRelationInput}ObjectSchema, ${orderByWithRelationInput}ObjectSchema.array()]).optional(), cursor: ${modelName}WhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), distinct: z.array(${modelName}ScalarFieldEnumSchema).optional() }),`; + const fields = `${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} where: ${modelName}WhereInputObjectSchema.optional(), orderBy: z.union([${orderByWithRelationInput}ObjectSchema, ${orderByWithRelationInput}ObjectSchema.array()]).optional(), cursor: ${modelName}WhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), distinct: z.array(${modelName}ScalarFieldEnumSchema).optional()`; + codeBody += `findFirst: ${this.wrapWithZodObject(fields, mode)},`; operations.push(['findFirst', origModelName]); } @@ -502,7 +508,8 @@ export const ${this.name}ObjectSchema: SchemaType = ${schema} as SchemaType;`; `import { ${modelName}WhereUniqueInputObjectSchema } from '../objects/${modelName}WhereUniqueInput.schema'`, `import { ${modelName}ScalarFieldEnumSchema } from '../enums/${modelName}ScalarFieldEnum.schema'` ); - codeBody += `findMany: z.object({ ${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} where: ${modelName}WhereInputObjectSchema.optional(), orderBy: z.union([${orderByWithRelationInput}ObjectSchema, ${orderByWithRelationInput}ObjectSchema.array()]).optional(), cursor: ${modelName}WhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), distinct: z.array(${modelName}ScalarFieldEnumSchema).optional() }),`; + const fields = `${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} where: ${modelName}WhereInputObjectSchema.optional(), orderBy: z.union([${orderByWithRelationInput}ObjectSchema, ${orderByWithRelationInput}ObjectSchema.array()]).optional(), cursor: ${modelName}WhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), distinct: z.array(${modelName}ScalarFieldEnumSchema).optional()`; + codeBody += `findMany: ${this.wrapWithZodObject(fields, mode)},`; operations.push(['findMany', origModelName]); } @@ -518,7 +525,8 @@ export const ${this.name}ObjectSchema: SchemaType = ${schema} as SchemaType;`; const dataSchema = generateUnchecked ? `z.union([${modelName}CreateInputObjectSchema, ${modelName}UncheckedCreateInputObjectSchema])` : `${modelName}CreateInputObjectSchema`; - codeBody += `create: z.object({ ${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} data: ${dataSchema} }),`; + const fields = `${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} data: ${dataSchema}`; + codeBody += `create: ${this.wrapWithZodObject(fields, mode)},`; operations.push(['create', origModelName]); } @@ -526,7 +534,8 @@ export const ${this.name}ObjectSchema: SchemaType = ${schema} as SchemaType;`; imports.push( `import { ${modelName}CreateManyInputObjectSchema } from '../objects/${modelName}CreateManyInput.schema'` ); - codeBody += `createMany: z.object({ data: z.union([${modelName}CreateManyInputObjectSchema, z.array(${modelName}CreateManyInputObjectSchema)]), skipDuplicates: z.boolean().optional() }),`; + const fields = `data: z.union([${modelName}CreateManyInputObjectSchema, z.array(${modelName}CreateManyInputObjectSchema)]), skipDuplicates: z.boolean().optional()`; + codeBody += `createMany: ${this.wrapWithZodObject(fields, mode)},`; operations.push(['createMany', origModelName]); } @@ -534,7 +543,8 @@ export const ${this.name}ObjectSchema: SchemaType = ${schema} as SchemaType;`; imports.push( `import { ${modelName}WhereUniqueInputObjectSchema } from '../objects/${modelName}WhereUniqueInput.schema'` ); - codeBody += `'delete': z.object({ ${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} where: ${modelName}WhereUniqueInputObjectSchema }),`; + const fields = `${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} where: ${modelName}WhereUniqueInputObjectSchema`; + codeBody += `'delete': ${this.wrapWithZodObject(fields, mode)},`; operations.push(['delete', origModelName]); } @@ -542,7 +552,8 @@ export const ${this.name}ObjectSchema: SchemaType = ${schema} as SchemaType;`; imports.push( `import { ${modelName}WhereInputObjectSchema } from '../objects/${modelName}WhereInput.schema'` ); - codeBody += `deleteMany: z.object({ where: ${modelName}WhereInputObjectSchema.optional() }),`; + const fields = `where: ${modelName}WhereInputObjectSchema.optional()`; + codeBody += `deleteMany: ${this.wrapWithZodObject(fields, mode)},`; operations.push(['deleteMany', origModelName]); } @@ -559,7 +570,8 @@ export const ${this.name}ObjectSchema: SchemaType = ${schema} as SchemaType;`; const dataSchema = generateUnchecked ? `z.union([${modelName}UpdateInputObjectSchema, ${modelName}UncheckedUpdateInputObjectSchema])` : `${modelName}UpdateInputObjectSchema`; - codeBody += `update: z.object({ ${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} data: ${dataSchema}, where: ${modelName}WhereUniqueInputObjectSchema }),`; + const fields = `${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} data: ${dataSchema}, where: ${modelName}WhereUniqueInputObjectSchema`; + codeBody += `update: ${this.wrapWithZodObject(fields, mode)},`; operations.push(['update', origModelName]); } @@ -576,7 +588,8 @@ export const ${this.name}ObjectSchema: SchemaType = ${schema} as SchemaType;`; const dataSchema = generateUnchecked ? `z.union([${modelName}UpdateManyMutationInputObjectSchema, ${modelName}UncheckedUpdateManyInputObjectSchema])` : `${modelName}UpdateManyMutationInputObjectSchema`; - codeBody += `updateMany: z.object({ data: ${dataSchema}, where: ${modelName}WhereInputObjectSchema.optional() }),`; + const fields = `data: ${dataSchema}, where: ${modelName}WhereInputObjectSchema.optional()`; + codeBody += `updateMany: ${this.wrapWithZodObject(fields, mode)},`; operations.push(['updateMany', origModelName]); } @@ -598,7 +611,8 @@ export const ${this.name}ObjectSchema: SchemaType = ${schema} as SchemaType;`; const updateSchema = generateUnchecked ? `z.union([${modelName}UpdateInputObjectSchema, ${modelName}UncheckedUpdateInputObjectSchema])` : `${modelName}UpdateInputObjectSchema`; - codeBody += `upsert: z.object({ ${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} where: ${modelName}WhereUniqueInputObjectSchema, create: ${createSchema}, update: ${updateSchema} }),`; + const fields = `${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} where: ${modelName}WhereUniqueInputObjectSchema, create: ${createSchema}, update: ${updateSchema}`; + codeBody += `upsert: ${this.wrapWithZodObject(fields, mode)},`; operations.push(['upsert', origModelName]); } @@ -644,9 +658,10 @@ export const ${this.name}ObjectSchema: SchemaType = ${schema} as SchemaType;`; `import { ${modelName}WhereUniqueInputObjectSchema } from '../objects/${modelName}WhereUniqueInput.schema'` ); - codeBody += `aggregate: z.object({ where: ${modelName}WhereInputObjectSchema.optional(), orderBy: z.union([${orderByWithRelationInput}ObjectSchema, ${orderByWithRelationInput}ObjectSchema.array()]).optional(), cursor: ${modelName}WhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), ${aggregateOperations.join( + const fields = `where: ${modelName}WhereInputObjectSchema.optional(), orderBy: z.union([${orderByWithRelationInput}ObjectSchema, ${orderByWithRelationInput}ObjectSchema.array()]).optional(), cursor: ${modelName}WhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), ${aggregateOperations.join( ', ' - )} }),`; + )}`; + codeBody += `aggregate: ${this.wrapWithZodObject(fields, mode)},`; operations.push(['aggregate', modelName]); } @@ -657,9 +672,10 @@ export const ${this.name}ObjectSchema: SchemaType = ${schema} as SchemaType;`; `import { ${modelName}ScalarWhereWithAggregatesInputObjectSchema } from '../objects/${modelName}ScalarWhereWithAggregatesInput.schema'`, `import { ${modelName}ScalarFieldEnumSchema } from '../enums/${modelName}ScalarFieldEnum.schema'` ); - codeBody += `groupBy: z.object({ where: ${modelName}WhereInputObjectSchema.optional(), orderBy: z.union([${modelName}OrderByWithAggregationInputObjectSchema, ${modelName}OrderByWithAggregationInputObjectSchema.array()]).optional(), having: ${modelName}ScalarWhereWithAggregatesInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), by: z.array(${modelName}ScalarFieldEnumSchema), ${aggregateOperations.join( + const fields = `where: ${modelName}WhereInputObjectSchema.optional(), orderBy: z.union([${modelName}OrderByWithAggregationInputObjectSchema, ${modelName}OrderByWithAggregationInputObjectSchema.array()]).optional(), having: ${modelName}ScalarWhereWithAggregatesInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), by: z.array(${modelName}ScalarFieldEnumSchema), ${aggregateOperations.join( ', ' - )} }),`; + )}`; + codeBody += `groupBy: ${this.wrapWithZodObject(fields, mode)},`; operations.push(['groupBy', origModelName]); } @@ -674,7 +690,8 @@ export const ${this.name}ObjectSchema: SchemaType = ${schema} as SchemaType;`; `import { ${modelName}CountAggregateInputObjectSchema } from '../objects/${modelName}CountAggregateInput.schema'` ); - codeBody += `count: z.object({ where: ${modelName}WhereInputObjectSchema.optional(), orderBy: z.union([${orderByWithRelationInput}ObjectSchema, ${orderByWithRelationInput}ObjectSchema.array()]).optional(), cursor: ${modelName}WhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), distinct: z.array(${modelName}ScalarFieldEnumSchema).optional(), select: z.union([ z.literal(true), ${modelName}CountAggregateInputObjectSchema ]).optional() })`; + const fields = `where: ${modelName}WhereInputObjectSchema.optional(), orderBy: z.union([${orderByWithRelationInput}ObjectSchema, ${orderByWithRelationInput}ObjectSchema.array()]).optional(), cursor: ${modelName}WhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), distinct: z.array(${modelName}ScalarFieldEnumSchema).optional(), select: z.union([ z.literal(true), ${modelName}CountAggregateInputObjectSchema ]).optional()`; + codeBody += `count: ${this.wrapWithZodObject(fields, mode)},`; operations.push(['count', origModelName]); } diff --git a/tests/integration/tests/plugins/zod.test.ts b/tests/integration/tests/plugins/zod.test.ts index e30eaa27a..5af7f4077 100644 --- a/tests/integration/tests/plugins/zod.test.ts +++ b/tests/integration/tests/plugins/zod.test.ts @@ -864,7 +864,7 @@ describe('Zod plugin tests', () => { } plugin zod { - provider = "@core/zod" + provider = '@core/zod' } model User { @@ -910,7 +910,7 @@ describe('Zod plugin tests', () => { ).toBeFalsy(); }); - it('can work in non-strict mode', async () => { + it('works in strip mode', async () => { const { zodSchemas } = await loadSchema( ` datasource db { @@ -923,8 +923,8 @@ describe('Zod plugin tests', () => { } plugin zod { - provider = "@core/zod" - strict = false + provider = '@core/zod' + mode = 'strip' } model User { @@ -938,18 +938,91 @@ describe('Zod plugin tests', () => { ); const schemas = zodSchemas.models; - expect( - schemas.UserSchema.safeParse({ id: 1, email: 'abc@zenstack.dev', password: 'abc123', x: 1 }).success - ).toBeTruthy(); + let parsed = schemas.UserSchema.safeParse({ id: 1, email: 'abc@zenstack.dev', password: 'abc123', x: 1 }); + expect(parsed.success).toBeTruthy(); + expect(parsed.data.x).toBeUndefined(); - expect( - schemas.UserCreateSchema.safeParse({ email: 'abc@zenstack.dev', password: 'abc123', x: 1 }).success - ).toBeTruthy(); + parsed = schemas.UserCreateSchema.safeParse({ email: 'abc@zenstack.dev', password: 'abc123', x: 1 }); + expect(parsed.success).toBeTruthy(); + expect(parsed.data.x).toBeUndefined(); - expect( - zodSchemas.input.UserInputSchema.create.safeParse({ - data: { id: 1, email: 'abc@zenstack.dev', password: 'abc123', x: 1 }, - }).success - ).toBeTruthy(); + parsed = zodSchemas.input.UserInputSchema.create.safeParse({ + data: { id: 1, email: 'abc@zenstack.dev', password: 'abc123', x: 1 }, + }); + expect(parsed.success).toBeTruthy(); + expect(parsed.data.data.x).toBeUndefined(); + }); + + it('works in passthrough mode', async () => { + const { zodSchemas } = await loadSchema( + ` + datasource db { + provider = 'postgresql' + url = env('DATABASE_URL') + } + + generator js { + provider = 'prisma-client-js' + } + + plugin zod { + provider = '@core/zod' + mode = 'passthrough' + } + + model User { + id Int @id @default(autoincrement()) + email String @unique @email @endsWith('@zenstack.dev') + password String + @@validate(length(password, 6, 20)) + } + `, + { addPrelude: false, pushDb: false } + ); + + const schemas = zodSchemas.models; + let parsed = schemas.UserSchema.safeParse({ id: 1, email: 'abc@zenstack.dev', password: 'abc123', x: 1 }); + expect(parsed.success).toBeTruthy(); + expect(parsed.data.x).toBe(1); + + parsed = schemas.UserCreateSchema.safeParse({ email: 'abc@zenstack.dev', password: 'abc123', x: 1 }); + expect(parsed.success).toBeTruthy(); + expect(parsed.data.x).toBe(1); + + parsed = zodSchemas.input.UserInputSchema.create.safeParse({ + data: { id: 1, email: 'abc@zenstack.dev', password: 'abc123', x: 1 }, + }); + expect(parsed.success).toBeTruthy(); + expect(parsed.data.data.x).toBe(1); + }); + + it('complains about invalid mode', async () => { + await expect( + loadSchema( + ` + datasource db { + provider = 'postgresql' + url = env('DATABASE_URL') + } + + generator js { + provider = 'prisma-client-js' + } + + plugin zod { + provider = '@core/zod' + mode = 'xyz' + } + + model User { + id Int @id @default(autoincrement()) + email String @unique @email @endsWith('@zenstack.dev') + password String + @@validate(length(password, 6, 20)) + } + `, + { addPrelude: false, pushDb: false } + ) + ).rejects.toThrow(/Invalid mode/); }); });