Skip to content

Instantly share code, notes, and snippets.

@calebmer
Last active December 8, 2017 14:25
Show Gist options
  • Save calebmer/45208eed9059b04c4c5e7ea4133f60c1 to your computer and use it in GitHub Desktop.
Save calebmer/45208eed9059b04c4c5e7ea4133f60c1 to your computer and use it in GitHub Desktop.
Tiny GraphQL Client Ideas

Notes

Principles

  1. The client must help create applications that are very small. This means not only should the runtime be small, but also the query artifacts that will be included in the bundle. Including the entire query AST could be counter to this goal. Other options are only including the query text, or only including a query identifier (persisted queries) in the bundle.
  2. In order to serve the first principle queries must be statically defined outside of JavaScript. GraphQL AST “selection sets” should be transformed into minimal viable data shape representations to inform the client. These transformed “data shapes” can be imported and used by the client.
  3. Static type checking of query data should be a feature of the client and not an afterthought. GraphQL is statically typed after all. Since we are building query files, and importing the built artifcats adding types should be easy.
  4. Data from newer queries must be merged with data from older queries automatically so the UI stays consistent.
  5. Mutations and subscriptions are just queries with different execution semantics and data identification behaviors.
  6. Data is immutable and query resolution is queued so that any operation can be easily rolled back to avoid corrupting data.
  7. Event sourcing? This is more of an implementation detail, but event sourcing may lead to a smaller, faster client. The database model vs. the event streaming (Kafka like) model. By adopting an event sourcing model we don’t need to normalize/denormalize. Just merge data as it comes in.
// Client runtime.
import { Client } from 'graphql-client'
// Built using the compiler, may also have types!
import { PostQuery, PersonQuery, AddCommentMutation } from 'MyOperations.graphql'
const client = new Client(executeFn)
client.watch(PersonQuery, data => console.log(data)) // In reality this would probably render instead of logging.
client.execute(PostQuery, { /* variables */ }) // Nothing is logged...
client.execute(PersonQuery, { /* variables */ }) // The data returned from this request is logged.
client.execute(PostQuery, { /* variables */ }) // Assuming data from `PersonQuery` changed, the updated data is logged.
client.execute(AddCommentMutation, { /* variables */ }) // The updated data is logged.
import { GraphQL } from 'graphql-client-react'
import { ProfilePageQuery } from 'ProfilePage.graphql'
import { ProfileHeader } from './ProfileHeader'
// The `GraphQL` component watches the query and executes once on mount,
// and again everytime it sucesfully updates.
//
// The `GraphQL` component could also be an HOC if that is more to your
// taste.
export function ProfilePage ({ personID }) {
return (
<GraphQL operation={ProfilePageQuery} variables={{ personID }}>
{({ loading, errors, data, execute }) => data && (
<div>
<ProfileHeader profile={data.profile}/>
<ProfileBody profile={data.profile}/>
</div>
)}
</GraphQL>
)
}
// If we wanted types: import { ProfileHeaderFragment } from 'ProfileHeader.graphql'
export function ProfileHeader ({ profile } /* :: { profile: ProfileHeaderFragment } */) {
return (
<header>
<h1>{profile.name}</h1>
<p>{profile.description}</p>
</header>
)
}
query ProfilePageQuery($personID: ID!) {
profile: personByID(id: $personID) {
...ProfileHeaderFragment @import(from: "ProfileHeader.graphql")
...ProfileBodyFragment @import(from: "ProfileBody.graphql")
}
}
# Imports are basically just syntax sugar. They would break present
# tooling, but that’s fixable.
fragment ProfileHeaderFragment {
name
description
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment