Hooks
The most common thing for a plugin to do is to register schema hooks.
The GraphQL hooks allow you to manipulate the argument (specification) that is being passed to the GraphQL object constructors before the objects are constructed. You can think of hooks as wrappers around the original object spec, like this:
const MyType = newWithHooks(GraphQLObjectType, spec);
// is equivalent to:
const MyType = new GraphQLObjectType(hook3(hook2(hook1(spec))));
Plugins declare which hooks they'd like to register as seen in the plugin documentation.
Every hook callback function must synchronously return a value - either the value that it was passed in as the first argument or a derivative of it. Generally we prefer the input object to be mutated for performance reasons.
Hook functions for a given hook name run by default in the order they were
registered, which is why the order of plugins is sometimes relevant, however
plugin authors are encouraged to use the graphile-config
features to declare
the before
/after
for either their plugins or the individual hooks.
Stages of the build process
The hookName
that you register the function for must match
one of the supported hooks.
The general flow is:
- A new Build object with the basic functionality is created
- The
build
hook allows plugins to add new utility methods to thebuild
object itself, or overwrite previously declared ones. - A
Behavior
instance is added to the Build object, and behaviors for all the relevant entities are registered. - The build object is frozen to prevent further modification.
- The
init
hook acts as the setup phase where all possible types should be registered viabuild.registerObjectType
,build.registerUnionType
, etc. - The schema is constructed internally using
newWithHooks(GraphQLSchema, …)
, where thequery
,mutation
andsubscription
root operations are provided by the respective default plugins (e.g.QueryPlugin
). This in turn triggers all the various hooks to be called in a recursive fashion as types, fields, arguments and so on are created. - The
finalize
hook allows plugins to replace the schema that has been built with an alternative (likely derivative) schema, should that be desired. It also opens an opportunity to do something with the built schema (for example log it out) before it is returned.
This hook system makes the library both powerful and flexible, at the expense of
traceability - instead of having a clear declarative import
, the origin of a
called method might be in any of the used plugins, or even multiple ones. See
PostGraphile's Debugging instructions
for how to alleviate this.
Hook callback functions will be called with 3 arguments:
- The input object (e.g. the spec that would be passed to the GraphQLObjectType constructor)
- The
Build
object (see below) - The
Context
object (see below) which contains ascope
property
Build object (Build
)
The Build Object contains a number of helpers and sources of information relevant to the current build of the GraphQL API. If you're in watch mode then every time a new schema is generated a new build object will be used.
Plugins may extend the build
object via the build
hook. Once the build
hook is complete the build object is frozen.
The most commonly used methods are:
build.extend(obj1, obj2)
- returns a new object based on a non-destructive merge ofobj1
andobj2
(will not overwrite keys!) - normally used at the return value for a hookbuild.graphql
- equivalent torequire('graphql')
, but helps ensure GraphQL version clashes do not occurbuild.inflection
- carries all the inflector functions for names
See Build Object for more.
Context object (Context
)
The Context Object contains the information
relevant to the current hook. Most importantly it contains the scope
(an
object based on the third argument passed to newWithHooks
) but it also
contains a number of other useful things. Here's some of the more commonly used
ones:
scope
- an object based on the third argument tonewWithHooks
orfieldWithHooks
; for deeper hooks (such asGraphQLObjectType_fields_field
) the scope from shallower hooks (such asGraphQLObjectType
) are merged in.Self
- only available on deferred hooks (those that are called after the object is created, e.g.GraphQLObjectType:fields
) this is the object that has been created, allowing recursive references.fieldWithHooks(scope, spec)
- onGraphQLObjectType_fields
, used for adding a field if you need access to the field helpers (or want to define a scope)
Namespaces
Properties added to the Build
object or set on the Context.scope
should be
namespaced so that they do not conflict; for example postgraphile
uses the
pg
namespace: pgSql
, pgIntrospection
, isPgTableType
, etc