From b19bbab8dcad59d675db935cb759677eab14ba97 Mon Sep 17 00:00:00 2001 From: Yiming Date: Wed, 17 Apr 2024 11:38:33 +0800 Subject: [PATCH] fix(zod): field with `auth()` and `@default()` should be generated as optional (#1266) --- packages/plugins/trpc/src/generator.ts | 2 +- packages/schema/src/cli/index.ts | 4 +- packages/schema/src/plugins/enhancer/index.ts | 4 +- packages/schema/src/plugins/zod/generator.ts | 2 +- .../src/plugins/zod/utils/schema-gen.ts | 18 +++-- packages/sdk/src/prisma.ts | 6 +- tests/integration/tests/cli/plugins.test.ts | 38 ++++++++++- .../tests/regression/issue-1241.test.ts | 68 +++++++++---------- .../tests/regression/issue-1265.test.ts | 27 ++++++++ 9 files changed, 120 insertions(+), 49 deletions(-) create mode 100644 tests/integration/tests/regression/issue-1265.test.ts diff --git a/packages/plugins/trpc/src/generator.ts b/packages/plugins/trpc/src/generator.ts index 9d2cc5724..cf57a1baf 100644 --- a/packages/plugins/trpc/src/generator.ts +++ b/packages/plugins/trpc/src/generator.ts @@ -83,7 +83,7 @@ function createAppRouter( hiddenModels: string[], generateModelActions: string[] | undefined, generateClientHelpers: string[] | undefined, - zmodel: Model, + _zmodel: Model, zodSchemasImport: string, options: PluginOptions ) { diff --git a/packages/schema/src/cli/index.ts b/packages/schema/src/cli/index.ts index f1a5c4cf0..f430f7662 100644 --- a/packages/schema/src/cli/index.ts +++ b/packages/schema/src/cli/index.ts @@ -109,9 +109,9 @@ export function createProgram() { .command('generate') .description('Run code generation.') .addOption(schemaOption) - .addOption(new Option('-o, --output ', 'default output directory for built-in plugins')) + .addOption(new Option('-o, --output ', 'default output directory for core plugins')) .addOption(new Option('--no-default-plugins', 'do not run default plugins')) - .addOption(new Option('--no-compile', 'do not compile the output of built-in plugins')) + .addOption(new Option('--no-compile', 'do not compile the output of core plugins')) .addOption(noVersionCheckOption) .addOption(noDependencyCheck) .action(generateAction); diff --git a/packages/schema/src/plugins/enhancer/index.ts b/packages/schema/src/plugins/enhancer/index.ts index 79e8fd6e6..0c82acfba 100644 --- a/packages/schema/src/plugins/enhancer/index.ts +++ b/packages/schema/src/plugins/enhancer/index.ts @@ -1,4 +1,4 @@ -import { PluginError, RUNTIME_PACKAGE, createProject, resolvePath, type PluginFunction } from '@zenstackhq/sdk'; +import { PluginError, createProject, resolvePath, type PluginFunction } from '@zenstackhq/sdk'; import path from 'path'; import { getDefaultOutputFolder } from '../plugin-utils'; import { EnhancerGenerator } from './enhance'; @@ -31,7 +31,7 @@ const run: PluginFunction = async (model, options, _dmmf, globalOptions) => { // resolve it relative to the schema path prismaClientPath = path.relative(path.dirname(options.schemaPath), prismaClientPathAbs); } else { - prismaClientPath = `${RUNTIME_PACKAGE}/models`; + prismaClientPath = `.zenstack/models`; } } diff --git a/packages/schema/src/plugins/zod/generator.ts b/packages/schema/src/plugins/zod/generator.ts index 5e542540d..f2f628b30 100644 --- a/packages/schema/src/plugins/zod/generator.ts +++ b/packages/schema/src/plugins/zod/generator.ts @@ -305,7 +305,7 @@ export class ZodSchemaGenerator { writer.write(`const baseSchema = z.object(`); writer.inlineBlock(() => { scalarFields.forEach((field) => { - writer.writeLine(`${field.name}: ${makeFieldSchema(field, true)},`); + writer.writeLine(`${field.name}: ${makeFieldSchema(field)},`); }); }); writer.writeLine(');'); diff --git a/packages/schema/src/plugins/zod/utils/schema-gen.ts b/packages/schema/src/plugins/zod/utils/schema-gen.ts index 07412513a..9f79a3c66 100644 --- a/packages/schema/src/plugins/zod/utils/schema-gen.ts +++ b/packages/schema/src/plugins/zod/utils/schema-gen.ts @@ -21,8 +21,9 @@ import { } from '@zenstackhq/sdk/ast'; import { upperCaseFirst } from 'upper-case-first'; import { name } from '..'; +import { isDefaultWithAuth } from '../../enhancer/enhancer-utils'; -export function makeFieldSchema(field: DataModelField, respectDefault = false) { +export function makeFieldSchema(field: DataModelField) { if (isDataModel(field.type.reference?.ref)) { if (field.type.array) { // array field is always optional @@ -139,15 +140,20 @@ export function makeFieldSchema(field: DataModelField, respectDefault = false) { } } - if (respectDefault) { + if (field.attributes.some(isDefaultWithAuth)) { + // field uses `auth()` in `@default()`, this was transformed into a pseudo default + // value, while compiling to zod we should turn it into an optional field instead + // of `.default()` + schema += '.nullish()'; + } else { const schemaDefault = getFieldSchemaDefault(field); - if (schemaDefault) { + if (schemaDefault !== undefined) { schema += `.default(${schemaDefault})`; } - } - if (field.type.optional) { - schema += '.nullish()'; + if (field.type.optional) { + schema += '.nullish()'; + } } return schema; diff --git a/packages/sdk/src/prisma.ts b/packages/sdk/src/prisma.ts index ef642f014..b45dd7cfb 100644 --- a/packages/sdk/src/prisma.ts +++ b/packages/sdk/src/prisma.ts @@ -2,6 +2,7 @@ import type { DMMF } from '@prisma/generator-helper'; import { getDMMF as _getDMMF, type GetDMMFOptions } from '@prisma/internals'; +import { DEFAULT_RUNTIME_LOAD_PATH } from '@zenstackhq/runtime'; import path from 'path'; import { RUNTIME_PACKAGE } from './constants'; import type { PluginOptions } from './types'; @@ -14,7 +15,10 @@ export function getPrismaClientImportSpec(importingFromDir: string, options: Plu return '@prisma/client'; } - if (options.prismaClientPath.startsWith(RUNTIME_PACKAGE)) { + if ( + options.prismaClientPath.startsWith(RUNTIME_PACKAGE) || + options.prismaClientPath.startsWith(DEFAULT_RUNTIME_LOAD_PATH) + ) { return options.prismaClientPath; } diff --git a/tests/integration/tests/cli/plugins.test.ts b/tests/integration/tests/cli/plugins.test.ts index c0465bb0a..9ef16a31d 100644 --- a/tests/integration/tests/cli/plugins.test.ts +++ b/tests/integration/tests/cli/plugins.test.ts @@ -26,10 +26,10 @@ describe('CLI Plugins Tests', () => { const PACKAGE_MANAGERS = ['npm' /*, 'pnpm', 'pnpm-workspace'*/] as const; - function zenstackGenerate(pm: (typeof PACKAGE_MANAGERS)[number]) { + function zenstackGenerate(pm: (typeof PACKAGE_MANAGERS)[number], output?: string) { switch (pm) { case 'npm': - run(`ZENSTACK_TEST=0 npx zenstack generate`); + run(`ZENSTACK_TEST=0 npx zenstack generate${output ? ' --output ' + output : ''}`); break; // case 'pnpm': // case 'pnpm-workspace': @@ -275,4 +275,38 @@ ${BASE_MODEL} run('npx tsc'); } }); + + it('all plugins custom core output path', async () => { + for (const pm of PACKAGE_MANAGERS) { + console.log('[PACKAGE MANAGER]', pm); + await initProject(pm); + + let schemaContent = ` +generator client { + provider = "prisma-client-js" +} + +${BASE_MODEL} + `; + for (const plugin of plugins) { + if (!plugin.includes('trp')) { + schemaContent += `\n${plugin}`; + } + } + + schemaContent += `plugin trpc { + provider = '@zenstackhq/trpc' + output = 'lib/trpc' + zodSchemasImport = '../../../zen/zod' + }`; + + fs.writeFileSync('schema.zmodel', schemaContent); + + // generate + zenstackGenerate(pm, './zen'); + + // compile + run('npx tsc'); + } + }); }); diff --git a/tests/integration/tests/regression/issue-1241.test.ts b/tests/integration/tests/regression/issue-1241.test.ts index 3a53f567c..e5d94c9b7 100644 --- a/tests/integration/tests/regression/issue-1241.test.ts +++ b/tests/integration/tests/regression/issue-1241.test.ts @@ -5,40 +5,40 @@ describe('issue 1241', () => { it('regression', async () => { const { enhance, prisma } = await loadSchema( ` - model User { - id String @id @default(uuid()) - todos Todo[] - - @@auth - @@allow('all', true) - } - - model Todo { - id String @id @default(uuid()) - - user_id String - user User @relation(fields: [user_id], references: [id]) - - images File[] @relation("todo_images") - documents File[] @relation("todo_documents") - - @@allow('all', true) - } - - model File { - id String @id @default(uuid()) - s3_key String @unique - label String - - todo_image_id String? - todo_image Todo? @relation("todo_images", fields: [todo_image_id], references: [id]) - - todo_document_id String? - todo_document Todo? @relation("todo_documents", fields: [todo_document_id], references: [id]) - - @@allow('all', true) - } - `, + model User { + id String @id @default(uuid()) + todos Todo[] + + @@auth + @@allow('all', true) + } + + model Todo { + id String @id @default(uuid()) + + user_id String + user User @relation(fields: [user_id], references: [id]) + + images File[] @relation("todo_images") + documents File[] @relation("todo_documents") + + @@allow('all', true) + } + + model File { + id String @id @default(uuid()) + s3_key String @unique + label String + + todo_image_id String? + todo_image Todo? @relation("todo_images", fields: [todo_image_id], references: [id]) + + todo_document_id String? + todo_document Todo? @relation("todo_documents", fields: [todo_document_id], references: [id]) + + @@allow('all', true) + } + `, { logPrismaQuery: true } ); diff --git a/tests/integration/tests/regression/issue-1265.test.ts b/tests/integration/tests/regression/issue-1265.test.ts new file mode 100644 index 000000000..cd7df4636 --- /dev/null +++ b/tests/integration/tests/regression/issue-1265.test.ts @@ -0,0 +1,27 @@ +import { loadSchema } from '@zenstackhq/testtools'; + +describe('issue 1265', () => { + it('regression', async () => { + const { zodSchemas } = await loadSchema( + ` + model User { + id String @id @default(uuid()) + posts Post[] + @@allow('all', true) + } + + model Post { + id String @id @default(uuid()) + title String @default('xyz') + userId String @default(auth().id) + user User @relation(fields: [userId], references: [id]) + @@allow('all', true) + } + `, + { fullZod: true, pushDb: false } + ); + + expect(zodSchemas.models.PostCreateSchema.safeParse({ title: 'Post 1' }).success).toBeTruthy(); + expect(zodSchemas.input.PostInputSchema.create.safeParse({ data: { title: 'Post 1' } }).success).toBeTruthy(); + }); +});