@pothos/plugin-prisma 0.18.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +420 -315
- package/esm/field-builder.d.ts.map +1 -1
- package/esm/field-builder.js +19 -6
- package/esm/field-builder.js.map +1 -1
- package/esm/generator.js +4 -0
- package/esm/generator.js.map +1 -1
- package/esm/global-types.d.ts +23 -5
- package/esm/global-types.d.ts.map +1 -1
- package/esm/index.d.ts +4 -1
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +46 -1
- package/esm/index.js.map +1 -1
- package/esm/model-loader.d.ts +5 -6
- package/esm/model-loader.d.ts.map +1 -1
- package/esm/model-loader.js +18 -78
- package/esm/model-loader.js.map +1 -1
- package/esm/prisma-field-builder.d.ts +25 -6
- package/esm/prisma-field-builder.d.ts.map +1 -1
- package/esm/prisma-field-builder.js +86 -118
- package/esm/prisma-field-builder.js.map +1 -1
- package/esm/schema-builder.js +14 -4
- package/esm/schema-builder.js.map +1 -1
- package/esm/types.d.ts +74 -77
- package/esm/types.d.ts.map +1 -1
- package/esm/types.js +1 -0
- package/esm/types.js.map +1 -1
- package/esm/{cursors.d.ts → util/cursors.d.ts} +5 -5
- package/esm/util/cursors.d.ts.map +1 -0
- package/esm/{cursors.js → util/cursors.js} +0 -0
- package/esm/util/cursors.js.map +1 -0
- package/{lib/refs.d.ts → esm/util/datamodel.d.ts} +4 -8
- package/esm/util/datamodel.d.ts.map +1 -0
- package/esm/{refs.js → util/datamodel.js} +2 -25
- package/esm/util/datamodel.js.map +1 -0
- package/esm/util/deep-equal.d.ts +2 -0
- package/esm/util/deep-equal.d.ts.map +1 -0
- package/esm/util/deep-equal.js +39 -0
- package/esm/util/deep-equal.js.map +1 -0
- package/esm/util/loader-map.d.ts +6 -0
- package/esm/util/loader-map.d.ts.map +1 -0
- package/esm/{loader-map.js → util/loader-map.js} +10 -12
- package/esm/util/loader-map.js.map +1 -0
- package/esm/util/map-query.d.ts +6 -0
- package/esm/util/map-query.d.ts.map +1 -0
- package/esm/util/map-query.js +169 -0
- package/esm/util/map-query.js.map +1 -0
- package/esm/util/relation-map.d.ts +9 -0
- package/esm/util/relation-map.d.ts.map +1 -0
- package/esm/util/relation-map.js +20 -0
- package/esm/util/relation-map.js.map +1 -0
- package/esm/util/selections.d.ts +20 -0
- package/esm/util/selections.d.ts.map +1 -0
- package/esm/util/selections.js +139 -0
- package/esm/util/selections.js.map +1 -0
- package/lib/field-builder.d.ts.map +1 -1
- package/lib/field-builder.js +30 -13
- package/lib/field-builder.js.map +1 -1
- package/lib/generator.js +9 -1
- package/lib/generator.js.map +1 -1
- package/lib/global-types.d.ts +23 -5
- package/lib/global-types.d.ts.map +1 -1
- package/lib/index.d.ts +4 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +50 -1
- package/lib/index.js.map +1 -1
- package/lib/model-loader.d.ts +5 -6
- package/lib/model-loader.d.ts.map +1 -1
- package/lib/model-loader.js +19 -79
- package/lib/model-loader.js.map +1 -1
- package/lib/prisma-field-builder.d.ts +25 -6
- package/lib/prisma-field-builder.d.ts.map +1 -1
- package/lib/prisma-field-builder.js +92 -124
- package/lib/prisma-field-builder.js.map +1 -1
- package/lib/schema-builder.js +22 -8
- package/lib/schema-builder.js.map +1 -1
- package/lib/types.d.ts +74 -77
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js +2 -0
- package/lib/types.js.map +1 -1
- package/lib/{cursors.d.ts → util/cursors.d.ts} +5 -5
- package/lib/util/cursors.d.ts.map +1 -0
- package/lib/{cursors.js → util/cursors.js} +0 -0
- package/lib/util/cursors.js.map +1 -0
- package/{esm/refs.d.ts → lib/util/datamodel.d.ts} +4 -8
- package/lib/util/datamodel.d.ts.map +1 -0
- package/lib/{refs.js → util/datamodel.js} +3 -29
- package/lib/util/datamodel.js.map +1 -0
- package/lib/util/deep-equal.d.ts +2 -0
- package/lib/util/deep-equal.d.ts.map +1 -0
- package/lib/util/deep-equal.js +43 -0
- package/lib/util/deep-equal.js.map +1 -0
- package/lib/util/loader-map.d.ts +6 -0
- package/lib/util/loader-map.d.ts.map +1 -0
- package/lib/{loader-map.js → util/loader-map.js} +10 -12
- package/lib/util/loader-map.js.map +1 -0
- package/lib/util/map-query.d.ts +6 -0
- package/lib/util/map-query.d.ts.map +1 -0
- package/lib/util/map-query.js +175 -0
- package/lib/util/map-query.js.map +1 -0
- package/lib/util/relation-map.d.ts +9 -0
- package/lib/util/relation-map.d.ts.map +1 -0
- package/lib/util/relation-map.js +24 -0
- package/lib/util/relation-map.js.map +1 -0
- package/lib/util/selections.d.ts +20 -0
- package/lib/util/selections.d.ts.map +1 -0
- package/lib/util/selections.js +148 -0
- package/lib/util/selections.js.map +1 -0
- package/package.json +8 -8
- package/src/field-builder.ts +22 -5
- package/src/generator.ts +18 -0
- package/src/global-types.ts +59 -12
- package/src/index.ts +75 -1
- package/src/model-loader.ts +27 -106
- package/src/prisma-field-builder.ts +195 -152
- package/src/schema-builder.ts +28 -7
- package/src/types.ts +155 -102
- package/src/{cursors.ts → util/cursors.ts} +3 -3
- package/src/{refs.ts → util/datamodel.ts} +3 -44
- package/src/util/deep-equal.ts +51 -0
- package/src/{loader-map.ts → util/loader-map.ts} +13 -13
- package/src/util/map-query.ts +327 -0
- package/src/util/relation-map.ts +36 -0
- package/src/util/selections.ts +192 -0
- package/esm/cursors.d.ts.map +0 -1
- package/esm/cursors.js.map +0 -1
- package/esm/loader-map.d.ts +0 -6
- package/esm/loader-map.d.ts.map +0 -1
- package/esm/loader-map.js.map +0 -1
- package/esm/refs.d.ts.map +0 -1
- package/esm/refs.js.map +0 -1
- package/esm/util/index.d.ts +0 -5
- package/esm/util/index.d.ts.map +0 -1
- package/esm/util/index.js +0 -16
- package/esm/util/index.js.map +0 -1
- package/esm/util/map-includes.d.ts +0 -6
- package/esm/util/map-includes.d.ts.map +0 -1
- package/esm/util/map-includes.js +0 -184
- package/esm/util/map-includes.js.map +0 -1
- package/esm/util/merge-includes.d.ts +0 -3
- package/esm/util/merge-includes.d.ts.map +0 -1
- package/esm/util/merge-includes.js +0 -91
- package/esm/util/merge-includes.js.map +0 -1
- package/lib/cursors.d.ts.map +0 -1
- package/lib/cursors.js.map +0 -1
- package/lib/loader-map.d.ts +0 -6
- package/lib/loader-map.d.ts.map +0 -1
- package/lib/loader-map.js.map +0 -1
- package/lib/refs.d.ts.map +0 -1
- package/lib/refs.js.map +0 -1
- package/lib/util/index.d.ts +0 -5
- package/lib/util/index.d.ts.map +0 -1
- package/lib/util/index.js +0 -30
- package/lib/util/index.js.map +0 -1
- package/lib/util/map-includes.d.ts +0 -6
- package/lib/util/map-includes.d.ts.map +0 -1
- package/lib/util/map-includes.js +0 -189
- package/lib/util/map-includes.js.map +0 -1
- package/lib/util/merge-includes.d.ts +0 -3
- package/lib/util/merge-includes.d.ts.map +0 -1
- package/lib/util/merge-includes.js +0 -96
- package/lib/util/merge-includes.js.map +0 -1
- package/src/util/index.ts +0 -26
- package/src/util/map-includes.ts +0 -328
- package/src/util/merge-includes.ts +0 -121
package/README.md
CHANGED
|
@@ -4,70 +4,79 @@ This plugin provides tighter integration with prisma, making it easier to define
|
|
|
4
4
|
types, and helps solve n+1 queries for relations. It also has integrations for the relay plugin to
|
|
5
5
|
make defining nodes and connections easy and efficient.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
and reason about, so this plugin tries to make things as clear as possible by providing query
|
|
24
|
-
options to resolvers and a loading user code to initiate the actual queries. The options generally
|
|
25
|
-
only contain `include`s for nested relations (connection fields provide more complex query options).
|
|
26
|
-
The exception to this, is that we provide a default resolver for relations that can handle querying
|
|
27
|
-
for a relation if data was not pre-loaded by a parent field. This query used by this resolver is
|
|
28
|
-
simple, and described in detail below.
|
|
29
|
-
|
|
30
|
-
With this simple approach, we get an API that is easy to understand, but still provides a lot of
|
|
31
|
-
value and functionality.
|
|
7
|
+
This plugin is NOT required to use prisma with Pothos, but does make things a lot easier and more
|
|
8
|
+
efficient. See the [Using Prisma without a plugin](#using-prisma-without-a-plugin) section below for
|
|
9
|
+
more details.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- 🎨 Quickly define GraphQL types based on your Prisma models
|
|
14
|
+
- 🦺 Strong type-safety throughout the entire API
|
|
15
|
+
- 🤝 Automatically resolve relationships defined in your database
|
|
16
|
+
- 🎣 Automatic Query optimization to efficiently load the specific data needed to resolve a query
|
|
17
|
+
(solves common N+1 issues)
|
|
18
|
+
- 💅 Types and fields in GraphQL schema are not implicitly tied to the column names or types in your
|
|
19
|
+
database.
|
|
20
|
+
- 🔀 Relay integration for defining nodes and connections that can be efficiently loaded.
|
|
21
|
+
- 📚 Supports multiple GraphQL models based on the same Database model
|
|
22
|
+
- 🧮 Count fields can easily be added to objects and connections
|
|
32
23
|
|
|
33
24
|
## Example
|
|
34
25
|
|
|
35
26
|
Here is a quick example of what an API using this plugin might look like. There is a more thorough
|
|
36
27
|
breakdown of what the methods and options used in the example below.
|
|
37
28
|
|
|
38
|
-
If you are looking for an example integrated with the
|
|
39
|
-
[relay plugin](https://pothos-graphql.dev/docs/plugins/relay), see the
|
|
40
|
-
[Relay integration](#relay-integration) section below.
|
|
41
|
-
|
|
42
29
|
```typescript
|
|
30
|
+
// Create an object type based on a prisma model
|
|
31
|
+
// without providing any custom type information
|
|
43
32
|
builder.prismaObject('User', {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
},
|
|
33
|
+
// findUnique is explained more below, and is
|
|
34
|
+
// required to safely resolve queries in some edge cases
|
|
47
35
|
findUnique: (user) => ({ id: user.id }),
|
|
48
36
|
fields: (t) => ({
|
|
37
|
+
// expose fields from the database
|
|
49
38
|
id: t.exposeID('id'),
|
|
50
39
|
email: t.exposeString('email'),
|
|
51
40
|
bio: t.string({
|
|
41
|
+
// automatically load the bio from the profile
|
|
42
|
+
// when this field is queried
|
|
43
|
+
select: {
|
|
44
|
+
profile: {
|
|
45
|
+
select: {
|
|
46
|
+
bio: true,
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
// user will be typed correctly to include the
|
|
51
|
+
// selected fields from above
|
|
52
52
|
resolve: (user) => user.profile.bio,
|
|
53
53
|
}),
|
|
54
|
+
// Load posts as list field.
|
|
54
55
|
posts: t.relation('posts', {
|
|
55
56
|
args: {
|
|
56
57
|
oldestFirst: t.arg.boolean(),
|
|
57
58
|
},
|
|
59
|
+
// Define custom query options that are applied when
|
|
60
|
+
// loading the post relation
|
|
58
61
|
query: (args, context) => ({
|
|
59
62
|
orderBy: {
|
|
60
63
|
createdAt: args.oldestFirst ? 'asc' : 'desc',
|
|
61
64
|
},
|
|
62
65
|
}),
|
|
63
66
|
}),
|
|
67
|
+
// creates relay connection that handles pagination
|
|
68
|
+
// using prisma's built in cursor based pagination
|
|
69
|
+
postsConnection: t.relatedConnection('posts', {
|
|
70
|
+
cursor: 'id',
|
|
71
|
+
}),
|
|
64
72
|
}),
|
|
65
73
|
});
|
|
66
74
|
|
|
67
|
-
|
|
68
|
-
|
|
75
|
+
// Create a relay node based a prisma model
|
|
76
|
+
builder.prismaNode('Post', {
|
|
77
|
+
findUnique: (id) => ({ id }),
|
|
78
|
+
id: { resolve: (post) => post.id },
|
|
69
79
|
fields: (t) => ({
|
|
70
|
-
id: t.exposeID('id'),
|
|
71
80
|
title: t.exposeString('title'),
|
|
72
81
|
author: t.relation('author'),
|
|
73
82
|
}),
|
|
@@ -75,10 +84,13 @@ builder.prismaObject('Post', {
|
|
|
75
84
|
|
|
76
85
|
builder.queryType({
|
|
77
86
|
fields: (t) => ({
|
|
87
|
+
// Define a field that issues an optimized prisma query
|
|
78
88
|
me: t.prismaField({
|
|
79
89
|
type: 'User',
|
|
80
90
|
resolve: async (query, root, args, ctx, info) =>
|
|
81
91
|
prisma.user.findUnique({
|
|
92
|
+
// the `query` argument will add in `include`s or `select`s to
|
|
93
|
+
// resolve as much of the request in a single query as possible
|
|
82
94
|
...query,
|
|
83
95
|
rejectOnNotFound: true,
|
|
84
96
|
where: { id: ctx.userId },
|
|
@@ -129,153 +141,17 @@ query {
|
|
|
129
141
|
|
|
130
142
|
Will result in 2 calls to prisma, one to resolve everything except `oldPosts`, and a second to
|
|
131
143
|
resolve everything inside `oldPosts`. Prisma can only resolve each relation once in a single query,
|
|
132
|
-
so we need a separate to handle the second `posts` relation.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
## Pothos + Prisma without a plugin
|
|
136
|
-
|
|
137
|
-
If you just want learn about the plugin, feel free to skip this section, but understanding how to
|
|
138
|
-
use prisma without a plugin may be useful for evaluating if this plugin is a good fit for your use
|
|
139
|
-
case.
|
|
140
|
-
|
|
141
|
-
Using prisma without a plugin is relatively straight forward using the `builder.objectRef` method.
|
|
142
|
-
|
|
143
|
-
The easiest way to create types backed by prisma looks something like:
|
|
144
|
-
|
|
145
|
-
```typescript
|
|
146
|
-
import { Post, PrismaClient, User } from '@prisma/client';
|
|
147
|
-
|
|
148
|
-
const db = new PrismaClient();
|
|
149
|
-
const UserObject = builder.objectRef<User>('User');
|
|
150
|
-
const PostObject = builder.objectRef<Post>('Post');
|
|
151
|
-
|
|
152
|
-
UserObject.implement({
|
|
153
|
-
fields: (t) => ({
|
|
154
|
-
id: t.exposeID('id'),
|
|
155
|
-
email: t.exposeString('email'),
|
|
156
|
-
posts: t.field({
|
|
157
|
-
type: [PostObject],
|
|
158
|
-
resolve: (user) =>
|
|
159
|
-
db.post.findMany({
|
|
160
|
-
where: { authorId: user.id },
|
|
161
|
-
}),
|
|
162
|
-
}),
|
|
163
|
-
}),
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
PostObject.implement({
|
|
167
|
-
fields: (t) => ({
|
|
168
|
-
id: t.exposeID('id'),
|
|
169
|
-
title: t.exposeString('title'),
|
|
170
|
-
author: t.field({
|
|
171
|
-
type: UserObject,
|
|
172
|
-
resolve: (post) =>
|
|
173
|
-
db.user.findUnique({ rejectOnNotFound: true, where: { id: post.authorId } }),
|
|
174
|
-
}),
|
|
175
|
-
}),
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
builder.queryType({
|
|
179
|
-
fields: (t) => ({
|
|
180
|
-
me: t.field({
|
|
181
|
-
type: UserObject,
|
|
182
|
-
resolve: (root, args, ctx) =>
|
|
183
|
-
db.user.findUnique({ rejectOnNotFound: true, where: { id: ctx.userId } }),
|
|
184
|
-
}),
|
|
185
|
-
}),
|
|
186
|
-
});
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
This sets up User, and Post objects with a few fields, and a `me` query that returns the current
|
|
190
|
-
user. There are a few things to note in this setup:
|
|
191
|
-
|
|
192
|
-
1. We split up the `builder.objectRef` and the `implement` calls, rather than calling
|
|
193
|
-
`builder.objectRef(...).implement(...)`. This prevents typescript from getting tripped up by the
|
|
194
|
-
circular references between posts and users.
|
|
195
|
-
2. We use rejectOnNotFound with our `findUnique` calls because those fields are not nullable.
|
|
196
|
-
Without this option, prisma will return a null if the object is not found. An alternative is to
|
|
197
|
-
mark these fields as nullable.
|
|
198
|
-
3. The refs to our object types are called `UserObject` and `PostObject`, this is because `User` and
|
|
199
|
-
`Post` are the names of the types imported from prisma. We could instead alias the types when we
|
|
200
|
-
import them so we can name the refs to our GraphQL types after the models.
|
|
201
|
-
|
|
202
|
-
This setup is fairly simple, but it is easy to see the n+1 issues we might run into. Prisma helps
|
|
203
|
-
with this by batching queries together, but there are also things we can do in our implementation to
|
|
204
|
-
improve things.
|
|
205
|
-
|
|
206
|
-
One thing we could do if we know we will usually be loading the author any time we load a post is to
|
|
207
|
-
make the author part of shape required for a post:
|
|
208
|
-
|
|
209
|
-
```typescript
|
|
210
|
-
const UserObject = builder.objectRef<User>('User');
|
|
211
|
-
// We add the author here in the objectRef
|
|
212
|
-
const PostObject = builder.objectRef<Post & { author: User }>('Post');
|
|
213
|
-
|
|
214
|
-
UserObject.implement({
|
|
215
|
-
fields: (t) => ({
|
|
216
|
-
id: t.exposeID('id'),
|
|
217
|
-
email: t.exposeString('email'),
|
|
218
|
-
posts: t.field({
|
|
219
|
-
type: [PostObject],
|
|
220
|
-
resolve: (user) =>
|
|
221
|
-
db.post.findMany({
|
|
222
|
-
// We now need to include the author when we query for posts
|
|
223
|
-
include: {
|
|
224
|
-
author: true,
|
|
225
|
-
},
|
|
226
|
-
where: { authorId: user.id },
|
|
227
|
-
}),
|
|
228
|
-
}),
|
|
229
|
-
}),
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
PostObject.implement({
|
|
233
|
-
fields: (t) => ({
|
|
234
|
-
id: t.exposeID('id'),
|
|
235
|
-
title: t.exposeString('title'),
|
|
236
|
-
author: t.field({
|
|
237
|
-
type: UserObject,
|
|
238
|
-
// Now we can just return the author from the post instead of querying for it
|
|
239
|
-
resolve: (post) => post.author,
|
|
240
|
-
}),
|
|
241
|
-
}),
|
|
242
|
-
});
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
We may not always want to query for the author though, so we could make the author optional and fall
|
|
246
|
-
back to using a query if it was not provided by the parent resolver:
|
|
247
|
-
|
|
248
|
-
```typescript
|
|
249
|
-
const PostObject = builder.objectRef<Post & { author?: User }>('Post');
|
|
250
|
-
|
|
251
|
-
PostObject.implement({
|
|
252
|
-
fields: (t) => ({
|
|
253
|
-
id: t.exposeID('id'),
|
|
254
|
-
title: t.exposeString('title'),
|
|
255
|
-
author: t.field({
|
|
256
|
-
type: UserObject,
|
|
257
|
-
resolve: (post) =>
|
|
258
|
-
post.author ?? db.user.findUnique({ rejectOnNotFound: true, where: { id: post.authorId } }),
|
|
259
|
-
}),
|
|
260
|
-
}),
|
|
261
|
-
});
|
|
262
|
-
```
|
|
144
|
+
so we need a separate to handle the second `posts` relation. These additional queries will use the
|
|
145
|
+
`findUnique` defined for the parent type to create a new efficient query to load any conflicting
|
|
146
|
+
relations.
|
|
263
147
|
|
|
264
|
-
|
|
265
|
-
incase it does not.
|
|
266
|
-
|
|
267
|
-
There are other patterns like dataloaders than can be used to reduce n+1 issues, and make your graph
|
|
268
|
-
more efficient, but they are too complex to describe here.
|
|
269
|
-
|
|
270
|
-
## Usage
|
|
271
|
-
|
|
272
|
-
### Install
|
|
148
|
+
## Install
|
|
273
149
|
|
|
274
150
|
```bash
|
|
275
151
|
yarn add @pothos/plugin-prisma
|
|
276
152
|
```
|
|
277
153
|
|
|
278
|
-
|
|
154
|
+
## Setup
|
|
279
155
|
|
|
280
156
|
This plugin requires a little more setup than other plugins because it integrates with the prisma to
|
|
281
157
|
generate some types that help the plugin better understand your prisma schema. Previous versions of
|
|
@@ -283,7 +159,7 @@ this plugin used to infer all required types from the prisma client itself, but
|
|
|
283
159
|
poor dev experience because the complex types slowed down editors, and some more advanced use cases
|
|
284
160
|
could not be typed correctly.
|
|
285
161
|
|
|
286
|
-
|
|
162
|
+
### Add a the `pothos` generator to your prisma schema
|
|
287
163
|
|
|
288
164
|
```
|
|
289
165
|
generator pothos {
|
|
@@ -294,7 +170,7 @@ generator pothos {
|
|
|
294
170
|
Now the types Pothos uses will be generated whenever you re-generate your prisma client. Run the
|
|
295
171
|
following command to re-generate the client and create the new types:
|
|
296
172
|
|
|
297
|
-
```
|
|
173
|
+
```sh
|
|
298
174
|
npx prisma generate
|
|
299
175
|
```
|
|
300
176
|
|
|
@@ -315,14 +191,16 @@ generator pothos {
|
|
|
315
191
|
}
|
|
316
192
|
```
|
|
317
193
|
|
|
318
|
-
|
|
194
|
+
### Set up the builder
|
|
319
195
|
|
|
320
196
|
```typescript
|
|
321
197
|
import SchemaBuilder from '@pothos/core';
|
|
322
198
|
import { PrismaClient } from '@prisma/client';
|
|
323
199
|
import PrismaPlugin from '@pothos/plugin-prisma';
|
|
324
|
-
// This is the default location for the generator, but this can be
|
|
325
|
-
//
|
|
200
|
+
// This is the default location for the generator, but this can be
|
|
201
|
+
// customized as described above.
|
|
202
|
+
// Using a type only import will help avoid issues with undeclared
|
|
203
|
+
// exports in esm mode
|
|
326
204
|
import type PrismaTypes from '@pothos/plugin-prisma/generated';
|
|
327
205
|
|
|
328
206
|
const prisma = new PrismaClient({});
|
|
@@ -339,9 +217,9 @@ const builder = new SchemaBuilder<{
|
|
|
339
217
|
|
|
340
218
|
It is strongly recommended NOT to put your prisma client into `Context`. This will result in slower
|
|
341
219
|
type-checking and a laggy developer experience in VSCode. See
|
|
342
|
-
https://github.com/microsoft/TypeScript/issues/45405 for more details.
|
|
220
|
+
[this issue](https://github.com/microsoft/TypeScript/issues/45405) for more details.
|
|
343
221
|
|
|
344
|
-
|
|
222
|
+
## Creating types with `builder.prismaObject`
|
|
345
223
|
|
|
346
224
|
`builder.prismaObject` takes 2 arguments:
|
|
347
225
|
|
|
@@ -353,7 +231,7 @@ https://github.com/microsoft/TypeScript/issues/45405 for more details.
|
|
|
353
231
|
builder.prismaObject('User', {
|
|
354
232
|
// Optional name for the object, defaults to the name of the prisma model
|
|
355
233
|
name: 'PostAuthor',
|
|
356
|
-
findUnique:
|
|
234
|
+
findUnique: (user) => ({ id: user.id }),
|
|
357
235
|
fields: (t) => ({
|
|
358
236
|
id: t.exposeID('id'),
|
|
359
237
|
email: t.exposeString('email'),
|
|
@@ -361,7 +239,7 @@ builder.prismaObject('User', {
|
|
|
361
239
|
});
|
|
362
240
|
|
|
363
241
|
builder.prismaObject('Post', {
|
|
364
|
-
findUnique:
|
|
242
|
+
findUnique: (post) => ({ id: post.id }),
|
|
365
243
|
fields: (t) => ({
|
|
366
244
|
id: t.exposeID('id'),
|
|
367
245
|
title: t.exposeString('title'),
|
|
@@ -370,12 +248,12 @@ builder.prismaObject('Post', {
|
|
|
370
248
|
```
|
|
371
249
|
|
|
372
250
|
So far, this is just creating some simple object types. They work just like any other object type in
|
|
373
|
-
Pothos.
|
|
374
|
-
|
|
251
|
+
Pothos. The main advantage of this is that we get the type information without using object refs, or
|
|
252
|
+
needing imports from prisma client.
|
|
375
253
|
|
|
376
254
|
The `findUnique` option is described more below.
|
|
377
255
|
|
|
378
|
-
|
|
256
|
+
## Adding prisma fields to non-prisma objects (including Query and Mutation)
|
|
379
257
|
|
|
380
258
|
There is a new `t.prismaField` method which can be used to define fields that resolve to your prisma
|
|
381
259
|
types:
|
|
@@ -407,72 +285,29 @@ You do not need to use this method, and the `builder.prismaObject` method return
|
|
|
407
285
|
can be used like any other object ref (with `t.field`), but using `t.prismaField` will allow you to
|
|
408
286
|
take advantage of more efficient queries.
|
|
409
287
|
|
|
410
|
-
The `query` object will contain an `include`
|
|
411
|
-
of the current query.
|
|
288
|
+
The `query` object will contain an object with `include` or `select` options to pre-load data needed
|
|
289
|
+
to resolve nested parts of the current query. The included/selected fields are based on which fields
|
|
290
|
+
are being queried, and the options provided when defining those fields and types.
|
|
412
291
|
|
|
413
|
-
|
|
414
|
-
be without this plugin.
|
|
415
|
-
|
|
416
|
-
### Adding relations
|
|
292
|
+
## Adding relations
|
|
417
293
|
|
|
418
294
|
You can add fields for relations using the `t.relation` method:
|
|
419
295
|
|
|
420
296
|
```typescript
|
|
421
|
-
builder.
|
|
422
|
-
findUnique: null,
|
|
297
|
+
builder.queryType({
|
|
423
298
|
fields: (t) => ({
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
db.post.findMany({
|
|
299
|
+
me: t.prismaField({
|
|
300
|
+
type: 'User',
|
|
301
|
+
resolve: async (query, root, args, ctx, info) =>
|
|
302
|
+
prisma.user.findUnique({
|
|
429
303
|
...query,
|
|
430
|
-
|
|
304
|
+
rejectOnNotFound: true,
|
|
305
|
+
where: { id: ctx.userId },
|
|
431
306
|
}),
|
|
432
307
|
}),
|
|
433
308
|
}),
|
|
434
309
|
});
|
|
435
310
|
|
|
436
|
-
builder.prismaObject('User', {
|
|
437
|
-
findUnique: null,
|
|
438
|
-
fields: (t) => ({
|
|
439
|
-
id: t.exposeID('id'),
|
|
440
|
-
title: t.exposeString('title'),
|
|
441
|
-
author: t.relation('author', {
|
|
442
|
-
resolve: (query, post) =>
|
|
443
|
-
db.user.findUnique({ ...query, rejectOnNotFound: true, where: { id: post.authorId } }),
|
|
444
|
-
}),
|
|
445
|
-
}),
|
|
446
|
-
});
|
|
447
|
-
```
|
|
448
|
-
|
|
449
|
-
`t.relation` defines a field that can be pre-loaded by a parent resolver, and most of the time, the
|
|
450
|
-
`resolve` function will NOT be called. This is VERY IMPORTANT to understand, because it is the
|
|
451
|
-
biggest place where you can introduce inconsistencies into your API with this plugin. The `resolve`
|
|
452
|
-
function is used to load the relationship if parent resolver did not pre-load the data needed to
|
|
453
|
-
resolve the field. This happens for a number of reasons:
|
|
454
|
-
|
|
455
|
-
1. The parent object was not loaded through a field defined with `t.prismaField`, or `t.relation`
|
|
456
|
-
2. The `query` object for the parent field was not spread into the query
|
|
457
|
-
3. The graphql query requested multiple fields that depended on the same relationship (described
|
|
458
|
-
more below)
|
|
459
|
-
|
|
460
|
-
These are all okay, and expected situations. Graphql APIs are very flexible, and magically pushing
|
|
461
|
-
everything into a single query is impossible for arbitrary queries. This is why we have a `resolve`
|
|
462
|
-
function than can load the relation IF it was not already loaded by the parent.
|
|
463
|
-
|
|
464
|
-
Like `t.prismaField`, the `resolve` function now as a new first argument that is a query that should
|
|
465
|
-
be spread into the query, and is used to load nested relationships.
|
|
466
|
-
|
|
467
|
-
### Find Unique
|
|
468
|
-
|
|
469
|
-
Because the `resolve` function is only used as a fallback, it is harder to test, and if done
|
|
470
|
-
incorrectly can introduce inconsistencies. While it shouldn't be too hard to get right, it might be
|
|
471
|
-
better to avoid it entirely. To do this, we can let the Prisma plugin generate these resolve
|
|
472
|
-
functions for you in a consistent and predictable way. We can do this by providing a findUnique
|
|
473
|
-
option for our object type. Defining a `findUnique` that is not null, will make `resolve` optional.
|
|
474
|
-
|
|
475
|
-
```typescript
|
|
476
311
|
builder.prismaObject('User', {
|
|
477
312
|
findUnique: (user) => ({ id: user.id }),
|
|
478
313
|
fields: (t) => ({
|
|
@@ -492,23 +327,90 @@ builder.prismaObject('Post', {
|
|
|
492
327
|
});
|
|
493
328
|
```
|
|
494
329
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
relationships into one query, and the findUnique queries should be very fast.
|
|
330
|
+
`t.relation` defines a field that can be pre-loaded by a parent resolver. This will create something
|
|
331
|
+
like `{ include: { author: true }}` that will be passed as part of the `query` argument of a
|
|
332
|
+
`prismaField` resolver. If the parent is another `relation` field, the includes will become nested,
|
|
333
|
+
and the full relation chain will be passed to the `prismaField` that started the chain.
|
|
500
334
|
|
|
501
|
-
For example
|
|
502
|
-
where requested, the generated prisma call would be something like:
|
|
335
|
+
For example the query:
|
|
503
336
|
|
|
504
|
-
```
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
337
|
+
```graphql
|
|
338
|
+
query {
|
|
339
|
+
me {
|
|
340
|
+
posts {
|
|
341
|
+
author {
|
|
342
|
+
id
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
the `me` `prismaField` would receive something like the following as its query parameter:
|
|
350
|
+
|
|
351
|
+
```ts
|
|
352
|
+
{
|
|
508
353
|
include: {
|
|
509
|
-
posts:
|
|
510
|
-
|
|
511
|
-
|
|
354
|
+
posts: {
|
|
355
|
+
include: {
|
|
356
|
+
author: true;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
This will work perfectly for the majority of queries. There are a number of edge cases that make it
|
|
364
|
+
impossible to resolve everything in a single query. When this happens the `findUnique` option is
|
|
365
|
+
used to ensure that everything is still loaded correctly, and split into as few efficient queries as
|
|
366
|
+
possible.
|
|
367
|
+
|
|
368
|
+
### Find Unique
|
|
369
|
+
|
|
370
|
+
The `findUnique` function will receive an instance of the prisma model the current type is defining,
|
|
371
|
+
and should return an object that will be passed as a `where` in a `prisma.findUnique`. Generally,
|
|
372
|
+
this will just be something like: `user => { id: user.id }` where `id` is the primary key for the
|
|
373
|
+
table.
|
|
374
|
+
|
|
375
|
+
When the prisma plugin encounters a query where the requirements for a field can not be satisfied,
|
|
376
|
+
it will call findUnique for the current prisma model, and include or select all properties that are
|
|
377
|
+
required for the fields that could not be resolved without an additional query.
|
|
378
|
+
|
|
379
|
+
The following are some edge cases that could cause an additional query to be necessary:
|
|
380
|
+
|
|
381
|
+
- The parent object was not loaded through a field defined with `t.prismaField`, or `t.relation`
|
|
382
|
+
- The root `prismaField` did not correctly spread the `query` arguments in is prisma call.
|
|
383
|
+
- The query selects multiple fields that use the same relation with different queries
|
|
384
|
+
- The query contains multiple aliases for the same relation field with different arguments in a way
|
|
385
|
+
that results in different query options for the relation.
|
|
386
|
+
- A relation field has a query that is incompatible with the default includes of the parent object
|
|
387
|
+
|
|
388
|
+
All of the above should be relatively uncommon in normal usage, but the plugin ensures that these
|
|
389
|
+
types of edge cases are automatically handled when they do occur.
|
|
390
|
+
|
|
391
|
+
### Without Find Unique
|
|
392
|
+
|
|
393
|
+
This is generally _NOT RECOMMENDED_, but you can set `findUnique` to null for some prisma objects.
|
|
394
|
+
Doing this will prevent the plugin from resolving queries for conflicting relations. Because of
|
|
395
|
+
this, you will need to provide a `resolve` method when defining relations, and some other options
|
|
396
|
+
(like field level selects, described below) will not be available. This `resolve` method is _ONLY
|
|
397
|
+
CALLED AS A FALLBACK_ when the relation has not already been loaded. This means that you should not
|
|
398
|
+
apply any sorting or filtering to the relation queried in the resolve method. Instead used the
|
|
399
|
+
`query` option described in the next section
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
builder.prismaObject('User', {
|
|
403
|
+
findUnique: null,
|
|
404
|
+
fields: (t) => ({
|
|
405
|
+
id: t.exposeID('id'),
|
|
406
|
+
posts: t.relation('posts', {
|
|
407
|
+
resolve: (query, user) =>
|
|
408
|
+
db.post.findMany({
|
|
409
|
+
...query,
|
|
410
|
+
where: { authorId: user.id },
|
|
411
|
+
}),
|
|
412
|
+
}),
|
|
413
|
+
}),
|
|
512
414
|
});
|
|
513
415
|
```
|
|
514
416
|
|
|
@@ -516,16 +418,15 @@ prisma.user.findUnique({
|
|
|
516
418
|
|
|
517
419
|
So far we have been describing very simple queries without any arguments, filtering, or sorting. For
|
|
518
420
|
`t.prismaField` definitions, you can add arguments to your field like normal, and pass them into
|
|
519
|
-
your prisma query as needed. For `t.relation` the flow is slightly different because we
|
|
520
|
-
|
|
521
|
-
|
|
421
|
+
your prisma query as needed. For `t.relation` the flow is slightly different because we are not
|
|
422
|
+
making a prisma query directly. We do this by adding a `query` option to our field options. Query
|
|
423
|
+
can either be a query object, or a method that returns a query object based on the field arguments.
|
|
522
424
|
|
|
523
425
|
```typescript
|
|
524
426
|
builder.prismaObject('User', {
|
|
525
427
|
findUnique: (user) => ({ id: user.id }),
|
|
526
428
|
fields: (t) => ({
|
|
527
429
|
id: t.exposeID('id'),
|
|
528
|
-
email: t.exposeString('email'),
|
|
529
430
|
posts: t.relation('posts', {
|
|
530
431
|
// We can define arguments like any other field
|
|
531
432
|
args: {
|
|
@@ -542,11 +443,11 @@ builder.prismaObject('User', {
|
|
|
542
443
|
});
|
|
543
444
|
```
|
|
544
445
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
`take`, `orderBy
|
|
548
|
-
context for the current request. Because it is used for pre-loading data, and solving n+1
|
|
549
|
-
can not be passed the `parent` object because it may not be loaded yet.
|
|
446
|
+
The returned query object will be added to the include section of the `query` argument that gets
|
|
447
|
+
passed into the first argument of the parent `t.prismaField`, and can include things like `where`,
|
|
448
|
+
`skip`, `take`, abd `orderBy`. The `query` function will be passed the arguments for the field, and
|
|
449
|
+
the context for the current request. Because it is used for pre-loading data, and solving n+1
|
|
450
|
+
issues, it can not be passed the `parent` object because it may not be loaded yet.
|
|
550
451
|
|
|
551
452
|
If your field has a `resolve` method the generated `query` will be passed in as part of the first
|
|
552
453
|
arg to your resolve function
|
|
@@ -568,20 +469,37 @@ builder.prismaObject('User', {
|
|
|
568
469
|
createdAt: args.oldestFirst ? 'asc' : 'desc',
|
|
569
470
|
},
|
|
570
471
|
}),
|
|
571
|
-
// query here will contain the orderBy (and any other properties returned by the query method)
|
|
472
|
+
// optional: query here will contain the orderBy (and any other properties returned by the query method)
|
|
572
473
|
resolve: (query, post) => db.post.findMany({ ...query, where: { id: post.authorId } }),
|
|
573
474
|
}),
|
|
574
475
|
}),
|
|
575
476
|
});
|
|
576
477
|
```
|
|
577
478
|
|
|
578
|
-
It is
|
|
479
|
+
It is _VERY IMPORTANT_ to put all your filtering and sorting into the query method rather than your
|
|
579
480
|
resolver because the resolver is only used as fallback, and any filtering that does not exist in the
|
|
580
481
|
query method will not be applied correctly. If you have a where in both your query and your
|
|
581
482
|
resolver, you will need to ensure these are merged correctly. It is generally better NOT to use a
|
|
582
483
|
custom resolver.
|
|
583
484
|
|
|
584
|
-
|
|
485
|
+
## relationCount
|
|
486
|
+
|
|
487
|
+
Prisma supports querying for
|
|
488
|
+
[relation counts](https://www.prisma.io/docs/concepts/components/prisma-client/aggregation-grouping-summarizing#count-relations)
|
|
489
|
+
which allow including counts for relations along side other `includes`. This does not currently
|
|
490
|
+
support any filters on the counts, but can give a total count for a relation.
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
builder.prismaObject('User', {
|
|
494
|
+
findUnique: (user) => ({ id: user.id }),
|
|
495
|
+
fields: (t) => ({
|
|
496
|
+
id: t.exposeID('id'),
|
|
497
|
+
postCount: t.relationCount('posts'),
|
|
498
|
+
}),
|
|
499
|
+
});
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
## Includes on types
|
|
585
503
|
|
|
586
504
|
In some cases, you may want to always pre-load certain relations. This can be helpful for defining
|
|
587
505
|
fields directly on type where the underlying data may come from a related table.
|
|
@@ -606,89 +524,140 @@ builder.prismaObject('User', {
|
|
|
606
524
|
});
|
|
607
525
|
```
|
|
608
526
|
|
|
609
|
-
|
|
527
|
+
## Select mode for types
|
|
610
528
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
529
|
+
By default, the prisma plugin will use `include` when including relations, or generating fallback
|
|
530
|
+
queries. This means we are always loading all columns of a table when loading it in a
|
|
531
|
+
`t.prismaField` or a `t.relation`. This is usually what we want, but in some cases, you may want to
|
|
532
|
+
select specific columns instead. This can be useful if you have tables with either a very large
|
|
533
|
+
number of columns, or specific columns with large payloads you want to avoid loading.
|
|
534
|
+
|
|
535
|
+
To do this, you can add a `select` instead of an include to your `prismaObject`:
|
|
615
536
|
|
|
616
537
|
```typescript
|
|
617
538
|
builder.prismaObject('User', {
|
|
539
|
+
select: {
|
|
540
|
+
id: true,
|
|
541
|
+
},
|
|
618
542
|
findUnique: (user) => ({ id: user.id }),
|
|
619
543
|
fields: (t) => ({
|
|
620
544
|
id: t.exposeID('id'),
|
|
621
|
-
|
|
545
|
+
email: t.exposeString('email'),
|
|
622
546
|
}),
|
|
623
547
|
});
|
|
624
548
|
```
|
|
625
549
|
|
|
626
|
-
|
|
550
|
+
At the very least, you will need to `select` the properties required by your `findUnique` function.
|
|
551
|
+
The `t.expose*` and `t.relation` methods will all automatically add selections for the exposed
|
|
552
|
+
fields _WHEN THEY ARE QUERIED_, ensuring that only the requested columns will be loaded from the
|
|
553
|
+
database.
|
|
627
554
|
|
|
628
|
-
|
|
629
|
-
[relay plugin](https://pothos-graphql.dev/docs/plugins/relay), which makes creating nodes and
|
|
630
|
-
connections very easy.
|
|
555
|
+
In addition to the `t.expose` and `t.relation`, you can also add custom selections to other fields:
|
|
631
556
|
|
|
632
|
-
|
|
557
|
+
```typescript
|
|
558
|
+
builder.prismaObject('User', {
|
|
559
|
+
select: {
|
|
560
|
+
id: true,
|
|
561
|
+
},
|
|
562
|
+
findUnique: (user) => ({ id: user.id }),
|
|
563
|
+
fields: (t) => ({
|
|
564
|
+
id: t.exposeID('id'),
|
|
565
|
+
email: t.exposeString('email'),
|
|
566
|
+
bio: t.string({
|
|
567
|
+
// This will select user.profile.bio when the the `bio` field is queried
|
|
568
|
+
select: {
|
|
569
|
+
profile: {
|
|
570
|
+
select: {
|
|
571
|
+
bio: true,
|
|
572
|
+
},
|
|
573
|
+
},
|
|
574
|
+
},
|
|
575
|
+
resolve: (user) => user.profile.bio,
|
|
576
|
+
}),
|
|
577
|
+
}),
|
|
578
|
+
});
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
## Type variants
|
|
633
582
|
|
|
634
|
-
The
|
|
583
|
+
The prisma plugin supports defining multiple GraphQL types based on the same prisma model.
|
|
584
|
+
Additional types are called `variants`. You will always need to have a "Primary" variant (defined as
|
|
585
|
+
described above). Additional variants can be defined by providing a `variant` option instead of a
|
|
586
|
+
`name` option when creating the type:
|
|
635
587
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
588
|
+
```typescript
|
|
589
|
+
const Viewer = builder.prismaObject('User', {
|
|
590
|
+
variant: 'Viewer',
|
|
591
|
+
findUnique: (user) => ({ id: user.id }),
|
|
592
|
+
fields: (t) => ({
|
|
593
|
+
id: t.exposeID('id'),
|
|
594
|
+
});
|
|
595
|
+
```
|
|
639
596
|
|
|
640
|
-
|
|
641
|
-
handles pre-loading like all the other fields.
|
|
597
|
+
You can define variant fields that reference one variant from another:
|
|
642
598
|
|
|
643
599
|
```typescript
|
|
644
|
-
builder.
|
|
645
|
-
|
|
646
|
-
|
|
600
|
+
const Viewer = builder.prismaObject('User', {
|
|
601
|
+
variant: 'Viewer',
|
|
602
|
+
findUnique: (user) => ({ id: user.id }),
|
|
603
|
+
fields: (t) => ({
|
|
604
|
+
id: t.exposeID('id'),
|
|
605
|
+
// Using the model name ('User') will reference the primary variant
|
|
606
|
+
user: t.variant('User'),
|
|
607
|
+
});
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
const User = builder.prismaNode('User', {
|
|
611
|
+
// Testing that user is typed correctly
|
|
612
|
+
authScopes: (user) => !!user.id,
|
|
613
|
+
interfaces: [Named],
|
|
614
|
+
id: {
|
|
615
|
+
resolve: (user) => user.id,
|
|
616
|
+
},
|
|
647
617
|
fields: (t) => ({
|
|
618
|
+
// To reference another variant, use the returned object Ref instead of the model name:
|
|
619
|
+
viewer: t.variant(Viewer, {}),
|
|
648
620
|
email: t.exposeString('email'),
|
|
649
|
-
posts: t.relatedConnection('posts', {
|
|
650
|
-
cursor: 'id',
|
|
651
|
-
args: {
|
|
652
|
-
oldestFirst: t.arg.boolean(),
|
|
653
|
-
},
|
|
654
|
-
query: (args, context) => ({
|
|
655
|
-
orderBy: {
|
|
656
|
-
createdAt: args.oldestFirst ? 'asc' : 'desc',
|
|
657
|
-
},
|
|
658
|
-
}),
|
|
659
|
-
}),
|
|
660
621
|
}),
|
|
661
622
|
});
|
|
623
|
+
```
|
|
662
624
|
|
|
663
|
-
|
|
625
|
+
You can also use variants when defining relations by providing a `type` option:
|
|
626
|
+
|
|
627
|
+
```typescript
|
|
628
|
+
const PostDraft = builder.prismaNode('Post', {
|
|
629
|
+
variant: 'PostDraft'
|
|
630
|
+
// This is used to load the node by id
|
|
664
631
|
findUnique: (id) => ({ id }),
|
|
632
|
+
// This is used to get the id from a node
|
|
665
633
|
id: { resolve: (post) => post.id },
|
|
634
|
+
// fields work just like they do for builder.prismaObject
|
|
666
635
|
fields: (t) => ({
|
|
667
636
|
title: t.exposeString('title'),
|
|
668
637
|
author: t.relation('author'),
|
|
669
638
|
}),
|
|
670
639
|
});
|
|
671
640
|
|
|
672
|
-
builder.
|
|
641
|
+
const Viewer = builder.prismaObject('User', {
|
|
642
|
+
variant: 'Viewer',
|
|
643
|
+
findUnique: (user) => ({ id: user.id }),
|
|
673
644
|
fields: (t) => ({
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
rejectOnNotFound: true,
|
|
680
|
-
where: { id: ctx.userId },
|
|
681
|
-
}),
|
|
682
|
-
}),
|
|
683
|
-
posts: t.prismaConnection({
|
|
684
|
-
type: 'Post',
|
|
685
|
-
cursor: 'id',
|
|
686
|
-
resolve: (query) => prisma.post.findMany(query),
|
|
645
|
+
id: t.exposeID('id'),
|
|
646
|
+
drafts: t.relation('posts', {
|
|
647
|
+
// This will cause this relation to use the PostDraft variant rather than the default Post variant
|
|
648
|
+
type: PostDraft,
|
|
649
|
+
query: { where: { draft: true } },
|
|
687
650
|
}),
|
|
688
|
-
})
|
|
651
|
+
});
|
|
689
652
|
});
|
|
690
653
|
```
|
|
691
654
|
|
|
655
|
+
## Relay integration
|
|
656
|
+
|
|
657
|
+
This plugin has extensive integration with the
|
|
658
|
+
[relay plugin](https://pothos-graphql.dev/docs/plugins/relay), which makes creating nodes and
|
|
659
|
+
connections very easy.
|
|
660
|
+
|
|
692
661
|
### `prismaNode`
|
|
693
662
|
|
|
694
663
|
The `prismaNode` method works just like the `prismaObject` method with a couple of small
|
|
@@ -746,6 +715,9 @@ builder.queryType({
|
|
|
746
715
|
connection. The `query` will contain the correct `take`, `skip`, and `cursor` options based on the
|
|
747
716
|
connection arguments (`before`, `after`, `first`, `last`), along with `include` options for nested
|
|
748
717
|
selections.
|
|
718
|
+
- `totalCount`: A function for loading the total count for the connection. This will add a
|
|
719
|
+
`totalCount` field to the connection object. The `totalCount` method will receive (`connection`,
|
|
720
|
+
`args`, `context`, `info`) as arguments
|
|
749
721
|
|
|
750
722
|
The created connection queries currently support the following combinations of connection arguments:
|
|
751
723
|
|
|
@@ -799,6 +771,8 @@ builder.prismaNode('User', {
|
|
|
799
771
|
passed to prisma.
|
|
800
772
|
- `defaultSize`: (default: 20) The default page size to use if `first` and `last` are not provided.
|
|
801
773
|
- `maxSize`: (default: 100) The maximum number of nodes returned for a connection.
|
|
774
|
+
- `query`: A method that accepts the `args` and `context` for the connection field, and returns
|
|
775
|
+
filtering and sorting logic that will be merged into the query for the relation.
|
|
802
776
|
- `resolve`: (optional) Used as a fallback when a connection is not pre-loaded. It is optional, and
|
|
803
777
|
generally should NOT be defined manually. If used it works like a combination of the `resolve`
|
|
804
778
|
method of `relation` and `prismaConnection`. The default will use the `findUnique` of the current
|
|
@@ -806,3 +780,134 @@ builder.prismaNode('User', {
|
|
|
806
780
|
relationships to improve query efficiency.
|
|
807
781
|
- `totalCount`: when set to true, this will add a `totalCount` field to the connection object. see
|
|
808
782
|
`relationCount` above for more details.
|
|
783
|
+
|
|
784
|
+
## Using Prisma without a plugin
|
|
785
|
+
|
|
786
|
+
Using prisma without a plugin is relatively straight forward using the `builder.objectRef` method.
|
|
787
|
+
|
|
788
|
+
The easiest way to create types backed by prisma looks something like:
|
|
789
|
+
|
|
790
|
+
```typescript
|
|
791
|
+
import { Post, PrismaClient, User } from '@prisma/client';
|
|
792
|
+
|
|
793
|
+
const db = new PrismaClient();
|
|
794
|
+
const UserObject = builder.objectRef<User>('User');
|
|
795
|
+
const PostObject = builder.objectRef<Post>('Post');
|
|
796
|
+
|
|
797
|
+
UserObject.implement({
|
|
798
|
+
fields: (t) => ({
|
|
799
|
+
id: t.exposeID('id'),
|
|
800
|
+
email: t.exposeString('email'),
|
|
801
|
+
posts: t.field({
|
|
802
|
+
type: [PostObject],
|
|
803
|
+
resolve: (user) =>
|
|
804
|
+
db.post.findMany({
|
|
805
|
+
where: { authorId: user.id },
|
|
806
|
+
}),
|
|
807
|
+
}),
|
|
808
|
+
}),
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
PostObject.implement({
|
|
812
|
+
fields: (t) => ({
|
|
813
|
+
id: t.exposeID('id'),
|
|
814
|
+
title: t.exposeString('title'),
|
|
815
|
+
author: t.field({
|
|
816
|
+
type: UserObject,
|
|
817
|
+
resolve: (post) =>
|
|
818
|
+
db.user.findUnique({ rejectOnNotFound: true, where: { id: post.authorId } }),
|
|
819
|
+
}),
|
|
820
|
+
}),
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
builder.queryType({
|
|
824
|
+
fields: (t) => ({
|
|
825
|
+
me: t.field({
|
|
826
|
+
type: UserObject,
|
|
827
|
+
resolve: (root, args, ctx) =>
|
|
828
|
+
db.user.findUnique({ rejectOnNotFound: true, where: { id: ctx.userId } }),
|
|
829
|
+
}),
|
|
830
|
+
}),
|
|
831
|
+
});
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
This sets up User, and Post objects with a few fields, and a `me` query that returns the current
|
|
835
|
+
user. There are a few things to note in this setup:
|
|
836
|
+
|
|
837
|
+
1. We split up the `builder.objectRef` and the `implement` calls, rather than calling
|
|
838
|
+
`builder.objectRef(...).implement(...)`. This prevents typescript from getting tripped up by the
|
|
839
|
+
circular references between posts and users.
|
|
840
|
+
2. We use rejectOnNotFound with our `findUnique` calls because those fields are not nullable.
|
|
841
|
+
Without this option, prisma will return a null if the object is not found. An alternative is to
|
|
842
|
+
mark these fields as nullable.
|
|
843
|
+
3. The refs to our object types are called `UserObject` and `PostObject`, this is because `User` and
|
|
844
|
+
`Post` are the names of the types imported from prisma. We could instead alias the types when we
|
|
845
|
+
import them so we can name the refs to our GraphQL types after the models.
|
|
846
|
+
|
|
847
|
+
This setup is fairly simple, but it is easy to see the n+1 issues we might run into. Prisma helps
|
|
848
|
+
with this by batching queries together, but there are also things we can do in our implementation to
|
|
849
|
+
improve things.
|
|
850
|
+
|
|
851
|
+
One thing we could do if we know we will usually be loading the author any time we load a post is to
|
|
852
|
+
make the author part of shape required for a post:
|
|
853
|
+
|
|
854
|
+
```typescript
|
|
855
|
+
const UserObject = builder.objectRef<User>('User');
|
|
856
|
+
// We add the author here in the objectRef
|
|
857
|
+
const PostObject = builder.objectRef<Post & { author: User }>('Post');
|
|
858
|
+
|
|
859
|
+
UserObject.implement({
|
|
860
|
+
fields: (t) => ({
|
|
861
|
+
id: t.exposeID('id'),
|
|
862
|
+
email: t.exposeString('email'),
|
|
863
|
+
posts: t.field({
|
|
864
|
+
type: [PostObject],
|
|
865
|
+
resolve: (user) =>
|
|
866
|
+
db.post.findMany({
|
|
867
|
+
// We now need to include the author when we query for posts
|
|
868
|
+
include: {
|
|
869
|
+
author: true,
|
|
870
|
+
},
|
|
871
|
+
where: { authorId: user.id },
|
|
872
|
+
}),
|
|
873
|
+
}),
|
|
874
|
+
}),
|
|
875
|
+
});
|
|
876
|
+
|
|
877
|
+
PostObject.implement({
|
|
878
|
+
fields: (t) => ({
|
|
879
|
+
id: t.exposeID('id'),
|
|
880
|
+
title: t.exposeString('title'),
|
|
881
|
+
author: t.field({
|
|
882
|
+
type: UserObject,
|
|
883
|
+
// Now we can just return the author from the post instead of querying for it
|
|
884
|
+
resolve: (post) => post.author,
|
|
885
|
+
}),
|
|
886
|
+
}),
|
|
887
|
+
});
|
|
888
|
+
```
|
|
889
|
+
|
|
890
|
+
We may not always want to query for the author though, so we could make the author optional and fall
|
|
891
|
+
back to using a query if it was not provided by the parent resolver:
|
|
892
|
+
|
|
893
|
+
```typescript
|
|
894
|
+
const PostObject = builder.objectRef<Post & { author?: User }>('Post');
|
|
895
|
+
|
|
896
|
+
PostObject.implement({
|
|
897
|
+
fields: (t) => ({
|
|
898
|
+
id: t.exposeID('id'),
|
|
899
|
+
title: t.exposeString('title'),
|
|
900
|
+
author: t.field({
|
|
901
|
+
type: UserObject,
|
|
902
|
+
resolve: (post) =>
|
|
903
|
+
post.author ?? db.user.findUnique({ rejectOnNotFound: true, where: { id: post.authorId } }),
|
|
904
|
+
}),
|
|
905
|
+
}),
|
|
906
|
+
});
|
|
907
|
+
```
|
|
908
|
+
|
|
909
|
+
With this setup, a parent resolver has the option to include the author, but we have a fallback
|
|
910
|
+
incase it does not.
|
|
911
|
+
|
|
912
|
+
There are other patterns like dataloaders than can be used to reduce n+1 issues, and make your graph
|
|
913
|
+
more efficient, but they are too complex to describe here.
|