@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.
Files changed (40) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +417 -49
  3. package/dts/drizzle-field-builder.d.ts +14 -3
  4. package/dts/drizzle-field-builder.d.ts.map +1 -1
  5. package/dts/types.d.ts +6 -0
  6. package/dts/types.d.ts.map +1 -1
  7. package/dts/utils/cursors.d.ts +6 -2
  8. package/dts/utils/cursors.d.ts.map +1 -1
  9. package/dts/utils/map-query.d.ts.map +1 -1
  10. package/esm/drizzle-field-builder.d.ts +16 -3
  11. package/esm/drizzle-field-builder.d.ts.map +1 -1
  12. package/esm/drizzle-field-builder.js +115 -7
  13. package/esm/drizzle-field-builder.js.map +1 -1
  14. package/esm/field-builder.js +33 -2
  15. package/esm/field-builder.js.map +1 -1
  16. package/esm/types.d.ts +6 -0
  17. package/esm/types.d.ts.map +1 -1
  18. package/esm/types.js.map +1 -1
  19. package/esm/utils/cursors.d.ts +6 -2
  20. package/esm/utils/cursors.d.ts.map +1 -1
  21. package/esm/utils/cursors.js +18 -2
  22. package/esm/utils/cursors.js.map +1 -1
  23. package/esm/utils/map-query.d.ts.map +1 -1
  24. package/esm/utils/map-query.js +25 -6
  25. package/esm/utils/map-query.js.map +1 -1
  26. package/lib/drizzle-field-builder.js +113 -5
  27. package/lib/drizzle-field-builder.js.map +1 -1
  28. package/lib/field-builder.js +33 -2
  29. package/lib/field-builder.js.map +1 -1
  30. package/lib/types.js.map +1 -1
  31. package/lib/utils/cursors.js +18 -2
  32. package/lib/utils/cursors.js.map +1 -1
  33. package/lib/utils/map-query.js +24 -6
  34. package/lib/utils/map-query.js.map +1 -1
  35. package/package.json +4 -4
  36. package/src/drizzle-field-builder.ts +207 -3
  37. package/src/field-builder.ts +40 -1
  38. package/src/types.ts +20 -1
  39. package/src/utils/cursors.ts +24 -3
  40. 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://orm.drizzle.team/docs/rqb for detailed documentation on the relations API.
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
- const db = drizzle({ client, schema });
26
+ type DrizzleRelations = typeof relations
26
27
 
27
28
  export interface PothosTypes {
28
- DrizzleSchema: typeof schema;
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-scope-auth';
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, schema });
56
+ const db = drizzle({ client, relations });
53
57
 
54
58
  export interface PothosTypes {
55
- DrizzleSchema: typeof schema;
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('first_name'),
76
- lastName: t.exposeString('last_name'),
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.first_name} ${user.last_name}`,
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
- first_name: true,
113
- last_name: true,
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})`.as('lowercaseName'),
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.first_name} ${user.last_name}`,
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: { first_name: true, last_name: true },
158
+ columns: { firstName: true, lastName: true },
156
159
  },
157
- resolve: (user, args, ctx, info) => `${user.first_name} ${user.last_name}`,
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})`.as('lowercaseName'),
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('first_name'),
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('first_name'),
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: (post, { eq }) => eq(post.published, true),
235
- orderBy: (post, { desc }) => desc(post.updatedAt),
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: (post, { eq }) => eq(post.published, false),
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: eq(posts.id, Number.parseInt(args.id, 10)),
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: (post, { eq }) => eq(post.published, false),
312
- orderBy: (post, ops) => ops.desc(post.updatedAt),
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: (user, { eq }) => eq(user.id, ctx.user.id),
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
- colum to use as the nodes `id` field.
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: (post, { eq }) => eq(post.published, 1),
409
- orderBy: (post) => ({
410
- desc: post.id,
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: (post, { eq }) => eq(post.published, true),
432
- orderBy: (post) => ({ desc: post.id }),
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>