Skip to content
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

Preview feature feedback: Prisma Client Extensions #16500

Closed
floelhoeffel opened this issue Nov 28, 2022 · 65 comments
Closed

Preview feature feedback: Prisma Client Extensions #16500

floelhoeffel opened this issue Nov 28, 2022 · 65 comments
Assignees
Labels
kind/feedback Issue for gathering feedback. team/client Issue for team Client. topic: clientExtensions topic: extend-client Extending the Prisma Client
Milestone

Comments

@floelhoeffel
Copy link

Please share your feedback about Prisma Client Extensions released in v4.7.0 in this issue.

If you encounter a bug, please open a bug report in this repo.

If the feature is working well for you, please share this in a comment below or leave a 👍 on this issue.
If you have any questions, don't hesitate to ask them in the #prisma-client channel in the Prisma Slack.

@floelhoeffel floelhoeffel added team/client Issue for team Client. topic: extend-client Extending the Prisma Client labels Nov 28, 2022
@eviefp eviefp added the kind/feedback Issue for gathering feedback. label Nov 28, 2022
@keanugrieves
Copy link

Useful feedback? I'm taking half a day just to ponder the implications of this giant leap forward. 🤩

@dodas

This comment was marked as duplicate.

@johnkm516

This comment was marked as resolved.

@jansedlon

This comment was marked as duplicate.

@quanglam2807

This comment was marked as resolved.

@anton-g

This comment was marked as resolved.

@gleuch

This comment was marked as resolved.

@StringKe

This comment was marked as resolved.

@meotimdihia

This comment was marked as resolved.

@JoeKarow

This comment was marked as resolved.

@gleuch

This comment was marked as resolved.

@StringKe

This comment was marked as resolved.

@StringKe

This comment was marked as resolved.

@ymc9

This comment was marked as resolved.

@SevInf

This comment was marked as resolved.

@SevInf
Copy link
Contributor

SevInf commented Dec 5, 2022

@ymc9 at the moment it is not possible for interactive transactions. Could you describe your usecase in more detail? Maybe we could suggest a workaround

@StringKe

This comment was marked as resolved.

@SevInf

This comment was marked as resolved.

@StringKe

This comment was marked as resolved.

@SevInf

This comment was marked as resolved.

@StringKe

This comment was marked as resolved.

@ymc9
Copy link

ymc9 commented Dec 5, 2022

@ymc9 at the moment it is not possible for interactive transactions. Could you describe your usecase in more detail? Maybe we could suggest a workaround

Thanks for the reply, @SevInf .

I'm working on a toolkit (https://zenstack.dev) that extends Prisma with authorization-related stuff. The way I did it before was by generating a wrapper around PrismClient and injecting authorization conditions into queries there. I'm excited to see custom queries extension because it has the potential to solve the problem more elegantly. Unfortunately, for some of the situations, I needed to use interactive transactions to "try" proceeding with an operation and then revert it afterward. That's why I'm asking for interactive transaction support.

Pseudo code:

prisma.$extends({
    query: {
        myModel: {
            async update({ args, query }) {
                prisma.$transaction(async() => {
                    inject(args);
                    const r = await query(args);
                    if (validate(r)) {
                        throw new Error('rejected');
                    }
                    return r;
                });
            },
        },
    },
});

The extension mechanisms look really cool. Thank you!

@alizahid

This comment was marked as duplicate.

@whygee-dev

This comment was marked as resolved.

@AndresRodH

This comment was marked as duplicate.

@jakeleventhal

This comment was marked as duplicate.

@aniravi24

This comment was marked as duplicate.

@andyjy
Copy link
Contributor

andyjy commented Apr 12, 2023

Suggested API change to make things more self-explanatory and reduce potential for confusion:

Prisma.getExtensionContext() => replace with separate methods Prisma.getExtendedClient() and Prisma.getExtendedClientModel()

(Which would essentially do the same as getExtensionContext(), but the former would use some internals to get a reference back from the model to the parent client object where necessary, and the latter throws if called within the client: {...} context where there is no such current model).

Currently: https://www.prisma.io/docs/concepts/components/prisma-client/client-extensions/client#example

client: {
  ...
  async $totalQueries() {
    // Prisma.getExtensionContext(this) in the following block
    // returns the current client instance
   ... await Prisma.getExtensionContext(this).$metrics.json()

vs. imagine if we could write:

client: {
  ...
  async $totalQueries() {
   ... await Prisma.getExtendedClient(this).$metrics.json()

Currently: https://www.prisma.io/docs/concepts/components/prisma-client/client-extensions/model#get-the-current-model-name-at-runtime :

// `ctx` refers to the current model
const ctx = Prisma.getExtensionContext(this)

// `ctx.name` returns the name of the current model
console.log(ctx.name)

vs. imagine if we could write:

const modelName = Prisma.getExtendedClientModel().name

Context / how I reached this suggestion

(Sharing in case the context is useful but leads you to a different conclusion vs. the suggestion above!)

I really struggled to understand the function and intended "job" of getExtensionContext() despite reading the docs about it and inspecting various code examples. Separately, I was fretting about trying to understand the interplay between multiple client extensions - (when) does order of application matter? How to implement extensions such that they don't bypass each-other when they call methods on the client directly? When to use getExtensionContext() vs. use the client parameter from Prisma.defineExtension((client) -> ...). Etc.

It finally clicked when I looked up the implementation of getExtensionContext() in the source .. and actually felt a little cheated(!) to realise it only resolved the this type for the client and didn't have any effect at runtime 🙈 - lots fell into place at that point!

Even then, I didn't fully understand the 2 different use cases for getExtensionContext() when called from different call-sites until part way through writing up this comment. Swapping out for two separate methods could ease the need for comments like the following in the docs:

I initially consisting suggesting a simple rename Prisma.getExtensionContext() => Prisma.resolveExtendedClientThisType() .. but - ugh.

Another gotcha this would resolve is confusing the purpose of Prisma.getExtensionContext().name. - I was observing this in the source of the Prisma Accelerate extension and was certain this was referring to the "name" of the extension itself as defined using the name property. Spent a while trying to figure what the code was doing based on this mistaken assumption. Not having two different things named "name" seems preferable 😁

I initially considered suggesting a rename Prisma.getExtensionContext().name => Prisma.getExtensionContext().model - or (better?) Prisma.getExtensionCurrentModel(). But no longer a trap if replacing getClientExtension() => getExtendedClientModel()

Also worth calling out in the docs how when overriding queries using query: {...} we can simply use the model property from the function parameters. (The current docs are organised in section categorised by outcome - "add custom method to client", "add custom method to model", "override query functions" etc - whereas once progressing beyond the basic examples I instead think in terms of "how do these building blocks work, e.g. now I've learnt about this method, when and how should I use it?")

@beacoding

This comment was marked as resolved.

@Maxime-J

This comment was marked as duplicate.

@millsp
Copy link
Member

millsp commented May 31, 2023

@beacoding could you please create a new issue with some level detail for us to be able to reproduce it?

@millsp
Copy link
Member

millsp commented May 31, 2023

@tacomanator @dodas @linjer I don't think this could be easily done at the moment. However, we have thought about introducing custom serializers/deserializers in the future and have an internal issue tracking this.

@millsp
Copy link
Member

millsp commented Jun 1, 2023

@whygee-dev This is a limitation of client extensions, they happen at runtime, not at the database level. This is similar to what @johnkm516 proposed in #16500 (comment). @johnkm516 could you please open a feature request?

@millsp
Copy link
Member

millsp commented Jun 1, 2023

Thanks @jackpordi @AndresRodH @jakeleventhal @aniravi24 @Faithfinder @alizahid for the feedback. We are now tracking your suggestions in an internal issue.

@millsp
Copy link
Member

millsp commented Jun 1, 2023

@dhmoon91 @SorenHolstHansen while your example is lacking async/await, you should be able to have working types. We have an issue about lower cased models causing type issues, could it be that? If not, can you please create a new issue?

@millsp
Copy link
Member

millsp commented Jun 1, 2023

@hayes I created an example over here for branded types via an extension. Did that work for you? If not, we welcome your feature request.

@millsp
Copy link
Member

millsp commented Jun 1, 2023

Hey @jansedlon, @Toinouze, @zackdotcomputer, @ostkrok and Nest users, we have a tracking issue for this over here #18628. Let us know what you think.

@Mahi would this solve your use-case in combination with advanced type-safety helpers.

@hayes
Copy link
Contributor

hayes commented Jun 1, 2023

thanks @millsp! I did something similar a few moths back, but haven't used it much (was more for users of Pothos than for something I needed myself). That totally works, and prefixing with $ is probably fine, although for something that would be included in a library rather than something that is added manually by a user to their own client, being able to use symbols rather than strings as extension keys might still be a nice feature. (Not sure if this is possible now, haven't tested this in months)

@millsp
Copy link
Member

millsp commented Jun 1, 2023

@SamuelMS please use the Prisma.defineExtension for full type-safety and inference.

@millsp
Copy link
Member

millsp commented Jun 1, 2023

@DylanNWatt You can use Prisma.defineExtension in combination with a factory/constructor pattern. Something like:

const MyExt = (myParam) => Prisma.defineExtension(...)

I created an internal issue to improve the docs.

@johnkm516
Copy link

johnkm516 commented Jun 13, 2023

@millsp

@whygee-dev This is a limitation of client extensions, they happen at runtime, not at the database level. This is similar to what @johnkm516 proposed in #16500 (comment). @johnkm516 could you please open a feature request?

There already is an existing issue #3102

@janpio
Copy link
Member

janpio commented Jun 20, 2023

Thanks everyone, Client Extensions are Generally Available since 4.16.0 🥳
If you have further feedback, you can open a new issue now. Thanks again!

@janpio janpio added this to the 4.16.0 milestone Jun 20, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/feedback Issue for gathering feedback. team/client Issue for team Client. topic: clientExtensions topic: extend-client Extending the Prisma Client
Projects
None yet
Development

No branches or pull requests