Nutty Peanut Marshmallow

    graphql-anywhere
    TypeScript icon, indicating that this package has built-in type declarations

    4.2.7 • Public • Published

    graphql-anywhere

    npm version Build Status

    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

    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.

    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.

    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.

    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.

    • 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

    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",
        }
      ],
    });

    Example: Generate mock data

    // 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',
        },
      },
    });

    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): 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:

    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>'
    );

    Install

    npm i graphql-anywhere

    DownloadsWeekly Downloads

    227,679

    Version

    4.2.7

    License

    MIT

    Unpacked Size

    168 kB

    Total Files

    38

    Last publish

    Collaborators

    • apollo-bot
    • benjamn
    • helfer
    • jbaxleyiii
    • peggyrayzis
    • sashko
    • tmeasday