@pothos/plugin-drizzle 0.18.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +351 -0
- package/README.md +784 -221
- package/dts/connection-helpers.d.ts +18 -8
- package/dts/connection-helpers.d.ts.map +1 -1
- package/dts/drizzle-field-builder.d.ts +4 -11
- package/dts/drizzle-field-builder.d.ts.map +1 -1
- package/dts/global-types.d.ts.map +1 -1
- package/dts/index.d.ts.map +1 -1
- package/dts/model-loader.d.ts +18 -17
- package/dts/model-loader.d.ts.map +1 -1
- package/dts/types.d.ts +81 -20
- package/dts/types.d.ts.map +1 -1
- package/dts/utils/adapter.d.ts +39 -0
- package/dts/utils/adapter.d.ts.map +1 -0
- package/dts/utils/await-selections.d.ts +12 -0
- package/dts/utils/await-selections.d.ts.map +1 -0
- package/dts/utils/config.d.ts +23 -3
- package/dts/utils/config.d.ts.map +1 -1
- package/dts/utils/cursors.d.ts +6 -6
- package/dts/utils/cursors.d.ts.map +1 -1
- package/dts/utils/map-query.d.ts +22 -21
- package/dts/utils/map-query.d.ts.map +1 -1
- package/dts/utils/path-info.d.ts +13 -0
- package/dts/utils/path-info.d.ts.map +1 -0
- package/dts/utils/relation-filter.d.ts +48 -0
- package/dts/utils/relation-filter.d.ts.map +1 -0
- package/dts/utils/selections.d.ts +2 -21
- package/dts/utils/selections.d.ts.map +1 -1
- package/esm/connection-helpers.d.ts +18 -8
- package/esm/connection-helpers.d.ts.map +1 -1
- package/esm/connection-helpers.js +52 -19
- package/esm/connection-helpers.js.map +1 -1
- package/esm/drizzle-field-builder.d.ts +4 -11
- package/esm/drizzle-field-builder.d.ts.map +1 -1
- package/esm/drizzle-field-builder.js +138 -96
- package/esm/drizzle-field-builder.js.map +1 -1
- package/esm/field-builder.js +61 -47
- package/esm/field-builder.js.map +1 -1
- package/esm/global-types.d.ts.map +1 -1
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +37 -61
- package/esm/index.js.map +1 -1
- package/esm/model-loader.d.ts +18 -17
- package/esm/model-loader.d.ts.map +1 -1
- package/esm/model-loader.js +76 -45
- package/esm/model-loader.js.map +1 -1
- package/esm/schema-builder.js +40 -149
- package/esm/schema-builder.js.map +1 -1
- package/esm/types.d.ts +81 -20
- package/esm/types.d.ts.map +1 -1
- package/esm/types.js +1 -0
- package/esm/types.js.map +1 -1
- package/esm/utils/adapter.d.ts +39 -0
- package/esm/utils/adapter.d.ts.map +1 -0
- package/esm/utils/adapter.js +137 -0
- package/esm/utils/adapter.js.map +1 -0
- package/esm/utils/await-selections.d.ts +12 -0
- package/esm/utils/await-selections.d.ts.map +1 -0
- package/esm/utils/await-selections.js +19 -0
- package/esm/utils/await-selections.js.map +1 -0
- package/esm/utils/config.d.ts +23 -3
- package/esm/utils/config.d.ts.map +1 -1
- package/esm/utils/config.js +1 -4
- package/esm/utils/config.js.map +1 -1
- package/esm/utils/cursors.d.ts +6 -6
- package/esm/utils/cursors.d.ts.map +1 -1
- package/esm/utils/cursors.js +55 -90
- package/esm/utils/cursors.js.map +1 -1
- package/esm/utils/map-query.d.ts +22 -21
- package/esm/utils/map-query.d.ts.map +1 -1
- package/esm/utils/map-query.js +40 -336
- package/esm/utils/map-query.js.map +1 -1
- package/esm/utils/path-info.d.ts +13 -0
- package/esm/utils/path-info.d.ts.map +1 -0
- package/esm/utils/path-info.js +37 -0
- package/esm/utils/path-info.js.map +1 -0
- package/esm/utils/relation-filter.d.ts +48 -0
- package/esm/utils/relation-filter.d.ts.map +1 -0
- package/esm/utils/relation-filter.js +92 -0
- package/esm/utils/relation-filter.js.map +1 -0
- package/esm/utils/selections.d.ts +2 -21
- package/esm/utils/selections.d.ts.map +1 -1
- package/esm/utils/selections.js +1 -133
- package/esm/utils/selections.js.map +1 -1
- package/lib/connection-helpers.js +51 -18
- package/lib/connection-helpers.js.map +1 -1
- package/lib/drizzle-field-builder.js +136 -94
- package/lib/drizzle-field-builder.js.map +1 -1
- package/lib/field-builder.js +58 -44
- package/lib/field-builder.js.map +1 -1
- package/lib/index.js +36 -60
- package/lib/index.js.map +1 -1
- package/lib/model-loader.js +76 -45
- package/lib/model-loader.js.map +1 -1
- package/lib/schema-builder.js +40 -149
- package/lib/schema-builder.js.map +1 -1
- package/lib/types.js +4 -0
- package/lib/types.js.map +1 -1
- package/lib/utils/adapter.js +147 -0
- package/lib/utils/adapter.js.map +1 -0
- package/lib/utils/await-selections.js +20 -0
- package/lib/utils/await-selections.js.map +1 -0
- package/lib/utils/config.js +1 -4
- package/lib/utils/config.js.map +1 -1
- package/lib/utils/cursors.js +47 -88
- package/lib/utils/cursors.js.map +1 -1
- package/lib/utils/map-query.js +28 -341
- package/lib/utils/map-query.js.map +1 -1
- package/lib/utils/path-info.js +39 -0
- package/lib/utils/path-info.js.map +1 -0
- package/lib/utils/relation-filter.js +91 -0
- package/lib/utils/relation-filter.js.map +1 -0
- package/lib/utils/selections.js +3 -158
- package/lib/utils/selections.js.map +1 -1
- package/package.json +8 -6
- package/src/connection-helpers.ts +167 -53
- package/src/drizzle-field-builder.ts +296 -176
- package/src/field-builder.ts +110 -77
- package/src/global-types.ts +0 -32
- package/src/index.ts +42 -68
- package/src/model-loader.ts +110 -71
- package/src/schema-builder.ts +76 -169
- package/src/types.ts +176 -44
- package/src/utils/adapter.ts +197 -0
- package/src/utils/await-selections.ts +31 -0
- package/src/utils/config.ts +1 -5
- package/src/utils/cursors.ts +53 -81
- package/src/utils/map-query.ts +51 -708
- package/src/utils/path-info.ts +40 -0
- package/src/utils/relation-filter.ts +185 -0
- package/src/utils/selections.ts +2 -207
- package/dts/utils/deep-equal.d.ts +0 -2
- package/dts/utils/deep-equal.d.ts.map +0 -1
- package/dts/utils/drizzle-graphql.d.ts +0 -2
- package/dts/utils/drizzle-graphql.d.ts.map +0 -1
- package/dts/utils/loader-map.d.ts +0 -11
- package/dts/utils/loader-map.d.ts.map +0 -1
- package/dts/utils/usage.d.ts +0 -5
- package/dts/utils/usage.d.ts.map +0 -1
- package/esm/utils/deep-equal.d.ts +0 -2
- package/esm/utils/deep-equal.d.ts.map +0 -1
- package/esm/utils/deep-equal.js +0 -45
- package/esm/utils/deep-equal.js.map +0 -1
- package/esm/utils/drizzle-graphql.d.ts +0 -2
- package/esm/utils/drizzle-graphql.d.ts.map +0 -1
- package/esm/utils/drizzle-graphql.js +0 -232
- package/esm/utils/drizzle-graphql.js.map +0 -1
- package/esm/utils/loader-map.d.ts +0 -11
- package/esm/utils/loader-map.d.ts.map +0 -1
- package/esm/utils/loader-map.js +0 -35
- package/esm/utils/loader-map.js.map +0 -1
- package/esm/utils/usage.d.ts +0 -5
- package/esm/utils/usage.d.ts.map +0 -1
- package/esm/utils/usage.js +0 -59
- package/esm/utils/usage.js.map +0 -1
- package/lib/utils/deep-equal.js +0 -55
- package/lib/utils/deep-equal.js.map +0 -1
- package/lib/utils/drizzle-graphql.js +0 -236
- package/lib/utils/drizzle-graphql.js.map +0 -1
- package/lib/utils/loader-map.js +0 -56
- package/lib/utils/loader-map.js.map +0 -1
- package/lib/utils/usage.js +0 -83
- package/lib/utils/usage.js.map +0 -1
- package/src/utils/deep-equal.ts +0 -57
- package/src/utils/drizzle-graphql.ts +0 -274
- package/src/utils/loader-map.ts +0 -49
- package/src/utils/usage.ts +0 -62
package/README.md
CHANGED
|
@@ -1,32 +1,94 @@
|
|
|
1
|
-
# Drizzle Plugin
|
|
1
|
+
# Drizzle Plugin for Pothos
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> **Note:**
|
|
4
|
+
This plugin uses drizzle's [relational query builder v2](https://orm.drizzle.team/docs/rqb-v2),
|
|
5
|
+
which ships in `drizzle-orm` 1.0. That release is still a release candidate, and npm's `latest`
|
|
6
|
+
tag is the 0.x line, so install drizzle with the `rc` tag. Its API can still change before 1.0 is
|
|
7
|
+
final.
|
|
8
|
+
|
|
9
|
+
If you are upgrading from an older version of this plugin, read drizzle's [relations v1 to v2
|
|
10
|
+
guide](https://orm.drizzle.team/docs/relations-v1-v2), then this package's changelog for the
|
|
11
|
+
Pothos specific changes.
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
The Drizzle plugin defines GraphQL types from your tables and plans database queries from GraphQL
|
|
15
|
+
selections. It loads relations and integrates with Relay nodes and connections.
|
|
16
|
+
|
|
17
|
+
## Getting started
|
|
18
|
+
|
|
19
|
+
Install the plugin and add it to your builder, as described in [Setup](https://pothos-graphql.dev/docs/plugins/drizzle/setup):
|
|
20
|
+
|
|
21
|
+
```package-install
|
|
22
|
+
npm install --save @pothos/plugin-drizzle drizzle-orm@rc
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Define an object type for a table, expose the columns you want in your API, and add the relations
|
|
26
|
+
you want clients to query. This example uses the builder, database client, and `userId` context
|
|
27
|
+
from Setup, with a users table related to posts:
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
const User = builder.drizzleObject('users', {
|
|
31
|
+
name: 'User',
|
|
32
|
+
fields: (t) => ({
|
|
33
|
+
firstName: t.exposeString('firstName'),
|
|
34
|
+
lastName: t.exposeString('lastName'),
|
|
35
|
+
posts: t.relation('posts'),
|
|
36
|
+
}),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
builder.drizzleObject('posts', {
|
|
40
|
+
name: 'Post',
|
|
41
|
+
fields: (t) => ({ title: t.exposeString('title') }),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
builder.queryType({
|
|
45
|
+
fields: (t) => ({
|
|
46
|
+
me: t.drizzleField({
|
|
47
|
+
type: 'users',
|
|
48
|
+
nullable: true,
|
|
49
|
+
resolve: (query, root, args, ctx) =>
|
|
50
|
+
db.query.users.findFirst(query({ where: { id: ctx.userId } })),
|
|
51
|
+
}),
|
|
52
|
+
}),
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Querying `me { firstName posts { title } }` loads the user and their posts in a single query
|
|
57
|
+
through drizzles relational query builder.
|
|
58
|
+
|
|
59
|
+
## Setup
|
|
60
|
+
|
|
61
|
+
### Installing
|
|
4
62
|
|
|
5
63
|
```package-install
|
|
6
|
-
npm install --save @pothos/plugin-drizzle drizzle-orm@
|
|
64
|
+
npm install --save @pothos/plugin-drizzle drizzle-orm@rc
|
|
7
65
|
```
|
|
8
66
|
|
|
9
|
-
The
|
|
10
|
-
|
|
11
|
-
https://rqbv2.drizzle-orm-fe.pages.dev/docs/relations-v2 for detailed documentation on the relations API.
|
|
67
|
+
The Drizzle plugin uses the relational query builder. Define the tables and relations your schema
|
|
68
|
+
will query; see Drizzle’s [relations API](https://orm.drizzle.team/docs/relations-v2).
|
|
12
69
|
|
|
13
70
|
Once you have configured your drizzle schema, you can initialize your Pothos
|
|
14
|
-
SchemaBuilder with the drizzle plugin
|
|
71
|
+
SchemaBuilder with the drizzle plugin. This example uses SQLite through `@libsql/client`;
|
|
72
|
+
install that driver and use the `relations` exported by your application. For another database,
|
|
73
|
+
use its Drizzle driver and matching `getTableConfig` import:
|
|
15
74
|
|
|
16
75
|
```ts
|
|
17
|
-
import {
|
|
76
|
+
import { createClient } from '@libsql/client';
|
|
77
|
+
import { drizzle } from 'drizzle-orm/libsql';
|
|
18
78
|
// Import the appropriate getTableConfig for your dialect
|
|
19
79
|
import { getTableConfig } from 'drizzle-orm/sqlite-core';
|
|
20
80
|
import SchemaBuilder from '@pothos/core';
|
|
21
81
|
import DrizzlePlugin from '@pothos/plugin-drizzle';
|
|
22
82
|
import { relations } from './db/relations';
|
|
23
83
|
|
|
84
|
+
const client = createClient({ url: 'file:./dev.db' });
|
|
24
85
|
const db = drizzle({ client, relations });
|
|
25
86
|
|
|
26
|
-
type DrizzleRelations = typeof relations
|
|
87
|
+
type DrizzleRelations = typeof relations;
|
|
27
88
|
|
|
28
89
|
export interface PothosTypes {
|
|
29
90
|
DrizzleRelations: DrizzleRelations;
|
|
91
|
+
Context: { userId: number };
|
|
30
92
|
}
|
|
31
93
|
|
|
32
94
|
const builder = new SchemaBuilder<PothosTypes>({
|
|
@@ -39,13 +101,17 @@ const builder = new SchemaBuilder<PothosTypes>({
|
|
|
39
101
|
});
|
|
40
102
|
```
|
|
41
103
|
|
|
42
|
-
|
|
104
|
+
The examples use an authenticated request context with `userId: number`. Supply it through your
|
|
105
|
+
GraphQL server; see [Context](https://pothos-graphql.dev/docs/guide/context).
|
|
106
|
+
|
|
107
|
+
#### Integration with other plugins
|
|
43
108
|
|
|
44
109
|
The drizzle plugin has integrations with several other plugins. While the `with-input` and `relay`
|
|
45
110
|
plugins are not required, many examples will assume these plugins have been installed:
|
|
46
111
|
|
|
47
112
|
```ts
|
|
48
|
-
import {
|
|
113
|
+
import { createClient } from '@libsql/client';
|
|
114
|
+
import { drizzle } from 'drizzle-orm/libsql';
|
|
49
115
|
import SchemaBuilder from '@pothos/core';
|
|
50
116
|
import DrizzlePlugin from '@pothos/plugin-drizzle';
|
|
51
117
|
import RelayPlugin from '@pothos/plugin-relay';
|
|
@@ -53,10 +119,12 @@ import WithInputPlugin from '@pothos/plugin-with-input';
|
|
|
53
119
|
import { getTableConfig } from 'drizzle-orm/sqlite-core';
|
|
54
120
|
import { relations } from './db/relations';
|
|
55
121
|
|
|
122
|
+
const client = createClient({ url: 'file:./dev.db' });
|
|
56
123
|
const db = drizzle({ client, relations });
|
|
57
124
|
|
|
58
125
|
export interface PothosTypes {
|
|
59
126
|
DrizzleRelations: typeof relations;
|
|
127
|
+
Context: { userId: number };
|
|
60
128
|
}
|
|
61
129
|
|
|
62
130
|
const builder = new SchemaBuilder<PothosTypes>({
|
|
@@ -69,13 +137,32 @@ const builder = new SchemaBuilder<PothosTypes>({
|
|
|
69
137
|
});
|
|
70
138
|
```
|
|
71
139
|
|
|
72
|
-
|
|
140
|
+
#### Plugin options
|
|
141
|
+
|
|
142
|
+
- `client`: the drizzle client, or a function returning one from the request context.
|
|
143
|
+
- `getTableConfig`: the `getTableConfig` of your dialect, used to read primary keys and unique
|
|
144
|
+
constraints.
|
|
145
|
+
- `relations`: the relations passed to `drizzle()`.
|
|
146
|
+
- `defaultConnectionSize` / `maxConnectionSize`: the page size a connection uses when the query
|
|
147
|
+
does not ask for one (defaults to 20), and the largest size it will accept (defaults to 100).
|
|
148
|
+
Both can also be set per field with `defaultSize` and `maxSize`.
|
|
149
|
+
- `filterConnectionTotalCount`: see [Connection totalCount](https://pothos-graphql.dev/docs/plugins/drizzle/connections#connection-totalcount).
|
|
150
|
+
- `skipDeferredFragments`: selections inside a `@defer` fragment are left out of the planned query
|
|
151
|
+
by default, and the fragment's fields are loaded through [fallback queries](https://pothos-graphql.dev/docs/plugins/drizzle/relations#fallback-queries)
|
|
152
|
+
when it resolves. Set this to `false` to plan deferred selections with the rest of the query.
|
|
153
|
+
|
|
154
|
+
## Drizzle Objects
|
|
155
|
+
|
|
156
|
+
Use the builder and database client from [Setup](https://pothos-graphql.dev/docs/plugins/drizzle/setup). The examples below use a `users` table
|
|
157
|
+
with an integer `id` and string `firstName` and `lastName` columns.
|
|
158
|
+
|
|
159
|
+
### Defining Objects
|
|
73
160
|
|
|
74
161
|
The `builder.drizzleObject` method can be used to define GraphQL Object types based on a drizzle
|
|
75
162
|
table:
|
|
76
163
|
|
|
77
164
|
```ts
|
|
78
|
-
const
|
|
165
|
+
const User = builder.drizzleObject('users', {
|
|
79
166
|
name: 'User',
|
|
80
167
|
fields: (t) => ({
|
|
81
168
|
firstName: t.exposeString('firstName'),
|
|
@@ -85,16 +172,16 @@ const UserRef = builder.drizzleObject('users', {
|
|
|
85
172
|
```
|
|
86
173
|
|
|
87
174
|
You will be able to "expose" any column in the table, and GraphQL fields do not need to match the
|
|
88
|
-
names of the columns in your database. The returned `
|
|
175
|
+
names of the columns in your database. The returned `User` can be used like any other `ObjectRef`
|
|
89
176
|
in Pothos.
|
|
90
177
|
|
|
91
|
-
|
|
178
|
+
### Custom fields
|
|
92
179
|
|
|
93
180
|
You will often want to define fields in your API that do not correspond to a specific database
|
|
94
|
-
column.
|
|
181
|
+
column. For example, replace the object definition above with a computed full name:
|
|
95
182
|
|
|
96
183
|
```ts
|
|
97
|
-
const
|
|
184
|
+
const User = builder.drizzleObject('users', {
|
|
98
185
|
name: 'User',
|
|
99
186
|
fields: (t) => ({
|
|
100
187
|
fullName: t.string({
|
|
@@ -104,81 +191,70 @@ const UserRef = builder.drizzleObject('users', {
|
|
|
104
191
|
});
|
|
105
192
|
```
|
|
106
193
|
|
|
107
|
-
|
|
194
|
+
### Drizzle Fields
|
|
108
195
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
196
|
+
Drizzle objects and relations allow you to define parts of your schema backed by your drizzle
|
|
197
|
+
schema, but don't provide a clear entry point into this Graph of data. To make your drizzle objects
|
|
198
|
+
queryable, we will need to add fields that return our drizzle objects. This can be done using the
|
|
199
|
+
`t.drizzleField` method. This can be used to define fields on the root `Query` type, or any other
|
|
200
|
+
object type in your schema:
|
|
112
201
|
|
|
113
202
|
```ts
|
|
114
|
-
|
|
115
|
-
name: 'User',
|
|
116
|
-
select: {
|
|
117
|
-
columns: {
|
|
118
|
-
firstName: true,
|
|
119
|
-
lastName: true,
|
|
120
|
-
},
|
|
121
|
-
with: {
|
|
122
|
-
profile: true,
|
|
123
|
-
},
|
|
124
|
-
extras: {
|
|
125
|
-
lowercaseName: (users, sql) => sql<string>`lower(${users.firstName})`
|
|
126
|
-
},
|
|
127
|
-
},
|
|
203
|
+
builder.queryType({
|
|
128
204
|
fields: (t) => ({
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
205
|
+
user: t.drizzleField({
|
|
206
|
+
type: 'users',
|
|
207
|
+
nullable: true,
|
|
208
|
+
args: {
|
|
209
|
+
id: t.arg.id({ required: true }),
|
|
210
|
+
},
|
|
211
|
+
resolve: (query, root, args, ctx) =>
|
|
212
|
+
db.query.users.findFirst(
|
|
213
|
+
query({
|
|
214
|
+
where: {
|
|
215
|
+
id: Number.parseInt(args.id, 10),
|
|
216
|
+
},
|
|
217
|
+
}),
|
|
218
|
+
),
|
|
134
219
|
}),
|
|
135
|
-
|
|
136
|
-
|
|
220
|
+
users: t.drizzleField({
|
|
221
|
+
type: ['users'],
|
|
222
|
+
resolve: (query, root, args, ctx) => db.query.users.findMany(query()),
|
|
137
223
|
}),
|
|
138
224
|
}),
|
|
139
225
|
});
|
|
140
226
|
```
|
|
141
227
|
|
|
142
|
-
|
|
143
|
-
|
|
228
|
+
The `resolve` function of a `drizzleField` receives a `query` function. Call it and pass its result
|
|
229
|
+
to a Drizzle `findFirst` or `findMany` query. The `query` function optionally accepts any
|
|
230
|
+
arguments that are normally passed into the query, and will merge these options with the selection
|
|
231
|
+
used to resolve data for the nested GraphQL selections.
|
|
144
232
|
|
|
145
|
-
|
|
233
|
+
#### `drizzleFieldWithInput`
|
|
146
234
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
235
|
+
With the [with-input plugin](https://pothos-graphql.dev/docs/plugins/with-input),
|
|
236
|
+
`t.drizzleFieldWithInput` combines `t.drizzleField` with `t.fieldWithInput`. The `input` fields
|
|
237
|
+
become an input object argument, and the resolver still receives the `query` function as its first
|
|
238
|
+
argument. This replaces the `user` field above:
|
|
150
239
|
|
|
151
240
|
```ts
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}),
|
|
162
|
-
bio: t.string({
|
|
163
|
-
select: {
|
|
164
|
-
with: { profile: true },
|
|
165
|
-
},
|
|
166
|
-
resolve: (user) => user.profile.bio,
|
|
167
|
-
}),
|
|
168
|
-
email: t.string({
|
|
169
|
-
select: {
|
|
170
|
-
extras: {
|
|
171
|
-
lowercaseName: (users, sql) => sql<string>`lower(${users.firstName})`
|
|
172
|
-
},
|
|
173
|
-
},
|
|
174
|
-
resolve: (user) => `${user.lowercaseName}@example.com`,
|
|
175
|
-
}),
|
|
241
|
+
builder.queryFields((t) => ({
|
|
242
|
+
user: t.drizzleFieldWithInput({
|
|
243
|
+
type: 'users',
|
|
244
|
+
nullable: true,
|
|
245
|
+
input: {
|
|
246
|
+
id: t.input.id({ required: true }),
|
|
247
|
+
},
|
|
248
|
+
resolve: (query, root, args, ctx) =>
|
|
249
|
+
db.query.users.findFirst(query({ where: { id: Number.parseInt(args.input.id, 10) } })),
|
|
176
250
|
}),
|
|
177
|
-
});
|
|
251
|
+
}));
|
|
178
252
|
```
|
|
179
253
|
|
|
180
254
|
## Relations
|
|
181
255
|
|
|
256
|
+
### Relations
|
|
257
|
+
|
|
182
258
|
Drizzles relational query builder allows you to define the relationships between your tables. The
|
|
183
259
|
`t.relation` method makes it easy to add fields to your GraphQL API that implement those
|
|
184
260
|
relations:
|
|
@@ -212,7 +288,7 @@ builder.drizzleObject('users', {
|
|
|
212
288
|
The relation will automatically define GraphQL fields of the appropriate type based on the relation
|
|
213
289
|
defined in your drizzle schema.
|
|
214
290
|
|
|
215
|
-
|
|
291
|
+
### Relation queries
|
|
216
292
|
|
|
217
293
|
For some cases, exposing relations as fields without any customization works great, but in some
|
|
218
294
|
cases you may want to apply some filtering or ordering to your relations. This can be done by
|
|
@@ -253,117 +329,42 @@ builder.drizzleObject('users', {
|
|
|
253
329
|
|
|
254
330
|
The query API enables you to define args and convert them into parameters that will be passed into
|
|
255
331
|
the relational query builder. The `query` callback receives `(args, ctx, pathInfo)` where `pathInfo`
|
|
256
|
-
|
|
257
|
-
relation query builder api [here](https://orm.drizzle.team/docs/rqb#querying)
|
|
258
|
-
|
|
259
|
-
## Drizzle Fields
|
|
260
|
-
|
|
261
|
-
Drizzle objects and relations allow you to define parts of your schema backed by your drizzle
|
|
262
|
-
schema, but don't provide a clear entry point into this Graph of data. To make your drizzle objects
|
|
263
|
-
queryable, we will need to add fields that return our drizzle objects. This can be done using the
|
|
264
|
-
`t.drizzleField` method. This can be used to define fields on the root `Query` type, or any other
|
|
265
|
-
object type in your schema:
|
|
266
|
-
|
|
267
|
-
```ts
|
|
268
|
-
builder.queryType({
|
|
269
|
-
fields: (t) => ({
|
|
270
|
-
post: t.drizzleField({
|
|
271
|
-
type: 'posts',
|
|
272
|
-
args: {
|
|
273
|
-
id: t.arg.id({ required: true }),
|
|
274
|
-
},
|
|
275
|
-
resolve: (query, root, args, ctx) =>
|
|
276
|
-
db.query.posts.findFirst(
|
|
277
|
-
query({
|
|
278
|
-
where: {
|
|
279
|
-
id: Number.parseInt(args.id, 10),
|
|
280
|
-
},
|
|
281
|
-
}),
|
|
282
|
-
),
|
|
283
|
-
}),
|
|
284
|
-
posts: t.drizzleField({
|
|
285
|
-
type: ['posts'],
|
|
286
|
-
resolve: (query, root, args, ctx) => db.query.posts.findMany(query()),
|
|
287
|
-
}),
|
|
288
|
-
}),
|
|
289
|
-
});
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
The `resolve` function of a `drizzleField` will be passed a `query` function that MUST be called and
|
|
293
|
-
passed to a drizzle `findOne` or `findMany` query. The `query` function optionally accepts any
|
|
294
|
-
arguments that are normally passed into the query, and will merge these options with the selection
|
|
295
|
-
used to resolve data for the nested GraphQL selections.
|
|
332
|
+
describes where in the GraphQL query the relation is being loaded:
|
|
296
333
|
|
|
297
|
-
|
|
334
|
+
- `path`: a list of `ParentType.fieldName` strings, from the root field down to the field being
|
|
335
|
+
resolved (eg. `['Query.user', 'User.posts']`).
|
|
336
|
+
- `segments`: one object per entry in `path`, with `field` (the field name), `alias` (the alias used
|
|
337
|
+
in the query, or the field name if none), `parentType` (the name of the type the field is defined
|
|
338
|
+
on), and `isList` (whether the field returns a list).
|
|
298
339
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
- A `variant` option that can be passed instead of a name on `drizzleObjects`
|
|
303
|
-
- The ability to pass an `ObjectRef` to the `type` option of `t.relation` and other similar fields
|
|
304
|
-
- A `t.field` method that works similar to `t.relation`, but is used to define a GraphQL field that
|
|
305
|
-
references a variant of the same record.
|
|
340
|
+
You can read more about the relation query builder api
|
|
341
|
+
[here](https://orm.drizzle.team/docs/rqb-v2)
|
|
306
342
|
|
|
307
|
-
|
|
308
|
-
// Viewer type representing the current user
|
|
309
|
-
export const Viewer = builder.drizzleObject('users', {
|
|
310
|
-
variant: 'Viewer',
|
|
311
|
-
select: {},
|
|
312
|
-
fields: (t) => ({
|
|
313
|
-
id: t.exposeID('id'),
|
|
314
|
-
// A reference to the normal user type so normal user fields can be queried
|
|
315
|
-
user: t.variant('users'),
|
|
316
|
-
// Adding drafts to View allows a user to fetch their own drafts without exposing it for Other Users in the API
|
|
317
|
-
drafts: t.relation('posts', {
|
|
318
|
-
query: {
|
|
319
|
-
where: {
|
|
320
|
-
published: false,
|
|
321
|
-
},
|
|
322
|
-
orderBy: {
|
|
323
|
-
updatedAt: 'desc',
|
|
324
|
-
},
|
|
325
|
-
},
|
|
326
|
-
}),
|
|
327
|
-
}),
|
|
328
|
-
});
|
|
343
|
+
### Fallback queries
|
|
329
344
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
me: t.drizzleField({
|
|
333
|
-
// We can use the ref returned by builder.drizzleObject to define our `drizzleField`
|
|
334
|
-
type: Viewer,
|
|
335
|
-
resolve: (query, root, args, ctx) =>
|
|
336
|
-
db.query.users.findFirst(
|
|
337
|
-
query({
|
|
338
|
-
where: {
|
|
339
|
-
id: ctx.user.id,
|
|
340
|
-
},
|
|
341
|
-
}),
|
|
342
|
-
),
|
|
343
|
-
}),
|
|
344
|
-
}),
|
|
345
|
-
});
|
|
345
|
+
A field whose data is not on the row it resolves from is loaded with a fallback query. This happens
|
|
346
|
+
when:
|
|
346
347
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
viewer: t.variant(Viewer, {
|
|
353
|
-
isNull: (user, args, ctx) => user.id !== ctx.user?.id,
|
|
354
|
-
}),
|
|
355
|
-
}),
|
|
356
|
-
});
|
|
357
|
-
```
|
|
348
|
+
- The parent row was not loaded through a `t.drizzleField`, `t.relation`, or connection. This
|
|
349
|
+
covers rows a resolver queried itself, and rows that came from somewhere else entirely.
|
|
350
|
+
- A `drizzleField` resolver did not pass the result of `query()` to drizzle.
|
|
351
|
+
- A relation's arguments conflict with a sibling selection of the same relation that was planned
|
|
352
|
+
first.
|
|
358
353
|
|
|
354
|
+
Fallback queries are batched. Every row of a table that needs the same selection in the same tick
|
|
355
|
+
is loaded with one `findMany` filtered on the primary key, or on the first unique column for a
|
|
356
|
+
table that has no primary key, and the rows are matched back to their parents. If the query does
|
|
357
|
+
not return a row for a parent, because it was deleted since it was loaded, or never came from the
|
|
358
|
+
table, that field rejects with `Model users(1) not found`, where the value in parentheses is the
|
|
359
|
+
key that was looked up.
|
|
359
360
|
|
|
360
|
-
|
|
361
|
+
### Related field
|
|
361
362
|
|
|
362
363
|
The `t.relatedField` method allows you to define a field based on a relation that uses custom
|
|
363
364
|
selections, including aggregations like counts. This is useful when you want to expose derived data
|
|
364
365
|
from a relation without loading the full related records.
|
|
365
366
|
|
|
366
|
-
|
|
367
|
+
#### Count aggregations
|
|
367
368
|
|
|
368
369
|
One common use case is adding a count field that efficiently counts related records:
|
|
369
370
|
|
|
@@ -394,7 +395,31 @@ The `buildFilter` function passed to `select` generates the appropriate SQL filt
|
|
|
394
395
|
relation definition. This is no different than using `t.field`, but the `buildFilter` helper makes
|
|
395
396
|
it easier to filter for the related records.
|
|
396
397
|
|
|
397
|
-
|
|
398
|
+
`t.relatedField` also accepts the normal field options (`description`, `deprecationReason`,
|
|
399
|
+
`extensions`, and options added by other plugins like `authScopes`). Its `resolve` may be async,
|
|
400
|
+
and receives the resolve `info` as its fourth argument.
|
|
401
|
+
|
|
402
|
+
#### SQLite many-to-many filters
|
|
403
|
+
|
|
404
|
+
On SQLite, `buildFilter` can look up related target identities through the junction join when the
|
|
405
|
+
target has a non-null unique key. PostgreSQL retains an `EXISTS` predicate to avoid an additional
|
|
406
|
+
target join.
|
|
407
|
+
|
|
408
|
+
For a custom `RAW` scope in a Drizzle relation definition, use the table supplied to the callback:
|
|
409
|
+
|
|
410
|
+
```ts
|
|
411
|
+
where: {
|
|
412
|
+
RAW: (target) => sql`${target.published} = true`,
|
|
413
|
+
},
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
This lets Drizzle use the target's alias inside the lookup. A static SQL scope referencing the
|
|
417
|
+
original target table, such as ``RAW: sql`${posts.published} = true` ``, is passed through unchanged
|
|
418
|
+
and can still force SQLite to scan the target table. The same applies to a callback that ignores
|
|
419
|
+
its table argument and references `posts` directly. Object filters such as
|
|
420
|
+
`where: { published: true }` also use the supplied alias.
|
|
421
|
+
|
|
422
|
+
### Related count
|
|
398
423
|
|
|
399
424
|
For the common case of counting related records, there's a simpler `t.relatedCount` method that handles
|
|
400
425
|
all the boilerplate for you:
|
|
@@ -426,62 +451,124 @@ publishedPostsCount: t.relatedCount('posts', {
|
|
|
426
451
|
where: (args, ctx) => args.category
|
|
427
452
|
? and(eq(posts.published, true), eq(posts.category, args.category))
|
|
428
453
|
: eq(posts.published, true),
|
|
429
|
-
})
|
|
454
|
+
});
|
|
430
455
|
```
|
|
431
456
|
|
|
432
|
-
|
|
457
|
+
For a many-to-many relation (one defined with `.through(...)`), `t.relatedCount` counts distinct
|
|
458
|
+
related rows, so a row reachable through two junction rows counts once. A `t.relatedConnection`'s
|
|
459
|
+
`totalCount` counts the rows the connection pages over instead, which is one per junction row,
|
|
460
|
+
since that is what the relational query builder returns for the relation.
|
|
433
461
|
|
|
434
|
-
|
|
435
|
-
easy to comply with these best practices, the drizzle plugin has built in support for defining relay
|
|
436
|
-
`nodes` and `connections`.
|
|
462
|
+
## Selections
|
|
437
463
|
|
|
438
|
-
|
|
464
|
+
### Type selections
|
|
439
465
|
|
|
440
|
-
|
|
441
|
-
|
|
466
|
+
By default, a `drizzleObject` gives its resolvers access to all columns of the table. For tables
|
|
467
|
+
with many columns, it can be more efficient to only select the needed columns. You can configure the
|
|
468
|
+
selected columns, and relations by passing a `select` option when defining the type:
|
|
442
469
|
|
|
443
470
|
```ts
|
|
444
|
-
builder.
|
|
471
|
+
const User = builder.drizzleObject('users', {
|
|
445
472
|
name: 'User',
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
473
|
+
select: {
|
|
474
|
+
columns: {
|
|
475
|
+
firstName: true,
|
|
476
|
+
lastName: true,
|
|
477
|
+
},
|
|
478
|
+
with: {
|
|
479
|
+
profile: true,
|
|
480
|
+
},
|
|
481
|
+
extras: {
|
|
482
|
+
lowercaseName: (users, { sql }) => sql<string>`lower(${users.firstName})`
|
|
483
|
+
},
|
|
449
484
|
},
|
|
450
485
|
fields: (t) => ({
|
|
451
|
-
|
|
452
|
-
|
|
486
|
+
fullName: t.string({
|
|
487
|
+
resolve: (user, args, ctx, info) => `${user.firstName} ${user.lastName}`,
|
|
488
|
+
}),
|
|
489
|
+
bio: t.string({
|
|
490
|
+
nullable: true,
|
|
491
|
+
resolve: (user) => user.profile?.bio,
|
|
492
|
+
}),
|
|
493
|
+
lowercaseName: t.string({
|
|
494
|
+
resolve: (user) => user.lowercaseName,
|
|
495
|
+
}),
|
|
453
496
|
}),
|
|
454
497
|
});
|
|
455
498
|
```
|
|
456
499
|
|
|
457
|
-
|
|
500
|
+
Any selections added to the type will be available to consume in all resolvers. Columns that are not
|
|
501
|
+
selected can still be exposed as before.
|
|
458
502
|
|
|
459
|
-
|
|
503
|
+
### Field selections
|
|
460
504
|
|
|
461
|
-
|
|
505
|
+
The previous example allows you to control what gets selected by default, but you often want to only
|
|
506
|
+
select the columns that are required to fulfill a specific field. You can do this by adding the
|
|
507
|
+
appropriate selections on each field. Replace the preceding User definition with:
|
|
462
508
|
|
|
463
509
|
```ts
|
|
464
|
-
builder.
|
|
510
|
+
const User = builder.drizzleObject('users', {
|
|
465
511
|
name: 'User',
|
|
512
|
+
select: {},
|
|
466
513
|
fields: (t) => ({
|
|
467
|
-
|
|
514
|
+
fullName: t.string({
|
|
515
|
+
select: {
|
|
516
|
+
columns: { firstName: true, lastName: true },
|
|
517
|
+
},
|
|
518
|
+
resolve: (user, args, ctx, info) => `${user.firstName} ${user.lastName}`,
|
|
519
|
+
}),
|
|
520
|
+
bio: t.string({
|
|
521
|
+
nullable: true,
|
|
522
|
+
select: {
|
|
523
|
+
with: { profile: true },
|
|
524
|
+
},
|
|
525
|
+
resolve: (user) => user.profile?.bio,
|
|
526
|
+
}),
|
|
527
|
+
lowercaseName: t.string({
|
|
528
|
+
select: {
|
|
529
|
+
extras: {
|
|
530
|
+
lowercaseName: (users, { sql }) => sql<string>`lower(${users.firstName})`
|
|
531
|
+
},
|
|
532
|
+
},
|
|
533
|
+
resolve: (user) => user.lowercaseName,
|
|
534
|
+
}),
|
|
468
535
|
}),
|
|
469
536
|
});
|
|
470
537
|
```
|
|
471
538
|
|
|
472
|
-
|
|
473
|
-
customize the Connection and Edge types, options for these types can be passed as additional
|
|
474
|
-
arguments to `t.relatedConnection`, just like `t.connection` from the relay plugin. See the
|
|
475
|
-
[relay plugin docs](https://pothos-graphql.dev/docs/plugins/relay) for more details.
|
|
539
|
+
## Connections
|
|
476
540
|
|
|
477
|
-
|
|
478
|
-
|
|
541
|
+
Use the [builder setup with Relay](https://pothos-graphql.dev/docs/plugins/drizzle/setup#integration-with-other-plugins) and register the users
|
|
542
|
+
and posts object types. Examples defining the same field or helper are alternatives.
|
|
479
543
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
544
|
+
### Related connections
|
|
545
|
+
|
|
546
|
+
To implement a relation as a connection, you can use `t.relatedConnection` instead of `t.relation`:
|
|
547
|
+
|
|
548
|
+
```ts
|
|
549
|
+
builder.drizzleNode('users', {
|
|
550
|
+
name: 'User',
|
|
551
|
+
id: { column: (user) => user.id },
|
|
552
|
+
fields: (t) => ({
|
|
553
|
+
posts: t.relatedConnection('posts'),
|
|
554
|
+
}),
|
|
555
|
+
});
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
This will automatically define the `Connection`, and `Edge` types, and their respective fields. To
|
|
559
|
+
customize the Connection and Edge types, options for these types can be passed as additional
|
|
560
|
+
arguments to `t.relatedConnection`, just like `t.connection` from the relay plugin. See the
|
|
561
|
+
[relay plugin docs](https://pothos-graphql.dev/docs/plugins/relay) for more details.
|
|
562
|
+
|
|
563
|
+
You can also define a `query` like with `t.relation`. The only difference with `t.relatedConnection`
|
|
564
|
+
is that the `orderBy` format is slightly changed.
|
|
565
|
+
|
|
566
|
+
To comply with the relay spec and efficiently support backwards pagination, some queries need to be
|
|
567
|
+
performed in reverse order, which requires inverting the orderBy clause. To do this automatically,
|
|
568
|
+
the `t.relatedConnection` method accepts orderBy as an object keyed by column name with `'asc'` or
|
|
569
|
+
`'desc'` values, like `{ createdAt: 'desc' }`, rather than using the `asc(column)` and
|
|
570
|
+
`desc(column)` helpers from drizzle. orderBy can also be returned as a single column, or an array of
|
|
571
|
+
columns when ordering by multiple columns, which orders ascending.
|
|
485
572
|
|
|
486
573
|
Ordering defaults to using the table `primaryKey`, and the orderBy columns will also be used to
|
|
487
574
|
derive the connections cursor.
|
|
@@ -489,6 +576,7 @@ derive the connections cursor.
|
|
|
489
576
|
```ts
|
|
490
577
|
builder.drizzleNode('users', {
|
|
491
578
|
name: 'User',
|
|
579
|
+
id: { column: (user) => user.id },
|
|
492
580
|
fields: (t) => ({
|
|
493
581
|
posts: t.relatedConnection('posts', {
|
|
494
582
|
query: () => ({
|
|
@@ -504,13 +592,14 @@ builder.drizzleNode('users', {
|
|
|
504
592
|
});
|
|
505
593
|
```
|
|
506
594
|
|
|
507
|
-
|
|
595
|
+
#### Connection totalCount
|
|
508
596
|
|
|
509
597
|
You can add a `totalCount` field to your connection by setting the `totalCount` option to `true`:
|
|
510
598
|
|
|
511
599
|
```ts
|
|
512
600
|
builder.drizzleNode('users', {
|
|
513
601
|
name: 'User',
|
|
602
|
+
id: { column: (user) => user.id },
|
|
514
603
|
fields: (t) => ({
|
|
515
604
|
posts: t.relatedConnection('posts', {
|
|
516
605
|
totalCount: true,
|
|
@@ -547,7 +636,29 @@ query {
|
|
|
547
636
|
}
|
|
548
637
|
```
|
|
549
638
|
|
|
550
|
-
|
|
639
|
+
The count applies the `where` returned by the field's `query`, so it counts the same rows the
|
|
640
|
+
connection paginates (only published posts in the example above). To count every related row
|
|
641
|
+
regardless of the filter, set `filterConnectionTotalCount: false` in the `drizzle` plugin options:
|
|
642
|
+
|
|
643
|
+
```ts
|
|
644
|
+
const builder = new SchemaBuilder<PothosTypes>({
|
|
645
|
+
plugins: [RelayPlugin, DrizzlePlugin],
|
|
646
|
+
drizzle: {
|
|
647
|
+
client: db,
|
|
648
|
+
getTableConfig,
|
|
649
|
+
relations,
|
|
650
|
+
// count every related row for totalCount, ignoring the where from query (defaults to true)
|
|
651
|
+
filterConnectionTotalCount: false,
|
|
652
|
+
},
|
|
653
|
+
});
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
A `where` on the relation itself always applies to the count, as does the junction table of a
|
|
657
|
+
many-to-many relation defined with `.through(...)`. The count joins the junction table the same way
|
|
658
|
+
the rows do, so a row that matches the junction twice counts twice, and appears twice in the
|
|
659
|
+
connection.
|
|
660
|
+
|
|
661
|
+
### Drizzle connections
|
|
551
662
|
|
|
552
663
|
Similar to `t.drizzleField`, `t.drizzleConnection` allows you to define a connection field that acts
|
|
553
664
|
as an entry point to your drizzle query. The `orderBy` in `t.drizzleConnection` works the same way
|
|
@@ -572,7 +683,7 @@ builder.queryFields((t) => ({
|
|
|
572
683
|
}));
|
|
573
684
|
```
|
|
574
685
|
|
|
575
|
-
|
|
686
|
+
#### drizzleConnection totalCount
|
|
576
687
|
|
|
577
688
|
You can add a `totalCount` field to a `drizzleConnection` by providing a `totalCount` callback function that returns the count:
|
|
578
689
|
|
|
@@ -601,7 +712,7 @@ The `totalCount` callback receives the same arguments as a normal resolver (`par
|
|
|
601
712
|
|
|
602
713
|
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.
|
|
603
714
|
|
|
604
|
-
|
|
715
|
+
#### Indirect relations as connections
|
|
605
716
|
|
|
606
717
|
In many cases, you can define many to many connections via drizzle relations, allowing the `relatedConnection` API to work across
|
|
607
718
|
more complex relations. In some cases you may want to define a connection for a relation not expressed directly as a relation in
|
|
@@ -619,7 +730,7 @@ const Role = builder.drizzleObject('roles', {
|
|
|
619
730
|
|
|
620
731
|
|
|
621
732
|
|
|
622
|
-
// Create connection helpers for the
|
|
733
|
+
// Create connection helpers for the userRoles join table. This will allow you
|
|
623
734
|
// to use the normal t.connection with a drizzle type
|
|
624
735
|
const rolesConnection = drizzleConnectionHelpers(builder, 'userRoles', {
|
|
625
736
|
// select the data needed for the nodes
|
|
@@ -633,10 +744,11 @@ const rolesConnection = drizzleConnectionHelpers(builder, 'userRoles', {
|
|
|
633
744
|
resolveNode: (userRole) => userRole.role,
|
|
634
745
|
});
|
|
635
746
|
|
|
636
|
-
builder.drizzleObjectField('
|
|
747
|
+
builder.drizzleObjectField('users', 'rolesConnection', (t) =>
|
|
637
748
|
t.connection({
|
|
638
749
|
// The type for the Node
|
|
639
750
|
type: Role,
|
|
751
|
+
nodeNullable: true,
|
|
640
752
|
// since we are not using t.relatedConnection we need to manually
|
|
641
753
|
// include the selections for our connection
|
|
642
754
|
select: (args, ctx, nestedSelection) => ({
|
|
@@ -661,7 +773,7 @@ connections share the same model, and pagination happens directly on a relation
|
|
|
661
773
|
if that relation is nested).
|
|
662
774
|
|
|
663
775
|
```ts
|
|
664
|
-
const commentConnectionHelpers = drizzleConnectionHelpers(builder, '
|
|
776
|
+
const commentConnectionHelpers = drizzleConnectionHelpers(builder, 'comments');
|
|
665
777
|
|
|
666
778
|
const SelectPost = builder.drizzleObject('posts', {
|
|
667
779
|
fields: (t) => ({
|
|
@@ -679,7 +791,7 @@ const SelectPost = builder.drizzleObject('posts', {
|
|
|
679
791
|
});
|
|
680
792
|
```
|
|
681
793
|
|
|
682
|
-
|
|
794
|
+
Replace the preceding `rolesConnection` helper and field to add filtering and ordering:
|
|
683
795
|
|
|
684
796
|
```ts
|
|
685
797
|
const rolesConnection = drizzleConnectionHelpers(builder, 'userRoles', {
|
|
@@ -689,7 +801,7 @@ const rolesConnection = drizzleConnectionHelpers(builder, 'userRoles', {
|
|
|
689
801
|
// define an order
|
|
690
802
|
orderBy: {
|
|
691
803
|
roleId: 'asc',
|
|
692
|
-
}
|
|
804
|
+
},
|
|
693
805
|
// define a filter
|
|
694
806
|
where: {
|
|
695
807
|
accepted: true,
|
|
@@ -707,9 +819,10 @@ const rolesConnection = drizzleConnectionHelpers(builder, 'userRoles', {
|
|
|
707
819
|
});
|
|
708
820
|
|
|
709
821
|
|
|
710
|
-
builder.drizzleObjectField('
|
|
822
|
+
builder.drizzleObjectField('users', 'rolesConnection', (t) =>
|
|
711
823
|
t.connection({
|
|
712
824
|
type: Role,
|
|
825
|
+
nodeNullable: true,
|
|
713
826
|
// add the args from the connection helper to the field
|
|
714
827
|
args: rolesConnection.getArgs(),
|
|
715
828
|
select: (args, ctx, nestedSelection) => ({
|
|
@@ -722,9 +835,10 @@ builder.drizzleObjectField('User', 'rolesConnection', (t) =>
|
|
|
722
835
|
);
|
|
723
836
|
```
|
|
724
837
|
|
|
725
|
-
|
|
838
|
+
#### Extending connection edges
|
|
726
839
|
|
|
727
|
-
|
|
840
|
+
This alternative exposes the join row’s `createdAt` on each edge. It assumes a registered
|
|
841
|
+
`DateTime` scalar whose output type is `Date`.
|
|
728
842
|
|
|
729
843
|
```typescript
|
|
730
844
|
const rolesConnection = drizzleConnectionHelpers(builder, 'userRoles', {
|
|
@@ -736,10 +850,11 @@ const rolesConnection = drizzleConnectionHelpers(builder, 'userRoles', {
|
|
|
736
850
|
resolveNode: (userRole) => userRole.role,
|
|
737
851
|
});
|
|
738
852
|
|
|
739
|
-
builder.drizzleObjectFields('
|
|
853
|
+
builder.drizzleObjectFields('users', (t) => ({
|
|
740
854
|
rolesConnection: t.connection(
|
|
741
855
|
{
|
|
742
856
|
type: Role,
|
|
857
|
+
nodeNullable: true,
|
|
743
858
|
select: (args, ctx, nestedSelection) => ({
|
|
744
859
|
with: {
|
|
745
860
|
userRoles: rolesConnection.getQuery(args, ctx, nestedSelection),
|
|
@@ -769,7 +884,7 @@ builder.drizzleObjectFields('User', (t) => ({
|
|
|
769
884
|
}));
|
|
770
885
|
```
|
|
771
886
|
|
|
772
|
-
|
|
887
|
+
#### `drizzleConnectionHelpers` for non-relation connections
|
|
773
888
|
|
|
774
889
|
You can also use `drizzleConnectionHelpers` for non-relation connections where you want a connection where your edges and nodes are not the same type.
|
|
775
890
|
|
|
@@ -788,20 +903,468 @@ const rolesConnection = drizzleConnectionHelpers(builder, 'userRoles', {
|
|
|
788
903
|
builder.queryFields((t) => ({
|
|
789
904
|
roles: t.connection({
|
|
790
905
|
type: Role,
|
|
906
|
+
nodeNullable: true,
|
|
791
907
|
args: {
|
|
792
908
|
userId: t.arg.int({ required: true }),
|
|
793
909
|
},
|
|
794
|
-
nodeNullable: true,
|
|
795
910
|
resolve: async (_, args, ctx, info) => {
|
|
796
911
|
const query = rolesConnection.getQuery(args, ctx, info);
|
|
797
912
|
const userRoles = await db.query.userRoles.findMany({
|
|
798
913
|
...query,
|
|
799
914
|
where: {
|
|
800
|
-
|
|
801
|
-
userId: args.userId,
|
|
915
|
+
AND: [query.where ?? {}, { userId: args.userId }],
|
|
802
916
|
},
|
|
803
917
|
});
|
|
804
918
|
return rolesConnection.resolve(userRoles, args, ctx);
|
|
805
919
|
},
|
|
806
920
|
}),
|
|
807
921
|
}));
|
|
922
|
+
```
|
|
923
|
+
|
|
924
|
+
## Ordering and cursors
|
|
925
|
+
|
|
926
|
+
### Ordering and cursors
|
|
927
|
+
|
|
928
|
+
Connections page with a cursor that records where in the ordering the previous page ended. This
|
|
929
|
+
applies to `t.relatedConnection`, `t.drizzleConnection`, and `drizzleConnectionHelpers`.
|
|
930
|
+
|
|
931
|
+
#### Unique orderings
|
|
932
|
+
|
|
933
|
+
`orderBy: { createdAt: 'desc' }` does not describe a unique ordering. Rows with the same timestamp
|
|
934
|
+
can be returned in a different order on each query, so paging through the connection may return a row
|
|
935
|
+
twice, or skip it.
|
|
936
|
+
|
|
937
|
+
Pothos adds the primary key to the ordering when the columns you provide are not already unique, so
|
|
938
|
+
this:
|
|
939
|
+
|
|
940
|
+
```ts
|
|
941
|
+
orderBy: { createdAt: 'desc' }
|
|
942
|
+
```
|
|
943
|
+
|
|
944
|
+
is queried as `createdAt desc, id desc`. You can add the tie breaker yourself if you want it in a
|
|
945
|
+
different position or direction:
|
|
946
|
+
|
|
947
|
+
```ts
|
|
948
|
+
orderBy: { createdAt: 'desc', id: 'asc' }
|
|
949
|
+
```
|
|
950
|
+
|
|
951
|
+
Orderings that already contain the primary key, or a unique column or constraint whose columns are
|
|
952
|
+
all marked `notNull()`, are used as provided. Nullable unique columns are not treated as unique,
|
|
953
|
+
because rows containing a `null` are still tied with each other. Uniqueness declared with
|
|
954
|
+
`uniqueIndex()` is not detected, so those orderings still get the primary key appended.
|
|
955
|
+
|
|
956
|
+
Primary keys are used whether or not their columns are marked `notNull()`, since SQL makes primary
|
|
957
|
+
key columns non-nullable anyway. That includes composite keys declared with
|
|
958
|
+
`primaryKey({ columns: [...] })`.
|
|
959
|
+
|
|
960
|
+
Nothing is appended when Pothos cannot find a key it can rely on, which means tables with no primary
|
|
961
|
+
key, and tables whose only unique column is nullable. Those connections keep the ordering you wrote,
|
|
962
|
+
so give them a tie breaker yourself.
|
|
963
|
+
|
|
964
|
+
#### Nullable ordering columns
|
|
965
|
+
|
|
966
|
+
A row whose ordering value is `null` cannot be paged past. `null` is not greater or less than
|
|
967
|
+
anything in SQL, so no row compares as coming after it and the next page comes back empty. The same
|
|
968
|
+
applies to an ordering expression that can evaluate to `null`.
|
|
969
|
+
|
|
970
|
+
Order by columns that are `notNull()`, or give the expression a real value to fall back to with
|
|
971
|
+
`coalesce`.
|
|
972
|
+
|
|
973
|
+
#### Cursor values and precision
|
|
974
|
+
|
|
975
|
+
A cursor stores the values your driver returned to JavaScript, and those values are compared against
|
|
976
|
+
the column when the next page is requested. If the value in the cursor is not the value the database
|
|
977
|
+
has, the comparison will not match the rows you expect.
|
|
978
|
+
|
|
979
|
+
This comes up with timestamps. `timestamp({ mode: 'date' })` returns a JavaScript `Date`, which only
|
|
980
|
+
holds milliseconds, while a Postgres `timestamptz` column stores microseconds. A row stored at
|
|
981
|
+
`.086068` produces a cursor containing `.086`, and `created_at < '.086'` does not match the other
|
|
982
|
+
rows in that millisecond, so they are never returned on any page.
|
|
983
|
+
|
|
984
|
+
There are two ways to avoid this.
|
|
985
|
+
|
|
986
|
+
The first is to map the column so the full value reaches JavaScript. `mode: 'string'` returns the
|
|
987
|
+
timestamp as text, with the microseconds intact:
|
|
988
|
+
|
|
989
|
+
```ts
|
|
990
|
+
export const posts = pgTable('posts', {
|
|
991
|
+
id: serial('id').primaryKey(),
|
|
992
|
+
createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }).notNull(),
|
|
993
|
+
});
|
|
994
|
+
```
|
|
995
|
+
|
|
996
|
+
The cursor stores that text, and Postgres parses it back to the value it stored. The column is still
|
|
997
|
+
what gets ordered and compared, so an index on it is still used. The field is typed as a `string`
|
|
998
|
+
rather than a `Date`.
|
|
999
|
+
|
|
1000
|
+
The second is to order by an expression that returns the full value, which lets you keep the `Date`
|
|
1001
|
+
mapping:
|
|
1002
|
+
|
|
1003
|
+
```ts
|
|
1004
|
+
builder.queryFields((t) => ({
|
|
1005
|
+
posts: t.drizzleConnection({
|
|
1006
|
+
type: 'posts',
|
|
1007
|
+
resolve: (query) =>
|
|
1008
|
+
db.query.posts.findMany(
|
|
1009
|
+
query({
|
|
1010
|
+
extras: {
|
|
1011
|
+
createdAtExact: (table) =>
|
|
1012
|
+
sql`to_char(${table.createdAt} AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"')`,
|
|
1013
|
+
},
|
|
1014
|
+
orderBy: { createdAtExact: 'desc' },
|
|
1015
|
+
}),
|
|
1016
|
+
),
|
|
1017
|
+
}),
|
|
1018
|
+
}));
|
|
1019
|
+
```
|
|
1020
|
+
|
|
1021
|
+
#### Ordering by an expression
|
|
1022
|
+
|
|
1023
|
+
`orderBy` accepts the name of any extra declared in the same query. Pothos orders by the expression,
|
|
1024
|
+
builds the cursor from the value it returns, and compares the expression when paging:
|
|
1025
|
+
|
|
1026
|
+
```ts
|
|
1027
|
+
query({
|
|
1028
|
+
extras: { titleLength: (table) => sql`length(${table.title})` },
|
|
1029
|
+
orderBy: { titleLength: 'asc' },
|
|
1030
|
+
})
|
|
1031
|
+
```
|
|
1032
|
+
|
|
1033
|
+
Extras used this way should be written as a callback. Drizzle aliases the table it queries and passes
|
|
1034
|
+
that alias to the callback, so an expression built from the imported table object will reference a
|
|
1035
|
+
name the query does not have.
|
|
1036
|
+
|
|
1037
|
+
With `drizzleConnectionHelpers`, declare the extra in `query` rather than in `select`. Only `query`
|
|
1038
|
+
is read when the ordering is resolved.
|
|
1039
|
+
|
|
1040
|
+
The expression also needs to sort in the same order its values compare. A timestamp formatted as
|
|
1041
|
+
fixed width UTC text sorts the same way it does chronologically, but a local time or variable width
|
|
1042
|
+
format does not, and will skip rows.
|
|
1043
|
+
|
|
1044
|
+
Ordering by an expression cannot use an index on the underlying column. If the table is large enough
|
|
1045
|
+
to need one, add an index on the expression.
|
|
1046
|
+
|
|
1047
|
+
## Relay
|
|
1048
|
+
|
|
1049
|
+
### Relay integration
|
|
1050
|
+
|
|
1051
|
+
Relay provides some very useful best practices that are useful for most GraphQL APIs. To make it
|
|
1052
|
+
easy to comply with these best practices, the drizzle plugin has built in support for defining relay
|
|
1053
|
+
`nodes` and `connections`.
|
|
1054
|
+
|
|
1055
|
+
### Relay Nodes
|
|
1056
|
+
|
|
1057
|
+
Defining relay nodes works just like defining normal `drizzleObject`s, but requires specifying a
|
|
1058
|
+
column to use as the node's `id` field.
|
|
1059
|
+
|
|
1060
|
+
```ts
|
|
1061
|
+
builder.drizzleNode('users', {
|
|
1062
|
+
name: 'User',
|
|
1063
|
+
id: {
|
|
1064
|
+
column: (user) => user.id,
|
|
1065
|
+
// other options for the ID field can be passed here
|
|
1066
|
+
},
|
|
1067
|
+
fields: (t) => ({
|
|
1068
|
+
firstName: t.exposeString('firstName'),
|
|
1069
|
+
lastName: t.exposeString('lastName'),
|
|
1070
|
+
}),
|
|
1071
|
+
});
|
|
1072
|
+
```
|
|
1073
|
+
|
|
1074
|
+
The id column can also be set to a list of columns for types with a composite primary key.
|
|
1075
|
+
|
|
1076
|
+
## Type variants
|
|
1077
|
+
|
|
1078
|
+
Use the [builder setup with Relay](https://pothos-graphql.dev/docs/plugins/drizzle/setup#integration-with-other-plugins), and include
|
|
1079
|
+
`Context: { user: { id: number } }` in `PothosTypes`. These examples assume an authenticated user
|
|
1080
|
+
and a registered posts type. The `users` table has a posts relation.
|
|
1081
|
+
|
|
1082
|
+
### Variants
|
|
1083
|
+
|
|
1084
|
+
It is often useful to be able to define multiple object types based on the same table. This can be
|
|
1085
|
+
done using a feature called `variants`. The `variants` API consists of 3 parts:
|
|
1086
|
+
|
|
1087
|
+
- A `variant` option that can be passed instead of a name on `drizzleObjects`
|
|
1088
|
+
- The ability to pass an `ObjectRef` to the `type` option of `t.relation` and other similar fields
|
|
1089
|
+
- A `t.variant` method that works similar to `t.relation`, but is used to define a GraphQL field that
|
|
1090
|
+
references a variant of the same record.
|
|
1091
|
+
|
|
1092
|
+
```ts
|
|
1093
|
+
// Viewer type representing the current user
|
|
1094
|
+
export const Viewer = builder.drizzleObject('users', {
|
|
1095
|
+
variant: 'Viewer',
|
|
1096
|
+
select: {},
|
|
1097
|
+
fields: (t) => ({
|
|
1098
|
+
id: t.exposeID('id'),
|
|
1099
|
+
// A reference to the normal user type so normal user fields can be queried
|
|
1100
|
+
user: t.variant('users'),
|
|
1101
|
+
// Adding drafts to View allows a user to fetch their own drafts without exposing it for Other Users in the API
|
|
1102
|
+
drafts: t.relation('posts', {
|
|
1103
|
+
query: {
|
|
1104
|
+
where: {
|
|
1105
|
+
published: false,
|
|
1106
|
+
},
|
|
1107
|
+
orderBy: {
|
|
1108
|
+
updatedAt: 'desc',
|
|
1109
|
+
},
|
|
1110
|
+
},
|
|
1111
|
+
}),
|
|
1112
|
+
}),
|
|
1113
|
+
});
|
|
1114
|
+
|
|
1115
|
+
builder.queryType({
|
|
1116
|
+
fields: (t) => ({
|
|
1117
|
+
me: t.drizzleField({
|
|
1118
|
+
// We can use the ref returned by builder.drizzleObject to define our `drizzleField`
|
|
1119
|
+
type: Viewer,
|
|
1120
|
+
resolve: (query, root, args, ctx) =>
|
|
1121
|
+
db.query.users.findFirst(
|
|
1122
|
+
query({
|
|
1123
|
+
where: {
|
|
1124
|
+
id: ctx.user.id,
|
|
1125
|
+
},
|
|
1126
|
+
}),
|
|
1127
|
+
),
|
|
1128
|
+
}),
|
|
1129
|
+
}),
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
builder.drizzleNode('users', {
|
|
1133
|
+
name: 'User',
|
|
1134
|
+
id: { column: (user) => user.id },
|
|
1135
|
+
fields: (t) => ({
|
|
1136
|
+
firstName: t.exposeString('firstName'),
|
|
1137
|
+
// This field will resolve to the Viewer type, but be set to null if the user is not the current user
|
|
1138
|
+
viewer: t.variant(Viewer, {
|
|
1139
|
+
isNull: (user, args, ctx) => user.id !== ctx.user?.id,
|
|
1140
|
+
}),
|
|
1141
|
+
}),
|
|
1142
|
+
});
|
|
1143
|
+
```
|
|
1144
|
+
|
|
1145
|
+
As an alternative to the User node definition above, a `t.variant` field can have a `select` of its own. It is planned along with the variant's
|
|
1146
|
+
type-level selection when the variant is queried through that field:
|
|
1147
|
+
|
|
1148
|
+
```ts
|
|
1149
|
+
builder.drizzleNode('users', {
|
|
1150
|
+
name: 'User',
|
|
1151
|
+
id: { column: (user) => user.id },
|
|
1152
|
+
fields: (t) => ({
|
|
1153
|
+
viewer: t.variant(Viewer, {
|
|
1154
|
+
// loaded with the row when `viewer` is selected
|
|
1155
|
+
select: { columns: { email: true } },
|
|
1156
|
+
isNull: (user, args, ctx) => user.id !== ctx.user?.id,
|
|
1157
|
+
}),
|
|
1158
|
+
}),
|
|
1159
|
+
});
|
|
1160
|
+
```
|
|
1161
|
+
|
|
1162
|
+
Two variants of one table selected for the same row have their type-level selections merged into a
|
|
1163
|
+
single query, which can fail if they disagree. See
|
|
1164
|
+
[Conflicting selections between variants](https://pothos-graphql.dev/docs/plugins/drizzle/query-planning#conflicting-selections-between-variants).
|
|
1165
|
+
|
|
1166
|
+
## Interfaces
|
|
1167
|
+
|
|
1168
|
+
### Interfaces
|
|
1169
|
+
|
|
1170
|
+
`builder.drizzleInterface` works just like `builder.drizzleObject`, and can be used to define
|
|
1171
|
+
either the primary type or a variant of a table as an interface that other variants implement. The
|
|
1172
|
+
interface's `select` is planned whenever a field returns the interface, and an implementation's own
|
|
1173
|
+
`select` is planned when a fragment narrows to it. Selections are not inherited, so an
|
|
1174
|
+
implementation that exposes more columns will need a `select` of its own.
|
|
1175
|
+
|
|
1176
|
+
```ts
|
|
1177
|
+
export const Viewer = builder.drizzleInterface('users', {
|
|
1178
|
+
variant: 'Viewer',
|
|
1179
|
+
select: { columns: { id: true, role: true } },
|
|
1180
|
+
resolveType: (user) => (user.role === 'admin' ? 'AdminViewer' : 'MemberViewer'),
|
|
1181
|
+
fields: (t) => ({
|
|
1182
|
+
id: t.exposeID('id'),
|
|
1183
|
+
user: t.variant('users'),
|
|
1184
|
+
}),
|
|
1185
|
+
});
|
|
1186
|
+
|
|
1187
|
+
builder.drizzleObject('users', {
|
|
1188
|
+
variant: 'AdminViewer',
|
|
1189
|
+
interfaces: [Viewer],
|
|
1190
|
+
select: { columns: { permissions: true } },
|
|
1191
|
+
fields: (t) => ({
|
|
1192
|
+
permissions: t.exposeStringList('permissions'),
|
|
1193
|
+
}),
|
|
1194
|
+
});
|
|
1195
|
+
|
|
1196
|
+
builder.drizzleObject('users', {
|
|
1197
|
+
variant: 'MemberViewer',
|
|
1198
|
+
interfaces: [Viewer],
|
|
1199
|
+
select: { columns: { id: true } },
|
|
1200
|
+
});
|
|
1201
|
+
```
|
|
1202
|
+
|
|
1203
|
+
Fields can be added to an interface later with `builder.drizzleInterfaceField` and
|
|
1204
|
+
`builder.drizzleInterfaceFields`, which take the interface ref (or the table name) like their
|
|
1205
|
+
`drizzleObjectField(s)` counterparts:
|
|
1206
|
+
|
|
1207
|
+
```ts
|
|
1208
|
+
builder.drizzleInterfaceFields(Viewer, (t) => ({
|
|
1209
|
+
posts: t.relatedConnection('posts'),
|
|
1210
|
+
}));
|
|
1211
|
+
```
|
|
1212
|
+
|
|
1213
|
+
An object type implementing a drizzle interface must be based on the same table. A plain object
|
|
1214
|
+
type that implements one is planned with the interface's table, so fragments on it will select the
|
|
1215
|
+
relations it inherits.
|
|
1216
|
+
|
|
1217
|
+
## Query planning
|
|
1218
|
+
|
|
1219
|
+
This page describes how the plugin turns a GraphQL query into drizzle queries, which is worth
|
|
1220
|
+
knowing when a schema issues more queries than you expect.
|
|
1221
|
+
|
|
1222
|
+
### How fields get their data
|
|
1223
|
+
|
|
1224
|
+
A field either reads its data from a row that has already been loaded, or runs a query of its own.
|
|
1225
|
+
|
|
1226
|
+
A field's `select` is planned into the query of the nearest ancestor that runs one: a
|
|
1227
|
+
`t.drizzleField`, a `t.relation`, a connection, or a [fallback query](https://pothos-graphql.dev/docs/plugins/drizzle/relations#fallback-queries). The field
|
|
1228
|
+
then reads what it needs off the loaded row, without a query of its own. A field runs its own query
|
|
1229
|
+
when its `resolve` queries drizzle directly, and when the plugin issues a fallback query for a
|
|
1230
|
+
relation that is missing from the row.
|
|
1231
|
+
|
|
1232
|
+
A field can do both. A `t.drizzleField`, or any other field with a `select`, nested under one of
|
|
1233
|
+
those ancestors has its `select` planned into the parent's row, and still runs its own query when
|
|
1234
|
+
it resolves.
|
|
1235
|
+
|
|
1236
|
+
A field-level `select` is merged into the same query as its siblings and the type-level selection,
|
|
1237
|
+
rather than being kept separate for that field. Two selections of the same relation share a place
|
|
1238
|
+
in that query only when their arguments (`where`, `orderBy`, `limit`, ...) match. When they differ,
|
|
1239
|
+
the first one planned wins, and the other is loaded with a query of its own. A type-level `select`
|
|
1240
|
+
is planned before any field's selection, no matter where they appear in the document, and fields
|
|
1241
|
+
are planned in the order they are selected.
|
|
1242
|
+
|
|
1243
|
+
### Nested selections
|
|
1244
|
+
|
|
1245
|
+
A `select` function receives `nestedSelection`, which plans the selection beneath the field for the
|
|
1246
|
+
field's own type. It returns the query config for that table, or a `many` config for a list field.
|
|
1247
|
+
Any keys you pass in are kept as they were given, so `columns` passed to `nestedSelection` will
|
|
1248
|
+
still narrow the parent shape:
|
|
1249
|
+
|
|
1250
|
+
```ts
|
|
1251
|
+
builder.drizzleObject('users', {
|
|
1252
|
+
name: 'User',
|
|
1253
|
+
fields: (t) => ({
|
|
1254
|
+
previewPosts: t.field({
|
|
1255
|
+
type: [Post],
|
|
1256
|
+
select: (args, ctx, nestedSelection) => ({
|
|
1257
|
+
// what the query selects on the posts, limited to one
|
|
1258
|
+
with: { posts: nestedSelection({ limit: 1 }) },
|
|
1259
|
+
}),
|
|
1260
|
+
resolve: (user) => user.posts,
|
|
1261
|
+
}),
|
|
1262
|
+
}),
|
|
1263
|
+
});
|
|
1264
|
+
```
|
|
1265
|
+
|
|
1266
|
+
`nestedSelection(query, path, type)` takes two more arguments:
|
|
1267
|
+
|
|
1268
|
+
- `path`: a list of field names, for selecting a field nested under the field's type. A segment can
|
|
1269
|
+
also be written as `{ name, type }` to pin the implementation the field must be found under.
|
|
1270
|
+
- `type`: a member of an interface or union, to read the selection as that type.
|
|
1271
|
+
|
|
1272
|
+
The `nestedSelection` function also carries the `path` and `segments` of the field being planned,
|
|
1273
|
+
which are described under [Relation queries](https://pothos-graphql.dev/docs/plugins/drizzle/relations#relation-queries).
|
|
1274
|
+
|
|
1275
|
+
### Async selections
|
|
1276
|
+
|
|
1277
|
+
Selections are synchronous unless the schema opts in with `AsyncSelections: true`. This example
|
|
1278
|
+
uses request context methods that asynchronously return a user ID and a preview limit:
|
|
1279
|
+
|
|
1280
|
+
```ts
|
|
1281
|
+
const builder = new SchemaBuilder<{
|
|
1282
|
+
DrizzleRelations: typeof relations;
|
|
1283
|
+
AsyncSelections: true;
|
|
1284
|
+
Context: {
|
|
1285
|
+
currentUserId: () => Promise<number>;
|
|
1286
|
+
previewSize: () => Promise<number>;
|
|
1287
|
+
};
|
|
1288
|
+
}>({
|
|
1289
|
+
plugins: [DrizzlePlugin],
|
|
1290
|
+
drizzle: {
|
|
1291
|
+
client: db,
|
|
1292
|
+
getTableConfig,
|
|
1293
|
+
relations,
|
|
1294
|
+
},
|
|
1295
|
+
});
|
|
1296
|
+
```
|
|
1297
|
+
|
|
1298
|
+
With the opt-in, `select` functions, relation `query` callbacks, `relatedCount` `where` callbacks,
|
|
1299
|
+
and the `select` and `query` callbacks of `drizzleConnectionHelpers` may be async. Without it they
|
|
1300
|
+
are typed as synchronous, and an async callback is a type error.
|
|
1301
|
+
|
|
1302
|
+
The plugin still builds a single query. It waits for the callbacks, and merges what they return
|
|
1303
|
+
after every synchronous selection, in document order. `t.relation`, `t.relatedCount`,
|
|
1304
|
+
`t.drizzleField`, `t.drizzleConnection` and `t.relatedConnection` settle their plan before the
|
|
1305
|
+
resolver runs, and need no changes.
|
|
1306
|
+
|
|
1307
|
+
```ts
|
|
1308
|
+
builder.drizzleObject('users', {
|
|
1309
|
+
name: 'User',
|
|
1310
|
+
fields: (t) => ({
|
|
1311
|
+
posts: t.relation('posts', {
|
|
1312
|
+
query: async (args, ctx) => ({ where: { authorId: await ctx.currentUserId() } }),
|
|
1313
|
+
}),
|
|
1314
|
+
previewPosts: t.field({
|
|
1315
|
+
type: [Post],
|
|
1316
|
+
select: async (args, ctx, nestedSelection) => ({
|
|
1317
|
+
with: { posts: await nestedSelection({ limit: await ctx.previewSize() }) },
|
|
1318
|
+
}),
|
|
1319
|
+
resolve: (user) => user.posts,
|
|
1320
|
+
}),
|
|
1321
|
+
}),
|
|
1322
|
+
});
|
|
1323
|
+
```
|
|
1324
|
+
|
|
1325
|
+
`await` what `nestedSelection` returns before putting it in the selection, and the same for the
|
|
1326
|
+
`nestedQuery` passed as the fourth argument of a `t.relatedField` `select`. A selection that
|
|
1327
|
+
contains the promise itself will throw, and so will a `select` that returns while a nested
|
|
1328
|
+
selection it started is still pending. Calling `nestedSelection` and discarding a synchronous
|
|
1329
|
+
result is not detected, and the nested selection will not be loaded with the parent.
|
|
1330
|
+
|
|
1331
|
+
Pass `awaitSelections: true` to `drizzleConnectionHelpers(...).getQuery`, and `await` the query it
|
|
1332
|
+
returns. Without it, an async selection beneath the field throws, and whether there is one
|
|
1333
|
+
depends on the incoming document rather than on the callback you wrote. A connection helper also
|
|
1334
|
+
throws when its own `select` or `query` is async, whatever the document asked for:
|
|
1335
|
+
|
|
1336
|
+
```ts
|
|
1337
|
+
select: async (args, ctx, nestedSelection) => ({
|
|
1338
|
+
with: {
|
|
1339
|
+
comments: await commentConnectionHelpers.getQuery(args, ctx, nestedSelection, {
|
|
1340
|
+
awaitSelections: true,
|
|
1341
|
+
}),
|
|
1342
|
+
},
|
|
1343
|
+
}),
|
|
1344
|
+
```
|
|
1345
|
+
|
|
1346
|
+
`awaitSelections` is a per-call option, and is available whether or not the schema sets
|
|
1347
|
+
`AsyncSelections`.
|
|
1348
|
+
|
|
1349
|
+
When `AsyncSelections: true`, also `await` a connection helper's `resolve()` before inspecting or
|
|
1350
|
+
spreading its result: an async `query` callback makes resolution asynchronous. Returning its
|
|
1351
|
+
result directly from a GraphQL resolver remains supported.
|
|
1352
|
+
|
|
1353
|
+
### Conflicting selections between variants
|
|
1354
|
+
|
|
1355
|
+
When a query selects two variants of one table for the same row, either with a fragment on each
|
|
1356
|
+
under one field, or through a `t.variant` field, the plugin will throw a `PothosValidationError` if
|
|
1357
|
+
their `select` options ask for the same relation with different arguments:
|
|
1358
|
+
|
|
1359
|
+
```
|
|
1360
|
+
PothosValidationError: Type-level selections of Viewer and Admin conflict on relation "posts".
|
|
1361
|
+
Move the relation arguments to a field-level select on one of the types.
|
|
1362
|
+
```
|
|
1363
|
+
|
|
1364
|
+
The same applies to two variants defining the same `extras` key with different functions
|
|
1365
|
+
(`... conflict on extra "lowercaseName"`). Both variants describe one row, so their `select`
|
|
1366
|
+
options are merged into a single query.
|
|
1367
|
+
|
|
1368
|
+
To fix this, move the relation with its arguments, or the extra, into the `select` of the field
|
|
1369
|
+
that needs it on one of the variants. A field-level selection that conflicts with what the row
|
|
1370
|
+
already holds falls back to a query for that field, rather than failing the request.
|