Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

GraphQL ergonomics #2403

ntucker started this conversation in Ideas
Discussion options

This is explore possible ergonomics improvements to GraphQL support. For context, the initial library was built to support 100% of the powers of Rest Hooks high performance high data integrity with helpers built on the low level API. To expand upon GraphQL support more specializations can be created on top of. These of course will always be completely optional.

https://ntucker.notion.site/GQLResource-3879592f59a044f5a461f91ed0cbfb9e

Other previous discussions: #361

Before

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

After

export const userDetail = gql.query(
 'user ',
 { name: 'String' },
 User,
);

Before

const createReview = gql.mutation(
 (v: {
 ep: string;
 review: { stars: number; commentary: string };
 }) => `mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
 createReview(episode: $ep, review: $review) {
 stars
 commentary
 }
 }`,
 { createReview: Review },
);

After

const createReview = gql.mutation(
 'createReview',
 { episode; 'Episode!', review: 'ReviewInput!' },
 Review,
);

Types can be registered in the construction of base endpoint

const gql = new GQLEndpoint(
 'https://swapi-graphql.netlify.app/.netlify/functions/index',
 { schema: { 'ReviewInput': { stars: 'Number', commentary: 'String' } }},
);
You must be logged in to vote

Replies: 1 comment

Comment options

ntucker
May 16, 2024
Maintainer Author

Nested arguments are supplemented with gql-custom schemas that inherit from base.

We might need to specify ‘... on Repository’ - not sure if this is not always the case

Before

gql.query(
 (v: { login: string }) => `query getByPinned($login: String!) {
 user(login: $login) {
 pinnedItems(first: 6, types: REPOSITORY) {
 nodes {
 ... on Repository {
 name
 owner { login }
 id
 description
 createdAt
 stargazerCount
 isFork
 forkCount
 isPrivate
 }
 }
 }
 }
 }`,
 {
	 user: {
		 pinnedItems: { nodes: [GqlRepository] },
			}
		},
 )

After

gql.query(
	`getByPinned($login: String!)`,
	{
		user: {
			pinnedItems: new schema.Object(
				{ nodes: [GqlRepository.join('owner')] },
				 `pinnedItems(first: 6, types: REPOSITORY)`
			)
		}
	},
);

Before

`
query {
 getItems {
 id
 name
 price
 currency
 }
}
`

After

gql.query(
	`getItems`,
	{ getItems: new schema.Collection([Item]) },
);

TODO: Check that mutation with user(user: $user) rather than updateUser(user: $user) is valid and works

Before

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!) {
 user(user: $user) {
 id
 name
 username
 email
 phone
 website
 }
 }`,
 { updateUser: User },
 ),
};

After

export const UserResource = {
 get: gql.query(`getUser($id: ID!)`, { user: User }),
 update: gql.mutation(`updateUser($user: User!)`, { user: user }),
};
UserResource.update({ id: 'myid', username: 'bob' });
const UserResource = createResource({
	schema: Todo,
	methods: ['get', 'update'],
});

Schema Include/Exclude

Base definition should include all related fields in schema definition.

However, by not including a default value you can (by default) exclude requesting them and joining on them during denormalization.

TODO: Make sure denormalization doesn’t require any members that do not have a default value.

// defaults to not requesting author
class Post extends GQLEntity {
	title = '';
	content = '';
	
	static schema = {
		author: User,
		related: [Post],
	}
}
const getPost = gql.query(`getPost($id: ID!)`, { post: Post.join('author', 'related') });
const getPosts = gql.query(
	`getPosts($first: Number)`,
	{ getPosts: new schema.Collection([Post.join('author')]) }
);
getPost('myid');
getPosts(6);

Entity.join

  • GQLEntity validation is based on default members
  • join creates an extended class that includes defaults based on the schema definition
    • this makes that join item required
    • this also has the affect of changing the auto-generated gql string
    • this also ensures the resulting type includes the extra fields
  • join must be memoized since schemas are used as MemoCache keys

Design Decisions

  • graphql’s include/exclude fields are mostly useful for nested items. in fact, it’s usually a bad idea to selectively exclude simple scalar fields.
    • Therefore we make the default case to automatically include all non-nested fields; and make it easy to specify which nested items should by default be included or not.
    • Then we make it easy to selectively include any related item
    • We still want schema definitions as a central source of truth - so our base Entity should know about all potential relationships (i.e., everything in the gql schema)
  • Various nesting pieces might include additional parameters
    • Therefore, we add additional arguments to support those in our gql - specific schemas.
  • Schemas definitions have the structure of the gql query, therefore should be used to generate the whole query
  • Top level of query or mutation is a function name and signature - we can use typescript magic to extract full definition from just the signature string
You must be logged in to vote
0 replies
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
Ideas
Labels
None yet
1 participant

AltStyle によって変換されたページ (->オリジナル) /