paljs / prisma-tools

Prisma tools to help you generate CRUD system for GraphQL servers
https://paljs.com
MIT License
683 stars 54 forks source link

Consider allowing defaultFields to be invokable #180

Closed terion-name closed 3 years ago

terion-name commented 3 years ago

https://github.com/paljs/prisma-tools/blob/7060f4e4f840ce4b195bd9ea6d667a9c95e5758b/packages/plugins/src/select.ts#L197

It is an often situation: let's say you have a computable field, that is not present in schema:

{
    typeDefs: gql`
        extend type User {
            prefixedId: String
        }
    `,
    resolvers: {
        User: {
            prefixedId: ({id}) => `user-${id}`
        }
    }
}

This field relies on other fields presence and requires them (in this case id field).

One can add to defaultFields {User: {id: true}}, but this is rude: why to fetch excessive data when it is not needed? With such things as id it can be acceptable, but sometimes you need more complex ones and why to burn requests to DB without need?

In Yoga server there was such thing like field resolver represented as an object {resolve, fragment}, and this signalled the server to include the fragment if this field is queried. Here this is not appliable, but we can do something this:

// this is how I've done it for myself
import {PrismaSelect as PalJSPrismaSelect} from "@paljs/plugins";

export default class PrismaSelect extends PalJSPrismaSelect {
    filterBy(modelName: string, selectObject: any) {
        const model = this.model(modelName);
        if (model && typeof selectObject === 'object') {
            let defaultFields = {};
            if (this.defaultFields && this.defaultFields[modelName]) {
///////////////////////
// --------->>> here is the magic
///////////////////////
                defaultFields = typeof this.defaultFields[modelName] === "function"
                 ? this.defaultFields[modelName](selectObject)
                 : this.defaultFields[modelName];
            }
            const filteredObject = {
                ...selectObject,
                select: { ...defaultFields },
            };
            Object.keys(selectObject.select).forEach((key) => {
                const field = this.field(key, model);
                if (field) {
                    if (field.kind !== 'object') {
                        filteredObject.select[key] = true;
                    } else {
                        const subModelFilter = this.filterBy(
                            field.type,
                            selectObject.select[key],
                        );
                        if (Object.keys(subModelFilter.select).length > 0) {
                            filteredObject.select[key] = subModelFilter;
                        }
                    }
                }
            });
            return filteredObject;
        } else {
            return selectObject;
        }
    }
}

This tiny trick allows us to do the following:

const select = new PrismaSelect(info, {
        dmmf: datamodel.dmmf,
        defaultFields: {
            User({select}) {
                // if computed field is queried — get the required
                if (select.prefixedId) {
                    return {id: true}
                }
            }
        }
    });

This is still not as flexible as fragments in yoga (where you could add a fragment even for other models), but will work for the most scenarios

AhmedElywa commented 3 years ago

It's good idea