keystone icon indicating copy to clipboard operation
keystone copied to clipboard

Add a new `graphql.typeName` for the `structure` field to support deduplication of GraphQL types

Open borisno2 opened this issue 2 years ago • 4 comments

Adds graphql.typeName to structured JSON field to allow structured JSON to have global types Note if the types don't match it will cause GraphQL runtime errors, this will also not work with relationships

borisno2 avatar Apr 12 '23 07:04 borisno2

🦋 Changeset detected

Latest commit: 15c0e11f49a00f4b6b025068d0c1d39ea82f35a9

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@keystone-6/fields-document Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

changeset-bot[bot] avatar Apr 12 '23 07:04 changeset-bot[bot]

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 15c0e11f49a00f4b6b025068d0c1d39ea82f35a9:

Sandbox Source
@keystone-6/sandbox Configuration

codesandbox-ci[bot] avatar Apr 12 '23 07:04 codesandbox-ci[bot]

Can you add an example to the test sandbox?

dcousens avatar Apr 12 '23 11:04 dcousens

This relates to a more general problem I've hit around GraphQL type re-usage across fields and custom mutations. Currently there's no way (that I know of; please correct me) that let's you share/reuse a GraphQL types between multiple virtual fields, nor is it possible to share a GraphQL type between a virtual fields and GraphQL extensions.

Part if the problem is that, when you need to refer to existing list types for a virtual field, you use the lists argument, eg. graphql.field({ type: lists.Post.types.output, ... }), as documented here. But if you want to do the same thing in extendGraphqlSchema you reference the base object, like this: graphql.field({ type: base.object('Post'), ... }).

In my current project we have managed to reuse types across multiple GraphQL extensions by closuring over base then passing around an object of types, but it kinda sucks. Something like this:

export const extendGraphqlSchema: ExtendGraphqlSchema = graphql.extend((base) => {
  const loaders = buildLoaders(base);
  const dynamicGqlTypes = buildDynamicGqlTypes(base, loaders);

  return {
    query: {
      dodads: dodadsQuery({ dynamicGqlTypes }),
      thingamajigs: thingamajigsQuery({ dynamicGqlTypes }),
      search: searchQuery({ base, dynamicGqlTypes }),
    },
    mutation: {
      createDodad: createDodadMutation({ dynamicGqlTypes }),
      updateDodad: updateDodadMutation({ dynamicGqlTypes }),
      createThingamajig: createThingamajigMutation({ dynamicGqlTypes }),
      updateThingamajig: updateThingamajigMutation({ dynamicGqlTypes }),
    },
  };
});

If you could lazily create and cache them like this...

let dodadGraphQLType;

function getDodadGraphQLType(base) {
  if (dodadGraphQLType) return dodadGraphQLType;
 dodadGraphQLType = graphql.object({
    name: 'DodadOutput',
    fields: {
      // ...
    },
  });
  return dodadGraphQLType;
}

Then you could just pull the types you needed into each mutation separately but this doesn't work because Keystone creates two schemas (the standard one and another for sudo operations) – you need two distinct GraphQL types (ie. the JS object describing the type) to be created so they can closure the two different base objects and reference the correct list types (which also have standard and sudo versions).

Note, even if this did work, you'd still have problems re-using the types between extensions and virtual fields because (as above) when defining virtual field types you have a lists arg but no base object – you need to abstract that difference as well.

Shouldn't Keystone have a single place we can define GraphQL types for a schema? Then developers can refer to those types by their GraphQL type names (which is always unique) when they're needed for custom queries/mutations, virtual fields, structure fields, etc. This new config (graphql.types?) wouldn't initially replace the existing places you can define GraphQL types, just supplement them.

molomby avatar Feb 26 '24 00:02 molomby