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