diff --git a/packages/schema/src/plugins/enhancer/enhance/index.ts b/packages/schema/src/plugins/enhancer/enhance/index.ts index 62b1d03e6..f4d2e5325 100644 --- a/packages/schema/src/plugins/enhancer/enhance/index.ts +++ b/packages/schema/src/plugins/enhancer/enhance/index.ts @@ -214,54 +214,59 @@ export function enhance(prisma: any, context?: EnhancementContext<${authTypePara // calculate a relative output path to output the logical prisma client into enhancer's output dir const prismaClientOutDir = path.join(path.relative(zmodelDir, this.outDir), LOGICAL_CLIENT_GENERATION_PATH); - try { - await prismaGenerator.generate({ - provider: '@internal', // doesn't matter - schemaPath: this.options.schemaPath, - output: logicalPrismaFile, - overrideClientGenerationPath: prismaClientOutDir, - mode: 'logical', - }); + await prismaGenerator.generate({ + provider: '@internal', // doesn't matter + schemaPath: this.options.schemaPath, + output: logicalPrismaFile, + overrideClientGenerationPath: prismaClientOutDir, + mode: 'logical', + }); - // generate the prisma client + // generate the prisma client - // only run prisma client generator for the logical schema - const prismaClientGeneratorName = this.getPrismaClientGeneratorName(this.model); - let generateCmd = `prisma generate --schema "${logicalPrismaFile}" --generator=${prismaClientGeneratorName}`; + // only run prisma client generator for the logical schema + const prismaClientGeneratorName = this.getPrismaClientGeneratorName(this.model); + let generateCmd = `prisma generate --schema "${logicalPrismaFile}" --generator=${prismaClientGeneratorName}`; - const prismaVersion = getPrismaVersion(); - if (!prismaVersion || semver.gte(prismaVersion, '5.2.0')) { - // add --no-engine to reduce generation size if the prisma version supports - generateCmd += ' --no-engine'; - } + const prismaVersion = getPrismaVersion(); + if (!prismaVersion || semver.gte(prismaVersion, '5.2.0')) { + // add --no-engine to reduce generation size if the prisma version supports + generateCmd += ' --no-engine'; + } + try { + // run 'prisma generate' + await execPackage(generateCmd, { stdio: 'ignore' }); + } catch { + await trackPrismaSchemaError(logicalPrismaFile); try { - // run 'prisma generate' - await execPackage(generateCmd, { stdio: 'ignore' }); + // run 'prisma generate' again with output to the console + await execPackage(generateCmd); } catch { - await trackPrismaSchemaError(logicalPrismaFile); - try { - // run 'prisma generate' again with output to the console - await execPackage(generateCmd); - } catch { - // noop - } - throw new PluginError(name, `Failed to run "prisma generate" on logical schema: ${logicalPrismaFile}`); + // noop } + throw new PluginError(name, `Failed to run "prisma generate" on logical schema: ${logicalPrismaFile}`); + } + + // make a bunch of typing fixes to the generated prisma client + await this.processClientTypes(path.join(this.outDir, LOGICAL_CLIENT_GENERATION_PATH)); - // make a bunch of typing fixes to the generated prisma client - await this.processClientTypes(path.join(this.outDir, LOGICAL_CLIENT_GENERATION_PATH)); + const dmmf = await getDMMF({ datamodel: fs.readFileSync(logicalPrismaFile, { encoding: 'utf-8' }) }); - return { - prismaSchema: logicalPrismaFile, - // load the dmmf of the logical prisma schema - dmmf: await getDMMF({ datamodel: fs.readFileSync(logicalPrismaFile, { encoding: 'utf-8' }) }), - }; - } finally { + try { + // clean up temp schema if (fs.existsSync(logicalPrismaFile)) { fs.rmSync(logicalPrismaFile); } + } catch { + // ignore errors } + + return { + prismaSchema: logicalPrismaFile, + // load the dmmf of the logical prisma schema + dmmf, + }; } private getPrismaClientGeneratorName(model: Model) { @@ -287,12 +292,12 @@ export function enhance(prisma: any, context?: EnhancementContext<${authTypePara this.model.declarations .filter((d): d is DataModel => isDelegateModel(d)) .forEach((dm) => { - delegateInfo.push([ - dm, - this.model.declarations.filter( - (d): d is DataModel => isDataModel(d) && d.superTypes.some((s) => s.ref === dm) - ), - ]); + const concreteModels = this.model.declarations.filter( + (d): d is DataModel => isDataModel(d) && d.superTypes.some((s) => s.ref === dm) + ); + if (concreteModels.length > 0) { + delegateInfo.push([dm, concreteModels]); + } }); // transform index.d.ts and save it into a new file (better perf than in-line editing) diff --git a/packages/schema/src/plugins/prisma/schema-generator.ts b/packages/schema/src/plugins/prisma/schema-generator.ts index 5440e03cf..58ecd68f4 100644 --- a/packages/schema/src/plugins/prisma/schema-generator.ts +++ b/packages/schema/src/plugins/prisma/schema-generator.ts @@ -374,6 +374,11 @@ export class PrismaSchemaGenerator { // for the given model, find relation fields of delegate model type, find all concrete models // of the delegate model and generate an auxiliary opposite relation field to each of them decl.fields.forEach((f) => { + // don't process fields inherited from a delegate model + if (f.$inheritedFrom && isDelegateModel(f.$inheritedFrom)) { + return; + } + const fieldType = f.type.reference?.ref; if (!isDataModel(fieldType)) { return; diff --git a/tests/regression/tests/issue-1415.test.ts b/tests/regression/tests/issue-1415.test.ts new file mode 100644 index 000000000..791413557 --- /dev/null +++ b/tests/regression/tests/issue-1415.test.ts @@ -0,0 +1,22 @@ +import { loadSchema } from '@zenstackhq/testtools'; + +describe('issue 1415', () => { + it('regression', async () => { + await loadSchema( + ` + model User { + id String @id @default(cuid()) + prices Price[] + } + + model Price { + id String @id @default(cuid()) + owner User @relation(fields: [ownerId], references: [id]) + ownerId String @default(auth().id) + priceType String + @@delegate(priceType) + } + ` + ); + }); +}); diff --git a/tests/regression/tests/issue-1416.test.ts b/tests/regression/tests/issue-1416.test.ts new file mode 100644 index 000000000..5c18d6d4e --- /dev/null +++ b/tests/regression/tests/issue-1416.test.ts @@ -0,0 +1,37 @@ +import { loadSchema } from '@zenstackhq/testtools'; + +describe('issue 1416', () => { + it('regression', async () => { + await loadSchema( + ` + model User { + id String @id @default(cuid()) + role String + } + + model Price { + id String @id @default(nanoid(6)) + entity Entity? @relation(fields: [entityId], references: [id]) + entityId String? + priceType String + @@delegate(priceType) + } + + model MyPrice extends Price { + foo String + } + + model Entity { + id String @id @default(nanoid(6)) + price Price[] + type String + @@delegate(type) + } + + model MyEntity extends Entity { + foo String + } + ` + ); + }); +});