@pothos/plugin-drizzle 0.14.0 → 0.16.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 +17 -0
- package/README.md +417 -49
- 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/dts/utils/map-query.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 +115 -7
- 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/esm/utils/map-query.d.ts.map +1 -1
- package/esm/utils/map-query.js +25 -6
- package/esm/utils/map-query.js.map +1 -1
- package/lib/drizzle-field-builder.js +113 -5
- 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/lib/utils/map-query.js +24 -6
- package/lib/utils/map-query.js.map +1 -1
- package/package.json +4 -4
- package/src/drizzle-field-builder.ts +207 -3
- package/src/field-builder.ts +40 -1
- package/src/types.ts +20 -1
- package/src/utils/cursors.ts +24 -3
- package/src/utils/map-query.ts +26 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @pothos/plugin-drizzle
|
|
2
2
|
|
|
3
|
+
## 0.16.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 3509aab: Add new field builder methods and connection enhancements:
|
|
8
|
+
- `t.relatedField`: New method for creating custom fields based on relations with custom selections using the `buildFilter` callback
|
|
9
|
+
- `t.relatedCount`: Simplified method for counting related records, with optional `where` filter support
|
|
10
|
+
- `totalCount: true` option for `relatedConnection`: Adds a `totalCount` field to related connections
|
|
11
|
+
- `totalCount` callback for `drizzleConnection`: Allows adding a `totalCount` field to drizzle connections
|
|
12
|
+
- `totalCountOnly` optimization: Skips the main query when only `totalCount` is requested (for both connection types)
|
|
13
|
+
|
|
14
|
+
## 0.15.0
|
|
15
|
+
|
|
16
|
+
### Minor Changes
|
|
17
|
+
|
|
18
|
+
- 27d5aae: Fix mappings when merging selections across fragments
|
|
19
|
+
|
|
3
20
|
## 0.14.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://
|
|
12
|
-
|
|
13
|
-
In addition to defining relations, the Pothos drizzle plugin also requires defining a `primaryKey`
|
|
14
|
-
for every table used as a `drizzleObject`, `drizzleNode`, or `drizzleInterface`.
|
|
11
|
+
https://rqbv2.drizzle-orm-fe.pages.dev/docs/relations-v2 for detailed documentation on the relations API.
|
|
15
12
|
|
|
16
13
|
Once you have configured you have configured you 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`,
|
|
@@ -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
|
}),
|
|
@@ -351,7 +363,7 @@ easy to comply with these best practices, the drizzle plugin has built in suppor
|
|
|
351
363
|
## Relay Nodes
|
|
352
364
|
|
|
353
365
|
Defining relay nodes works just like defining normal `drizzleObject`s, but requires specifying a
|
|
354
|
-
|
|
366
|
+
column to use as the nodes `id` field.
|
|
355
367
|
|
|
356
368
|
```ts
|
|
357
369
|
builder.drizzleNode('users', {
|
|
@@ -369,6 +381,78 @@ builder.drizzleNode('users', {
|
|
|
369
381
|
|
|
370
382
|
The id column can also be set to a list of columns for types with a composite primary key.
|
|
371
383
|
|
|
384
|
+
## Related field
|
|
385
|
+
|
|
386
|
+
The `t.relatedField` method allows you to define a field based on a relation that uses custom
|
|
387
|
+
selections, including aggregations like counts. This is useful when you want to expose derived data
|
|
388
|
+
from a relation without loading the full related records.
|
|
389
|
+
|
|
390
|
+
### Count aggregations
|
|
391
|
+
|
|
392
|
+
One common use case is adding a count field that efficiently counts related records:
|
|
393
|
+
|
|
394
|
+
```ts
|
|
395
|
+
import { count } from 'drizzle-orm';
|
|
396
|
+
|
|
397
|
+
builder.drizzleNode('users', {
|
|
398
|
+
name: 'User',
|
|
399
|
+
id: { column: (user) => user.id },
|
|
400
|
+
fields: (t) => ({
|
|
401
|
+
firstName: t.exposeString('firstName'),
|
|
402
|
+
// Add a count of related posts
|
|
403
|
+
postsCount: t.relatedField('posts', {
|
|
404
|
+
type: 'Int',
|
|
405
|
+
// buildFilter creates the correct WHERE clause for the relation
|
|
406
|
+
select: (buildFilter) => ({
|
|
407
|
+
extras: {
|
|
408
|
+
postsCount: (parent) => db.$count(posts, buildFilter(parent)),
|
|
409
|
+
},
|
|
410
|
+
}),
|
|
411
|
+
resolve: (user) => user.postsCount,
|
|
412
|
+
}),
|
|
413
|
+
}),
|
|
414
|
+
});
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
The `buildFilter` function passed to `select` generates the appropriate SQL filter based on the
|
|
418
|
+
relation definition. This is no different than using `t.field`, but the `buildFilter` helper makes
|
|
419
|
+
it easier to filter for the related records.
|
|
420
|
+
|
|
421
|
+
## Related count
|
|
422
|
+
|
|
423
|
+
For the common case of counting related records, there's a simpler `t.relatedCount` method that handles
|
|
424
|
+
all the boilerplate for you:
|
|
425
|
+
|
|
426
|
+
```ts
|
|
427
|
+
builder.drizzleNode('users', {
|
|
428
|
+
name: 'User',
|
|
429
|
+
id: { column: (user) => user.id },
|
|
430
|
+
fields: (t) => ({
|
|
431
|
+
firstName: t.exposeString('firstName'),
|
|
432
|
+
// Simple count of all related comments
|
|
433
|
+
commentsCount: t.relatedCount('comments'),
|
|
434
|
+
// Count with a where filter
|
|
435
|
+
publishedPostsCount: t.relatedCount('posts', {
|
|
436
|
+
where: eq(posts.published, true),
|
|
437
|
+
}),
|
|
438
|
+
}),
|
|
439
|
+
});
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
The `where` option accepts either a static SQL filter or a function that receives the field arguments
|
|
443
|
+
and context:
|
|
444
|
+
|
|
445
|
+
```ts
|
|
446
|
+
publishedPostsCount: t.relatedCount('posts', {
|
|
447
|
+
args: {
|
|
448
|
+
category: t.arg.string(),
|
|
449
|
+
},
|
|
450
|
+
where: (args, ctx) => args.category
|
|
451
|
+
? and(eq(posts.published, true), eq(posts.category, args.category))
|
|
452
|
+
: eq(posts.published, true),
|
|
453
|
+
}),
|
|
454
|
+
```
|
|
455
|
+
|
|
372
456
|
## Related connections
|
|
373
457
|
|
|
374
458
|
To implement a relation as a connection, you can use `t.relatedConnection` instead of `t.relation`:
|
|
@@ -405,16 +489,61 @@ builder.drizzleNode('users', {
|
|
|
405
489
|
fields: (t) => ({
|
|
406
490
|
posts: t.relatedConnection('posts', {
|
|
407
491
|
query: () => ({
|
|
408
|
-
where:
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
492
|
+
where: {
|
|
493
|
+
published: true,
|
|
494
|
+
},
|
|
495
|
+
orderBy: {
|
|
496
|
+
id: 'desc',
|
|
497
|
+
},
|
|
498
|
+
}),
|
|
499
|
+
}),
|
|
500
|
+
}),
|
|
501
|
+
});
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
### Connection totalCount
|
|
505
|
+
|
|
506
|
+
You can add a `totalCount` field to your connection by setting the `totalCount` option to `true`:
|
|
507
|
+
|
|
508
|
+
```ts
|
|
509
|
+
builder.drizzleNode('users', {
|
|
510
|
+
name: 'User',
|
|
511
|
+
fields: (t) => ({
|
|
512
|
+
posts: t.relatedConnection('posts', {
|
|
513
|
+
totalCount: true,
|
|
514
|
+
query: () => ({
|
|
515
|
+
where: {
|
|
516
|
+
published: true,
|
|
517
|
+
},
|
|
518
|
+
orderBy: {
|
|
519
|
+
id: 'desc',
|
|
520
|
+
},
|
|
412
521
|
}),
|
|
413
522
|
}),
|
|
414
523
|
}),
|
|
415
524
|
});
|
|
416
525
|
```
|
|
417
526
|
|
|
527
|
+
This will automatically add a `totalCount` field to the connection type. The count query is only
|
|
528
|
+
executed when the `totalCount` field is actually requested in the GraphQL query, and it's included
|
|
529
|
+
as a subquery in the main database query for efficiency.
|
|
530
|
+
|
|
531
|
+
```graphql
|
|
532
|
+
query {
|
|
533
|
+
user(id: "...") {
|
|
534
|
+
posts(first: 10) {
|
|
535
|
+
totalCount
|
|
536
|
+
edges {
|
|
537
|
+
node {
|
|
538
|
+
id
|
|
539
|
+
title
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
```
|
|
546
|
+
|
|
418
547
|
## Drizzle connections
|
|
419
548
|
|
|
420
549
|
Similar to `t.drizzleField`, `t.drizzleConnection` allows you to define a connection field that acts
|
|
@@ -428,10 +557,249 @@ builder.queryFields((t) => ({
|
|
|
428
557
|
resolve: (query, root, args, ctx) =>
|
|
429
558
|
db.query.posts.findMany(
|
|
430
559
|
query({
|
|
431
|
-
where:
|
|
432
|
-
|
|
560
|
+
where: {
|
|
561
|
+
published: true,
|
|
562
|
+
},
|
|
563
|
+
orderBy: {
|
|
564
|
+
id: 'desc',
|
|
565
|
+
},
|
|
566
|
+
}),
|
|
567
|
+
),
|
|
568
|
+
}),
|
|
569
|
+
}));
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
### drizzleConnection totalCount
|
|
573
|
+
|
|
574
|
+
You can add a `totalCount` field to a `drizzleConnection` by providing a `totalCount` callback function that returns the count:
|
|
575
|
+
|
|
576
|
+
```ts
|
|
577
|
+
builder.queryFields((t) => ({
|
|
578
|
+
posts: t.drizzleConnection({
|
|
579
|
+
type: 'posts',
|
|
580
|
+
// Use db.$count() for a simple count query
|
|
581
|
+
totalCount: () => db.$count(posts, eq(posts.published, true)),
|
|
582
|
+
resolve: (query, root, args, ctx) =>
|
|
583
|
+
db.query.posts.findMany(
|
|
584
|
+
query({
|
|
585
|
+
where: {
|
|
586
|
+
published: true,
|
|
587
|
+
},
|
|
588
|
+
orderBy: {
|
|
589
|
+
id: 'desc',
|
|
590
|
+
},
|
|
433
591
|
}),
|
|
434
592
|
),
|
|
435
593
|
}),
|
|
436
594
|
}));
|
|
437
595
|
```
|
|
596
|
+
|
|
597
|
+
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.
|
|
598
|
+
|
|
599
|
+
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.
|
|
600
|
+
|
|
601
|
+
### Indirect relations as connections
|
|
602
|
+
|
|
603
|
+
In many cases, you can define many to many connections via drizzle relations, allowing the `relatedConnection` API to work across
|
|
604
|
+
more complex relations. In some cases you may want to define a connection for a relation not expressed directly as a relation in
|
|
605
|
+
your drizzle schema. For these cases, you can use the `drizzleConnectionHelpers`, which allows you to define connection with the `t.connection` API.
|
|
606
|
+
|
|
607
|
+
```typescript
|
|
608
|
+
// Create a drizzle object for the node type of your connection
|
|
609
|
+
const Role = builder.drizzleObject('roles', {
|
|
610
|
+
name: 'Role',
|
|
611
|
+
fields: (t) => ({
|
|
612
|
+
id: t.exposeID('id'),
|
|
613
|
+
name: t.exposeString('name'),
|
|
614
|
+
}),
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
// Create connection helpers for the media type. This will allow you
|
|
620
|
+
// to use the normal t.connection with a drizzle type
|
|
621
|
+
const rolesConnection = drizzleConnectionHelpers(builder, 'userRoles', {
|
|
622
|
+
// select the data needed for the nodes
|
|
623
|
+
select: (nestedSelection) => ({
|
|
624
|
+
with: {
|
|
625
|
+
// use nestedSelection to create the correct selection for the node
|
|
626
|
+
role: nestedSelection(),
|
|
627
|
+
},
|
|
628
|
+
}),
|
|
629
|
+
// resolve the node from the returned list item
|
|
630
|
+
resolveNode: (userRole) => userRole.role,
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
builder.drizzleObjectField('User', 'rolesConnection', (t) =>
|
|
634
|
+
t.connection({
|
|
635
|
+
// The type for the Node
|
|
636
|
+
type: Role,
|
|
637
|
+
// since we are not using t.relatedConnection we need to manually
|
|
638
|
+
// include the selections for our connection
|
|
639
|
+
select: (args, ctx, nestedSelection) => ({
|
|
640
|
+
with: {
|
|
641
|
+
userRoles: rolesConnection.getQuery(args, ctx, nestedSelection),
|
|
642
|
+
},
|
|
643
|
+
}),
|
|
644
|
+
resolve: (post, args, ctx) =>
|
|
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
|
+
}));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { type CompatibleTypes, type ExposeNullability, type FieldKind, type FieldRef, type InferredFieldOptionKeys, type InputFieldMap, type InterfaceParam, type NormalizeArgs, ObjectRef, type PluginName, type SchemaTypes, type ShapeFromTypeParam, type TypeParam } from '@pothos/core';
|
|
2
|
-
import { type InferSelectModel, type TableRelationalConfig } from 'drizzle-orm';
|
|
1
|
+
import { type CompatibleTypes, type ExposeNullability, type FieldKind, type FieldRef, type InferredFieldOptionKeys, type InputFieldMap, type InputShapeFromFields, type InterfaceParam, type NormalizeArgs, ObjectRef, type PluginName, type SchemaTypes, type ShapeFromTypeParam, type TypeParam } from '@pothos/core';
|
|
2
|
+
import { type BuildQueryResult, type DBQueryConfig, type InferSelectModel, type SQL, type TableRelationalConfig } from 'drizzle-orm';
|
|
3
3
|
import type { DrizzleRef } from './interface-ref';
|
|
4
|
-
import type { DrizzleConnectionShape, ListRelation, RelatedConnectionOptions, RelatedFieldOptions, ShapeFromConnection, TypesForRelation, VariantFieldOptions } from './types';
|
|
4
|
+
import type { DrizzleConnectionShape, ListRelation, RelatedConnectionOptions, RelatedCountOptions, RelatedFieldOptions, ShapeFromConnection, TypesForRelation, VariantFieldOptions } from './types';
|
|
5
5
|
declare const RootBuilder: {
|
|
6
6
|
new <Types extends SchemaTypes, Shape, Kind extends FieldKind>(builder: PothosSchemaTypes.SchemaBuilder<Types>, kind: FieldKind, graphqlKind: PothosSchemaTypes.PothosKindToGraphQLType[FieldKind]): PothosSchemaTypes.RootFieldBuilder<Types, Shape, Kind>;
|
|
7
7
|
};
|
|
@@ -72,6 +72,17 @@ export declare class DrizzleObjectFieldBuilder<Types extends SchemaTypes, TableC
|
|
|
72
72
|
relation<Field extends keyof TableConfig['relations'], Nullable extends boolean, Args extends InputFieldMap, ResolveReturnShape>(name: Field, ...allArgs: NormalizeArgs<[
|
|
73
73
|
options: RelatedFieldOptions<Types, TableConfig, Field, Nullable, Args, ResolveReturnShape, Shape>
|
|
74
74
|
]>): FieldRef<Types, TypesForRelation<Types, TableConfig['relations'][Field]>, 'Object'>;
|
|
75
|
+
relatedField<Field extends keyof TableConfig['relations'], Type extends TypeParam<Types>, Nullable extends boolean, Args extends InputFieldMap, Select extends DBQueryConfig<'many', Types['DrizzleRelations'], TableConfig>, ShapeWithSelection = Shape & BuildQueryResult<Types['DrizzleRelations'], TableConfig, Select & {
|
|
76
|
+
columns: {};
|
|
77
|
+
}>>(relationName: Field, options: {
|
|
78
|
+
type: Type;
|
|
79
|
+
nullable?: Nullable;
|
|
80
|
+
args?: Args;
|
|
81
|
+
description?: string;
|
|
82
|
+
select: (buildFilter: (parentTable: TableConfig['table']) => SQL, args: Args extends InputFieldMap ? InputShapeFromFields<Args> : {}, ctx: Types['Context'], nestedQuery: (query: DBQueryConfig<'many', Types['DrizzleRelations'], TableConfig>) => DBQueryConfig<'many', Types['DrizzleRelations'], TableConfig>) => Select;
|
|
83
|
+
resolve: (parent: ShapeWithSelection, args: Args extends InputFieldMap ? InputShapeFromFields<Args> : {}, ctx: Types['Context'], info: unknown) => ShapeFromTypeParam<Types, Type, Nullable>;
|
|
84
|
+
}): FieldRef<Types, ShapeFromTypeParam<Types, Type, Nullable>, 'DrizzleObject'>;
|
|
85
|
+
relatedCount<Field extends keyof TableConfig['relations'], Args extends InputFieldMap, Where extends SQL | undefined>(relationName: Field, ...allArgs: NormalizeArgs<[options: RelatedCountOptions<Types, Shape, Args, Where>]>): FieldRef<Types, number, 'DrizzleObject'>;
|
|
75
86
|
expose<Type extends TypeParam<Types>, Nullable extends boolean, ResolveReturnShape, Name extends CompatibleTypes<Types, ExposableShape, Type, Nullable>>(...args: NormalizeArgs<[
|
|
76
87
|
name: Name,
|
|
77
88
|
options: ExposeNullability<Types, Type, ExposableShape, Name, Nullable> & Omit<PothosSchemaTypes.ObjectFieldOptions<Types, Shape, Type, Nullable, {}, ResolveReturnShape>, 'nullable' | 'select' | InferredFieldOptionKeys>
|