[BUG] Nested read of polymorphic relation doesn't return base fields
Description and expected behavior
Schema
model Asset {
@@map('assets')
id String @id @default(cuid())
url String
downloadUrl String
pathname String
size BigInt
contentType String
@@delegate(contentType)
}
model Image extends Asset {
@@map('images')
events Event[]
}
model Sound extends Asset {
@@map('sounds')
events Event[]
}
abstract model BaseEvent {
id String @id @default(cuid())
imageId String?
soundId String?
}
model Event extends BaseEvent {
@@map('events')
name String? @trim @length(0, 64, 'name can\'t be more than 64 characters long')
image Image? @relation(fields: [imageId], references: [id], onUpdate: Restrict, onDelete: SetNull)
sound Sound? @relation(fields: [soundId], references: [id], onUpdate: Restrict, onDelete: SetNull)
}
I'm using the rpc API like this:
api/model/event/findUnique
query:
{
"where": { "id": "cm1b10vcg00084h7ffoww8neb" },
"include": {
"image": true,
"sound": true
}
}
Returned result (I removed meta for brevity):
{
"data": {
"id": "cm1b10vcg00084h7ffoww8neb",
"imageId": "cm1kt45rz0001udptc2qwsd6z",
"soundId": "cm1m064qi0001vflqmyhsyrfy",
"name": "Test",
"image": {
"id": "cm1kt45rz0001udptc2qwsd6z"
},
"sound": {
"id": "cm1m064qi0001vflqmyhsyrfy"
}
}
}
I expected image and sound to contain the fields from Asset.
Currently I don't think there is a way I can modify the query to achieve this, so I'm going to make an extension to modify the returned data in the meantime.
Environment (please complete the following information):
- ZenStack version: 2.6.1
- Prisma version: 5.20.0
- Database type: Postgresql
Just incase anyone has this same problem and needs a workaround, I thought i'd share here. This extension returns the basefields for image and sound.
Prisma.defineExtension({
name: 'EventFetchPolyRelations',
query: {
event: {
async findUnique({ args, query }) {
if (args.include?.image === true) {
args.include.image = {
include: {
delegate_aux_asset: true,
},
};
} else if (typeof args.include?.image == 'object') {
args.include.image.include = {
...args.include.image.include,
delegate_aux_asset: true,
};
}
if (args.include?.sound === true) {
args.include.sound = {
include: {
delegate_aux_asset: true,
},
};
} else if (typeof args.include?.sound == 'object') {
args.include.sound.include = {
...args.include.sound.include,
delegate_aux_asset: true,
};
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const mergeAuxResult = (result: any): any => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
const delegate_aux_asset = result.delegate_aux_asset;
if (delegate_aux_asset == null) {
return result;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const mergedResult = {
...result,
...delegate_aux_asset,
};
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
delete mergedResult.delegate_aux_asset;
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
delete mergedResult.contentType;
return mergedResult;
};
const result = await query(args);
if (result?.image) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
result.image = mergeAuxResult(result.image);
}
if (result?.sound) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
result.sound = mergeAuxResult(result.sound);
}
return result;
},
},
},
});
API now returns this:
{
"data": {
"id": "cm1b10vcg00084h7ffoww8neb",
"imageId": "cm1kt45rz0001udptc2qwsd6z",
"soundId": "cm1m064qi0001vflqmyhsyrfy",
"name": "Test",
"image": {
"id": "cm1kt45rz0001udptc2qwsd6z",
"url": "/api/asset/serve/test0b7104fc-849f-4be3-b706-700aa32cd021.png",
"downloadUrl": "/api/asset/serve/test0b7104fc-849f-4be3-b706-700aa32cd021.png",
"pathname": "test0b7104fc-849f-4be3-b706-700aa32cd021.png",
"size": "52593",
},
"sound": {
"id": "cm1m064qi0001vflqmyhsyrfy",
"url": "/api/asset/serve/sample465d60c02-cafb-41e7-ba90-f19b8f7f9fc2.m4a",
"downloadUrl": "/api/asset/serve/sample465d60c02-cafb-41e7-ba90-f19b8f7f9fc2.m4a",
"pathname": "cm1b10hh800014h7f94q6xrki/sample4.m4a",
"size": "4028043",
}
}
}
OK so plot twist, seems that this was actually caused by my setup of plugins:
generator client {
provider = 'prisma-client-js'
output = '../prisma/client'
previewFeatures = ['relationJoins']
}
datasource db {
provider = 'postgresql'
url = env('DATABASE_URL')
directUrl = env('DATABASE_URL_NON_POOLING')
}
plugin prisma {
provider = '@core/prisma'
output = '../prisma/schema.prisma'
format = true
}
plugin hooks {
provider = '@zenstackhq/tanstack-query'
target = 'react'
version = 'v5'
output = '../hooks'
useSuperJson = true
}
plugin zod {
provider = '@core/zod'
output = '../zod'
compile = false
}
plugin enhancer {
provider = '@core/enhancer'
generatePermissionChecker = true
compile = true
}
I had been importing my base client from the output of the client generator. This always worked fine until I started using Polymorphic models.
If I comment out the output of the generator:
generator client {
provider = 'prisma-client-js'
// output = '../prisma/client'
previewFeatures = ['relationJoins']
}
and Import my client from @prisma/client, my above issue doesn't happen anymore (I removed my extension as well).
I eventually discovered this when I went to use one of the hooks, and the args didn't let me use an include for image or sound. The type the hooks were including didn't have the fields image or sound but the generated prisma client did, so there was some mismatch there.
Is this expected?
The docs do mention about getting the correct types, but nothing about the runtime client::
https://zenstack.dev/docs/guides/polymorphism#polymorphic-typescript-types
Hi @onimitch , my apologies for the late response. I tried with v2.7.4 and it seems to work (with or without a custom prisma client output location). It may have been fixed by a previous change. Could you upgrade and try again? Thanks!
Just re-tested in 2.8.0 with custom client location re-enabled in my code and everything seems fine, thanks!