Skip to content

[Feature Request] Custom RPC db operations #1387

Open
@Ataraxy

Description

@Ataraxy

Would it be possible to allow for custom db ops so that they can be used with the RPC client? It seems that they're hard coded.

For example, If I want to create a prisma client extension that will do a findManyAndCount on a model, I would like to be able to also use it via RPC.

const findManyAndCount = {
  name: 'findManyAndCount',
  model: {
    $allModels: {
      findManyAndCount<Model, Args>(
        this: Model,
        args: Prisma.Exact<Args, Prisma.Args<Model, 'findMany'>>
      ): Promise<
        [
          Prisma.Result<Model, Args, 'findMany'>,
          number,
          Args extends { take: number } ? number : undefined
        ]
      > {
        return prisma.$transaction([
          (this as any).findMany(args),
          (this as any).count({ where: (args as any).where }),
        ]) as any;
      },
    },
  },
};

Though as an aside I'm unsure if this above extension would even work with an enhanced client but that's besides the point.

This example could of course be done with two separate queries client side or if something like a zenstack transactions api is put in such as this thread is discussing: #1203

Still such a feature could prove to be useful for other model related extensions.

Maybe it can be as simple as adding an options object that can be passed to the handler (perhaps a customOps property that is an array) and if the op exists in it then it will allow it through and not throw an error. It may also need to skip the part right after that validates the zodschema if this were the case.

switch (dbOp) {
case 'create':
case 'createMany':
case 'upsert':
if (method !== 'POST') {
return {
status: 400,
body: this.makeError('invalid request method, only POST is supported'),
};
}
if (!requestBody) {
return { status: 400, body: this.makeError('missing request body') };
}
args = requestBody;
// TODO: upsert's status code should be conditional
resCode = 201;
break;
case 'findFirst':
case 'findUnique':
case 'findMany':
case 'aggregate':
case 'groupBy':
case 'count':
if (method !== 'GET') {
return {
status: 400,
body: this.makeError('invalid request method, only GET is supported'),
};
}
try {
args = query?.q ? this.unmarshalQ(query.q as string, query.meta as string | undefined) : {};
} catch {
return { status: 400, body: this.makeError('invalid "q" query parameter') };
}
break;
case 'update':
case 'updateMany':
if (method !== 'PUT' && method !== 'PATCH') {
return {
status: 400,
body: this.makeError('invalid request method, only PUT AND PATCH are supported'),
};
}
if (!requestBody) {
return { status: 400, body: this.makeError('missing request body') };
}
args = requestBody;
break;
case 'delete':
case 'deleteMany':
if (method !== 'DELETE') {
return {
status: 400,
body: this.makeError('invalid request method, only DELETE is supported'),
};
}
try {
args = query?.q ? this.unmarshalQ(query.q as string, query.meta as string | undefined) : {};
} catch {
return { status: 400, body: this.makeError('invalid "q" query parameter') };
}
break;
default:
return { status: 400, body: this.makeError('invalid operation: ' + op) };
}
const { error, zodErrors, data: parsedArgs } = await this.processRequestPayload(args, model, dbOp, zodSchemas);

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions