DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» is a community of 968,547 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Cover image for sifting thru the types
Matthew Foley
Matthew Foley

Posted on

sifting thru the types

Flow state is a rare treat for me these days. The last time I can remember being in that zone was working on a GraphiQL implementation for Open Sauced, https://explore.opensauced.pizza. The Open Sauced project makes use of OneGraph, to handle authentication and persisted query features in working with the GitHub GraphQL API. This was the first I had worked on any kind of GraphiQL implementation, so for those of you at the point I was at then, GraphiQL is an Open Source project that can be used to interact with a GraphQL API in an ad-hoc and dynamic way, allowing a developer to iterate quickly on features from a data retrieval standpoint. This post is about the PR #2 in the repo.

@0vortex had laid a lot of the groundwork for this implementation and as he brought myself and @bdougieyo up to speed on the where things were at, the problem to solve was having too much stuff to interact with. OneGraph brings a lot of value to the table in working with GraphQL, and this is evident in the number and breadth of APIs that it works with. For some reference, take a look at this image (or play with their GraphiQL tool).
OneGraph GraphiQL Screenshot
The vision for https://explore.opensauced.pizza was to have a rapid prototyping tool specific to Open Sauced, and that's why having all these other services in the explorer tool is too much. We also wanted to let users quickly reproduce the queries we use in Open Sauced so that when a feature calls for an adjustment - this way, when its time to iterate on an existing feature, there's very little friction to finding that starting point. For reference, here's what the Explorer portion on our implementation looks like.
explore.opensauced.pizza screenshot

Before I forget to explain this part, the flow state came from the fun challenge of chopping up OneGraph's GraphQL schema, on the client side. I searched around, and I really couldn't find any practical examples of this. So now I got to venture into data science-y territory of manipulating a dataset iteratively :-)

When you look at a GraphQL result of an introspection query (where a GraphQL endpoint describes itself), it's mainly a long flat list of "types". These types refer to each other in different ways, and the GraphiQL tool uses all of this data to build up a dynamic UI to work with valid GraphQL queries. Also, a GraphQL schema gets validated in handling all of the cross references and such. In order to get things the way we wanted it, we needed to modify the schema to only include the pieces we absolutely needed. I tried unsuccessfully to take the approach of picking the parts of the schema I wanted and followed the links outward to extrapolate the required parts. Don't remember exactly why but it DID NOT WORK. So I settled on the opposite - tear out the parts I didn't want and work with whatever was left. This explains the wicked long regex I used!

Normally, the way GraphiQL React components would work with the schema would be something like this line:

this.setState({
  schema: buildClientSchema(result.data) 
});
Enter fullscreen mode Exit fullscreen mode

But instead, the way the changes read:

// To modify schema, we follow this process:
// 1) Remove all the types we don't want, based on regex match
// 2) Strip out all of the dependencies that matched the same regex
// 3) Remove types of kind=OBJECT that have had their fields emptied out (to satisfy schema validation)
const filteredTypes = result.data.__schema.types
  .filter(nodeBlackListFn)
  .map(stripPrefixedDeps) 
  .filter(emptyObjectFilterFn);
const filteredData = {
  __schema: {
    ...result.data.__schema, 
    types: filteredTypes
  }
};
this.setState({ schema: buildClientSchema(filteredData) });
Enter fullscreen mode Exit fullscreen mode

And now utility functions that make this work:

const blacklistRe = /(adroll|airtable|apollo|asana|box|brex|bundlephobia|chargebee|clearbit|cloudflare|contentful|crunchbase|descuri|devTo|dribbble|dropbox|eggheadio|emailNode|eventil|facebookBusiness|fedex|firebase|google|googleAds|hubspot|immigrationGraph|intercom|logdna|mailchimp|meetup|mixpanel|mux|netlify|notion|npm|openCollective|orbit|productHunt|quickbooks|rss|salesforce|slack|spotify|stripe|trello|twilio|twitchTv|twitter|ups|usps|ynab|youTube|youTubeSearch|youTubeVideo|zeit|zendesk)/i;
const typeBlackListFn = (f) => {
  return !(
    (f.type && f.type.name && blacklistRe.test(f.type.name))
    || (f.name && blacklistRe.test(f.name))
    || (f.type && f.type.ofType && f.type.ofType.name && blacklistRe.test(f.type.ofType.name))
  );
}
// Filter function for picking things that are not blacklisted
const nodeBlackListFn = (f) => {
  return !(
    (f.type && f.type.name && blacklistRe.test(f.type.name))
    || (f.name && blacklistRe.test(f.name))
  );
}
// Strips out dependencies that are blacklisted
const stripPrefixedDeps = (type) => {
  return {
    ...type,
    fields: type.fields ? type.fields.filter(typeBlackListFn) : type.fields,
    inputFields: type.inputFields ? type.inputFields.filter(typeBlackListFn) : type.inputFields,
    possibleTypes: type.possibleTypes ? type.possibleTypes.filter(typeBlackListFn) : type.possibleTypes
  }
};
// Removes OBJECT types that have had all of their fields stripped out.
const emptyObjectFilterFn = (type) => {
  return type.kind !== "OBJECT" || type.fields.length > 0;
};
Enter fullscreen mode Exit fullscreen mode

There was one other feature in the same PR, that I'll talk about next week!

Top comments (1)

Collapse
 
0vortex profile image
TED Vortex (Teodor Eugen DuΘ›ulescu)

I like the way you write :D
I'll try to give you WAAAAAY more stuff to write about :D

Need a better mental model for async/await?

Check out this classic DEV post on the subject.

β­οΈπŸŽ€ JavaScript Visualized: Promises & Async/Await

async await