Plugin Lifecycle
Plugins are executed in order of their usage, and inject functionality serially, so aim to keep your plugins simple and standalone as much as possible.
The before
functions are called in order, and then the after
function of each hook will be
called in the same order when the phase has been done.
The basic API allows you to keep the JS closure between before
and after
, for example:
const myPlugin = {
onParse({ params }) {
// This is the before function
return ({ result }) => {
// The is the after function,
// JS Closure allow me to access the params here as well
}
}
}
All plugin lifecycle methods are executed in FIFO order (First-in, First-out) to make timing more consistent.
The Improved context
While in regular GraphQL executions, the context
is built right before execute
happens, in
envelop
we allow plugin developers to take part it that context from all phases.
While calling getEnveloped
function (the result of envelop({ plugins: [ ... ]}))
, you can pass
any custom object that will be the base for your GraphQL execution context
.
In most cases, you’ll pass the incoming HTTP request (or, just the relevant parts of it) to make it available for the plugins you use:
import * as GraphQLJS from 'graphql'
import { envelop, useEngine } from '@envelop/core'
const getEnveloped = envelop({
plugins: [
useEngine(GraphQLJS)
// ... plugins
]
})
myHttpServer.on('request', async req => {
const { parse, validate, contextFactory, execute, schema } = getEnveloped({ req })
// ...
})
Plugins might also use this context
to keep a contextual reference for objects they might need
across stages.
So for example, if you wish to make some data from parse
stage available for you in execute
phase, you can extend the context while running onParse
and then access that data in onExecute
:
const myPlugin = {
onParse({ extendContext }) {
extendContext({
myVar: 'test'
})
},
onExecute({ args }) {
const myVar = args.contextValue.myVar
}
}
Plugins API
You can find the complete signature for plugins API here.
onPluginInit(api)
This method is called only once when the plugin is being initialized. This hook is triggered only once and is useful for setting up the plugin.
API
setSchema
- sets the initial schema to be used.plugins
- list of all other loaded plugins.addPlugin
- adds a plugin to the list of plugins.
onEnveloped(api)
This method is called every time getEnveloped
is called, and per request. This is useful if you
need to do some setup right before an incoming execution flow.
API
setSchema
- sets the initial schema to be used.context
- the initial context object passed togetEnveloped
, including the context built by plugins that ran before.extendContext
- extends the initial context object with additional fields.
onParse(api)
Called every time an operation is being executed.
before
params
- the original parameters passes toparse
function.parseFn
- the currentparse
function. By default, it’s the one fromgraphql
package.setParseFn
- Replaces theparse
function with a custom function.setParsedDocument
- sets the parse result. Setting this will skip callingparse
for the executed operation.context
- the context object built so far by other plugins.extendContext
- extends the context object with additional fields.
after
result
- the resultDocumentNode
of the parsing function.replaceParseResult
- replaces the parsed result.context
- the context object built so far by other plugins.extendContext
- extends the context object with additional fields.
onValidate(api)
Called every time an operation is being executed.
before
params
- the original parameters passes tovalidate
function.validateFn
- the currentvalidate
function. By default, it’s the one fromgraphql
package.setValidationFn
- Replaces thevalidate
function with a custom function.addValidationRule
- Adds a validation rule to the list of default validation rules (as defined ingraphql
package).setResult
- sets the validation result. Setting this will skip callingvalidate
for the executed operation.context
- the context object built so far by other plugins.extendContext
- extends the context object with additional fields.
after
valid
- A boolean indicates if the validation passed.result
-null
in case of a valid document, otherwise an array of validation errors.context
- the context object built so far by other plugins.extendContext
- extends the context object with additional fields.
onContext(api)
Called every time an operation is being executed. Used for building the GraphQL context incrementally.
before
context
- the context object built so far by other plugins.extendContext
- extends the context object with additional fields.
after
context
- the eventual built context, by all plugins.extendContext
- extends the context object with additional fields.
onExecute(api)
Called every time an operation is being executed. The return value of this function is extended and allows you to hook into resolver calls if needed.
before
args
- arguments pass toexecute
(contains the document, variables and everything else that is needed for executing the GraphQL operation)executeFn
- theexecute
function to use, by default it’s the one fromgraphql
package.setExecuteFn
- replaces theexecute
function.setResultAndStopExecution
- sets the result of the execution immediately. Calling this function will stop execution.extendContext
- allow you to extend the context before executing the operation.
You can return an object
from that function, with the following fields:
onExecuteDone
Triggered when the execution of the operation is done.
result
- the execution result, or AsyncIterable in case of stream response.setResult
- replaces the result. can either be with data or errors.
Since envelop
aims to support stream responses (for live queries, or @stream/@defer
), the
result
might be an AsyncIterable
of multiple execution results.
If you wish your plugin to support this, please make sure to use the
handleStreamOrSingleExecutionResult
helper, like that:
import { handleStreamOrSingleExecutionResult, Plugin } from '@envelop/types'
const myPlugin = (): Plugin => {
return {
onExecute({ args }) {
return {
onExecuteDone(payload) {
return handleStreamOrSingleExecutionResult(payload, ({ result, setResult }) => {
// Here you can access the result, and modify it with setResult if needed
})
}
}
}
}
}
Alternately, if you don’t need to support stream responses, you can use the isAsyncIterable
function as a type-guard:
import { isAsyncIterable, Plugin } from '@envelop/types'
const myPlugin = (): Plugin => {
return {
onExecute({ args }) {
return {
onExecuteDone({ result, setResult }) {
if (!isAsyncIterable(result)) return
// Here you can access result, and modify it with setResult if needed
}
}
}
}
}
onSubscribe(api)
Called every time a subscription
operation is being executed. The return value of this function is
extended, and allow you to hooks into resolver calls if needed.
before
args
- arguments passes tosubscribe
(contains the document, variables and everything else that needed for executing the GraphQL operation)subscribeFn
- thesubscribe
function to use, by default it’s the one fromgraphql
package.setSubscribeFn
- replaces thesubscribe
function.extendContext
- allow you to extend the context before executing the operation.setResultAndStopExecution
- sets the result of the execution immediately. Calling this function will stop execution.
You can return an object
from that function, with the following fields:
onSubscribeResult
Triggered when subscription result is being emitted from a subscription
execution.
result
- the subscription result.setResult
- replaces the result. can either be with data or errors.
onSchemaChange(api)
envelop
allow you to manage a reference to a schema, that you can later access and use within your
server.
Some plugins (like gateway implementations) could potentially change the schema while running, so
envelop
will trigger that event in case of a schema change after all plugins have initialized.
API
schema
- theGraphQLSchema
replaceSchema
- replaces the schema. Calling this will triggeronSchemaChange
for all other plugins (except for the one that initiated the change);