@pothos/plugin-drizzle 0.15.0 → 0.16.1
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 +17 -0
- package/README.md +419 -51
- package/dts/drizzle-field-builder.d.ts +14 -3
- package/dts/drizzle-field-builder.d.ts.map +1 -1
- package/dts/types.d.ts +6 -0
- package/dts/types.d.ts.map +1 -1
- package/dts/utils/cursors.d.ts +6 -2
- package/dts/utils/cursors.d.ts.map +1 -1
- package/esm/drizzle-field-builder.d.ts +16 -3
- package/esm/drizzle-field-builder.d.ts.map +1 -1
- package/esm/drizzle-field-builder.js +144 -8
- package/esm/drizzle-field-builder.js.map +1 -1
- package/esm/field-builder.js +33 -2
- package/esm/field-builder.js.map +1 -1
- package/esm/types.d.ts +6 -0
- package/esm/types.d.ts.map +1 -1
- package/esm/types.js.map +1 -1
- package/esm/utils/cursors.d.ts +6 -2
- package/esm/utils/cursors.d.ts.map +1 -1
- package/esm/utils/cursors.js +18 -2
- package/esm/utils/cursors.js.map +1 -1
- package/lib/drizzle-field-builder.js +142 -6
- package/lib/drizzle-field-builder.js.map +1 -1
- package/lib/field-builder.js +33 -2
- package/lib/field-builder.js.map +1 -1
- package/lib/types.js.map +1 -1
- package/lib/utils/cursors.js +18 -2
- package/lib/utils/cursors.js.map +1 -1
- package/package.json +5 -5
- package/src/drizzle-field-builder.ts +246 -3
- package/src/field-builder.ts +40 -1
- package/src/types.ts +20 -1
- package/src/utils/cursors.ts +24 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @pothos/plugin-drizzle
|
|
2
2
|
|
|
3
|
+
## 0.16.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 9993d01: Fix a bug where selecting only connection count at the same time as another field selected the same relation would cause an error"
|
|
8
|
+
|
|
9
|
+
## 0.16.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 3509aab: Add new field builder methods and connection enhancements:
|
|
14
|
+
- `t.relatedField`: New method for creating custom fields based on relations with custom selections using the `buildFilter` callback
|
|
15
|
+
- `t.relatedCount`: Simplified method for counting related records, with optional `where` filter support
|
|
16
|
+
- `totalCount: true` option for `relatedConnection`: Adds a `totalCount` field to related connections
|
|
17
|
+
- `totalCount` callback for `drizzleConnection`: Allows adding a `totalCount` field to drizzle connections
|
|
18
|
+
- `totalCountOnly` optimization: Skips the main query when only `totalCount` is requested (for both connection types)
|
|
19
|
+
|
|
3
20
|
## 0.15.0
|
|
4
21
|
|
|
5
22
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -3,35 +3,38 @@
|
|
|
3
3
|
## Installing
|
|
4
4
|
|
|
5
5
|
```package-install
|
|
6
|
-
npm install --save @pothos/plugin-drizzle
|
|
6
|
+
npm install --save @pothos/plugin-drizzle drizzle-orm@beta
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
The drizzle plugin is built on top of drizzles relational query builder, and requires that you
|
|
10
10
|
define and configure all the relevant relations in your drizzle schema. See
|
|
11
|
-
https://
|
|
11
|
+
https://rqbv2.drizzle-orm-fe.pages.dev/docs/relations-v2 for detailed documentation on the relations API.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
for every table used as a `drizzleObject`, `drizzleNode`, or `drizzleInterface`.
|
|
15
|
-
|
|
16
|
-
Once you have configured you have configured you drizzle schema, you can initialize your Pothos
|
|
13
|
+
Once you have configured your drizzle schema, you can initialize your Pothos
|
|
17
14
|
SchemaBuilder with the drizzle plugin:
|
|
18
15
|
|
|
19
16
|
```ts
|
|
20
|
-
import * as schema from './schema';
|
|
21
17
|
import { drizzle } from 'drizzle-orm/...';
|
|
18
|
+
// Import the appropriate getTableConfig for your dialect
|
|
19
|
+
import { getTableConfig } from 'drizzle-orm/sqlite-core';
|
|
22
20
|
import SchemaBuilder from '@pothos/core';
|
|
23
21
|
import DrizzlePlugin from '@pothos/plugin-drizzle';
|
|
22
|
+
import { relations } from './db/relations';
|
|
23
|
+
|
|
24
|
+
const db = drizzle({ client, relations });
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
type DrizzleRelations = typeof relations
|
|
26
27
|
|
|
27
28
|
export interface PothosTypes {
|
|
28
|
-
|
|
29
|
+
DrizzleRelations: DrizzleRelations;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
const builder = new SchemaBuilder<PothosTypes>({
|
|
32
33
|
plugins: [DrizzlePlugin],
|
|
33
34
|
drizzle: {
|
|
34
|
-
client: db,
|
|
35
|
+
client: db, // or (ctx) => db if you want to create a request specific client
|
|
36
|
+
getTableConfig,
|
|
37
|
+
relations,
|
|
35
38
|
},
|
|
36
39
|
});
|
|
37
40
|
```
|
|
@@ -42,23 +45,26 @@ The drizzle plugin has integrations with several other plugins. While the `with-
|
|
|
42
45
|
plugins are not required, many examples will assume these plugins have been installed:
|
|
43
46
|
|
|
44
47
|
```ts
|
|
45
|
-
import * as schema from './schema';
|
|
46
48
|
import { drizzle } from 'drizzle-orm/...';
|
|
47
49
|
import SchemaBuilder from '@pothos/core';
|
|
48
50
|
import DrizzlePlugin from '@pothos/plugin-drizzle';
|
|
49
|
-
import RelayPlugin from '@pothos/plugin-
|
|
51
|
+
import RelayPlugin from '@pothos/plugin-relay';
|
|
50
52
|
import WithInputPlugin from '@pothos/plugin-with-input';
|
|
53
|
+
import { getTableConfig } from 'drizzle-orm/sqlite-core';
|
|
54
|
+
import { relations } from './db/relations';
|
|
51
55
|
|
|
52
|
-
const db = drizzle({ client,
|
|
56
|
+
const db = drizzle({ client, relations });
|
|
53
57
|
|
|
54
58
|
export interface PothosTypes {
|
|
55
|
-
|
|
59
|
+
DrizzleRelations: typeof relations;
|
|
56
60
|
}
|
|
57
61
|
|
|
58
62
|
const builder = new SchemaBuilder<PothosTypes>({
|
|
59
63
|
plugins: [RelayPlugin, WithInputPlugin, DrizzlePlugin],
|
|
60
64
|
drizzle: {
|
|
61
65
|
client: db,
|
|
66
|
+
getTableConfig,
|
|
67
|
+
relations,
|
|
62
68
|
},
|
|
63
69
|
});
|
|
64
70
|
```
|
|
@@ -72,8 +78,8 @@ table:
|
|
|
72
78
|
const UserRef = builder.drizzleObject('users', {
|
|
73
79
|
name: 'User',
|
|
74
80
|
fields: (t) => ({
|
|
75
|
-
firstName: t.exposeString('
|
|
76
|
-
lastName: t.exposeString('
|
|
81
|
+
firstName: t.exposeString('firstName'),
|
|
82
|
+
lastName: t.exposeString('lastName'),
|
|
77
83
|
}),
|
|
78
84
|
});
|
|
79
85
|
```
|
|
@@ -92,7 +98,7 @@ const UserRef = builder.drizzleObject('users', {
|
|
|
92
98
|
name: 'User',
|
|
93
99
|
fields: (t) => ({
|
|
94
100
|
fullName: t.string({
|
|
95
|
-
resolve: (user, args, ctx, info) => `${user.
|
|
101
|
+
resolve: (user, args, ctx, info) => `${user.firstName} ${user.lastName}`,
|
|
96
102
|
}),
|
|
97
103
|
}),
|
|
98
104
|
});
|
|
@@ -109,19 +115,19 @@ const UserRef = builder.drizzleObject('users', {
|
|
|
109
115
|
name: 'User',
|
|
110
116
|
select: {
|
|
111
117
|
columns: {
|
|
112
|
-
|
|
113
|
-
|
|
118
|
+
firstName: true,
|
|
119
|
+
lastName: true,
|
|
114
120
|
},
|
|
115
121
|
with: {
|
|
116
122
|
profile: true,
|
|
117
123
|
},
|
|
118
124
|
extras: {
|
|
119
|
-
lowercaseName: sql<string>`lower(${users.firstName})
|
|
125
|
+
lowercaseName: (users, sql) => sql<string>`lower(${users.firstName})`
|
|
120
126
|
},
|
|
121
127
|
},
|
|
122
128
|
fields: (t) => ({
|
|
123
129
|
fullName: t.string({
|
|
124
|
-
resolve: (user, args, ctx, info) => `${user.
|
|
130
|
+
resolve: (user, args, ctx, info) => `${user.firstName} ${user.lastName}`,
|
|
125
131
|
}),
|
|
126
132
|
bio: t.string({
|
|
127
133
|
resolve: (user) => user.profile.bio,
|
|
@@ -145,30 +151,24 @@ appropriate selections on each field:
|
|
|
145
151
|
```ts
|
|
146
152
|
const UserRef = builder.drizzleObject('users', {
|
|
147
153
|
name: 'User',
|
|
148
|
-
select: {
|
|
149
|
-
// By default all columns are selected, so this is required to default to an empty selection
|
|
150
|
-
columns: {},
|
|
151
|
-
},
|
|
154
|
+
select: {},
|
|
152
155
|
fields: (t) => ({
|
|
153
156
|
fullName: t.string({
|
|
154
157
|
select: {
|
|
155
|
-
columns: {
|
|
158
|
+
columns: { firstName: true, lastName: true },
|
|
156
159
|
},
|
|
157
|
-
resolve: (user, args, ctx, info) => `${user.
|
|
160
|
+
resolve: (user, args, ctx, info) => `${user.firstName} ${user.lastName}`,
|
|
158
161
|
}),
|
|
159
162
|
bio: t.string({
|
|
160
163
|
select: {
|
|
161
|
-
// Currently, adding a selection without explicitly defining columns will cause all columns to be selected
|
|
162
|
-
columns: {},
|
|
163
164
|
with: { profile: true },
|
|
164
165
|
},
|
|
165
166
|
resolve: (user) => user.profile.bio,
|
|
166
167
|
}),
|
|
167
168
|
email: t.string({
|
|
168
169
|
select: {
|
|
169
|
-
columns: {},
|
|
170
170
|
extras: {
|
|
171
|
-
lowercaseName: sql<string>`lower(${users.firstName})
|
|
171
|
+
lowercaseName: (users, sql) => sql<string>`lower(${users.firstName})`
|
|
172
172
|
},
|
|
173
173
|
},
|
|
174
174
|
resolve: (user) => `${user.lowercaseName}@example.com`,
|
|
@@ -180,7 +180,7 @@ const UserRef = builder.drizzleObject('users', {
|
|
|
180
180
|
## Relations
|
|
181
181
|
|
|
182
182
|
Drizzles relational query builder allows you to define the relationships between your tables. The
|
|
183
|
-
`
|
|
183
|
+
`t.relation` method makes it easy to add fields to your GraphQL API that implement those
|
|
184
184
|
relations:
|
|
185
185
|
|
|
186
186
|
```ts
|
|
@@ -202,7 +202,7 @@ builder.drizzleObject('posts', {
|
|
|
202
202
|
builder.drizzleObject('users', {
|
|
203
203
|
name: 'User',
|
|
204
204
|
fields: (t) => ({
|
|
205
|
-
firstName: t.exposeString('
|
|
205
|
+
firstName: t.exposeString('firstName'),
|
|
206
206
|
profile: t.relation('profile'),
|
|
207
207
|
posts: t.relation('posts'),
|
|
208
208
|
}),
|
|
@@ -222,7 +222,7 @@ specifying a `query` option on the relation:
|
|
|
222
222
|
builder.drizzleObject('users', {
|
|
223
223
|
name: 'User',
|
|
224
224
|
fields: (t) => ({
|
|
225
|
-
firstName: t.exposeString('
|
|
225
|
+
firstName: t.exposeString('firstName'),
|
|
226
226
|
posts: t.relation('posts', {
|
|
227
227
|
args: {
|
|
228
228
|
limit: t.arg.int(),
|
|
@@ -231,13 +231,19 @@ builder.drizzleObject('users', {
|
|
|
231
231
|
query: (args) => ({
|
|
232
232
|
limit: args.limit ?? 10,
|
|
233
233
|
offset: args.offset ?? 0,
|
|
234
|
-
where:
|
|
235
|
-
|
|
234
|
+
where: {
|
|
235
|
+
published: true,
|
|
236
|
+
},
|
|
237
|
+
orderBy: {
|
|
238
|
+
updatedAt: 'desc',
|
|
239
|
+
},
|
|
236
240
|
}),
|
|
237
241
|
}),
|
|
238
242
|
drafts: t.relation('posts', {
|
|
239
243
|
query: {
|
|
240
|
-
where:
|
|
244
|
+
where: {
|
|
245
|
+
published: false,
|
|
246
|
+
},
|
|
241
247
|
},
|
|
242
248
|
}),
|
|
243
249
|
}),
|
|
@@ -267,7 +273,9 @@ builder.queryType({
|
|
|
267
273
|
resolve: (query, root, args, ctx) =>
|
|
268
274
|
db.query.posts.findFirst(
|
|
269
275
|
query({
|
|
270
|
-
where:
|
|
276
|
+
where: {
|
|
277
|
+
id: Number.parseInt(args.id, 10),
|
|
278
|
+
},
|
|
271
279
|
}),
|
|
272
280
|
),
|
|
273
281
|
}),
|
|
@@ -298,9 +306,7 @@ done using a feature called `variants`. The `variants` API consists of 3 parts:
|
|
|
298
306
|
// Viewer type representing the current user
|
|
299
307
|
export const Viewer = builder.drizzleObject('users', {
|
|
300
308
|
variant: 'Viewer',
|
|
301
|
-
select: {
|
|
302
|
-
columns: {},
|
|
303
|
-
},
|
|
309
|
+
select: {},
|
|
304
310
|
fields: (t) => ({
|
|
305
311
|
id: t.exposeID('id'),
|
|
306
312
|
// A reference to the normal user type so normal user fields can be queried
|
|
@@ -308,8 +314,12 @@ export const Viewer = builder.drizzleObject('users', {
|
|
|
308
314
|
// Adding drafts to View allows a user to fetch their own drafts without exposing it for Other Users in the API
|
|
309
315
|
drafts: t.relation('posts', {
|
|
310
316
|
query: {
|
|
311
|
-
where:
|
|
312
|
-
|
|
317
|
+
where: {
|
|
318
|
+
published: false,
|
|
319
|
+
},
|
|
320
|
+
orderBy: {
|
|
321
|
+
updatedAt: 'desc',
|
|
322
|
+
},
|
|
313
323
|
},
|
|
314
324
|
}),
|
|
315
325
|
}),
|
|
@@ -323,7 +333,9 @@ builder.queryType({
|
|
|
323
333
|
resolve: (query, root, args, ctx) =>
|
|
324
334
|
db.query.users.findFirst(
|
|
325
335
|
query({
|
|
326
|
-
where:
|
|
336
|
+
where: {
|
|
337
|
+
id: ctx.user.id,
|
|
338
|
+
},
|
|
327
339
|
}),
|
|
328
340
|
),
|
|
329
341
|
}),
|
|
@@ -342,6 +354,79 @@ builder.drizzleNode('users', {
|
|
|
342
354
|
});
|
|
343
355
|
```
|
|
344
356
|
|
|
357
|
+
|
|
358
|
+
## Related field
|
|
359
|
+
|
|
360
|
+
The `t.relatedField` method allows you to define a field based on a relation that uses custom
|
|
361
|
+
selections, including aggregations like counts. This is useful when you want to expose derived data
|
|
362
|
+
from a relation without loading the full related records.
|
|
363
|
+
|
|
364
|
+
### Count aggregations
|
|
365
|
+
|
|
366
|
+
One common use case is adding a count field that efficiently counts related records:
|
|
367
|
+
|
|
368
|
+
```ts
|
|
369
|
+
import { count } from 'drizzle-orm';
|
|
370
|
+
|
|
371
|
+
builder.drizzleNode('users', {
|
|
372
|
+
name: 'User',
|
|
373
|
+
id: { column: (user) => user.id },
|
|
374
|
+
fields: (t) => ({
|
|
375
|
+
firstName: t.exposeString('firstName'),
|
|
376
|
+
// Add a count of related posts
|
|
377
|
+
postsCount: t.relatedField('posts', {
|
|
378
|
+
type: 'Int',
|
|
379
|
+
// buildFilter creates the correct WHERE clause for the relation
|
|
380
|
+
select: (buildFilter) => ({
|
|
381
|
+
extras: {
|
|
382
|
+
postsCount: (parent) => db.$count(posts, buildFilter(parent)),
|
|
383
|
+
},
|
|
384
|
+
}),
|
|
385
|
+
resolve: (user) => user.postsCount,
|
|
386
|
+
}),
|
|
387
|
+
}),
|
|
388
|
+
});
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
The `buildFilter` function passed to `select` generates the appropriate SQL filter based on the
|
|
392
|
+
relation definition. This is no different than using `t.field`, but the `buildFilter` helper makes
|
|
393
|
+
it easier to filter for the related records.
|
|
394
|
+
|
|
395
|
+
## Related count
|
|
396
|
+
|
|
397
|
+
For the common case of counting related records, there's a simpler `t.relatedCount` method that handles
|
|
398
|
+
all the boilerplate for you:
|
|
399
|
+
|
|
400
|
+
```ts
|
|
401
|
+
builder.drizzleNode('users', {
|
|
402
|
+
name: 'User',
|
|
403
|
+
id: { column: (user) => user.id },
|
|
404
|
+
fields: (t) => ({
|
|
405
|
+
firstName: t.exposeString('firstName'),
|
|
406
|
+
// Simple count of all related comments
|
|
407
|
+
commentsCount: t.relatedCount('comments'),
|
|
408
|
+
// Count with a where filter
|
|
409
|
+
publishedPostsCount: t.relatedCount('posts', {
|
|
410
|
+
where: eq(posts.published, true),
|
|
411
|
+
}),
|
|
412
|
+
}),
|
|
413
|
+
});
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
The `where` option accepts either a static SQL filter or a function that receives the field arguments
|
|
417
|
+
and context:
|
|
418
|
+
|
|
419
|
+
```ts
|
|
420
|
+
publishedPostsCount: t.relatedCount('posts', {
|
|
421
|
+
args: {
|
|
422
|
+
category: t.arg.string(),
|
|
423
|
+
},
|
|
424
|
+
where: (args, ctx) => args.category
|
|
425
|
+
? and(eq(posts.published, true), eq(posts.category, args.category))
|
|
426
|
+
: eq(posts.published, true),
|
|
427
|
+
}),
|
|
428
|
+
```
|
|
429
|
+
|
|
345
430
|
## Relay integration
|
|
346
431
|
|
|
347
432
|
Relay provides some very useful best practices that are useful for most GraphQL APIs. To make it
|
|
@@ -351,7 +436,7 @@ easy to comply with these best practices, the drizzle plugin has built in suppor
|
|
|
351
436
|
## Relay Nodes
|
|
352
437
|
|
|
353
438
|
Defining relay nodes works just like defining normal `drizzleObject`s, but requires specifying a
|
|
354
|
-
|
|
439
|
+
column to use as the nodes `id` field.
|
|
355
440
|
|
|
356
441
|
```ts
|
|
357
442
|
builder.drizzleNode('users', {
|
|
@@ -405,16 +490,61 @@ builder.drizzleNode('users', {
|
|
|
405
490
|
fields: (t) => ({
|
|
406
491
|
posts: t.relatedConnection('posts', {
|
|
407
492
|
query: () => ({
|
|
408
|
-
where:
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
493
|
+
where: {
|
|
494
|
+
published: true,
|
|
495
|
+
},
|
|
496
|
+
orderBy: {
|
|
497
|
+
id: 'desc',
|
|
498
|
+
},
|
|
412
499
|
}),
|
|
413
500
|
}),
|
|
414
501
|
}),
|
|
415
502
|
});
|
|
416
503
|
```
|
|
417
504
|
|
|
505
|
+
### Connection totalCount
|
|
506
|
+
|
|
507
|
+
You can add a `totalCount` field to your connection by setting the `totalCount` option to `true`:
|
|
508
|
+
|
|
509
|
+
```ts
|
|
510
|
+
builder.drizzleNode('users', {
|
|
511
|
+
name: 'User',
|
|
512
|
+
fields: (t) => ({
|
|
513
|
+
posts: t.relatedConnection('posts', {
|
|
514
|
+
totalCount: true,
|
|
515
|
+
query: () => ({
|
|
516
|
+
where: {
|
|
517
|
+
published: true,
|
|
518
|
+
},
|
|
519
|
+
orderBy: {
|
|
520
|
+
id: 'desc',
|
|
521
|
+
},
|
|
522
|
+
}),
|
|
523
|
+
}),
|
|
524
|
+
}),
|
|
525
|
+
});
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
This will automatically add a `totalCount` field to the connection type. The count query is only
|
|
529
|
+
executed when the `totalCount` field is actually requested in the GraphQL query, and it's included
|
|
530
|
+
as a subquery in the main database query for efficiency.
|
|
531
|
+
|
|
532
|
+
```graphql
|
|
533
|
+
query {
|
|
534
|
+
user(id: "...") {
|
|
535
|
+
posts(first: 10) {
|
|
536
|
+
totalCount
|
|
537
|
+
edges {
|
|
538
|
+
node {
|
|
539
|
+
id
|
|
540
|
+
title
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
```
|
|
547
|
+
|
|
418
548
|
## Drizzle connections
|
|
419
549
|
|
|
420
550
|
Similar to `t.drizzleField`, `t.drizzleConnection` allows you to define a connection field that acts
|
|
@@ -428,10 +558,248 @@ builder.queryFields((t) => ({
|
|
|
428
558
|
resolve: (query, root, args, ctx) =>
|
|
429
559
|
db.query.posts.findMany(
|
|
430
560
|
query({
|
|
431
|
-
where:
|
|
432
|
-
|
|
561
|
+
where: {
|
|
562
|
+
published: true,
|
|
563
|
+
},
|
|
564
|
+
orderBy: {
|
|
565
|
+
id: 'desc',
|
|
566
|
+
},
|
|
433
567
|
}),
|
|
434
568
|
),
|
|
435
569
|
}),
|
|
436
570
|
}));
|
|
437
571
|
```
|
|
572
|
+
|
|
573
|
+
### drizzleConnection totalCount
|
|
574
|
+
|
|
575
|
+
You can add a `totalCount` field to a `drizzleConnection` by providing a `totalCount` callback function that returns the count:
|
|
576
|
+
|
|
577
|
+
```ts
|
|
578
|
+
builder.queryFields((t) => ({
|
|
579
|
+
posts: t.drizzleConnection({
|
|
580
|
+
type: 'posts',
|
|
581
|
+
// Use db.$count() for a simple count query
|
|
582
|
+
totalCount: () => db.$count(posts, eq(posts.published, true)),
|
|
583
|
+
resolve: (query, root, args, ctx) =>
|
|
584
|
+
db.query.posts.findMany(
|
|
585
|
+
query({
|
|
586
|
+
where: {
|
|
587
|
+
published: true,
|
|
588
|
+
},
|
|
589
|
+
orderBy: {
|
|
590
|
+
id: 'desc',
|
|
591
|
+
},
|
|
592
|
+
}),
|
|
593
|
+
),
|
|
594
|
+
}),
|
|
595
|
+
}));
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
The `totalCount` callback receives the same arguments as a normal resolver (`parent`, `args`, `context`, `info`), allowing you to implement custom count logic based on the query context. The example above uses `db.$count()` for a simple count, but you can use any Drizzle query approach.
|
|
599
|
+
|
|
600
|
+
When only the `totalCount` field is requested (without `edges` or `nodes`), the main query is skipped entirely and only the count query is executed for efficiency.
|
|
601
|
+
|
|
602
|
+
### Indirect relations as connections
|
|
603
|
+
|
|
604
|
+
In many cases, you can define many to many connections via drizzle relations, allowing the `relatedConnection` API to work across
|
|
605
|
+
more complex relations. In some cases you may want to define a connection for a relation not expressed directly as a relation in
|
|
606
|
+
your drizzle schema. For these cases, you can use the `drizzleConnectionHelpers`, which allows you to define connection with the `t.connection` API.
|
|
607
|
+
|
|
608
|
+
```typescript
|
|
609
|
+
// Create a drizzle object for the node type of your connection
|
|
610
|
+
const Role = builder.drizzleObject('roles', {
|
|
611
|
+
name: 'Role',
|
|
612
|
+
fields: (t) => ({
|
|
613
|
+
id: t.exposeID('id'),
|
|
614
|
+
name: t.exposeString('name'),
|
|
615
|
+
}),
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
// Create connection helpers for the media type. This will allow you
|
|
621
|
+
// to use the normal t.connection with a drizzle type
|
|
622
|
+
const rolesConnection = drizzleConnectionHelpers(builder, 'userRoles', {
|
|
623
|
+
// select the data needed for the nodes
|
|
624
|
+
select: (nestedSelection) => ({
|
|
625
|
+
with: {
|
|
626
|
+
// use nestedSelection to create the correct selection for the node
|
|
627
|
+
role: nestedSelection(),
|
|
628
|
+
},
|
|
629
|
+
}),
|
|
630
|
+
// resolve the node from the returned list item
|
|
631
|
+
resolveNode: (userRole) => userRole.role,
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
builder.drizzleObjectField('User', 'rolesConnection', (t) =>
|
|
635
|
+
t.connection({
|
|
636
|
+
// The type for the Node
|
|
637
|
+
type: Role,
|
|
638
|
+
// since we are not using t.relatedConnection we need to manually
|
|
639
|
+
// include the selections for our connection
|
|
640
|
+
select: (args, ctx, nestedSelection) => ({
|
|
641
|
+
with: {
|
|
642
|
+
userRoles: rolesConnection.getQuery(args, ctx, nestedSelection),
|
|
643
|
+
},
|
|
644
|
+
}),
|
|
645
|
+
// This helper takes a list of nodes and formats them for the connection
|
|
646
|
+
resolve: (user, args, ctx) => {
|
|
647
|
+
return rolesConnection.resolve(user.userRoles, args, ctx, user);
|
|
648
|
+
},
|
|
649
|
+
}),
|
|
650
|
+
);
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
The above example assumes that you are paginating a relation to a join table, where the pagination
|
|
654
|
+
args are applied based on the relation to that join table, but the nodes themselves are nested
|
|
655
|
+
deeper.
|
|
656
|
+
|
|
657
|
+
`drizzleConnectionHelpers` can also be used to manually create a connection where the edge and
|
|
658
|
+
connections share the same model, and pagination happens directly on a relation to nodes type (even
|
|
659
|
+
if that relation is nested).
|
|
660
|
+
|
|
661
|
+
```ts
|
|
662
|
+
const commentConnectionHelpers = drizzleConnectionHelpers(builder, 'Comment');
|
|
663
|
+
|
|
664
|
+
const SelectPost = builder.drizzleObject('posts', {
|
|
665
|
+
fields: (t) => ({
|
|
666
|
+
title: t.exposeString('title'),
|
|
667
|
+
comments: t.connection({
|
|
668
|
+
type: commentConnectionHelpers.ref,
|
|
669
|
+
select: (args, ctx, nestedSelection) => ({
|
|
670
|
+
with: {
|
|
671
|
+
comments: commentConnectionHelpers.getQuery(args, ctx, nestedSelection),
|
|
672
|
+
},
|
|
673
|
+
}),
|
|
674
|
+
resolve: (parent, args, ctx) => commentConnectionHelpers.resolve(parent.comments, args, ctx),
|
|
675
|
+
}),
|
|
676
|
+
}),
|
|
677
|
+
});
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
Arguments, ordering and filtering can also be defined in the helpers:
|
|
681
|
+
|
|
682
|
+
```ts
|
|
683
|
+
const rolesConnection = drizzleConnectionHelpers(builder, 'userRoles', {
|
|
684
|
+
// define additional arguments
|
|
685
|
+
args: (t) => ({}),
|
|
686
|
+
query: (args) => ({
|
|
687
|
+
// define an order
|
|
688
|
+
orderBy: {
|
|
689
|
+
roleId: 'asc',
|
|
690
|
+
}
|
|
691
|
+
// define a filter
|
|
692
|
+
where: {
|
|
693
|
+
accepted: true,
|
|
694
|
+
}
|
|
695
|
+
}),
|
|
696
|
+
// select the data needed for the nodes
|
|
697
|
+
select: (nestedSelection) => ({
|
|
698
|
+
with: {
|
|
699
|
+
// use nestedSelection to create the correct selection for the node
|
|
700
|
+
role: nestedSelection(),
|
|
701
|
+
},
|
|
702
|
+
}),
|
|
703
|
+
// resolve the node from the returned list item
|
|
704
|
+
resolveNode: (userRole) => userRole.role,
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
builder.drizzleObjectField('User', 'rolesConnection', (t) =>
|
|
709
|
+
t.connection({
|
|
710
|
+
type: Role,
|
|
711
|
+
// add the args from the connection helper to the field
|
|
712
|
+
args: rolesConnection.getArgs(),
|
|
713
|
+
select: (args, ctx, nestedSelection) => ({
|
|
714
|
+
with: {
|
|
715
|
+
userRoles: rolesConnection.getQuery(args, ctx, nestedSelection),
|
|
716
|
+
},
|
|
717
|
+
}),
|
|
718
|
+
resolve: (user, args, ctx) => rolesConnection.resolve(user.userRoles, args, ctx, user),
|
|
719
|
+
}),
|
|
720
|
+
);
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
### Extending connection edges
|
|
724
|
+
|
|
725
|
+
In some cases you may want to expose some data from an indirect connection on the edge object.
|
|
726
|
+
|
|
727
|
+
```typescript
|
|
728
|
+
const rolesConnection = drizzleConnectionHelpers(builder, 'userRoles', {
|
|
729
|
+
select: (nestedSelection) => ({
|
|
730
|
+
with: {
|
|
731
|
+
role: nestedSelection(),
|
|
732
|
+
},
|
|
733
|
+
}),
|
|
734
|
+
resolveNode: (userRole) => userRole.role,
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
builder.drizzleObjectFields('User', (t) => ({
|
|
738
|
+
rolesConnection: t.connection(
|
|
739
|
+
{
|
|
740
|
+
type: Role,
|
|
741
|
+
select: (args, ctx, nestedSelection) => ({
|
|
742
|
+
with: {
|
|
743
|
+
userRoles: rolesConnection.getQuery(args, ctx, nestedSelection),
|
|
744
|
+
},
|
|
745
|
+
}),
|
|
746
|
+
resolve: (user, args, ctx) =>
|
|
747
|
+
rolesConnection.resolve(
|
|
748
|
+
user.userRoles,
|
|
749
|
+
args,
|
|
750
|
+
ctx,
|
|
751
|
+
user,
|
|
752
|
+
),
|
|
753
|
+
},
|
|
754
|
+
{},
|
|
755
|
+
// options for the edge object
|
|
756
|
+
{
|
|
757
|
+
// define the additional fields on the edge object
|
|
758
|
+
fields: (edge) => ({
|
|
759
|
+
createdAt: edge.field({
|
|
760
|
+
type: 'DateTime',
|
|
761
|
+
// the parent shape for edge fields is inferred from the connections resolve function
|
|
762
|
+
resolve: (role) => role.createdAt,
|
|
763
|
+
}),
|
|
764
|
+
}),
|
|
765
|
+
},
|
|
766
|
+
),
|
|
767
|
+
}));
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
### `drizzleConnectionHelpers` for non-relation connections
|
|
771
|
+
|
|
772
|
+
You can also use `drizzleConnectionHelpers` for non-relation connections where you want a connection where your edges and nodes are not the same type.
|
|
773
|
+
|
|
774
|
+
Note that when doing this, you need to be careful to properly merge the `where` clause generated by the connection helper with any additional `where` clause you need to apply to your query
|
|
775
|
+
|
|
776
|
+
```typescript
|
|
777
|
+
const rolesConnection = drizzleConnectionHelpers(builder, 'userRoles', {
|
|
778
|
+
select: (nestedSelection) => ({
|
|
779
|
+
with: {
|
|
780
|
+
role: nestedSelection(),
|
|
781
|
+
},
|
|
782
|
+
}),
|
|
783
|
+
resolveNode: (userRole) => userRole.role,
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
builder.queryFields((t) => ({
|
|
787
|
+
roles: t.connection({
|
|
788
|
+
type: Role,
|
|
789
|
+
args: {
|
|
790
|
+
userId: t.arg.int({ required: true }),
|
|
791
|
+
},
|
|
792
|
+
nodeNullable: true,
|
|
793
|
+
resolve: async (_, args, ctx, info) => {
|
|
794
|
+
const query = rolesConnection.getQuery(args, ctx, info);
|
|
795
|
+
const userRoles = await db.query.userRoles.findMany({
|
|
796
|
+
...query,
|
|
797
|
+
where: {
|
|
798
|
+
...query.where,
|
|
799
|
+
userId: args.userId,
|
|
800
|
+
},
|
|
801
|
+
});
|
|
802
|
+
return rolesConnection.resolve(userRoles, args, ctx);
|
|
803
|
+
},
|
|
804
|
+
}),
|
|
805
|
+
}));
|