zenstack icon indicating copy to clipboard operation
zenstack copied to clipboard

[BUG] Nested read of polymorphic relation doesn't return base fields

Open onimitch opened this issue 1 year ago • 2 comments

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

onimitch avatar Sep 30 '24 11:09 onimitch

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",
        }
    }
}

onimitch avatar Sep 30 '24 13:09 onimitch

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

onimitch avatar Sep 30 '24 14:09 onimitch

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!

ymc9 avatar Nov 01 '24 19:11 ymc9

Just re-tested in 2.8.0 with custom client location re-enabled in my code and everything seems fine, thanks!

onimitch avatar Nov 06 '24 11:11 onimitch