From 08b2b56c960ddca5e28c3c4f0fb07ea2da63e7a2 Mon Sep 17 00:00:00 2001 From: Jason Kuhrt Date: Mon, 17 May 2021 12:24:36 -0400 Subject: [PATCH 1/5] tests: test & document 1:n relation projection --- README.md | 2 +- .../__snapshots__/relation1ToN.test.ts.snap | 38 +++++++++++ tests/integration/relation1ToN.test.ts | 66 +++++++++++++++++++ 3 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 tests/integration/__snapshots__/relation1ToN.test.ts.snap create mode 100644 tests/integration/relation1ToN.test.ts diff --git a/README.md b/README.md index cd9ad6bbd..b01591abb 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ export const schema = makeSchema({ ##### Midterm - [x] ([#25](https://github.com/prisma/nexus-prisma/pull/25), [#36](https://github.com/prisma/nexus-prisma/issues/36)) Support for Prisma Model field types relating to other Models 1:1 -- [ ] Support for Prisma Model field types relating to other Models 1:n +- [x] () Support for Prisma Model field types relating to other Models 1:n - [ ] Support for Prisma Model field types relating to other Models n:n ##### Longterm diff --git a/tests/integration/__snapshots__/relation1ToN.test.ts.snap b/tests/integration/__snapshots__/relation1ToN.test.ts.snap new file mode 100644 index 000000000..8e13123ca --- /dev/null +++ b/tests/integration/__snapshots__/relation1ToN.test.ts.snap @@ -0,0 +1,38 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`can project user-to-posts relationship: graphqlOperationExecutionResult 1`] = ` +Object { + "data": Object { + "users": Array [ + Object { + "id": "user1", + "posts": Array [ + Object { + "id": "post1", + }, + Object { + "id": "post2", + }, + ], + }, + ], + }, +} +`; + +exports[`can project user-to-posts relationship: graphqlSchemaSDL 1`] = ` +" +type Query { + users: [User] +} + +type User { + id: ID! + posts: [Post!]! +} + +type Post { + id: ID! +} +" +`; diff --git a/tests/integration/relation1ToN.test.ts b/tests/integration/relation1ToN.test.ts new file mode 100644 index 000000000..dab957c32 --- /dev/null +++ b/tests/integration/relation1ToN.test.ts @@ -0,0 +1,66 @@ +import endent from 'endent' +import { gql } from 'graphql-tag' +import { objectType, queryType } from 'nexus' +import { testIntegration } from '../__helpers__' + +testIntegration({ + description: 'can project user-to-posts relationship', + datasourceSchema: endent` + model User { + id String @id + posts Post[] + } + model Post { + id String @id + author User? @relation(fields: [authorId], references: [id]) + authorId String + } + `, + datasourceSeed(prisma) { + return prisma.user.create({ + data: { + id: 'user1', + posts: { + create: [{ id: 'post1' }, { id: 'post2' }], + }, + }, + }) + }, + apiSchema({ User, Post }) { + return [ + queryType({ + definition(t) { + t.list.field('users', { + type: 'User', + resolve(_, __, ctx) { + return ctx.prisma.user.findMany() + }, + }) + }, + }), + objectType({ + name: User.$name, + definition(t) { + t.field(User.id.name, User.id) + t.field(User.posts.name, User.posts) + }, + }), + objectType({ + name: Post.$name, + definition(t) { + t.field(Post.id.name, Post.id) + }, + }), + ] + }, + apiClientQuery: gql` + query { + users { + id + posts { + id + } + } + } + `, +}) From 6165f461845a27b4f9f9db3189f152adba71f8cb Mon Sep 17 00:00:00 2001 From: Jason Kuhrt Date: Mon, 17 May 2021 12:40:22 -0400 Subject: [PATCH 2/5] tidy --- README.md | 160 ++++++++++++++++-- .../__snapshots__/relation1To1.test.ts.snap | 40 ++++- .../__snapshots__/relation1ToN.test.ts.snap | 2 +- tests/integration/relation1To1.test.ts | 78 ++++----- tests/integration/relation1ToN.test.ts | 22 +-- 5 files changed, 237 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index b01591abb..e1817c554 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,8 @@ Official Prisma plugin for Nexus. - [Project 1:1 Relation](#project-11-relation) - [Example: Full 1:1](#example-full-11) - [Limitation: Nullable on Without-Relation-Scalar Side](#limitation-nullable-on-without-relation-scalar-side) + - [Project 1:n Relation](#project-1n-relation) + - [Example: Full 1:n](#example-full-1n) - [Prisma ID field to GraphQL ID scalar type mapping](#prisma-id-field-to-graphql-id-scalar-type-mapping) - [Prisma Schema docs re-used for GraphQL schema doc](#prisma-schema-docs-re-used-for-graphql-schema-doc) - [Prisma Schema docs re-used for JSDoc](#prisma-schema-docs-re-used-for-jsdoc) @@ -105,7 +107,7 @@ export const schema = makeSchema({ ##### Midterm - [x] ([#25](https://github.com/prisma/nexus-prisma/pull/25), [#36](https://github.com/prisma/nexus-prisma/issues/36)) Support for Prisma Model field types relating to other Models 1:1 -- [x] () Support for Prisma Model field types relating to other Models 1:n +- [x] ([#38](https://github.com/prisma/nexus-prisma/pull/38)) Support for Prisma Model field types relating to other Models 1:n - [ ] Support for Prisma Model field types relating to other Models n:n ##### Longterm @@ -237,6 +239,13 @@ new ApolloServer({ You can project [1:1 relationships](https://www.prisma.io/docs/concepts/components/prisma-schema/relations#one-to-one-relations) into your API. +#### Example: Tests + +The integration test suite is a useful reference as it is declarative (easy to read) and gives a known-working example spanning from database all the way to executed GraphQL document. + +- [Tests](https://github.com/prisma/nexus-prisma/blob/main/tests/integration/relation1To1.test.ts) +- [Snapshots](https://github.com/prisma/nexus-prisma/blob/main/tests/integration/__snapshots__/relation1To1.test.ts.snap) + #### Example: Full 1:1 ```prisma @@ -259,6 +268,17 @@ model Profile { import { User, Profile } from 'nexus-prisma' +queryType({ + definition(t) { + t.nonNull.list.nonNull.field('users', { + type: 'User', + resolve(_, __, ctx) { + return ctx.prisma.user.findMany() + }, + }) + }, +}) + objectType({ name: User.$name, definition(t) { @@ -273,22 +293,15 @@ objectType({ t.field(Profile.id.name, Profile.id) }, }) - -queryType({ - definition(t) { - t.list.field('users', { - type: 'User', - resolve(_, __, ctx) { - return ctx.prisma.user.findMany() - }, - }) - }, -}) ``` ```graphql # API Schema Represented in GraphQL SDL (this is generated by Nexus) +type Query { + users: [User!]! +} + type User { id: ID profile: Profile @@ -408,6 +421,129 @@ objectType({ }) ``` +### Project 1:n Relation + +You can project [1:n relationships](https://www.prisma.io/docs/concepts/components/prisma-schema/relations#one-to-many-relations) into your API. + +#### Example: Tests + +The integration test suite is a useful reference as it is declarative (easy to read) and gives a known-working example spanning from database all the way to executed GraphQL document. + +- [Tests](https://github.com/prisma/nexus-prisma/blob/main/tests/integration/relation1ToN.test.ts) +- [Snapshots](https://github.com/prisma/nexus-prisma/blob/main/tests/integration/__snapshots__/relation1ToN.test.ts.snap) + +#### Example: Full 1:n + +```prisma +// Database Schema + +model User { + id String @id + posts Post[] +} + +model Post { + id String @id + author User? @relation(fields: [authorId], references: [id]) + authorId String +} +``` + +```ts +// API Schema + +import { User, Post } from 'nexus-prisma' + +queryType({ + definition(t) { + t.nonNull.list.nonNull.field('users', { + type: 'User', + resolve(_, __, ctx) { + return ctx.prisma.user.findMany() + }, + }) + }, +}) + +objectType({ + name: User.$name, + definition(t) { + t.field(User.id.name, User.id) + t.field(User.posts.name, User.posts) + }, +}) + +objectType({ + name: Post.$name, + definition(t) { + t.field(Post.id.name, Post.id) + }, +}) +``` + +```graphql +# API Schema Represented in GraphQL SDL (this is generated by Nexus) + +type Query { + users: [User] +} + +type User { + id: ID! + posts: [Post!]! +} + +type Post { + id: ID! +} +``` + +```ts +// Example Database Data (for following example) + +await prisma.user.create({ + data: { + id: 'user1', + posts: { + create: [{ id: 'post1' }, { id: 'post2' }], + }, + }, +}) +``` + +```graphql +# Example API Client Query + +query { + users { + id + posts { + id + } + } +} +``` + +```json +{ + "data": { + "users": [ + { + "id": "user1", + "posts": [ + { + "id": "post1" + }, + { + "id": "post2" + } + ] + } + ] + } +} +``` + ### Prisma ID field to GraphQL ID scalar type mapping All `@id` fields in your Prisma Schema get projected as `ID` types, not `String` types. diff --git a/tests/integration/__snapshots__/relation1To1.test.ts.snap b/tests/integration/__snapshots__/relation1To1.test.ts.snap index a07d4b090..8b3d9099a 100644 --- a/tests/integration/__snapshots__/relation1To1.test.ts.snap +++ b/tests/integration/__snapshots__/relation1To1.test.ts.snap @@ -1,5 +1,41 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Nullable on Without-Relation-Scalar Side limitation can be worked around by wrapping type in an explicit nonNull: graphqlOperationExecutionResult 1`] = ` +Object { + "data": Object { + "users": Array [ + Object { + "id": "user1", + "profile": Object { + "id": "profile1", + "user": Object { + "id": "user1", + }, + }, + }, + ], + }, +} +`; + +exports[`Nullable on Without-Relation-Scalar Side limitation can be worked around by wrapping type in an explicit nonNull: graphqlSchemaSDL 1`] = ` +" +type Query { + users: [User!]! +} + +type User { + id: ID! + profile: Profile! +} + +type Profile { + id: ID! + user: User! +} +" +`; + exports[`can project relationship in opposite direction of where @relation is defined, but the field will be nullable: graphqlOperationExecutionResult 1`] = ` Object { "data": Object { @@ -21,7 +57,7 @@ Object { exports[`can project relationship in opposite direction of where @relation is defined, but the field will be nullable: graphqlSchemaSDL 1`] = ` " type Query { - users: [User] + users: [User!]! } type User { @@ -54,7 +90,7 @@ Object { exports[`can project user-to-profile relationship: graphqlSchemaSDL 1`] = ` " type Query { - users: [User] + users: [User!]! } type User { diff --git a/tests/integration/__snapshots__/relation1ToN.test.ts.snap b/tests/integration/__snapshots__/relation1ToN.test.ts.snap index 8e13123ca..889d67763 100644 --- a/tests/integration/__snapshots__/relation1ToN.test.ts.snap +++ b/tests/integration/__snapshots__/relation1ToN.test.ts.snap @@ -23,7 +23,7 @@ Object { exports[`can project user-to-posts relationship: graphqlSchemaSDL 1`] = ` " type Query { - users: [User] + users: [User!]! } type User { diff --git a/tests/integration/relation1To1.test.ts b/tests/integration/relation1To1.test.ts index c74923f3c..643c68b1a 100644 --- a/tests/integration/relation1To1.test.ts +++ b/tests/integration/relation1To1.test.ts @@ -16,23 +16,11 @@ testIntegration({ user User? } `, - datasourceSeed(prisma) { - return prisma.user.create({ - data: { - id: 'user1', - profile: { - create: { - id: 'profile1', - }, - }, - }, - }) - }, apiSchema({ User, Profile }) { return [ queryType({ definition(t) { - t.list.field('users', { + t.nonNull.list.nonNull.field('users', { type: 'User', resolve(_, __, ctx) { return ctx.prisma.user.findMany() @@ -55,6 +43,18 @@ testIntegration({ }), ] }, + async datasourceSeed(prisma) { + await prisma.user.create({ + data: { + id: 'user1', + profile: { + create: { + id: 'profile1', + }, + }, + }, + }) + }, apiClientQuery: gql` query { users { @@ -82,23 +82,11 @@ testIntegration({ user User? } `, - datasourceSeed(prisma) { - return prisma.user.create({ - data: { - id: 'user1', - profile: { - create: { - id: 'profile1', - }, - }, - }, - }) - }, apiSchema({ User, Profile }) { return [ queryType({ definition(t) { - t.list.field('users', { + t.nonNull.list.nonNull.field('users', { type: 'User', resolve(_, __, ctx) { return ctx.prisma.user.findMany() @@ -122,6 +110,18 @@ testIntegration({ }), ] }, + async datasourceSeed(prisma) { + await prisma.user.create({ + data: { + id: 'user1', + profile: { + create: { + id: 'profile1', + }, + }, + }, + }) + }, apiClientQuery: gql` query { users { @@ -151,23 +151,11 @@ testIntegration({ user User? } `, - datasourceSeed(prisma) { - return prisma.user.create({ - data: { - id: 'user1', - profile: { - create: { - id: 'profile1', - }, - }, - }, - }) - }, apiSchema({ User, Profile }) { return [ queryType({ definition(t) { - t.list.field('users', { + t.nonNull.list.nonNull.field('users', { type: 'User', resolve(_, __, ctx) { return ctx.prisma.user.findMany() @@ -194,6 +182,18 @@ testIntegration({ }), ] }, + async datasourceSeed(prisma) { + await prisma.user.create({ + data: { + id: 'user1', + profile: { + create: { + id: 'profile1', + }, + }, + }, + }) + }, apiClientQuery: gql` query { users { diff --git a/tests/integration/relation1ToN.test.ts b/tests/integration/relation1ToN.test.ts index dab957c32..76871cd63 100644 --- a/tests/integration/relation1ToN.test.ts +++ b/tests/integration/relation1ToN.test.ts @@ -16,21 +16,11 @@ testIntegration({ authorId String } `, - datasourceSeed(prisma) { - return prisma.user.create({ - data: { - id: 'user1', - posts: { - create: [{ id: 'post1' }, { id: 'post2' }], - }, - }, - }) - }, apiSchema({ User, Post }) { return [ queryType({ definition(t) { - t.list.field('users', { + t.nonNull.list.nonNull.field('users', { type: 'User', resolve(_, __, ctx) { return ctx.prisma.user.findMany() @@ -53,6 +43,16 @@ testIntegration({ }), ] }, + async datasourceSeed(prisma) { + await prisma.user.create({ + data: { + id: 'user1', + posts: { + create: [{ id: 'post1' }, { id: 'post2' }], + }, + }, + }) + }, apiClientQuery: gql` query { users { From a79dd725d8a89eecad23029bdcf39c08530dd402 Mon Sep 17 00:00:00 2001 From: Jason Kuhrt Date: Mon, 17 May 2021 12:42:50 -0400 Subject: [PATCH 3/5] update toc --- README.md | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e1817c554..fb6c73658 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,11 @@ Official Prisma plugin for Nexus. - [Limitation: Hardcoded key for Prisma Client on GraphQL Context](#limitation-hardcoded-key-for-prisma-client-on-graphql-context) - [Example: Exposing Prisma Client on GraphQL Context with Apollo Server](#example-exposing-prisma-client-on-graphql-context-with-apollo-server) - [Project 1:1 Relation](#project-11-relation) + - [Example: Tests](#example-tests) - [Example: Full 1:1](#example-full-11) - [Limitation: Nullable on Without-Relation-Scalar Side](#limitation-nullable-on-without-relation-scalar-side) - [Project 1:n Relation](#project-1n-relation) + - [Example: Tests](#example-tests-1) - [Example: Full 1:n](#example-full-1n) - [Prisma ID field to GraphQL ID scalar type mapping](#prisma-id-field-to-graphql-id-scalar-type-mapping) - [Prisma Schema docs re-used for GraphQL schema doc](#prisma-schema-docs-re-used-for-graphql-schema-doc) diff --git a/package.json b/package.json index 1a7a8709f..6c2b1c9aa 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "nexus_prisma": "./dist/cli/nexus-prisma.js" }, "scripts": { - "reflect:toc": "markdown-toc README.md -i --maxdepth 4", + "reflect:toc": "markdown-toc README.md -i --maxdepth 4 && prettier --write README.md", "format": "prettier --write .", "format:check": "prettier --check .", "lint": "eslint . --ext .ts,.tsx --fix", From 48990e3ac987a5b6e09db426ec852e980e35ffad Mon Sep 17 00:00:00 2001 From: Jason Kuhrt Date: Mon, 17 May 2021 12:56:12 -0400 Subject: [PATCH 4/5] add recipe for custom resolvers --- README.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/README.md b/README.md index fb6c73658..f2b76d46f 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ Official Prisma plugin for Nexus. - [Prisma Schema docs re-used for JSDoc](#prisma-schema-docs-re-used-for-jsdoc) - [Refined DX](#refined-dx) - [Recipes](#recipes) + - [Project relation with custom resolver logic](#project-relation-with-custom-resolver-logic) - [Supply custom custom scalars to your GraphQL schema](#supply-custom-custom-scalars-to-your-graphql-schema) - [Notes](#notes) @@ -659,6 +660,47 @@ PEER_DEPENDENCY_CHECK=false|0 ## Recipes +### Project relation with custom resolver logic + +Nexus Prisma generates default GraphQL resolvers for your model _relation fields_. However you may want to run custom logic in the resolver. This is easy to do. The following show a few ways. + +1. **Wrap Style** You can access the default resolver within your own custom resolver. + + ```ts + objectType({ + name: User.$name, + definition(t) { + t.field(User.id.name, User.id) + t.field(User.posts.name, { + ...User.posts, + resolve(...args) { + // Your custom before-logic here + const result = await User.posts.resolve(...args) + // Your custom afer-logic here + return result + }, + }) + }, + }) + ``` + +2. **Replace Style** You can simply opt out of using the default resolver completely: + + ```ts + objectType({ + name: User.$name, + definition(t) { + t.field(User.id.name, User.id) + t.field(User.posts.name, { + ...User.posts, + resolve(...args) { + // Your custom logic here + }, + }) + }, + }) + ``` + ### Supply custom custom scalars to your GraphQL schema The following is a brief example how you could add your own custom GraphQL custom scalars to satisfy Nexus Prisma. Note that most of the time using the defaults exported by `nexus-prisma/scalars` will probably be good enough for you. From 38875492aa31d2f4f78d1d175477386dbb06bf89 Mon Sep 17 00:00:00 2001 From: Jason Kuhrt Date: Mon, 17 May 2021 12:58:02 -0400 Subject: [PATCH 5/5] adjust roadmap items --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f2b76d46f..1c30399a0 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,8 @@ export const schema = makeSchema({ - [x] ([#4](https://github.com/prisma/nexus-prisma/issues/4)) Support for Prisma Model field types that map to standard GraphQL scalars - [x] ([#8](https://github.com/prisma/nexus-prisma/issues/8)) Support for Prisma Model field types of `DateTime` & `Json` - [x] ([#16](https://github.com/prisma/nexus-prisma/issues/16)) Support for Prisma enums +- [x] ([#25](https://github.com/prisma/nexus-prisma/pull/25), [#36](https://github.com/prisma/nexus-prisma/issues/36)) Basic support for Prisma Model field types relating to other Models 1:1 +- [x] ([#38](https://github.com/prisma/nexus-prisma/pull/38)) Basic support for Prisma Model field types relating to other Models 1:n ##### Shortterm @@ -109,8 +111,6 @@ export const schema = makeSchema({ ##### Midterm -- [x] ([#25](https://github.com/prisma/nexus-prisma/pull/25), [#36](https://github.com/prisma/nexus-prisma/issues/36)) Support for Prisma Model field types relating to other Models 1:1 -- [x] ([#38](https://github.com/prisma/nexus-prisma/pull/38)) Support for Prisma Model field types relating to other Models 1:n - [ ] Support for Prisma Model field types relating to other Models n:n ##### Longterm