envelop icon indicating copy to clipboard operation
envelop copied to clipboard

feat: `perform` function that does parsing, validation, context assembly and execution/subscription

Open enisdenjo opened this issue 2 years ago • 11 comments

Closes #1493

Necessary for #1490

perform function usage

const { perform } = getEnveloped({ initial: 'ctx' });

const { operationName, query, variables } = JSON.parse(payload);

// Will never throw GraphQL errors
const result = await perform({ operationName, query, variables }, { extension: 'ctx' });

// Result will contain errors for parsing/validation issues or the execution/subscription result(s)
return JSON.stringify(result);

onPerform and onPerformDone plugin usage

import { Plugin } from '@envelop/core';

function usePerform(): Plugin {
  return {
    onPerform({ context, extendContext, params, setParams, setResult }) {
      // before performing

      // context: assembled context
      // extendContext: extend assembled context
      // params: GraphQL parameters passed to perform
      // setParams: replace GraphQL parameters before performing
      // setResult: sets an early result for immediate response

      return {
        onPerformDone({ result, setResult }) {
          // after performing

          // result: perform result, contains errors or result(s)
          // setResult: replace the result
        },
      };
    },
  };
}

TODO

  • [x] Documentation

enisdenjo avatar Sep 09 '22 17:09 enisdenjo

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Updated
envelop ✅ Ready (Inspect) Visit Preview Sep 15, 2022 at 1:35PM (UTC)

vercel[bot] avatar Sep 09 '22 17:09 vercel[bot]

🦋 Changeset detected

Latest commit: 01669df43b17b650668254a2035d7f0890489b02

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 35 packages
Name Type
@envelop/core Minor
@envelop/testing Major
@envelop/apollo-datasources Major
@envelop/apollo-federation Major
@envelop/apollo-server-errors Major
@envelop/apollo-tracing Major
@envelop/auth0 Major
@envelop/dataloader Major
@envelop/depth-limit Major
@envelop/disable-introspection Major
@envelop/execute-subscription-event Major
@envelop/extended-validation Major
@envelop/filter-operation-type Major
@envelop/fragment-arguments Major
@envelop/generic-auth Major
@envelop/graphql-jit Major
@envelop/graphql-middleware Major
@envelop/graphql-modules Major
@envelop/immediate-introspection Major
@envelop/live-query Major
@envelop/newrelic Major
@envelop/on-resolve Major
@envelop/opentelemetry Major
@envelop/operation-field-permissions Major
@envelop/parser-cache Major
@envelop/persisted-operations Major
@envelop/preload-assets Major
@envelop/prometheus Major
@envelop/rate-limiter Major
@envelop/resource-limitations Major
@envelop/response-cache Major
@envelop/sentry Major
@envelop/statsd Major
@envelop/validation-cache Major
@envelop/response-cache-redis Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

changeset-bot[bot] avatar Sep 09 '22 17:09 changeset-bot[bot]

diff --git a/website/algolia-lockfile.json b/website/algolia-lockfile.json
index d5281857..61eb96b1 100644
--- a/website/algolia-lockfile.json
+++ b/website/algolia-lockfile.json
@@ -361,6 +361,11 @@
                 "children": [],
                 "title": "`onSchemaChange(api)`",
                 "anchor": "onschemachangeapi"
+              },
+              {
+                "children": [],
+                "title": "`onPerform(api)`",
+                "anchor": "onperformapi"
               }
             ],
             "title": "`onPluginInit(api)`",

github-actions[bot] avatar Sep 09 '22 17:09 github-actions[bot]

🚀 Snapshot Release (alpha)

The latest changes of this PR are available as alpha on npm (based on the declared changesets):

Package Version Info
@envelop/core 3.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/apollo-datasources 2.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/apollo-federation 3.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/apollo-server-errors 4.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/apollo-tracing 4.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/auth0 4.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/dataloader 4.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/depth-limit 2.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/disable-introspection 4.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/execute-subscription-event 3.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/extended-validation 2.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/filter-operation-type 4.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/fragment-arguments 4.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/generic-auth 5.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/graphql-jit 5.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/graphql-middleware 4.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/graphql-modules 4.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/immediate-introspection 1.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/live-query 5.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/newrelic 5.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/on-resolve 2.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/opentelemetry 4.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/operation-field-permissions 4.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/parser-cache 5.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/persisted-operations 5.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/preload-assets 4.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/prometheus 7.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/rate-limiter 4.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/resource-limitations 3.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/response-cache 4.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/response-cache-redis 2.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/sentry 4.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/statsd 3.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/validation-cache 5.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/testing 5.0.0-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎
@envelop/types 2.4.1-alpha-20220909172432-a082c09b npm ↗︎ unpkg ↗︎

github-actions[bot] avatar Sep 09 '22 17:09 github-actions[bot]

❌ Benchmark Failed

Failed assertions detected: some GraphQL operations included in the loadtest are failing.

If the performance regression is expected, please increase the failing threshold.

     ✓ no_errors
     ✗ expected_result
      ↳  0% — ✓ 0 / ✗ 304912

     checks.............................................: 50.00%  ✓ 304912    ✗ 304912
     data_received......................................: 85 MB   564 kB/s
     data_sent..........................................: 132 MB  880 kB/s
     envelop_total......................................: avg=0s      min=0s       med=0s     max=0s       p(90)=0s     p(95)=0s    
     ✓ { mode:envelop-cache-jit }.......................: avg=0s      min=0s       med=0s     max=0s       p(90)=0s     p(95)=0s    
     ✓ { mode:envelop-just-cache }......................: avg=0s      min=0s       med=0s     max=0s       p(90)=0s     p(95)=0s    
     ✓ { mode:graphql-js }..............................: avg=0s      min=0s       med=0s     max=0s       p(90)=0s     p(95)=0s    
     ✓ { mode:prom-tracing }............................: avg=0s      min=0s       med=0s     max=0s       p(90)=0s     p(95)=0s    
     http_req_blocked...................................: avg=2.6µs   min=600ns    med=1.7µs  max=12.01ms  p(90)=2.2µs  p(95)=2.8µs 
     http_req_connecting................................: avg=243ns   min=0s       med=0s     max=11.05ms  p(90)=0s     p(95)=0s    
     http_req_duration..................................: avg=3.85ms  min=139.4µs  med=4.06ms max=164.41ms p(90)=5.99ms p(95)=7.25ms
     ✓ { mode:envelop-cache-and-no-internal-tracing }...: avg=3.48ms  min=142.3µs  med=3.59ms max=49.93ms  p(90)=5.09ms p(95)=6.33ms
     ✓ { mode:envelop-cache-jit }.......................: avg=3.54ms  min=154.2µs  med=3.79ms max=34.91ms  p(90)=5.21ms p(95)=6.51ms
     ✓ { mode:envelop-just-cache }......................: avg=3.62ms  min=147.3µs  med=3.78ms max=164.41ms p(90)=5.31ms p(95)=6.79ms
     ✓ { mode:graphql-js }..............................: avg=5.56ms  min=256.21µs med=5.67ms max=44.5ms   p(90)=7.57ms p(95)=9.11ms
     ✓ { mode:prom-tracing }............................: avg=3.46ms  min=139.4µs  med=3.54ms max=32.98ms  p(90)=5.24ms p(95)=6.6ms 
     http_req_failed....................................: 100.00% ✓ 304912    ✗ 0     
     http_req_receiving.................................: avg=39.15µs min=7.1µs    med=27.4µs max=17.81ms  p(90)=34.1µs p(95)=40.8µs
     http_req_sending...................................: avg=23.05µs min=4.4µs    med=10.4µs max=19.7ms   p(90)=14.3µs p(95)=19.9µs
     http_req_tls_handshaking...........................: avg=0s      min=0s       med=0s     max=0s       p(90)=0s     p(95)=0s    
     http_req_waiting...................................: avg=3.79ms  min=108.7µs  med=4ms    max=164.38ms p(90)=5.93ms p(95)=7.13ms
     http_reqs..........................................: 304912  2032.6089/s
     iteration_duration.................................: avg=4.94ms  min=232.41µs med=4.33ms max=988.66ms p(90)=6.34ms p(95)=7.98ms
     iterations.........................................: 304912  2032.6089/s
     vus................................................: 10      min=10      max=18  
     vus_max............................................: 20      min=20      max=20  

theguild-bot avatar Sep 09 '22 17:09 theguild-bot

How do I use the onPerformDone with Apollo Server?

I've changed the Apollo Server example with https://github.com/n1ru4l/envelop/pull/1515/commits/827fb179a91e8ec89b7c8460c09bc650eb112b72.

The examples need to be updated.

Not all examples support the perform function. Most of them require the isolated GraphQL functions.

I updated those that have support, simple-http and apollo-server.

Should perform be used within the testkit/replace the testkit?

I thought about this too, but didn't want to overdo this PR - thought about making a separate one. But, if you want, I can pack it into here.

enisdenjo avatar Sep 13 '22 11:09 enisdenjo

I thought about this too, but didn't want to overdo this PR - thought about making a separate one. But, if you want, I can pack it here.

I think this should be in here, as onPerformDone seems to now be mandatory for some plugins, otherwise, we need to mark what phases each plugin hooks into explicitly in order to let people know what to expect.

I think teaching people that they have to call this (additional?) function with the result is the easiest way.

n1ru4l avatar Sep 13 '22 12:09 n1ru4l

I added the perform function to the testkit and refactored all compatible tests to use it. Things to note:

  • Replaced all usages of execute with perform
  • mockPhase cannot be used with perform; however, no tests use it - does the testkit even need it? Other users?
  • Since perform catches errors and puts them inside the result, the Auth0 plugin test changed a bit: see here.

There is still no way for applying the onPerformDone logic when not using the perform function. Which makes it impossible for users that do not use perform to leverage plugins that use the onPerformDone hook 🤔 .

@n1ru4l that is the thing, if you don't use perform you cannot use onPerform (onPerformDone neither because it's returned from onPerform). Exactly because of this, I put onPerformDone in onPerform instead of it being a top level hook.

Perform is the only function in envelop that wraps parsing and validation errors - you must use it to leverage plugins using these hooks. The problem is described in #1493.

Do you have some alternatives maybe?

enisdenjo avatar Sep 15 '22 09:09 enisdenjo

If I understand well what n1ru4l says, it's not a matter of using or not perform. It's more about plugin developers not being able to use this hook since it can't know in advance if there users will use perform or the original functions. This makes the hook difficult to use and probably not very usefull for most plugins that aims to be reused and distributed as a package.

EmrysMyrddin avatar Sep 16 '22 15:09 EmrysMyrddin

It's more about plugin developers not being able to use this hook since it can't know in advance if there users will use perform or the original functions. This makes the hook difficult to use and probably not very usefull for most plugins that aims to be reused and distributed as a package.

Yes, I understand this too. But I don't see a lot of feasible solutions, maybe one of them would be: log a warning (throw error? too harsh?) if a plugin uses the perform hooks but the invoker didn't use perform. 🤔

enisdenjo avatar Sep 16 '22 16:09 enisdenjo

What if we augment each gql function so that it calls onPerformDone so that plugins still get some result manipulation capabilities?

  • parse - catch throws, format as exec result and run through hooks. If the invoker is parse from getEnvloped, take first error from the exec result and bubble it instead
  • validate - caught or returned errors are formatted as exec result and ran through hooks. If invoker is from getEnveloped, return just the errors from the exec result

This way, users can notice when their results are incomplete with respectful plugins and read about how to fix it - the plugins should state this.

However, if the plugin doesn't expect changing full exec results for parsing and validating - nobody will notice.

Still there is this question: how to invoke onPerform? Or alternatively inline onPerformDone?

enisdenjo avatar Sep 16 '22 19:09 enisdenjo