You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I often post useful answers in various locations (chat, issues, PRs, StackOverflow, etc). It'd be great if these answers were rewritten and inserted into the documentation for easy reference. Some might make sense in an FAQ format, some might make sense on the relevant pages of the documentation.
This thread collects some of these responses with the intention of later going through them and putting them into the docs - of course if someone wants to take on doing this that would be very welcome!
One response per message, please; each entry should:
have a checkbox - [ ] Added to docs to indicate completion
have a source URL or description (e.g. Discord, #help-and-support, 20th December 2019 @ 3:17pm UTC (please include timezone!))
have a copy of the original text unless it was posted on graphile GitHub in which case a link to the comment sufficies
I thought hooks were just called when schema entities are created
Correct.
didn't really understand what the extend stuff does
extend(a, {b: 2}) is roughly equivalent to Object.assign(a, {b: 2}) but with a tonne of extra built in protections and logging when fields conflict.
so the plugins are ran on a builder for an object before it is actually built (and thereby can overwrite each others methods)?
We start with an inflectors object that's something like {pluralize, singularize, camelCase}.
Hooks get called on the result of each other to calculate the final value, so you can think of it as:
constinitialInflectors={pluralize, singularize, camelCase};// Supplied by graphile-buildconstfinalInflectors=hook3(// Your Hook Herehook2(// from @graphile-contrib/pg-simplify-inflectorhook1(// from graphile-build-pginitialInflectors)));
In this situation you might have that hook1 is the graphile-build-pg hook that adds all the PG-specific inflectors (graphile-build doesn't care about PG), hook2 is the simplify inflector's hook, so you need to implement hook3 to augment that with your mods.
But who then calls these methods in the end, where is that documented?
graphile-build (aka Graphile Engine) is responsible for managing the calling of all the hooks. They all execute synchronously. They start here:
Especially with makeAddInflectorsPlugin I am lost, as the "inflection" hook is not documented anywhere…
You shouldn't need to even worry about any of this, the makeAddInflectorsPlugin (documented here) should completely abstract the whole hook system so you only need to worry about writing your override:
thanks for making such an awesome library! We've been using it in production for over a year now. We're starting to experiment with GraphQL subscriptions, but would like to be able to add subscriptions to non-Postgres things... for example, to consume a stream of Mixpanel events from Mixpanel's API and then route those back through our GraphQL API to a web client. Do you have examples or tips of how to use Postgraphile (with Koa) to serve GraphQL subscriptions without using the LISTEN/NOTIFY pg-pubsub library? (https://www.npmjs.com/package/@graphile/pg-pubsub)
We don't have any examples, but it's definitely possible.
You can implement it with makeExtendSchemaPlugin; the difference is the shape of the resolvers would change:
You can use inflectors from build.inflection; they typically take introspection results as input, you can get those from build.pgIntrospectionResultsByKind. For example, to map a column name to what it would be called in GraphQL, you could do something like:
Jest test to ensure RLS is enabled on all tables (from 2017).
import{withRootDb}from'../test_helpers';test('RLS is enabled for all tables in mindtree_public',()=>withRootDb(async(client)=>{const{ rows }=awaitclient.query(` WITH nsp AS ( SELECT oid FROM pg_namespace WHERE nspname = 'mindtree_public' ) SELECT relname AS "table" FROM pg_class INNER JOIN nsp ON (nsp.oid = pg_class.relnamespace) WHERE relkind = 'r' AND relrowsecurity IS NOT DISTINCT FROM FALSE `);expect(rows).toHaveLength(0);}));
@whollacsek:
By the way, I've always wondered if there's a case against using simpleCollections: "only"? Currently I have it disabled but I'm starting to find tedious to always have to go through nodes when passing data down to my React components.
@benjie:
Basically it comes down to these questions:
Do you need cursor pagination?
Do you need totalCount
Do you need aggregates to be performed over the collection?
If the answer to any of these questions is "yes" or "I will" or "I might" it's probably best to be a connection.
anxifer via Discord
Hi. I've defined a makeExtendSchemaPlugin and want to call graphql inside of my custom mutation which is defined in the plugin. The execute command on the graphql object requires to provide a DocumentNode Element. Is there some easy possibility to access my already existing mutations / queries and to pass that info to my graphql object?
You can indeed issue GraphQL requests from various contexts, including within a resolver. To do so you need the following:
Access to the graphql function from the graphql module
In a PostGraphile plugin, if you have access to the build object (which you usually will), you should get this from build.graphql.graphql
Failing that, you can import { graphql } from 'graphql' or const { graphql } = require('graphql'), but this has caveats.
A reference to the GraphQL schema object. You can get this from many sources:
in a resolver, you should extract it from info.schema
if you have access to the PostGraphile middleware, you can issue await postgraphileMiddleware.getGqlSchema()
if you don't need the PostGraphile middleware, you can use await createPostGraphileSchema(...) - see schema only usage - do this once and cache it because it's expensive to compute
A GraphQL operation (aka query, but includes mutations, subscriptions) to execute; this can be a string or an AST
The variables to feed to the operation (if necessary)
A valid GraphQL context for PostGraphile
inside a resolver, you can just pass the resolvers context straight through
in other situations, have a look at withPostGraphileContext in the schema only usage
Issuing a GraphQL operation from inside a resolver example:
/* * Assuming you have access to a `build` object, e.g. inside a * `makeExtendSchemaPlugin`, you can extract the `graphql` function * from the `graphql` library here like so: */const{graphql: { graphql }}=build;/* * Failing the above: `import { graphql } from 'graphql';` but beware of * duplicate `graphql` modules in your `node_modules` causing issues. */functionmyResolver(parent,args,context,info){// Whatever GraphQL query you wish to issue:constdocument=/* GraphQL */` query MyQuery($userId: Int!) { userById(id: $userId) { username } } `;// The name of the operation in your query document (optional)constoperationName='MyQuery';// The variables for the queryconstvariables={userId: args.userId};const{ data, errors }=awaitgraphql(info.schema,document,null,context,variables,operationName);returndata.userById.username;}
Added to docs Will not add to docs because this is not an approach that I like, it's a workaround of a limitation of V4 and will hopefully be addressed in V5.
Implicitly setting the relevant companyId argument from a JWT claim on all root level query fields (and removing the argument since it's implicit).
constmyPlugin=builder=>{builder.hook("GraphQLObjectType:fields:field",(field,_,{ Self, scope })=>{if(!field.args||!field.args.companyId||!scope.isRootQuery){returnfield;}// Remove the companyId field and default it from contextconst{ companyId, ...remainingArgs}=field.args;const{resolve: oldResolve}=field;return{
...field,args: remainingArgs,resolve(parent,args,context,info){returnoldResolve(parent,{
...args,// Grab the relevant companyId from the JWT, or some other value// from additionalGraphQLContextFromRequest if you prefercompanyId: context.jwtClaims.companyId,},context,info);}};});};
Wraps all resolvers returning a DateTime and automatically calls toISOString on them.
constMyPlugin=makeWrapResolversPlugin((context,build,field,options)=>build.graphql.getNamedType(field.type).name==="Datetime"
? field
: null,(field)=>async(resolver)=>{constd=awaitresolver();// TODO: handle case where `field` is a list.returndinstanceofDate ? d.toISOString() : d;});
For watch mode in production you might not want to install our whole watch schema with its event triggers and what not; instead you can use the “manual” watch trigger. Then just fire that NOTIFY after all your migrations have completed.
For this manual watch mode you still use watchPg: true, but you tell PostGraphile not to install the event listener (in production PostGraphile shouldn't have sufficient permissions to install it anyway, but explicitly stating not to removes it from your logs):
IMPORTANT: doSomethingWith is not safe: (see: tc39/proposal-async-iteration#126); replace it with an async-iterator (not an async generator) native library.
Wrapping an async iterator:
// THIS IS NOT SAFE, DO NOT USEasyncfunction*doSomethingWith(asyncIterator,context){console.log("Subscription started");try{forawait(constvalofasyncIterator){yieldval}}finally{console.log("Subscription ended");}}
e.g. as might be done when wrapping a subscribe method:
module.exports=(builder)=>{builder.hook('GraphQLObjectType:fields:field',(field,build,context)=>{if(!context.scope.isRootSubscription)returnfield;constoldSubscribe=field.subscribe;return{
...field,asyncsubscribe(rootValue,args,context/* DIFFERENT TO THE CONTEXT ABOVE */,info){constasyncIterator=awaitoldSubscribe(rootValue,args,context,info);constderivativeAsyncIterator=doSomethingWith(asyncIterator,context);returnderivativeAsyncIterator;}}},['MySubscribeWrapperThingy'],[],['PgSubscriptionResolver']);};
Personally I simplify things by using only one PostgreSQL role at runtime (I call this the "visitor" role, since anyone (authenticated or otherwise) who visits my site/app is a visitor) and then using RLS policies to govern what said visitor can do based on who they are. I split tables on permission boundaries, so the permissions granted (e.g. ability to update "name", "bio" field) are common to a table (e.g. "organizations") and the only decision is whether or not the current user can do those things (e.g. create policy update_admins on organizations for update using (id in (select list_of_org_ids_current_user_is_admin_of()))). Note the policy only chooses which rows can be affected; the role chooses which columns can be affected. If there are situations where you cannot split on permission boundaries and some role needs to also be able to manipulate a particular field where others can manipulate that record but not that field, then you can use a SECURITY DEFINER custom mutation function to enable operations that wouldn't normally be allowed by RBAC/RLS.
constMyFilterPlugin=builder=>{builder.hook("GraphQLObjectType:fields:field:args",(args,build,context)=>{const{pgSql: sql}=build;const{ addArgDataGenerator }=context;if(...it's not relevant to us...) {
returnargs;}addArgDataGenerator(fieldArgs=>({pgQuery(queryBuilder){queryBuilder.where(sql.fragment`my_condition = here`);},}));returnargs;},);};
The main parts you'd need to implement are a) finding out which fields are relevant to you (e.g. if pgFieldIntrospection relates to the table you care about), and b) adding the condition into the sql.fragment
If you just want to apply it to a specific field Foo.bar (the bar field on the Foo type) then the answer to (a) might be something like context.Self.name === 'Foo' && context.field.name === 'bar'
Activity
benjie commentedon Aug 19, 2019
Source: graphile/graphile-engine#471 (comment)
Correct.
extend(a, {b: 2})is roughly equivalent toObject.assign(a, {b: 2})but with a tonne of extra built in protections and logging when fields conflict.We start with an inflectors object that's something like
{pluralize, singularize, camelCase}.Hooks get called on the result of each other to calculate the final value, so you can think of it as:
In this situation you might have that
hook1is thegraphile-build-pghook that adds all the PG-specific inflectors (graphile-builddoesn't care about PG),hook2is the simplify inflector's hook, so you need to implementhook3to augment that with your mods.graphile-build(aka Graphile Engine) is responsible for managing the calling of all the hooks. They all execute synchronously. They start here:https://github.com/graphile/graphile-engine/blob/2c0ce76c55fe2208c1445534f1904b3844a771d2/packages/graphile-build/src/SchemaBuilder.js#L476-L493
You shouldn't need to even worry about any of this, the
makeAddInflectorsPlugin(documented here) should completely abstract the whole hook system so you only need to worry about writing your override:benjie commentedon Sep 13, 2019
How lookahead works/could work w.r.t. interfaces/unions: graphile/crystal#387 (comment)
benjie commentedon Sep 16, 2019
graphile/crystal#387 (comment) on how the lookahead system works (and might be modified to support unions/interfaces).
benjie commentedon Feb 3, 2020
We don't have any examples, but it's definitely possible.
You can implement it with
makeExtendSchemaPlugin; the difference is the shape of the resolvers would change:Your async iterator can return a stream of events from anywhere.
https://www.graphile.org/postgraphile/make-extend-schema-plugin/
https://discordapp.com/channels/489127045289476126/498852330754801666/673926777512656916
benjie commentedon Feb 7, 2020
Way to get the inflectors so you can write external code that depends on them without building the entire GraphQL schema again.
The above function gives you a way to get the
buildobject:You can use inflectors from
build.inflection; they typically take introspection results as input, you can get those frombuild.pgIntrospectionResultsByKind. For example, to map a column name to what it would be called in GraphQL, you could do something like:https://discordapp.com/channels/489127045289476126/498852330754801666/675281797080416289
benjie commentedon Feb 7, 2020
Jest test to ensure RLS is enabled on all tables (from 2017).
graphile/crystal#461 (comment)
benjie commentedon Feb 13, 2020
https://discordapp.com/channels/489127045289476126/498852330754801666/677462518419161089
benjie commentedon May 2, 2020
You can indeed issue GraphQL requests from various contexts, including within a resolver. To do so you need the following:
graphqlfunction from thegraphqlmodulebuild.graphql.graphqlimport { graphql } from 'graphql'orconst { graphql } = require('graphql'), but this has caveats.info.schemaawait postgraphileMiddleware.getGqlSchema()await createPostGraphileSchema(...)- see schema only usage - do this once and cache it because it's expensive to computewithPostGraphileContextin the schema only usageIssuing a GraphQL operation from inside a resolver example:
Explain how to call resolvers from resolvers
Explain how to call resolvers from resolvers
benjie commentedon Jul 29, 2020
Added to docsWill not add to docs because this is not an approach that I like, it's a workaround of a limitation of V4 and will hopefully be addressed in V5.Implicitly setting the relevant
companyIdargument from a JWT claim on all root level query fields (and removing the argument since it's implicit).benjie commentedon Sep 1, 2020
Wraps all resolvers returning a DateTime and automatically calls
toISOStringon them.-- graphile/crystal#1341 (comment)
benjie commentedon Sep 25, 2020
For watch mode in production you might not want to install our whole watch schema with its event triggers and what not; instead you can use the “manual” watch trigger. Then just fire that NOTIFY after all your migrations have completed.
For this manual watch mode you still use
watchPg: true, but you tell PostGraphile not to install the event listener (in production PostGraphile shouldn't have sufficient permissions to install it anyway, but explicitly stating not to removes it from your logs):Then when you're ready for PostGraphile to reload your schema, you send a
{"type":"manual"}payload on thepostgraphile_watchchannel:benjie commentedon Oct 16, 2020
IMPORTANT:
doSomethingWithis not safe: (see: tc39/proposal-async-iteration#126); replace it with an async-iterator (not an async generator) native library.Wrapping an async iterator:
-- graphile/crystal#1363 (comment)
e.g. as might be done when wrapping a
subscribemethod:-- graphile/crystal#1218 (comment)
benjie commentedon Oct 26, 2020
benjie commentedon Dec 24, 2020
Here's an example of a plugin that adds a condition to a PostGraphile collection by default: https://github.com/graphile-contrib/pg-omit-archived/blob/6ce933efc9e83bbf9415d3ab1393326111f84b42/index.js#L194-L255
The important part is the
addArgDataGenerator@ https://github.com/graphile-contrib/pg-omit-archived/blob/6ce933efc9e83bbf9415d3ab1393326111f84b42/index.js#L234Stripped down it'd be something like:
The main parts you'd need to implement are a) finding out which fields are relevant to you (e.g. if
pgFieldIntrospectionrelates to the table you care about), and b) adding the condition into thesql.fragmentIf you just want to apply it to a specific field
Foo.bar(thebarfield on theFootype) then the answer to (a) might be something likecontext.Self.name === 'Foo' && context.field.name === 'bar'If not relevant;
return argsearly.benjie commentedon Jan 13, 2021
A simple server (microservice) to enqueue jobs to Graphile Worker;
assertRequestIsAuthenticatedis left as an exercise to the reader: