Postgraphile: Support forwarding non error `raise` statements to the client

Created on 15 Nov 2019  Â·  12Comments  Â·  Source: graphile/postgraphile

I'm submitting a ...

  • [ ] bug report
  • [x] feature request
  • [ ] question

I often write custom mutations via Postgres functions, and would like to return extra context and logging from the function that doesn't easily fit into the return type, which is most often a record or set of records from a table.

A solution would be to return Postgres logs generated via RAISE statements to the client, so the standard mutation payload would include:

{
    "data": ...,
    "errors": ...,
    "logs": [
        {"level": "notice", "message": ...},
        ...
    ]
}

Ideally the order of the logs would match the order in which the raise statements were executed.

This would be possible for DEBUG, LOG, INFO, NOTICE, WARNING – EXEPTION would still include the result in errors.

Happy to work on this with a little guidance!

💅 enhancement

Most helpful comment

One of the great things about this approach is that it can be added to existing functions without changing their signature, which also means it can be added to triggers and what not too. Pretty cool, I think :grin:

yes that's very handy.

idealy from a graphql schema point of vue I would also like to be able to return a union type, eg:

interface RegistrationError {}
type Mutation {
  register(payload: RegistrationInput): User | RegistrationError
}

but I have no idea how this could be done from Postgres without being way too verbose / complex.
on the other hand what I have with the "RaisePlugin" before is enough and very easy to work with

All 12 comments

Take a look at the *:notice debug setting and at how operation hooks adds messages to the mutation payloads... I think combining these two techniques (with something new, not actually using that code, maybe) could work. WDYT?

I experimented a bit with something similar using @graphile/operation-hooks, it’s just playground code but here it is in case someone find something useful in here:

import { Plugin, Context } from "graphile-build"
import { OperationHook, GraphQLResolveInfoWithMessages } from "@graphile/operation-hooks"

const RaisePlugin: Plugin = function(builder) {
  builder.hook("init", (_, build) => {
    build.addOperationHook((fieldContext: Context<any>): OperationHook => {
      const {
        scope: { isRootMutation, isPgCreateMutationField, pgFieldIntrospection }
      } = fieldContext

      // If your hook should only apply to mutations you can do this:
      if (!isRootMutation) return {}

      // Defining the callback up front makes the code easier to read.
      const name = pgFieldIntrospection.name

      return {
        before: [{
          priority: 500,
          callback: (input, _args, context) => {
            context.notices = []
            context.noticesListener = (notice: any) => {
              context.notices.push(notice)
            }
            context.pgClient.on("notice", context.noticesListener)

            return input
          }
        }],
        // @ts-ignore
        after: [{
          priority: 500,
          callback: (input, _args, context, { graphileMeta: { messages } }: GraphQLResolveInfoWithMessages) => {
            context.pgClient.off("notice", context.noticesListener)
            console.log("after", name, input, context.notices, messages)

            messages.push(
              ...context.notices.map((notice: any) => ({
                level: notice.severity,
                message: notice.message,
                path: notice.column ? notice.column.split(".") : null
              }))
            );

            return input
          }
        }]
      }
    })

    return _
  })
}

export default RaisePlugin

Neat; do you think notices is something we want in the GraphQL schema explicitly, or is it more of a development-only thing?

Neat; do you think notices is something we want in the GraphQL schema explicitly, or is it more of a development-only thing?

@benjie it’s not a development-only thing, I'm using that because I only have custom mutations and I wanted to implement validation rules similar to the SQL hooks of @graphile/operation-hooks

I could do that in javascript but I’m trying to avoid having business logic spread across sql and js

Oh interesting; I was discussing extending operation-hooks with notices only a few hours ago with @demianuco. Demian is going to raise the result of our discussion as an issue against the operation-hooks repo, so we could perhaps discuss more of this specific feature there.

In fact your example above would be roughly how I'd implement it; my main concern right now is figuring out how to determine that the notices have been "flushed" so that it's safe to issue the context.pgClient.off("notice",...) call. One of the great things about this approach is that it can be added to existing functions without changing their signature, which also means it can be added to triggers and what not too. Pretty cool, I think :grin:

One of the great things about this approach is that it can be added to existing functions without changing their signature, which also means it can be added to triggers and what not too. Pretty cool, I think :grin:

yes that's very handy.

idealy from a graphql schema point of vue I would also like to be able to return a union type, eg:

interface RegistrationError {}
type Mutation {
  register(payload: RegistrationInput): User | RegistrationError
}

but I have no idea how this could be done from Postgres without being way too verbose / complex.
on the other hand what I have with the "RaisePlugin" before is enough and very easy to work with

I'm pleased to see that I'm not the only one that wants this functionality. I just posted a high level explanation of what I need, and now I realise there is a big overlap here (apologies for the duplicate, please feel free to combine the issues).

I'm happy to help in any way I want, but what I can tell you is that this would be used in production, and heavily. We are planning for this to be the back-bone of all (reactive) notifications from the back-end to the GraphQL clients. An evident use is form validation, which we must do in the back-end because we are going to server many different client platforms (web, mobile, external systems), so we cannot rely on a React validation on the client.

Another use for notifications is the ability to notify the user when a combination of non-evident circumstances are met, ones that do not depend only on the use-case being performed. For example, notifying a user they are running out of credits when they prepare their next session AND they only have limited credit left.

An early implementation of this is in: https://github.com/graphile/operation-hooks/pull/16 - would love some feedback.

@ben-pr-p In particular the above only uses NOTICE and requires an errcode of OPMSG. I think for collecting _all_ noticies/debug/info/etc we should actually do this at the PostGraphile middleware level, and we should discourage using it on production as it might leak information unintentionally. Is this something you just want during development or in production too?

I think that's a pretty good default! I want to use it in production and would appreciate being forced to specify via errcode "I want this to go into production".

Support for this has been merged into @graphile/operation-hooks

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ssomnoremac picture ssomnoremac  Â·  5Comments

jayp picture jayp  Â·  3Comments

5argon picture 5argon  Â·  4Comments

tazsingh picture tazsingh  Â·  3Comments

jwdotjs picture jwdotjs  Â·  5Comments