Skip to content

Commit

Permalink
Removes graphQLSchemaExtension export in favour of using either `gr…
Browse files Browse the repository at this point in the history
…aphql.extend` or `@graphql-tool/schema` (#7943)
  • Loading branch information
Josh Calder committed Sep 29, 2022
1 parent cacc4e1 commit 494cdef
Show file tree
Hide file tree
Showing 33 changed files with 480 additions and 399 deletions.
5 changes: 5 additions & 0 deletions .changeset/witty-bees-pull.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystone-6/core': major
---

Removes the `@graphql-tools/schema` wrapping functions `graphQLSchemaExtension` and `gql`. Developers should import `@graphql-tools/schema` themselves, or use `graphql` (as exported by `@keystone-6/core`).
2 changes: 1 addition & 1 deletion .github/workflows/core_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ jobs:
'blog.test.ts',
'document-field.test.ts',
'default-values.test.ts',
'extend-graphql-schema.test.ts',
'extend-graphql-schema-graphql-tools.test.ts',
'extend-graphql-schema-graphql-ts.test.ts',
'extend-graphql-schema-nexus.test.ts',
'json.test.ts',
Expand Down
7 changes: 3 additions & 4 deletions docs/components/docs/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -222,10 +222,9 @@ export function DocsNavigation() {
<NavItem href="/docs/guides/images-and-files">
Images & Files <Badge look="success">New</Badge>
</NavItem>
{/* Disable placeholder for now */}
{/* <NavItem href="/docs/guides/schema-extension" isPlaceholder>
Schema Extension
</NavItem> */}
<NavItem href="/docs/guides/schema-extension">
GraphQL Schema Extension<Badge look="success">New</Badge>
</NavItem>
<NavItem href="/docs/guides/testing">Testing</NavItem>
<NavItem href="/docs/guides/document-fields">Document Fields</NavItem>
<NavItem href="/docs/guides/document-field-demo">Document Field Demo</NavItem>
Expand Down
11 changes: 7 additions & 4 deletions docs/pages/docs/config/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -419,18 +419,21 @@ import type { ExtendGraphqlSchema } from '@keystone-6/core/types';
The `extendGraphqlSchema` config option allows you to extend the GraphQL API which is generated by Keystone based on your schema definition.
It has a TypeScript type of `ExtendGraphqlSchema`.

In general you will use the function `graphQLSchemaExtension({ typeDefs, resolvers })` to create your schema extension.
`extendGraphqlSchema` expects a function that takes the GraphQL Schema generated by Keystone and returns a valid GraphQL Schema

```typescript
import { config, graphQLSchemaExtension } from '@keystone-6/core';
import { config, graphql } from '@keystone-6/core';

export default config({
extendGraphqlSchema: graphQLSchemaExtension({ typeDefs, resolvers }),
extendGraphqlSchema: keystoneSchema => {
/* ... */
return newExtendedSchema
}
/* ... */
});
```

See the [schema extension guide](../guides/schema-extension) for more details on how to use `graphQLSchemaExtension()` to extend your GraphQL API.
See the [schema extension guide](../guides/schema-extension) for more details and tooling options on how to extend your GraphQL API.

## storage (images and files)

Expand Down
131 changes: 128 additions & 3 deletions docs/pages/docs/guides/schema-extension.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,131 @@
---
title: "Schema Extension"
description: "We've planned this page but not had a chance to write it yet."
title: "GraphQL Schema Extension"
description: "Learn how to extend your GraphQL Schema using extendGraphqlSchema."
---

{% coming-soon /%}
Keystone automatically generates a GraphQL schema based on your [Keystone config](../config/config). This schema contains all GraphQL types, queries and mutations based on your lists and ends up as the generated `schema.graphql` file found in the root of your Keystone project.
Generally changing the behavior of Keystone can be performed through [Hooks](../config/hooks), however, there are times when you need an extra GraphQL type or want a custom mutation or query, for these instances, Keystone has the `extendGraphqlSchema` option.

The `extendGraphqlSchema` option expects a function that takes the GraphQL Schema generated by Keystone and returns a valid GraphQL schema. You can then use this function to add or replace resolvers and types.

## Using Keystone's graphql.extend

Keystone exports `graphql` from `@keystone/core`, this uses [@graphql-ts/schema](https://docsmill.dev/npm/@graphql-ts/schema) which can be used in combination with `Context` from `.keystone/types` to extend your GraphQL schema in a type-safe way.

You can then import this into your Keystone configuration file

```ts
import { graphql } from '@keystone/core';
```

Then you can use `graphql.extend` to add custom resolvers to your GraphQL Schema.

The following example adds a custom mutation called `publishPost` to the `base` Keystone Schema. It accepts one argument (`args`) of `id` which cannot be null and has the type of ID (`graphql.nonNull(graphql.ID)`). It updates the Post with the corresponding `id` and sets its `status` to 'published' and `publishDate` to the current time.
It then returns the Post that is updated which has a GraphQL type of `Post` that is passed in from the `base` schema (`base.object('Post')`).

```ts
import { graphql, config } from '@keystone/core';
import { Context } from '.keystone/types';

export default config({
{/* ... */},
extendGraphqlSchema: graphql.extend(base => {
return {
mutation: {
publishPost: graphql.field({
type: base.object('Post'),
args: { id: graphql.arg({ type: graphql.nonNull(graphql.ID) }) },
resolve(source, { id }, context:Context) {
return context.db.Post.updateOne({
where: { id },
data: { status: 'published', publishDate: new Date().toISOString() },
});
},
}),
},
};
}),
});
```

{% hint kind="tip" %}
Note `context.db` is used in the resolver, this ensures the correct Internal object with the correct type is returned to GraphQL.
{% /hint %}

A full example project using `graphql-ts` can be found in [examples/extend-graphql-schema-graphql-ts](https://github.com/keystonejs/keystone/tree/main/examples/extend-graphql-schema-graphql-ts) on the Keystone GitHub repo.

## Using Third-Party Tools

As `extendGraphqlSchema` expects a function that returns a valid GraphQL schema you can also use third-party GraphQL schema tools to help generate or merge schemas.

### GraphQL-Tools Merge Schemas

[GraphQL Tools](https://www.graphql-tools.com/) `mergeSchemas` is a third-party package that can help with schema merging and adding custom resolvers and types, and then return an updated GraphQL schema to Keystone.

Start by installing `@graphql-tools/schema`

```bash
yarn add @graphql-tools/schema
```

Then import into your Keystone configuration

```ts
import { mergeSchemas } from '@graphql-tools/schema';
```

You can then write custom `resolvers` and `typeDefs` to merge with your Keystone `schema`. For example, to add the same custom mutation as above (`publishPost`), you would add the `typeDefs` for `publishPost` and then the `resolvers.Mutation.publishPost`
which performs the same function as above.

```ts
export default config({
{/* ... */},
extendGraphqlSchema: schema =>
mergeSchemas({
schemas: [schema],
typeDefs: `
type Mutation {
publishPost(id: ID!): Post
`,
resolvers: {
Mutation: {
publishPost: (root, { id }, context) => {
return context.db.Post.updateOne({
where: { id },
data: { status: 'published', publishDate: new Date().toUTCString() },
});
},
},
},
}),
});
```

{% if $nextRelease %}
{% hint kind="tip" %}
Note - Before version `3.0.0` of `@keystone-6/core`, `@graphql-tools/schema` was exported from `@keystone/core` as `graphQLSchemaExtension` this was removed in favor of using the tool directly if required
{% /hint %}
{% else /%}
{% hint kind="tip" %}
Note - `@graphql-tools/schema` is currently exported from `@keystone/core` as `graphQLSchemaExtension` this will be removed in the next major version of `@keystone-6/core` in favor of using the tool directly if required
{% /hint %}
{% /if %}

A full example project using `@graphql-tools/schema` can be found in [examples/extend-graphql-schema-graphql-tools](https://github.com/keystonejs/keystone/tree/main/examples/extend-graphql-schema-graphql-tools) on the Keystone GitHub repo.

## Related resources

{% related-content %}
{% well heading="Config API Reference" href="/docs/config/config" %}
The complete reference for the base keystone configuration
{% /well %}
{% well heading="Example Project: Extend GraphQL Schema with graphql-ts" href="https://github.com/keystonejs/keystone/tree/main/examples/extend-graphql-schema-graphql-ts" %}
A full keystone project illustrating how to extend your GraphQL schema using graphql-ts provided by Keystone.
{% /well %}
{% well heading="Example Project: Extend GraphQL Schema with GraphQL-Tools" href="https://github.com/keystonejs/keystone/tree/main/examples/extend-graphql-schema-graphql-tools" %}
A full keystone project illustrating how to extend your GraphQL schema using @graphql-tools/schema.
{% /well %}
{% well heading="Example Project: Extend GraphQL Schema with Nexus" href="https://github.com/keystonejs/keystone/tree/main/examples/extend-graphql-schema-nexus" %}
A full keystone project illustrating how to extend your GraphQL schema using Nexus and @graphql-tools/schema.
{% /well %}
{% /related-content %}
10 changes: 5 additions & 5 deletions examples/basic/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,11 @@ type Query {
uuid: ID!
}

type RandomNumber {
number: Int
generatedAt: Int
}

union AuthenticatedItem = User

type KeystoneMeta {
Expand Down Expand Up @@ -503,8 +508,3 @@ enum KeystoneAdminUISortDirection {
ASC
DESC
}

type RandomNumber {
number: Int
generatedAt: Int
}
66 changes: 33 additions & 33 deletions examples/basic/schema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { list, graphQLSchemaExtension, gql, graphql } from '@keystone-6/core';
import { list, graphql } from '@keystone-6/core';
import {
text,
relationship,
Expand All @@ -13,7 +13,7 @@ import {
import { document } from '@keystone-6/fields-document';
import { v4 } from 'uuid';
import { allowAll } from '@keystone-6/core/access';
import { Context, Lists } from '.keystone/types';
import { Lists } from '.keystone/types';

type AccessArgs = {
session?: {
Expand Down Expand Up @@ -182,39 +182,39 @@ export const lists: Lists = {
}),
};

// note this usage of the type is important because it tests that the generated types work
export const extendGraphqlSchema = graphQLSchemaExtension<Context>({
typeDefs: gql`
type Query {
randomNumber: RandomNumber
uuid: ID!
}
type RandomNumber {
number: Int
generatedAt: Int
}
type Mutation {
createRandomPosts: [Post!]!
}
`,
resolvers: {
RandomNumber: {
number(rootVal: { number: number }) {
return rootVal.number * 1000;
},
export const extendGraphqlSchema = graphql.extend(base => {
const RandomNumber = graphql.object<{ number: number }>()({
name: 'RandomNumber',
fields: {
number: graphql.field({ type: graphql.Int }),
generatedAt: graphql.field({
type: graphql.Int,
resolve() {
return Date.now();
},
}),
},
Mutation: {
createRandomPosts(root, args, context) {
const data = Array.from({ length: 238 }).map((x, i) => ({ title: `Post ${i}` }));
return context.db.Post.createMany({ data });
},
});

return {
mutation: {
createRandomPosts: graphql.field({
type: graphql.nonNull(graphql.list(graphql.nonNull(base.object('Post')))),
resolve: async (rootVal, args, context) => {
const data = Array.from({ length: 238 }).map((x, i) => ({ title: `Post ${i}` }));
return context.db.Post.createMany({ data });
},
}),
},
Query: {
randomNumber: () => ({
number: randomNumber(),
generatedAt: Date.now(),
query: {
randomNumber: graphql.field({
type: RandomNumber,
resolve: () => ({ number: randomNumber() }),
}),
uuid: graphql.field({
type: graphql.nonNull(graphql.ID),
resolve: () => v4(),
}),
uuid: () => v4(),
},
},
};
});
2 changes: 1 addition & 1 deletion examples/ecommerce/mutations/addToCart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Context } from '.keystone/types';

async function addToCart(
root: any,
{ productId }: { productId: string },
{ productId }: { productId?: string | null },
context: Context
): Promise<any> {
console.log('ADDING TO CART!');
Expand Down
30 changes: 15 additions & 15 deletions examples/ecommerce/mutations/index.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { graphQLSchemaExtension } from '@keystone-6/core';
import { graphql } from '@keystone-6/core';
import addToCart from './addToCart';
import checkout from './checkout';

// make a fake graphql tagged template literal
const graphql = String.raw;
export const extendGraphqlSchema = graphQLSchemaExtension({
typeDefs: graphql`
type Mutation {
addToCart(productId: ID): CartItem
checkout(token: String!): Order
}
`,
resolvers: {
Mutation: {
addToCart,
checkout,
export const extendGraphqlSchema = graphql.extend(base => {
return {
mutation: {
addToCart: graphql.field({
type: base.object('CartItem'),
args: { productId: graphql.arg({ type: graphql.ID }) },
resolve: addToCart,
}),
checkout: graphql.field({
type: base.object('Order'),
args: { token: graphql.arg({ type: graphql.nonNull(graphql.String) }) },
resolve: checkout,
}),
},
},
};
});
File renamed without changes.
File renamed without changes.
File renamed without changes.
19 changes: 19 additions & 0 deletions examples/extend-graphql-schema-graphql-tools/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@keystone-6/example-extend-graphql-schema-graphql-tools",
"version": "0.0.4",
"private": true,
"repository": "https://github.com/keystonejs/keystone/tree/main/examples/extend-graphql-schema-graphql-tools",
"license": "MIT",
"scripts": {
"build": "keystone build",
"dev": "keystone dev",
"start": "keystone start"
},
"dependencies": {
"@graphql-tools/schema": "^9.0.0",
"@keystone-6/core": "^2.0.0"
},
"devDependencies": {
"typescript": "~4.7.4"
}
}

1 comment on commit 494cdef

@vercel
Copy link

@vercel vercel bot commented on 494cdef Sep 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.