Skip to content

fix(delegate): base fields not properly wrapped inside relation layer when injected from policies #1736

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "zenstack-monorepo",
"version": "2.6.0",
"version": "2.6.1",
"description": "",
"scripts": {
"build": "pnpm -r build",
Expand Down
45 changes: 34 additions & 11 deletions packages/ide/jetbrains/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
2 changes: 1 addition & 1 deletion packages/ide/jetbrains/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ plugins {
}

group = "dev.zenstack"
version = "2.6.0"
version = "2.6.1"

repositories {
mavenCentral()
Expand Down
2 changes: 1 addition & 1 deletion packages/ide/jetbrains/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion packages/language/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion packages/misc/redwood/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/openapi/package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/swr/package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/tanstack-query/package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/trpc/package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
38 changes: 24 additions & 14 deletions packages/runtime/src/enhancements/node/delegate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) || {};

Expand All @@ -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;
}
Expand Down Expand Up @@ -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 };
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/schema/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/sdk",
"version": "2.6.0",
"version": "2.6.1",
"description": "ZenStack plugin development SDK",
"main": "index.js",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/server/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion packages/testtools/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/testtools",
"version": "2.6.0",
"version": "2.6.1",
"description": "ZenStack Test Tools",
"main": "index.js",
"private": true,
Expand Down
107 changes: 107 additions & 0 deletions tests/regression/tests/issue-1734.test.ts
Original file line number Diff line number Diff line change
@@ -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();
});
});
Loading