diff --git a/.changeset/twenty-cups-marry.md b/.changeset/twenty-cups-marry.md new file mode 100644 index 000000000..8707a0401 --- /dev/null +++ b/.changeset/twenty-cups-marry.md @@ -0,0 +1,5 @@ +--- +"@pothos/plugin-prisma": patch +--- + +Support Client Directives in Prisma plugin (@skip and @include) diff --git a/packages/plugin-prisma/src/util/map-query.ts b/packages/plugin-prisma/src/util/map-query.ts index 3787c98a1..80e39c29e 100644 --- a/packages/plugin-prisma/src/util/map-query.ts +++ b/packages/plugin-prisma/src/util/map-query.ts @@ -4,12 +4,15 @@ import { FieldNode, FragmentDefinitionNode, getArgumentValues, + getDirectiveValues, getNamedType, GraphQLField, + GraphQLIncludeDirective, GraphQLInterfaceType, GraphQLNamedType, GraphQLObjectType, GraphQLResolveInfo, + GraphQLSkipDirective, InlineFragmentNode, isInterfaceType, isObjectType, @@ -198,7 +201,7 @@ function resolveIndirectInclude( } function addNestedSelections( - type: GraphQLObjectType | GraphQLInterfaceType, + type: GraphQLInterfaceType | GraphQLObjectType, context: object, info: GraphQLResolveInfo, state: SelectionState, @@ -260,7 +263,7 @@ function addNestedSelections( } function addFieldSelection( - type: GraphQLObjectType | GraphQLInterfaceType, + type: GraphQLInterfaceType | GraphQLObjectType, context: object, info: GraphQLResolveInfo, state: SelectionState, @@ -271,6 +274,16 @@ function addFieldSelection( return; } + const skip = getDirectiveValues(GraphQLSkipDirective, selection, info.variableValues); + if (skip?.if === true) { + return; + } + + const include = getDirectiveValues(GraphQLIncludeDirective, selection, info.variableValues); + if (include?.if === false) { + return; + } + const field = type.getFields()[selection.name.value]; if (!field) { @@ -397,7 +410,7 @@ export function queryFromInfo { `); }); + it('skips fields based on @skip and @include directives', async () => { + const query = gql` + query { + me { + id + profile @skip(if: true) { + bio + } + posts(limit: 1) @include(if: false) { + id + } + } + } + `; + + const result = await execute({ + schema, + document: query, + contextValue: { user: { id: 1 } }, + }); + + expect(result).toMatchInlineSnapshot(` + { + "data": { + "me": { + "id": "VXNlcjox", + }, + }, + } + `); + + expect(queries).toMatchInlineSnapshot(` + [ + { + "action": "findUnique", + "args": { + "where": { + "id": 1, + }, + }, + "dataPath": [], + "model": "User", + "runInTransaction": false, + }, + ] + `); + }); + + it('includes fields based on @skip and @include directives', async () => { + const query = gql` + query { + me { + id + profile @skip(if: false) { + bio + } + posts(limit: 1) @include(if: true) { + id + } + } + } + `; + + const result = await execute({ + schema, + document: query, + contextValue: { user: { id: 1 } }, + }); + + expect(result).toMatchInlineSnapshot(` + { + "data": { + "me": { + "id": "VXNlcjox", + "posts": [ + { + "id": "250", + }, + ], + "profile": { + "bio": "Debitis perspiciatis unde sunt.", + }, + }, + }, + } + `); + + expect(queries).toMatchInlineSnapshot(` + [ + { + "action": "findUnique", + "args": { + "include": { + "posts": { + "include": { + "comments": { + "include": { + "author": true, + }, + "take": 3, + }, + }, + "orderBy": { + "createdAt": "desc", + }, + "take": 1, + "where": undefined, + }, + "profile": true, + }, + "where": { + "id": 1, + }, + }, + "dataPath": [], + "model": "User", + "runInTransaction": false, + }, + ] + `); + }); + it('queries decimals', async () => { const query = gql` query {