graphql-anywhere
Run a GraphQL query anywhere, without a GraphQL server or a schema. Just pass in one resolver. Use it together with graphql-tag.
npm install graphql-anywhere graphql-tag
I think there are a lot of potentially exciting use cases for a completely standalone and schema-less GraphQL execution engine. We use it in Apollo Client to read data from a Redux store with GraphQL.
Let's come up with some more ideas - below are some use cases to get you started!
API
resolver
: A single resolver, called for every field on the query.- Signature is:
(fieldName, rootValue, args, context, info) => any
- Signature is:
document
: A GraphQL document, as generated by the template literal fromgraphql-tag
rootValue
: The root value passed to the resolver when executing the root fieldscontext
: A context object passed to the resolver for every fieldvariables
: A dictionary of variables for the queryoptions
: Options for execution
Options
The last argument to the graphql
function is a set of graphql-anywhere
-specific options.
resultMapper
: Transform field results after execution.- Signature is:
(resultFields, resultRoot) => any
- Signature is:
fragmentMatcher
: Decide whether to execute a fragment. Default is to always execute all fragments.- Signature is:
(rootValue, typeCondition, context) => boolean
- Signature is:
Resolver info
info
, the 5th argument to the resolver, is an object with supplementary information about execution. Send a PR or open an issue if you need additional information here.
isLeaf
: A boolean that istrue
if this resolver is for a leaf field of the query, i.e. one that doesn't have a sub-selection.resultKey
: The key the result of this field will be put under. It's either the field name from the query, or the field alias.directives
: An object with information about all directives on this field. It's an object of the format{ [directiveName]: { [argumentName]: value }}
. So for example a field with@myDirective(hello: "world")
will be passed as{ myDirective: { hello: 'world' }}
. Note that fields can't have multiple directives with the same name, as written in the GraphQL spec.
Utilities
See https://www.apollographql.com/docs/react/advanced/fragments.html for examples of how you might use these.
;
doc
: a GraphQL document, as generated by the template literal fromgraphql-tag
, typically either a query or a fragment.data
: an object of data to be filtered by thedoc
Filter data
according to doc
.
;
doc
: a GraphQL document, as generated by the template literal fromgraphql-tag
, typically either a query or a fragment.data
: an object of data, as may have been filtered bydoc
.
Check that data
is of the form defined by the query or fragment. Throw an exception if not.
XpropTypes = foo: bar: isRequired
doc
: a GraphQL document, as generated by the template literal fromgraphql-tag
, typically either a query or a fragment.
Generate a React propType
checking function to ensure that the passed prop is in the right form.
Supported GraphQL features
Why do you even need a library for this? Well, running a GraphQL query isn't as simple as just traversing the AST, since there are some pretty neat features that make the language a bit more complex to execute.
- Arguments
- Variables
- Aliases
- Fragments, both named and inline
-
@skip
and@include
directives
If you come across a GraphQL feature not supported here, please file an issue.
Example: Filter a nested object
;; // I don't need all this stuff!const gitHubAPIResponse = "url": "https://api.github.com/repos/octocat/Hello-World/issues/1347" "title": "Found a bug" "body": "I'm having a problem with this." "user": "login": "octocat" "avatar_url": "https://github.com/images/error/octocat_happy.gif" "url": "https://api.github.com/users/octocat" "labels": "url": "https://api.github.com/repos/octocat/Hello-World/labels/bug" "name": "bug" "color": "f29513" ; // Write a query that gets just the fields we wantconst query = gql` { title user { login } labels { name } }`; // Define a resolver that just returns a propertyconst resolver = rootfieldName; // Filter the data!const result = ; assert;
Example: Generate mock data
// Write a query where the fields are types, but we alias themconst query = gql` { author { name: string age: int address { state: string } } }`; // Define a resolver that uses the field name to determine the type// Note that we get the actual name, not the alias, but the alias// is used to determine the location in the responseconst resolver = string: 'This is a string' int: 5fieldName || 'continue'; // Generate the object!const result = ; assert;
Example: Read from a Redux store generated with Normalizr
const data = result: 1 2 entities: articles: 1: id: 1 title: 'Some Article' author: 1 2: id: 2 title: 'Other Article' author: 1 users: 1: id: 1 name: 'Dan' ; const query = gql` { result { title author { name } } }`; const schema = articles: author: 'users' ; // This resolver is a bit more complex than others, since it has to// correctly handle the root object, values by ID, and scalar leafs.const resolver = fieldName rootValue args context: { if !rootValue return contextresult; const typename = rootValue__typename; // If this field is a reference according to the schema if typename && schematypename && schematypenamefieldName // Get the target type, and get it from entities by ID const targetType: string = schematypenamefieldName; return ; // This field is just a scalar return rootValuefieldName;}; const result = ; // This is the non-normalized data, with only the fields we asked for in our query!assert;
Example: Generate React components
You can use the resultMapper
option to convert your results into anything you like. In this case, we convert the result fields into children for a React component:
const resolver = { if fieldName === 'text' return argsvalue; return ;}; const reactMapper = { const reactChildren = Object; if root return ; return reactChildren0;}; : any { return ;} const query = gql` { div { s1: span(id: "my-id") { text(value: "This is text") } s2: span } }`; assert;