diff --git a/CHANGELOG.md b/CHANGELOG.md index 048c69ce6..6278d8cd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ You can find and compare releases at the [GitHub release page](https://github.co ## Unreleased +### Added + +- Add `@namespaced` directive for namespacing by separation of concerns https://github.com/nuwave/lighthouse/pull/2469 + ## v6.24.0 ### Added diff --git a/docs/6/api-reference/directives.md b/docs/6/api-reference/directives.md index ee1fd019f..e79a8e1db 100644 --- a/docs/6/api-reference/directives.md +++ b/docs/6/api-reference/directives.md @@ -2279,6 +2279,39 @@ extend type Query @namespace(field: "App\\Blog") { A [@namespace](#namespace) directive defined on a field directive wins in case of a conflict. +## @namespaced + +```graphql +""" +Provides a no-op field resolver that allows nesting of queries and mutations. +Useful to implement [namespacing by separation of concerns](https://www.apollographql.com/docs/technotes/TN0012-namespacing-by-separation-of-concern). +""" +directive @namespaced on FIELD_DEFINITION +``` + +The following example shows how one can namespace queries and mutations. + +```graphql +type Query { + post: PostQueries! @namespaced +} + +type PostQueries { + find(id: ID! @whereKey): Post @find + list(title: String @where(operator: "like")): [Post!]! @paginate +} + +type Mutation { + post: PostMutations! @namespaced +} + +type PostMutations { + create(input: PostCreateInput! @spread): Post! @create + update(input: PostUpdateInput! @spread): Post! @update + delete(id: ID! @whereKey): Post! @delete +} +``` + ## @neq ```graphql diff --git a/docs/master/api-reference/directives.md b/docs/master/api-reference/directives.md index ee1fd019f..e79a8e1db 100644 --- a/docs/master/api-reference/directives.md +++ b/docs/master/api-reference/directives.md @@ -2279,6 +2279,39 @@ extend type Query @namespace(field: "App\\Blog") { A [@namespace](#namespace) directive defined on a field directive wins in case of a conflict. +## @namespaced + +```graphql +""" +Provides a no-op field resolver that allows nesting of queries and mutations. +Useful to implement [namespacing by separation of concerns](https://www.apollographql.com/docs/technotes/TN0012-namespacing-by-separation-of-concern). +""" +directive @namespaced on FIELD_DEFINITION +``` + +The following example shows how one can namespace queries and mutations. + +```graphql +type Query { + post: PostQueries! @namespaced +} + +type PostQueries { + find(id: ID! @whereKey): Post @find + list(title: String @where(operator: "like")): [Post!]! @paginate +} + +type Mutation { + post: PostMutations! @namespaced +} + +type PostMutations { + create(input: PostCreateInput! @spread): Post! @create + update(input: PostUpdateInput! @spread): Post! @update + delete(id: ID! @whereKey): Post! @delete +} +``` + ## @neq ```graphql diff --git a/src/Schema/Directives/NamespacedDirective.php b/src/Schema/Directives/NamespacedDirective.php new file mode 100644 index 000000000..73a6aaff1 --- /dev/null +++ b/src/Schema/Directives/NamespacedDirective.php @@ -0,0 +1,25 @@ + true; + } +} diff --git a/tests/Integration/Schema/Directives/NamespacedDirectiveTest.php b/tests/Integration/Schema/Directives/NamespacedDirectiveTest.php new file mode 100644 index 000000000..904beddb3 --- /dev/null +++ b/tests/Integration/Schema/Directives/NamespacedDirectiveTest.php @@ -0,0 +1,135 @@ +schema .= /** @lang GraphQL */ ' + type Query { + user: UserQueries! @namespaced + } + + type UserQueries { + find(id: ID! @eq): User @find + list: [User!]! @all + } + + type Mutation { + user: UserMutations! @namespaced + } + + type UserMutations { + create(name: String!): User @create + update(id: ID!, name: String): User @update + delete(id: ID! @whereKey): User @update + } + + type User { + id: ID! + name: String! + } + '; + + $name = 'foo'; + $createUserResponse = $this->graphQL(/** @lang GraphQL */ ' + mutation ($name: String!) { + user { + create(name: $name) { + id + name + } + } + } + ', [ + 'name' => $name, + ]); + $createUserResponse->assertJson([ + 'data' => [ + 'user' => [ + 'create' => [ + 'name' => $name, + ], + ], + ], + ]); + $userID = $createUserResponse->json('data.user.create.id'); + + $this->graphQL(/** @lang GraphQL */ ' + query ($id: ID!) { + user { + find(id: $id) { + id + } + list { + id + } + } + } + ', [ + 'id' => $userID, + ])->assertExactJson([ + 'data' => [ + 'user' => [ + 'find' => [ + 'id' => $userID, + ], + 'list' => [ + [ + 'id' => $userID, + ], + ], + ], + ], + ]); + + $newName = 'bar'; + $this->graphQL(/** @lang GraphQL */ ' + mutation ($id: ID!, $name: String) { + user { + update(id: $id, name: $name) { + id + name + } + } + } + ', [ + 'id' => $userID, + 'name' => $newName, + ])->assertExactJson([ + 'data' => [ + 'user' => [ + 'update' => [ + 'id' => $userID, + 'name' => $newName, + ], + ], + ], + ]); + + $this->graphQL(/** @lang GraphQL */ ' + mutation ($id: ID!) { + user { + delete(id: $id) { + id + name + } + } + } + ', [ + 'id' => $userID, + ])->assertExactJson([ + 'data' => [ + 'user' => [ + 'delete' => [ + 'id' => $userID, + 'name' => $newName, + ], + ], + ], + ]); + } +}