UNPKG

graphql-anywhere

Version:
341 lines (273 loc) 9.06 kB
# graphql-anywhere [![npm version](https://badge.fury.io/js/graphql-anywhere.svg)](https://badge.fury.io/js/graphql-anywhere) [![Build Status](https://travis-ci.org/apollographql/graphql-anywhere.svg?branch=master)](https://travis-ci.org/apollostack/graphql-anywhere) > ⚠️ This package is no longer used by Apollo Client as of version 3.0, has been deprecated, and will no longer be maintained. ⚠️ Run a GraphQL query anywhere, without a GraphQL server or a schema. Just pass in one resolver. Use it together with [graphql-tag](https://github.com/apollographql/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](https://github.com/apollostack/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 ```js import graphql from 'graphql-anywhere' graphql(resolver, document, rootValue?, context?, variables?, options?) ``` - `resolver`: A single resolver, called for every field on the query. - Signature is: `(fieldName, rootValue, args, context, info) => any` - `document`: A GraphQL document, as generated by the template literal from `graphql-tag` - `rootValue`: The root value passed to the resolver when executing the root fields - `context`: A context object passed to the resolver for every field - `variables`: A dictionary of variables for the query - `options`: 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` - `fragmentMatcher`: Decide whether to execute a fragment. Default is to always execute all fragments. - Signature is: `(rootValue, typeCondition, context) => boolean` #### 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 is `true` 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. ```js import { filter } from 'graphql-anywhere' filter(doc, data); ``` - `doc`: a GraphQL document, as generated by the template literal from `graphql-tag`, typically either a query or a fragment. - `data`: an object of data to be filtered by the `doc` Filter `data` according to `doc`. ```js import { check } from 'graphql-anywhere' check(doc, data); ``` - `doc`: a GraphQL document, as generated by the template literal from `graphql-tag`, typically either a query or a fragment. - `data`: an object of data, as may have been filtered by `doc`. Check that `data` is of the form defined by the query or fragment. Throw an exception if not. ```js import { propType } from 'graphql-anywhere' X.propTypes = { foo: propType(doc), bar: propType(doc).isRequired, } ``` - `doc`: a GraphQL document, as generated by the template literal from `graphql-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. - [x] Arguments - [x] Variables - [x] Aliases - [x] Fragments, both named and inline - [x] `@skip` and `@include` directives If you come across a GraphQL feature not supported here, please file an issue. <a id="filter"></a> ## Example: Filter a nested object ```js import gql from 'graphql-tag'; import graphql from 'graphql-anywhere'; // 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 want const query = gql` { title user { login } labels { name } } `; // Define a resolver that just returns a property const resolver = (fieldName, root) => root[fieldName]; // Filter the data! const result = graphql( resolver, query, gitHubAPIResponse ); assert.deepEqual(result, { "title": "Found a bug", "user": { "login": "octocat", }, "labels": [ { "name": "bug", } ], }); ``` <a id="mock"></a> ## Example: Generate mock data ```js // Write a query where the fields are types, but we alias them const 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 response const resolver = (fieldName) => ({ string: 'This is a string', int: 5, }[fieldName] || 'continue'); // Generate the object! const result = graphql( resolver, query ); assert.deepEqual(result, { author: { name: 'This is a string', age: 5, address: { state: 'This is a string', }, }, }); ``` <a id="normalizr"></a> ## Example: Read from a Redux store generated with Normalizr ```js 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): any => { if (!rootValue) { return context.result.map((id) => assign({}, context.entities.articles[id], { __typename: 'articles', })); } const typename = rootValue.__typename; // If this field is a reference according to the schema if (typename && schema[typename] && schema[typename][fieldName]) { // Get the target type, and get it from entities by ID const targetType: string = schema[typename][fieldName]; return assign({}, context.entities[targetType][rootValue[fieldName]], { __typename: targetType, }); } // This field is just a scalar return rootValue[fieldName]; }; const result = graphql( resolver, query, null, data // pass data as context since we have to access it all the time ); // This is the non-normalized data, with only the fields we asked for in our query! assert.deepEqual(result, { result: [ { title: 'Some Article', author: { name: 'Dan', }, }, { title: 'Other Article', author: { name: 'Dan', }, }, ], }); ``` ## 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: ```js const resolver = (fieldName, root, args) => { if (fieldName === 'text') { return args.value; } return createElement(fieldName, args); }; const reactMapper = (childObj, root) => { const reactChildren = Object.keys(childObj).map(key => childObj[key]); if (root) { return cloneElement(root, root.props, ...reactChildren); } return reactChildren[0]; }; function gqlToReact(query): any { return graphql( resolver, query, '', null, null, { resultMapper: reactMapper }, ); } const query = gql` { div { s1: span(id: "my-id") { text(value: "This is text") } s2: span } } `; assert.equal( renderToStaticMarkup(gqlToReact(query)), '<div><span id="my-id">This is text</span><span></span></div>' ); ```