From bfe698390c689dbe4350f7989cc6a1974ff1aad5 Mon Sep 17 00:00:00 2001 From: Yiming Date: Mon, 23 Sep 2024 11:21:13 -0700 Subject: [PATCH] fix(delegate): base fields not properly wrapped inside relation layer when injected from policies (#1736) --- package.json | 2 +- packages/ide/jetbrains/CHANGELOG.md | 45 ++++++-- packages/ide/jetbrains/build.gradle.kts | 2 +- packages/ide/jetbrains/package.json | 2 +- packages/language/package.json | 2 +- packages/misc/redwood/package.json | 2 +- packages/plugins/openapi/package.json | 2 +- packages/plugins/swr/package.json | 2 +- packages/plugins/tanstack-query/package.json | 2 +- packages/plugins/trpc/package.json | 2 +- packages/runtime/package.json | 2 +- .../runtime/src/enhancements/node/delegate.ts | 38 ++++--- packages/schema/package.json | 2 +- packages/sdk/package.json | 2 +- packages/server/package.json | 2 +- packages/testtools/package.json | 2 +- tests/regression/tests/issue-1734.test.ts | 107 ++++++++++++++++++ 17 files changed, 179 insertions(+), 39 deletions(-) create mode 100644 tests/regression/tests/issue-1734.test.ts diff --git a/package.json b/package.json index 9d0b8cd6..5d51f97d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zenstack-monorepo", - "version": "2.6.0", + "version": "2.6.1", "description": "", "scripts": { "build": "pnpm -r build", diff --git a/packages/ide/jetbrains/CHANGELOG.md b/packages/ide/jetbrains/CHANGELOG.md index 1d9a8b78..fb266ad8 100644 --- a/packages/ide/jetbrains/CHANGELOG.md +++ b/packages/ide/jetbrains/CHANGELOG.md @@ -1,38 +1,61 @@ # Changelog ## [Unreleased] + +### Fixed + +- ZModel validation issues when accessing fields defined in a base model from `future().` or `this.`. + +## 2.5.0 + ### Added -- A new `path` parameter to the `@@validate` attribute for providing an optional path to the field that caused the error. + +- A new `path` parameter to the `@@validate` attribute for providing an optional path to the field that caused the error. ## 2.4.0 + ### Added -- The `uuid()` function is updated to support the new UUID version feature from Prisma. -## 2.3.0 +- The `uuid()` function is updated to support the new UUID version feature from Prisma. + +## 2.3.0 + ### Added -- New `check()` policy rule function. + +- New `check()` policy rule function. ### Fixed -- Fixed the issue with formatting schemas containing `Unsupported` type. + +- Fixed the issue with formatting schemas containing `Unsupported` type. ## 2.2.0 + ### Added -- Support comparing fields from different models in mutation policy rules ("create", "update", and "delete). + +- Support comparing fields from different models in mutation policy rules ("create", "update", and "delete). ## 2.1.0 + ### Added -- Support using ZModel type names (e.g., `DateTime`) as model field names. -- `auth()` is resolved from all reachable schema files. + +- Support using ZModel type names (e.g., `DateTime`) as model field names. +- `auth()` is resolved from all reachable schema files. ## 2.0.0 + ### Added -- ZenStack V2 release! + +- ZenStack V2 release! ## 1.11.0 + ### Added -- Added support to complex usage of `@@index` attribute like `@@index([content(ops: raw("gin_trgm_ops"))], type: Gin)`. + +- Added support to complex usage of `@@index` attribute like `@@index([content(ops: raw("gin_trgm_ops"))], type: Gin)`. + ### Fixed -- Fixed several ZModel validation issues related to model inheritance. + +- Fixed several ZModel validation issues related to model inheritance. ## 1.7.0 diff --git a/packages/ide/jetbrains/build.gradle.kts b/packages/ide/jetbrains/build.gradle.kts index 01d698c2..317a9d86 100644 --- a/packages/ide/jetbrains/build.gradle.kts +++ b/packages/ide/jetbrains/build.gradle.kts @@ -9,7 +9,7 @@ plugins { } group = "dev.zenstack" -version = "2.6.0" +version = "2.6.1" repositories { mavenCentral() diff --git a/packages/ide/jetbrains/package.json b/packages/ide/jetbrains/package.json index 20b806d5..6534c8a0 100644 --- a/packages/ide/jetbrains/package.json +++ b/packages/ide/jetbrains/package.json @@ -1,6 +1,6 @@ { "name": "jetbrains", - "version": "2.6.0", + "version": "2.6.1", "displayName": "ZenStack JetBrains IDE Plugin", "description": "ZenStack JetBrains IDE plugin", "homepage": "https://zenstack.dev", diff --git a/packages/language/package.json b/packages/language/package.json index 72753028..d5b2511c 100644 --- a/packages/language/package.json +++ b/packages/language/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/language", - "version": "2.6.0", + "version": "2.6.1", "displayName": "ZenStack modeling language compiler", "description": "ZenStack modeling language compiler", "homepage": "https://zenstack.dev", diff --git a/packages/misc/redwood/package.json b/packages/misc/redwood/package.json index c6f8a41d..c7d2650b 100644 --- a/packages/misc/redwood/package.json +++ b/packages/misc/redwood/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/redwood", "displayName": "ZenStack RedwoodJS Integration", - "version": "2.6.0", + "version": "2.6.1", "description": "CLI and runtime for integrating ZenStack with RedwoodJS projects.", "repository": { "type": "git", diff --git a/packages/plugins/openapi/package.json b/packages/plugins/openapi/package.json index 51100a85..456d9aa2 100644 --- a/packages/plugins/openapi/package.json +++ b/packages/plugins/openapi/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/openapi", "displayName": "ZenStack Plugin and Runtime for OpenAPI", - "version": "2.6.0", + "version": "2.6.1", "description": "ZenStack plugin and runtime supporting OpenAPI", "main": "index.js", "repository": { diff --git a/packages/plugins/swr/package.json b/packages/plugins/swr/package.json index 95dd7e3c..085547eb 100644 --- a/packages/plugins/swr/package.json +++ b/packages/plugins/swr/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/swr", "displayName": "ZenStack plugin for generating SWR hooks", - "version": "2.6.0", + "version": "2.6.1", "description": "ZenStack plugin for generating SWR hooks", "main": "index.js", "repository": { diff --git a/packages/plugins/tanstack-query/package.json b/packages/plugins/tanstack-query/package.json index da58f2b6..7b6249f8 100644 --- a/packages/plugins/tanstack-query/package.json +++ b/packages/plugins/tanstack-query/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/tanstack-query", "displayName": "ZenStack plugin for generating tanstack-query hooks", - "version": "2.6.0", + "version": "2.6.1", "description": "ZenStack plugin for generating tanstack-query hooks", "main": "index.js", "exports": { diff --git a/packages/plugins/trpc/package.json b/packages/plugins/trpc/package.json index bf501f4b..1794de51 100644 --- a/packages/plugins/trpc/package.json +++ b/packages/plugins/trpc/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/trpc", "displayName": "ZenStack plugin for tRPC", - "version": "2.6.0", + "version": "2.6.1", "description": "ZenStack plugin for tRPC", "main": "index.js", "repository": { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 3d1ca9d0..6c64aa7c 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/runtime", "displayName": "ZenStack Runtime Library", - "version": "2.6.0", + "version": "2.6.1", "description": "Runtime of ZenStack for both client-side and server-side environments.", "repository": { "type": "git", diff --git a/packages/runtime/src/enhancements/node/delegate.ts b/packages/runtime/src/enhancements/node/delegate.ts index 45e74ee3..5a9ff162 100644 --- a/packages/runtime/src/enhancements/node/delegate.ts +++ b/packages/runtime/src/enhancements/node/delegate.ts @@ -233,7 +233,7 @@ export class DelegateProxyHandler extends DefaultPrismaProxyHandler { } } - private async buildSelectIncludeHierarchy(model: string, args: any) { + private async buildSelectIncludeHierarchy(model: string, args: any, includeConcreteFields = true) { args = clone(args); const selectInclude: any = this.extractSelectInclude(args) || {}; @@ -257,7 +257,10 @@ export class DelegateProxyHandler extends DefaultPrismaProxyHandler { if (!selectInclude.select) { this.injectBaseIncludeRecursively(model, selectInclude); - await this.injectConcreteIncludeRecursively(model, selectInclude); + + if (includeConcreteFields) { + await this.injectConcreteIncludeRecursively(model, selectInclude); + } } return selectInclude; } @@ -342,19 +345,9 @@ export class DelegateProxyHandler extends DefaultPrismaProxyHandler { for (const subModel of subModels) { // include sub model relation field const subRelationName = this.makeAuxRelationName(subModel); - const includePayload: any = {}; - if (this.options.processIncludeRelationPayload) { - // use the callback in options to process the include payload, so enhancements - // like 'policy' can do extra work (e.g., inject policy rules) - await this.options.processIncludeRelationPayload( - this.prisma, - subModel.name, - includePayload, - this.options, - this.context - ); - } + // create a payload to include the sub model relation + const includePayload = await this.createConcreteRelationIncludePayload(subModel.name); if (selectInclude.select) { selectInclude.include = { [subRelationName]: includePayload, ...selectInclude.select }; @@ -366,6 +359,23 @@ export class DelegateProxyHandler extends DefaultPrismaProxyHandler { } } + private async createConcreteRelationIncludePayload(model: string) { + let result: any = {}; + + if (this.options.processIncludeRelationPayload) { + // use the callback in options to process the include payload, so enhancements + // like 'policy' can do extra work (e.g., inject policy rules) + await this.options.processIncludeRelationPayload(this.prisma, model, result, this.options, this.context); + + // the callback may directly reference fields from polymorphic bases, we need to fix it + // into a proper hierarchy by moving base field references to the base layer relations + const properHierarchy = await this.buildSelectIncludeHierarchy(model, result, false); + result = { ...result, ...properHierarchy }; + } + + return result; + } + // #endregion // #region create diff --git a/packages/schema/package.json b/packages/schema/package.json index 8d4cac6b..80f138e3 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -3,7 +3,7 @@ "publisher": "zenstack", "displayName": "ZenStack Language Tools", "description": "FullStack enhancement for Prisma ORM: seamless integration from database to UI", - "version": "2.6.0", + "version": "2.6.1", "author": { "name": "ZenStack Team" }, diff --git a/packages/sdk/package.json b/packages/sdk/package.json index a9eca651..77dac0c3 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/sdk", - "version": "2.6.0", + "version": "2.6.1", "description": "ZenStack plugin development SDK", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index c52801ab..f90d2ab9 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/server", - "version": "2.6.0", + "version": "2.6.1", "displayName": "ZenStack Server-side Adapters", "description": "ZenStack server-side adapters", "homepage": "https://zenstack.dev", diff --git a/packages/testtools/package.json b/packages/testtools/package.json index b4cdcc00..8d66d0d4 100644 --- a/packages/testtools/package.json +++ b/packages/testtools/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/testtools", - "version": "2.6.0", + "version": "2.6.1", "description": "ZenStack Test Tools", "main": "index.js", "private": true, diff --git a/tests/regression/tests/issue-1734.test.ts b/tests/regression/tests/issue-1734.test.ts new file mode 100644 index 00000000..6cfaff39 --- /dev/null +++ b/tests/regression/tests/issue-1734.test.ts @@ -0,0 +1,107 @@ +import { loadSchema } from '@zenstackhq/testtools'; +describe('issue 1734', () => { + it('regression', async () => { + const { enhance, enhanceRaw, prisma } = await loadSchema( + ` + abstract model Base { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + } + + model Profile extends Base { + displayName String + type String + + @@allow('read', true) + @@delegate(type) + } + + model User extends Profile { + username String @unique + access Access[] + organization Organization[] + } + + model Access extends Base { + user User @relation(fields: [userId], references: [id]) + userId String + + organization Organization @relation(fields: [organizationId], references: [id]) + organizationId String + + manage Boolean @default(false) + + superadmin Boolean @default(false) + + @@unique([userId,organizationId]) + } + + model Organization extends Profile { + owner User @relation(fields: [ownerId], references: [id]) + ownerId String @default(auth().id) + published Boolean @default(false) @allow('read', access?[user == auth()]) + access Access[] + } + + ` + ); + const db = enhance(); + const rootDb = enhanceRaw(prisma, undefined, { + kinds: ['delegate'], + }); + + const user = await rootDb.user.create({ + data: { + username: 'test', + displayName: 'test', + }, + }); + + const organization = await rootDb.organization.create({ + data: { + displayName: 'test', + owner: { + connect: { + id: user.id, + }, + }, + access: { + create: { + user: { + connect: { + id: user.id, + }, + }, + manage: true, + superadmin: true, + }, + }, + }, + }); + + const foundUser = await db.profile.findFirst({ + where: { + id: user.id, + }, + }); + expect(foundUser).toMatchObject(user); + + const foundOrg = await db.profile.findFirst({ + where: { + id: organization.id, + }, + }); + // published field not readable + expect(foundOrg).toMatchObject({ id: organization.id, displayName: 'test', type: 'Organization' }); + expect(foundOrg.published).toBeUndefined(); + + const foundOrg1 = await enhance({ id: user.id }).profile.findFirst({ + where: { + id: organization.id, + }, + }); + // published field readable + expect(foundOrg1.published).not.toBeUndefined(); + }); +});