@pothos/plugin-prisma 0.0.0-preview-20220211212258

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.
Files changed (155) hide show
  1. package/.turbo/turbo-build.log +17 -0
  2. package/.turbo/turbo-test.log +18 -0
  3. package/.turbo/turbo-type.log +5 -0
  4. package/CHANGELOG.md +355 -0
  5. package/LICENSE +6 -0
  6. package/README.md +808 -0
  7. package/babel.config.js +3 -0
  8. package/bin/generator.js +2 -0
  9. package/esm/cursors.d.ts +55 -0
  10. package/esm/cursors.d.ts.map +1 -0
  11. package/esm/cursors.js +106 -0
  12. package/esm/cursors.js.map +1 -0
  13. package/esm/field-builder.d.ts +2 -0
  14. package/esm/field-builder.d.ts.map +1 -0
  15. package/esm/field-builder.js +53 -0
  16. package/esm/field-builder.js.map +1 -0
  17. package/esm/generator.d.ts +2 -0
  18. package/esm/generator.d.ts.map +1 -0
  19. package/esm/generator.js +83 -0
  20. package/esm/generator.js.map +1 -0
  21. package/esm/global-types.d.ts +52 -0
  22. package/esm/global-types.d.ts.map +1 -0
  23. package/esm/global-types.js +2 -0
  24. package/esm/global-types.js.map +1 -0
  25. package/esm/index.d.ts +11 -0
  26. package/esm/index.d.ts.map +1 -0
  27. package/esm/index.js +14 -0
  28. package/esm/index.js.map +1 -0
  29. package/esm/loader-map.d.ts +6 -0
  30. package/esm/loader-map.d.ts.map +1 -0
  31. package/esm/loader-map.js +35 -0
  32. package/esm/loader-map.js.map +1 -0
  33. package/esm/model-loader.d.ts +18 -0
  34. package/esm/model-loader.d.ts.map +1 -0
  35. package/esm/model-loader.js +101 -0
  36. package/esm/model-loader.js.map +1 -0
  37. package/esm/node-ref.d.ts +12 -0
  38. package/esm/node-ref.d.ts.map +1 -0
  39. package/esm/node-ref.js +19 -0
  40. package/esm/node-ref.js.map +1 -0
  41. package/esm/object-ref.d.ts +7 -0
  42. package/esm/object-ref.d.ts.map +1 -0
  43. package/esm/object-ref.js +5 -0
  44. package/esm/object-ref.js.map +1 -0
  45. package/esm/package.json +3 -0
  46. package/esm/prisma-field-builder.d.ts +28 -0
  47. package/esm/prisma-field-builder.d.ts.map +1 -0
  48. package/esm/prisma-field-builder.js +204 -0
  49. package/esm/prisma-field-builder.js.map +1 -0
  50. package/esm/refs.d.ts +15 -0
  51. package/esm/refs.d.ts.map +1 -0
  52. package/esm/refs.js +64 -0
  53. package/esm/refs.js.map +1 -0
  54. package/esm/schema-builder.d.ts +2 -0
  55. package/esm/schema-builder.d.ts.map +1 -0
  56. package/esm/schema-builder.js +65 -0
  57. package/esm/schema-builder.js.map +1 -0
  58. package/esm/types.d.ts +177 -0
  59. package/esm/types.d.ts.map +1 -0
  60. package/esm/types.js +2 -0
  61. package/esm/types.js.map +1 -0
  62. package/esm/util/index.d.ts +5 -0
  63. package/esm/util/index.d.ts.map +1 -0
  64. package/esm/util/index.js +16 -0
  65. package/esm/util/index.js.map +1 -0
  66. package/esm/util/map-includes.d.ts +6 -0
  67. package/esm/util/map-includes.d.ts.map +1 -0
  68. package/esm/util/map-includes.js +184 -0
  69. package/esm/util/map-includes.js.map +1 -0
  70. package/esm/util/merge-includes.d.ts +3 -0
  71. package/esm/util/merge-includes.d.ts.map +1 -0
  72. package/esm/util/merge-includes.js +91 -0
  73. package/esm/util/merge-includes.js.map +1 -0
  74. package/lib/cursors.d.ts +55 -0
  75. package/lib/cursors.d.ts.map +1 -0
  76. package/lib/cursors.js +112 -0
  77. package/lib/cursors.js.map +1 -0
  78. package/lib/field-builder.d.ts +2 -0
  79. package/lib/field-builder.d.ts.map +1 -0
  80. package/lib/field-builder.js +65 -0
  81. package/lib/field-builder.js.map +1 -0
  82. package/lib/generator.d.ts +2 -0
  83. package/lib/generator.d.ts.map +1 -0
  84. package/lib/generator.js +104 -0
  85. package/lib/generator.js.map +1 -0
  86. package/lib/global-types.d.ts +52 -0
  87. package/lib/global-types.d.ts.map +1 -0
  88. package/lib/global-types.js +3 -0
  89. package/lib/global-types.js.map +1 -0
  90. package/lib/index.d.ts +11 -0
  91. package/lib/index.d.ts.map +1 -0
  92. package/lib/index.js +40 -0
  93. package/lib/index.js.map +1 -0
  94. package/lib/loader-map.d.ts +6 -0
  95. package/lib/loader-map.d.ts.map +1 -0
  96. package/lib/loader-map.js +41 -0
  97. package/lib/loader-map.js.map +1 -0
  98. package/lib/model-loader.d.ts +18 -0
  99. package/lib/model-loader.d.ts.map +1 -0
  100. package/lib/model-loader.js +105 -0
  101. package/lib/model-loader.js.map +1 -0
  102. package/lib/node-ref.d.ts +12 -0
  103. package/lib/node-ref.d.ts.map +1 -0
  104. package/lib/node-ref.js +22 -0
  105. package/lib/node-ref.js.map +1 -0
  106. package/lib/object-ref.d.ts +7 -0
  107. package/lib/object-ref.d.ts.map +1 -0
  108. package/lib/object-ref.js +9 -0
  109. package/lib/object-ref.js.map +1 -0
  110. package/lib/prisma-field-builder.d.ts +28 -0
  111. package/lib/prisma-field-builder.d.ts.map +1 -0
  112. package/lib/prisma-field-builder.js +208 -0
  113. package/lib/prisma-field-builder.js.map +1 -0
  114. package/lib/refs.d.ts +15 -0
  115. package/lib/refs.d.ts.map +1 -0
  116. package/lib/refs.js +73 -0
  117. package/lib/refs.js.map +1 -0
  118. package/lib/schema-builder.d.ts +2 -0
  119. package/lib/schema-builder.d.ts.map +1 -0
  120. package/lib/schema-builder.js +89 -0
  121. package/lib/schema-builder.js.map +1 -0
  122. package/lib/types.d.ts +177 -0
  123. package/lib/types.d.ts.map +1 -0
  124. package/lib/types.js +4 -0
  125. package/lib/types.js.map +1 -0
  126. package/lib/util/index.d.ts +5 -0
  127. package/lib/util/index.d.ts.map +1 -0
  128. package/lib/util/index.js +30 -0
  129. package/lib/util/index.js.map +1 -0
  130. package/lib/util/map-includes.d.ts +6 -0
  131. package/lib/util/map-includes.d.ts.map +1 -0
  132. package/lib/util/map-includes.js +189 -0
  133. package/lib/util/map-includes.js.map +1 -0
  134. package/lib/util/merge-includes.d.ts +3 -0
  135. package/lib/util/merge-includes.d.ts.map +1 -0
  136. package/lib/util/merge-includes.js +96 -0
  137. package/lib/util/merge-includes.js.map +1 -0
  138. package/package.json +71 -0
  139. package/src/cursors.ts +159 -0
  140. package/src/field-builder.ts +117 -0
  141. package/src/generator.ts +191 -0
  142. package/src/global-types.ts +196 -0
  143. package/src/index.ts +18 -0
  144. package/src/loader-map.ts +48 -0
  145. package/src/model-loader.ts +152 -0
  146. package/src/node-ref.ts +34 -0
  147. package/src/object-ref.ts +8 -0
  148. package/src/prisma-field-builder.ts +375 -0
  149. package/src/refs.ts +112 -0
  150. package/src/schema-builder.ts +121 -0
  151. package/src/types.ts +502 -0
  152. package/src/util/index.ts +26 -0
  153. package/src/util/map-includes.ts +329 -0
  154. package/src/util/merge-includes.ts +121 -0
  155. package/tsconfig.json +21 -0
package/README.md ADDED
@@ -0,0 +1,808 @@
1
+ # Prisma Plugin for Pothos
2
+
3
+ This plugin provides tighter integration with prisma, making it easier to define prisma based object
4
+ types, and helps solve n+1 queries for relations. It also has integrations for the relay plugin to
5
+ make defining nodes and connections easy and efficient.
6
+
7
+ ## Disclaimers
8
+
9
+ This plugin is experimental, and will have some breaking changes in the future. DO NOT USE this
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.
12
+
13
+ This plugin is NOT required to build graphs backed by prisma models, and I would not recommend using
14
+ it unless you have a solid understanding of how it will construct queries.
15
+
16
+ This plugin will allow common queries to be resolved through a single prisma query (prisma may still
17
+ turn this into multiple SQL queries), and provides reasonable, predictable and safe fallbacks for
18
+ more complex queries and edge cases. That being said, graphql APIs are complex, and it is important
19
+ to understand the queries your API is capable of executing.
20
+
21
+ The way this plugin resolves queries is designed to be efficient, while still being predictable and
22
+ easy to understand. Tools that try to automatically generate queries are often hard to understand
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.
32
+
33
+ ## Example
34
+
35
+ Here is a quick example of what an API using this plugin might look like. There is a more thorough
36
+ breakdown of what the methods and options used in the example below.
37
+
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
+ ```typescript
43
+ builder.prismaObject('User', {
44
+ include: {
45
+ profile: true,
46
+ },
47
+ findUnique: (user) => ({ id: user.id }),
48
+ fields: (t) => ({
49
+ id: t.exposeID('id'),
50
+ email: t.exposeString('email'),
51
+ bio: t.string({
52
+ resolve: (user) => user.profile.bio,
53
+ }),
54
+ posts: t.relation('posts', {
55
+ args: {
56
+ oldestFirst: t.arg.boolean(),
57
+ },
58
+ query: (args, context) => ({
59
+ orderBy: {
60
+ createdAt: args.oldestFirst ? 'asc' : 'desc',
61
+ },
62
+ }),
63
+ }),
64
+ }),
65
+ });
66
+
67
+ builder.prismaObject('Post', {
68
+ findUnique: (post) => ({ id: post.id }),
69
+ fields: (t) => ({
70
+ id: t.exposeID('id'),
71
+ title: t.exposeString('title'),
72
+ author: t.relation('author'),
73
+ }),
74
+ });
75
+
76
+ builder.queryType({
77
+ fields: (t) => ({
78
+ me: t.prismaField({
79
+ type: 'User',
80
+ resolve: async (query, root, args, ctx, info) =>
81
+ prisma.user.findUnique({
82
+ ...query,
83
+ rejectOnNotFound: true,
84
+ where: { id: ctx.userId },
85
+ }),
86
+ }),
87
+ }),
88
+ });
89
+ ```
90
+
91
+ Given this schema, you would be able to resolve a query like the following with a single prisma
92
+ query (which will still result in a few optimized SQL queries).
93
+
94
+ ```graphql
95
+ query {
96
+ me {
97
+ email
98
+ posts {
99
+ title
100
+ author {
101
+ id
102
+ }
103
+ }
104
+ }
105
+ }
106
+ ```
107
+
108
+ A query like
109
+
110
+ ```graphql
111
+ query {
112
+ me {
113
+ email
114
+ posts {
115
+ title
116
+ author {
117
+ id
118
+ }
119
+ }
120
+ oldPosts: posts(oldestFirst: true) {
121
+ title
122
+ author {
123
+ id
124
+ }
125
+ }
126
+ }
127
+ }
128
+ ```
129
+
130
+ Will result in 2 calls to prisma, one to resolve everything except `oldPosts`, and a second to
131
+ 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. This may seem slightly magical, but
133
+ should be predictable and hopefully easy to understand after reading the documentation below.
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
+ ```
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
273
+
274
+ ```bash
275
+ yarn add @pothos/plugin-prisma
276
+ ```
277
+
278
+ ### Setup
279
+
280
+ This plugin requires a little more setup than other plugins because it integrates with the prisma to
281
+ generate some types that help the plugin better understand your prisma schema. Previous versions of
282
+ this plugin used to infer all required types from the prisma client itself, but this resulted in a
283
+ poor dev experience because the complex types slowed down editors, and some more advanced use cases
284
+ could not be typed correctly.
285
+
286
+ #### Add a the `pothos` generator to your prisma schema
287
+
288
+ ```
289
+ generator pothos {
290
+ provider = "prisma-pothos-types"
291
+ }
292
+ ```
293
+
294
+ Now the types Pothos uses will be generated whenever you re-generate your prisma client. Run the
295
+ following command to re-generate the client and create the new types:
296
+
297
+ ```bash
298
+ npx prisma generate
299
+ ```
300
+
301
+ additional options:
302
+
303
+ - `clientOutput`: Where the generated code will import the PrismaClient from. The default is the
304
+ full path of wherever the client is generated. If you are checking in the generated file, using
305
+ `@prisma/client` is a good option.
306
+ - `output`: Where to write the generated types
307
+
308
+ Example with more options:
309
+
310
+ ```
311
+ generator pothos {
312
+ provider = "prisma-pothos-types"
313
+ clientOutput = "@prisma/client"
314
+ output = "./pothos-types.ts"
315
+ }
316
+ ```
317
+
318
+ #### Set up the builder
319
+
320
+ ```typescript
321
+ import SchemaBuilder from '@pothos/core';
322
+ import { PrismaClient } from '@prisma/client';
323
+ import PrismaPlugin from '@pothos/plugin-prisma';
324
+ // This is the default location for the generator, but this can be customized as described above
325
+ // Using a type only import will help avoid issues with undeclared exports in esm mode
326
+ import type PrismaTypes from '@pothos/plugin-prisma/generated';
327
+
328
+ const prisma = new PrismaClient({});
329
+
330
+ const builder = new SchemaBuilder<{
331
+ PrismaTypes: PrismaTypes;
332
+ }>({
333
+ plugins: [PrismaPlugin],
334
+ prisma: {
335
+ client: prisma,
336
+ },
337
+ });
338
+ ```
339
+
340
+ It is strongly recommended NOT to put your prisma client into `Context`. This will result in slower
341
+ type-checking and a laggy developer experience in VSCode. See
342
+ https://github.com/microsoft/TypeScript/issues/45405 for more details.
343
+
344
+ ### Creating some types with `builder.prismaObject`
345
+
346
+ `builder.prismaObject` takes 2 arguments:
347
+
348
+ 1. `name`: The name of the prisma model this new type represents
349
+ 2. `options`: options for the type being created, this is very similar to the options for any other
350
+ object type
351
+
352
+ ```typescript
353
+ builder.prismaObject('User', {
354
+ // Optional name for the object, defaults to the name of the prisma model
355
+ name: 'PostAuthor',
356
+ findUnique: null,
357
+ fields: (t) => ({
358
+ id: t.exposeID('id'),
359
+ email: t.exposeString('email'),
360
+ }),
361
+ });
362
+
363
+ builder.prismaObject('Post', {
364
+ findUnique: null,
365
+ fields: (t) => ({
366
+ id: t.exposeID('id'),
367
+ title: t.exposeString('title'),
368
+ }),
369
+ });
370
+ ```
371
+
372
+ So far, this is just creating some simple object types. They work just like any other object type in
373
+ Pothos. They main advantage of this is that we get the type information without using object refs,
374
+ or needing imports from prisma client.
375
+
376
+ The `findUnique` option is described more below.
377
+
378
+ ### Adding prisma fields to non-prisma objects (including Query and Mutation)
379
+
380
+ There is a new `t.prismaField` method which can be used to define fields that resolve to your prisma
381
+ types:
382
+
383
+ ```typescript
384
+ builder.queryType({
385
+ fields: (t) => ({
386
+ me: t.prismaField({
387
+ type: 'User',
388
+ resolve: async (query, root, args, ctx, info) =>
389
+ prisma.user.findUnique({
390
+ ...query,
391
+ rejectOnNotFound: true,
392
+ where: { id: ctx.userId },
393
+ }),
394
+ }),
395
+ }),
396
+ });
397
+ ```
398
+
399
+ This method works just like th normal `t.field` method with a couple of differences:
400
+
401
+ 1. The `type` option must contain the name of the prisma model (eg. `User` or `[User]` for a list
402
+ field).
403
+ 2. The `resolve` function has a new first argument `query` which should be spread into query prisma
404
+ query. This will be used to load data for nested relationships.
405
+
406
+ You do not need to use this method, and the `builder.prismaObject` method returns an object ref than
407
+ can be used like any other object ref (with `t.field`), but using `t.prismaField` will allow you to
408
+ take advantage of more efficient queries.
409
+
410
+ The `query` object will contain an `include` object to pre-load data needed to resolve nested parts
411
+ of the current query. This is based on fields defined with `t.relation` described below.
412
+
413
+ If there are no fields using `t.relation` in your query, everything is resolved exactly as it would
414
+ be without this plugin.
415
+
416
+ ### Adding relations
417
+
418
+ You can add fields for relations using the `t.relation` method:
419
+
420
+ ```typescript
421
+ builder.prismaObject('User', {
422
+ findUnique: null,
423
+ fields: (t) => ({
424
+ id: t.exposeID('id'),
425
+ email: t.exposeString('email'),
426
+ posts: t.relation('posts', {
427
+ resolve: (query, user) =>
428
+ db.post.findMany({
429
+ ...query,
430
+ where: { authorId: user.id },
431
+ }),
432
+ }),
433
+ }),
434
+ });
435
+
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
+ builder.prismaObject('User', {
477
+ findUnique: (user) => ({ id: user.id }),
478
+ fields: (t) => ({
479
+ id: t.exposeID('id'),
480
+ email: t.exposeString('email'),
481
+ posts: t.relation('posts'),
482
+ }),
483
+ });
484
+
485
+ builder.prismaObject('Post', {
486
+ findUnique: (post) => ({ id: post.id }),
487
+ fields: (t) => ({
488
+ id: t.exposeID('id'),
489
+ title: t.exposeString('title'),
490
+ author: t.relation('author'),
491
+ }),
492
+ });
493
+ ```
494
+
495
+ This greatly simplifies our object types. In these cases, the fallback resolve functions will
496
+ re-load the current object using the `findUnique` as the where clause, and then `include` the
497
+ relation for the current field. This can produce a slightly less efficient query than a manual
498
+ implementation because the parent object is re-loaded first, but it will batch multiple
499
+ relationships into one query, and the findUnique queries should be very fast.
500
+
501
+ For example, if a `User` was loaded without pre-loading, and both a `posts` and a `profile` relation
502
+ where requested, the generated prisma call would be something like:
503
+
504
+ ```typescript
505
+ prisma.user.findUnique({
506
+ rejectOnNotFound: true,
507
+ where: { id: user.id },
508
+ include: {
509
+ posts: true,
510
+ profile: true,
511
+ },
512
+ });
513
+ ```
514
+
515
+ ### Filters, Sorting, and arguments
516
+
517
+ So far we have been describing very simple queries without any arguments, filtering, or sorting. For
518
+ `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 need to make
520
+ sure we are loading the right data if we are pre-loading data in a parent resolver. We do this by
521
+ adding a `query` option to our field options.
522
+
523
+ ```typescript
524
+ builder.prismaObject('User', {
525
+ findUnique: (user) => ({ id: user.id }),
526
+ fields: (t) => ({
527
+ id: t.exposeID('id'),
528
+ email: t.exposeString('email'),
529
+ posts: t.relation('posts', {
530
+ // We can define arguments like any other field
531
+ args: {
532
+ oldestFirst: t.arg.boolean(),
533
+ },
534
+ // Then we can generate our query conditions based on the arguments
535
+ query: (args, context) => ({
536
+ orderBy: {
537
+ createdAt: args.oldestFirst ? 'asc' : 'desc',
538
+ },
539
+ }),
540
+ }),
541
+ }),
542
+ });
543
+ ```
544
+
545
+ This query will be part of the `query` that gets passed into the first argument of `resolve`
546
+ function for `t.relation` and `t.prismaField` based fields, and include things like `where`, `skip`,
547
+ `take`, `orderBy`, etc. The `query` function will be passed the arguments for the field, and the
548
+ context for the current request. Because it is used for pre-loading data, and solving n+1 issues, it
549
+ can not be passed the `parent` object because it may not be loaded yet.
550
+
551
+ If your field has a `resolve` method the generated `query` will be passed in as part of the first
552
+ arg to your resolve function
553
+
554
+ ```typescript
555
+ builder.prismaObject('User', {
556
+ findUnique: null,
557
+ fields: (t) => ({
558
+ id: t.exposeID('id'),
559
+ email: t.exposeString('email'),
560
+ posts: t.relation('posts', {
561
+ // We can define arguments like any other field
562
+ args: {
563
+ oldestFirst: t.arg.boolean(),
564
+ },
565
+ // Then we can generate our query conditions based on the arguments
566
+ query: (args, context) => ({
567
+ orderBy: {
568
+ createdAt: args.oldestFirst ? 'asc' : 'desc',
569
+ },
570
+ }),
571
+ // query here will contain the orderBy (and any other properties returned by the query method)
572
+ resolve: (query, post) => db.post.findMany({ ...query, where: { id: post.authorId } }),
573
+ }),
574
+ }),
575
+ });
576
+ ```
577
+
578
+ It is VERY IMPORTANT to put all your filtering and sorting into the query method rather than your
579
+ resolver because the resolver is only used as fallback, and any filtering that does not exist in the
580
+ query method will not be applied correctly. If you have a where in both your query and your
581
+ resolver, you will need to ensure these are merged correctly. It is generally better NOT to use a
582
+ custom resolver.
583
+
584
+ ### Includes on types
585
+
586
+ In some cases, you may want to always pre-load certain relations. This can be helpful for defining
587
+ fields directly on type where the underlying data may come from a related table.
588
+
589
+ ```typescript
590
+ builder.prismaObject('User', {
591
+ // This will always include the profile when a user object is loaded. Deeply nested relations can
592
+ // also be included this way.
593
+ include: {
594
+ profile: true,
595
+ },
596
+ findUnique: (user) => ({ id: user.id }),
597
+ fields: (t) => ({
598
+ id: t.exposeID('id'),
599
+ email: t.exposeString('email'),
600
+ bio: t.string({
601
+ // The profile relation will always be loaded, and user will now be typed to include the
602
+ // profile field so you can return the bio from the nested profile relation.
603
+ resolve: (user) => user.profile.bio,
604
+ }),
605
+ }),
606
+ });
607
+ ```
608
+
609
+ ### relationCount
610
+
611
+ Prisma supports querying for
612
+ [relation counts](https://www.prisma.io/docs/concepts/components/prisma-client/aggregation-grouping-summarizing#count-relations)
613
+ which allow including counts for relations along side other `includes`. This does not currently
614
+ support any filters on the counts, but can give a total count for a relation.
615
+
616
+ ```typescript
617
+ builder.prismaObject('User', {
618
+ findUnique: (user) => ({ id: user.id }),
619
+ fields: (t) => ({
620
+ id: t.exposeID('id'),
621
+ postCount: t.relationCount('posts'),
622
+ }),
623
+ });
624
+ ```
625
+
626
+ ## Relay integration
627
+
628
+ This plugin has extensive integration with the
629
+ [relay plugin](https://pothos-graphql.dev/docs/plugins/relay), which makes creating nodes and
630
+ connections very easy.
631
+
632
+ ### Example
633
+
634
+ The following example is similar to the one above with a few changes:
635
+
636
+ - the `User` and `Post` objects are now relay nodes
637
+ - the `posts` field on the `User` type is now a relay connection using cursor based pagination
638
+ - there is a new `users` query that is also a relay connection
639
+
640
+ Everything in this schema is still queryable via a single prisma query. The relay connections
641
+ handles pre-loading like all the other fields.
642
+
643
+ ```typescript
644
+ builder.prismaNode('User', {
645
+ findUnique: (id) => ({ id }),
646
+ id: { resolve: (user) => user.id },
647
+ fields: (t) => ({
648
+ 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
+ }),
661
+ });
662
+
663
+ builder.prismaNode('Post', {
664
+ findUnique: (id) => ({ id }),
665
+ id: { resolve: (post) => post.id },
666
+ fields: (t) => ({
667
+ title: t.exposeString('title'),
668
+ author: t.relation('author'),
669
+ }),
670
+ });
671
+
672
+ builder.queryType({
673
+ fields: (t) => ({
674
+ me: t.prismaField({
675
+ type: 'User',
676
+ resolve: async (query, root, args, ctx, info) =>
677
+ prisma.user.findUnique({
678
+ ...query,
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),
687
+ }),
688
+ }),
689
+ });
690
+ ```
691
+
692
+ ### `prismaNode`
693
+
694
+ The `prismaNode` method works just like the `prismaObject` method with a couple of small
695
+ differences:
696
+
697
+ - the `findUnique` function now only receives an id. This is to support relays ability to load nodes
698
+ by id.
699
+ - there is a new `id` option that mirrors the `id` option from `node` method of the relay plugin,
700
+ and must contain a resolve function that returns the id from an instance of the node.
701
+
702
+ ```typescript
703
+ builder.prismaNode('Post', {
704
+ // This is used to load the node by id
705
+ findUnique: (id) => ({ id }),
706
+ // This is used to get the id from a node
707
+ id: { resolve: (post) => post.id },
708
+ // fields work just like they do for builder.prismaObject
709
+ fields: (t) => ({
710
+ title: t.exposeString('title'),
711
+ author: t.relation('author'),
712
+ }),
713
+ });
714
+ ```
715
+
716
+ ### `prismaConnection`
717
+
718
+ The `prismaConnection` method on a field builder can be used to create a relay `connection` field
719
+ that also pre-loads all the data nested inside that connection.
720
+
721
+ ```typescript
722
+ builder.queryType({
723
+ fields: (t) => ({
724
+ posts: t.prismaConnection(
725
+ {
726
+ type: 'Post',
727
+ cursor: 'id',
728
+ resolve: (query, parent, args, context, info) => prisma.post.findMany({ ...query }),
729
+ }),
730
+ {}, // optional options for the Connection type
731
+ {}, // optional options for the Edge type),
732
+ ),
733
+ }),
734
+ });
735
+ ```
736
+
737
+ #### options
738
+
739
+ - `type`: the name of the prisma model being connected to
740
+ - `cursor`: a `@unique` column of the model being connected to. This is used as the `cursor` option
741
+ passed to prisma.
742
+ - `defaultSize`: (default: 20) The default page size to use if `first` and `last` are not provided.
743
+ - `maxSize`: (default: 100) The maximum number of nodes returned for a connection.
744
+ - `resolve`: Like the resolver for `prismaField`, the first argument is a `query` object that should
745
+ be spread into your prisma query. The `resolve` function should return an array of nodes for the
746
+ connection. The `query` will contain the correct `take`, `skip`, and `cursor` options based on the
747
+ connection arguments (`before`, `after`, `first`, `last`), along with `include` options for nested
748
+ selections.
749
+
750
+ The created connection queries currently support the following combinations of connection arguments:
751
+
752
+ - `first`, `last`, or `before`
753
+ - `first` and `before`
754
+ - `last` and `after`
755
+
756
+ Queries for other combinations are not as useful, and generally requiring loading all records
757
+ between 2 cursors, or between a cursor and the end of the set. Generating query options for these
758
+ cases is more complex and likely very inefficient, so they will currently throw an Error indicating
759
+ the argument combinations are not supported.
760
+
761
+ ### `relatedConnection`
762
+
763
+ The `relatedConnection` method can be used to create a relay `connection` field based on a relation
764
+ of the current model.
765
+
766
+ ```typescript
767
+ builder.prismaNode('User', {
768
+ findUnique: (id) => ({ id }),
769
+ id: { resolve: (user) => user.id },
770
+ fields: (t) => ({
771
+ // Connections can be very simple to define
772
+ simplePosts: t.relatedConnection('posts', {
773
+ cursor: 'id',
774
+ }),
775
+ // Or they can include custom arguments, and other options
776
+ posts: t.relatedConnection(
777
+ 'posts',
778
+ {
779
+ cursor: 'id',
780
+ args: {
781
+ oldestFirst: t.arg.boolean(),
782
+ },
783
+ query: (args, context) => ({
784
+ orderBy: {
785
+ createdAt: args.oldestFirst ? 'asc' : 'desc',
786
+ },
787
+ }),
788
+ },
789
+ {}, // optional options for the Connection type
790
+ {}, // optional options for the Edge type),
791
+ ),
792
+ }),
793
+ });
794
+ ```
795
+
796
+ #### options
797
+
798
+ - `cursor`: a `@unique` column of the model being connected to. This is used as the `cursor` option
799
+ passed to prisma.
800
+ - `defaultSize`: (default: 20) The default page size to use if `first` and `last` are not provided.
801
+ - `maxSize`: (default: 100) The maximum number of nodes returned for a connection.
802
+ - `resolve`: (optional) Used as a fallback when a connection is not pre-loaded. It is optional, and
803
+ generally should NOT be defined manually. If used it works like a combination of the `resolve`
804
+ method of `relation` and `prismaConnection`. The default will use the `findUnique` of the current
805
+ model, with an `include` for the current relation. It is also batched together with other
806
+ relationships to improve query efficiency.
807
+ - `totalCount`: when set to true, this will add a `totalCount` field to the connection object. see
808
+ `relationCount` above for more details.