Skip to main content

GQLEndpoint

GQLEndpoints are for GraphQL based protocols.

extends

GQLEndpoint extends Endpoint

Usage

Editor
import { GQLEndpoint, GQLEntity, schema } from '@data-client/graphql';

const gql = new GQLEndpoint('/');

export class User extends GQLEntity {
  name = '';
  username = '';
  email = '';
  phone = '';
  website = '';

  get profileImage() {
    return `https://i.pravatar.cc/64?img=${this.id + 4}`;
  }
}

export class Post extends GQLEntity {
  title = '';
  body = '';
  author = User.fromJS();

  static schema = {
    author: User,
  };
}

export const PostResource = {
  getList: gql.query(
    `query GetPosts($userId: ID) {
    post {
      id
      title
      body
      user {
        id
        name
        username
        email
        phone
        website
      }
    }
  }
`,
    { posts: new schema.Collection([Post]) },
  ),
};

export const UserResource = {
  get: gql.query(
    `query GetUser($id: ID!) {
    user(id: $id) {
      id
      name
      username
      email
      phone
      website
    }
  }
`,
    { user: User },
  ),
  update: gql.mutation(
    `mutation UpdateUser($user: User!) {
      updateUser(user: $user) {
        id
        name
        username
        email
        phone
        website
    }
  }`,
    { updateUser: User },
  ),
};
import { type Post } from './resources';

export default function PostItem({ post }: { post: Post }) {
  return (
    <div className="listItem spaced">
      <Avatar src={post.author.profileImage} />
      <div>
        <h4>{post.title}</h4>
        <small>by {post.author.name}</small>
      </div>
    </div>
  );
}
import { UserResource } from './resources';

export default function ProfileEdit({ userId }: { userId: number }) {
  const { user } = useSuspense(UserResource.get, { id: userId });
  const controller = useController();
  const handleChange = e =>
    controller.fetch(UserResource.update, {
      id: userId,
      name: e.currentTarget.value,
    });
  return (
    <div>
      <label>
        Name:{' '}
        <input
          type="text"
          value={user.name}
          onChange={handleChange}
        />
      </label>
    </div>
  );
}
import PostItem from './PostItem';
import ProfileEdit from './ProfileEdit';
import { PostResource } from './resources';

function PostList() {
  const userId = 1;
  const { posts } = useSuspense(PostResource.getList, { userId });
  return (
    <div>
      <ProfileEdit userId={userId} />
      <br />
      {posts.map(post => (
        <PostItem key={post.pk()} post={post} />
      ))}
    </div>
  );
}
render(<PostList />);
🔴 Live Preview
Store

query(gql, schema)

import { GQLEndpoint } from '@data-client/graphql';
import User from 'schema/User';

const gql = new GQLEndpoint('/');

export const getUser = gql.query(
(v: { name: string }) => `query getUser($name: String!) {
user(name: $name) {
id
name
email
}
}`,
{ user: User },
);

getUser({ name: 'bob' });

mutate(gql, schema)

import { GQLEndpoint } from '@data-client/graphql';
import User from 'schema/User';

const gql = new GQLEndpoint('/');

export const updateUser = gql.mutate(
(v: Partial<User>) => `query updateUser($user: User!) {
user(name: $user) {
id
name
email
}
}`,
{ user: User },
);

updateUser({ id: '5', name: 'bob', email: '[email protected]' });

Fetch Lifecycle

GQLEndpoint adds to Endpoint by providing customizations for a provided fetch method.

  1. Prepare fetch
    1. url
    2. getRequestInit()
  2. Perform fetch
    1. fetchResponse()
    2. parseResponse()
    3. process()
fetch implementation for GQLEndpoint
async function fetch(variables) {
return this.fetchResponse(
this.url,
this.getRequestInit(variables),
).then(res => this.process(res, variables));
}

Prepare Fetch

Members double as options (second constructor arg). While none are required, the first few have defaults.

url: string

GraphQL uses one url for all operations.

getRequestInit(body): RequestInit

Prepares RequestInit used in fetch. This is sent to fetchResponse

getQuery(variables): string

Prepare the query, to be sent as part of the body payload.

getHeaders(headers: HeadersInit): HeadersInit

Called by getRequestInit to determine HTTP Headers

This is often useful for authentication

warning

Don't use hooks here.

Handle fetch

fetchResponse(input, init): Promise

Performs the fetch call

parseResponse(response): Promise

Takes the Response and parses via .text() or .json()

process(value, ...args): any

Perform any transforms with the parsed result. Defaults to identity function.

Endpoint Life-Cycles

schema: Schema

Declarative definition of how to process responses

Not providing this option means no entities will be extracted.

import { GQLEntity, GQLEndpoint } from '@data-client/graphql';
const gql = new GQLEndpoint('https://nosy-baritone.glitch.me');

class User extends GQLEntity {
username = '';
}

export const getUser = gql.query(
(v: { name: string }) => `query GetUser($name: String!) {
user(name: $name) {
id
name
email
}
}`,
{ user: User },
);

dataExpiryLength?: number

Custom data cache lifetime for the fetched resource. Will override the value set in NetworkManager.

Learn more about expiry time

errorExpiryLength?: number

Custom data error lifetime for the fetched resource. Will override the value set in NetworkManager.

errorPolicy?: (error: any) => 'soft' | undefined

'soft' will use stale data (if exists) in case of error; undefined or not providing option will result in error.

Learn more about errorPolicy

errorPolicy(error) {
return error.status >= 500 ? 'soft' : undefined;
}

invalidIfStale: boolean

Indicates stale data should be considered unusable and thus not be returned from the cache. This means that useSuspense() will suspend when data is stale even if it already exists in cache.

pollFrequency: number

Frequency in millisecond to poll at. Requires using useSubscription() or useLive() to have an effect.

getOptimisticResponse: (snap, ...args) => fakePayload

When provided, any fetches with this endpoint will behave as though the fakePayload return value from this function was a succesful network response. When the actual fetch completes (regardless of failure or success), the optimistic update will be replaced with the actual network response.

extend(options): Endpoint

Can be used to further customize the endpoint definition

const gql = new GQLEndpoint('https://nosy-baritone.glitch.me');

const authGQL = gql.extend({
getHeaders(headers: HeadersInit): HeadersInit {
return {
...headers,
'Access-Token': getAuth(),
};
},
});