Code Monkey home page Code Monkey logo

graphql-anywhere's Introduction

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 http://dev.apollodata.com/react/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>'
);

graphql-anywhere's People

Contributors

helfer avatar greenkeeper[bot] avatar tmeasday avatar brysgo avatar cesarsolorzano avatar abergenw avatar kamilkisiela avatar jesenko avatar arendjr avatar calebmer avatar intellix avatar rwe avatar

Stargazers

Emmanuel Salomon avatar

Watchers

Adam Stankiewicz avatar James Cloos avatar  avatar

Forkers

itinsomnia

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.