@auth/dgraph-adapter 1.0.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/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@auth/dgraph-adapter",
3
+ "version": "1.0.0",
4
+ "description": "Dgraph adapter for Auth.js",
5
+ "homepage": "https://authjs.dev",
6
+ "repository": "https://github.com/nextauthjs/next-auth",
7
+ "bugs": {
8
+ "url": "https://github.com/nextauthjs/next-auth/issues"
9
+ },
10
+ "author": "Arnaud Derbey <arnaud@derbey.dev>",
11
+ "contributors": [
12
+ "Balázs Orbán <info@balazsorban.com>"
13
+ ],
14
+ "type": "module",
15
+ "types": "./index.d.ts",
16
+ "files": [
17
+ "*.js",
18
+ "*.d.ts*",
19
+ "lib",
20
+ "src"
21
+ ],
22
+ "exports": {
23
+ ".": {
24
+ "types": "./index.d.ts",
25
+ "import": "./index.js"
26
+ }
27
+ },
28
+ "license": "ISC",
29
+ "keywords": [
30
+ "next-auth",
31
+ "next.js",
32
+ "dgraph",
33
+ "graphql"
34
+ ],
35
+ "private": false,
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "devDependencies": {
40
+ "@types/jest": "^26.0.24",
41
+ "@types/jsonwebtoken": "^8.5.5",
42
+ "@types/node-fetch": "^2.5.11",
43
+ "jest": "^27.4.3",
44
+ "ts-jest": "^27.0.3",
45
+ "undici": "5.22.1",
46
+ "@next-auth/adapter-test": "0.0.0",
47
+ "@next-auth/tsconfig": "0.0.0"
48
+ },
49
+ "dependencies": {
50
+ "jsonwebtoken": "^8.5.1",
51
+ "@auth/core": "0.8.2"
52
+ },
53
+ "jest": {
54
+ "preset": "@next-auth/adapter-test/jest"
55
+ },
56
+ "scripts": {
57
+ "build": "tsc",
58
+ "test": "./tests/test.sh"
59
+ }
60
+ }
package/src/index.ts ADDED
@@ -0,0 +1,570 @@
1
+ /**
2
+ * <div style={{display: "flex", justifyContent: "space-between", alignItems: "center", padding: 16}}>
3
+ * <p style={{fontWeight: "normal"}}>Official <a href="https://dgraph.io/docs">Dgraph</a> adapter for Auth.js / NextAuth.js.</p>
4
+ * <a href="https://dgraph.io/">
5
+ * <img style={{display: "block"}} src="https://authjs.dev/img/adapters/dgraph.svg" width="100"/>
6
+ * </a>
7
+ * </div>
8
+ *
9
+ * ## Installation
10
+ *
11
+ * ```bash npm2yarn2pnpm
12
+ * npm install next-auth @auth/dgraph-adapter
13
+ * ```
14
+ *
15
+ * @module @auth/dgraph-adapter
16
+ */
17
+ import { client as dgraphClient } from "./lib/client"
18
+ import { format } from "./lib/utils"
19
+ import type { Adapter } from "@auth/core/adapters"
20
+ import type { DgraphClientParams } from "./lib/client"
21
+ import * as defaultFragments from "./lib/graphql/fragments"
22
+
23
+ export type { DgraphClientParams, DgraphClientError } from "./lib/client"
24
+
25
+ /** This is the interface of the Dgraph adapter options. */
26
+ export interface DgraphAdapterOptions {
27
+ /**
28
+ * The GraphQL {@link https://dgraph.io/docs/query-language/fragments/ Fragments} you can supply to the adapter
29
+ * to define how the shapes of the `user`, `account`, `session`, `verificationToken` entities look.
30
+ *
31
+ * By default the adapter will uses the [default defined fragments](https://github.com/nextauthjs/next-auth/blob/main/packages/adapter-dgraph/src/lib/graphql/fragments.ts)
32
+ * , this config option allows to extend them.
33
+ */
34
+ fragments?: {
35
+ User?: string
36
+ Account?: string
37
+ Session?: string
38
+ VerificationToken?: string
39
+ }
40
+ }
41
+
42
+ export { format }
43
+
44
+ /**
45
+ * ## Setup
46
+ *
47
+ * Add this adapter to your `pages/api/[...nextauth].js` next-auth configuration object:
48
+ *
49
+ * ```ts title="pages/api/auth/[...nextauth].js"
50
+ * import NextAuth from "next-auth"
51
+ * import { DgraphAdapter } from "@auth/dgraph-adapter"
52
+ *
53
+ * export default NextAuth({
54
+ * providers: [],
55
+ * adapter: DgraphAdapter({
56
+ * endpoint: process.env.DGRAPH_GRAPHQL_ENDPOINT,
57
+ * authToken: process.env.DGRAPH_GRAPHQL_KEY,
58
+ * // you can omit the following properties if you are running an unsecure schema
59
+ * authHeader: process.env.AUTH_HEADER, // default: "Authorization",
60
+ * jwtSecret: process.env.SECRET,
61
+ * }),
62
+ * })
63
+ * ```
64
+ *
65
+ * ### Unsecure schema
66
+ *
67
+ * The quickest way to use Dgraph is by applying the unsecure schema to your [local](https://dgraph.io/docs/graphql/admin/#modifying-a-schema) Dgraph instance or if using Dgraph [cloud](https://dgraph.io/docs/cloud/cloud-quick-start/#the-schema) you can paste the schema in the codebox to update.
68
+ *
69
+ * :::warning
70
+ * This approach is not secure or for production use, and does not require a `jwtSecret`.
71
+ * :::
72
+ *
73
+ * > This schema is adapted for use in Dgraph and based upon our main [schema](https://authjs.dev/reference/adapters#models)
74
+ *
75
+ * #### Example
76
+ *
77
+ *```graphql
78
+ * type Account {
79
+ * id: ID
80
+ * type: String
81
+ * provider: String @search(by: [hash])
82
+ * providerAccountId: String @search(by: [hash])
83
+ * refreshToken: String
84
+ * expires_at: Int64
85
+ * accessToken: String
86
+ * token_type: String
87
+ * refresh_token: String
88
+ * access_token: String
89
+ * scope: String
90
+ * id_token: String
91
+ * session_state: String
92
+ * user: User @hasInverse(field: "accounts")
93
+ * }
94
+ * type Session {
95
+ * id: ID
96
+ * expires: DateTime
97
+ * sessionToken: String @search(by: [hash])
98
+ * user: User @hasInverse(field: "sessions")
99
+ * }
100
+ * type User {
101
+ * id: ID
102
+ * name: String
103
+ * email: String @search(by: [hash])
104
+ * emailVerified: DateTime
105
+ * image: String
106
+ * accounts: [Account] @hasInverse(field: "user")
107
+ * sessions: [Session] @hasInverse(field: "user")
108
+ * }
109
+ *
110
+ * type VerificationToken {
111
+ * id: ID
112
+ * identifier: String @search(by: [hash])
113
+ * token: String @search(by: [hash])
114
+ * expires: DateTime
115
+ * }
116
+ *```
117
+ *
118
+ * ### Secure schema
119
+ *
120
+ * For production deployments you will want to restrict the access to the types used
121
+ * by next-auth. The main form of access control used in Dgraph is via `@auth` directive alongside types in the schema.
122
+ * #### Example
123
+ *
124
+ * ```graphql
125
+ * type Account
126
+ * @auth(
127
+ * delete: { rule: "{$nextAuth: { eq: true } }" }
128
+ * add: { rule: "{$nextAuth: { eq: true } }" }
129
+ * query: { rule: "{$nextAuth: { eq: true } }" }
130
+ * update: { rule: "{$nextAuth: { eq: true } }" }
131
+ * ) {
132
+ * id: ID
133
+ * type: String
134
+ * provider: String @search(by: [hash])
135
+ * providerAccountId: String @search(by: [hash])
136
+ * refreshToken: String
137
+ * expires_at: Int64
138
+ * accessToken: String
139
+ * token_type: String
140
+ * refresh_token: String
141
+ * access_token: String
142
+ * scope: String
143
+ * id_token: String
144
+ * session_state: String
145
+ * user: User @hasInverse(field: "accounts")
146
+ * }
147
+ * type Session
148
+ * @auth(
149
+ * delete: { rule: "{$nextAuth: { eq: true } }" }
150
+ * add: { rule: "{$nextAuth: { eq: true } }" }
151
+ * query: { rule: "{$nextAuth: { eq: true } }" }
152
+ * update: { rule: "{$nextAuth: { eq: true } }" }
153
+ * ) {
154
+ * id: ID
155
+ * expires: DateTime
156
+ * sessionToken: String @search(by: [hash])
157
+ * user: User @hasInverse(field: "sessions")
158
+ * }
159
+ * type User
160
+ * @auth(
161
+ * query: {
162
+ * or: [
163
+ * {
164
+ * rule: """
165
+ * query ($userId: String!) {queryUser(filter: { id: { eq: $userId } } ) {id}}
166
+ * """
167
+ * }
168
+ * { rule: "{$nextAuth: { eq: true } }" }
169
+ * ]
170
+ * }
171
+ * delete: { rule: "{$nextAuth: { eq: true } }" }
172
+ * add: { rule: "{$nextAuth: { eq: true } }" }
173
+ * update: {
174
+ * or: [
175
+ * {
176
+ * rule: """
177
+ * query ($userId: String!) {queryUser(filter: { id: { eq: $userId } } ) {id}}
178
+ * """
179
+ * }
180
+ * { rule: "{$nextAuth: { eq: true } }" }
181
+ * ]
182
+ * }
183
+ * ) {
184
+ * id: ID
185
+ * name: String
186
+ * email: String @search(by: [hash])
187
+ * emailVerified: DateTime
188
+ * image: String
189
+ * accounts: [Account] @hasInverse(field: "user")
190
+ * sessions: [Session] @hasInverse(field: "user")
191
+ * }
192
+ *
193
+ * type VerificationToken
194
+ * @auth(
195
+ * delete: { rule: "{$nextAuth: { eq: true } }" }
196
+ * add: { rule: "{$nextAuth: { eq: true } }" }
197
+ * query: { rule: "{$nextAuth: { eq: true } }" }
198
+ * update: { rule: "{$nextAuth: { eq: true } }" }
199
+ * ) {
200
+ * id: ID
201
+ * identifier: String @search(by: [hash])
202
+ * token: String @search(by: [hash])
203
+ * expires: DateTime
204
+ * }
205
+ *
206
+ * # Dgraph.Authorization {"VerificationKey":"<YOUR JWT SECRET HERE>","Header":"<YOUR AUTH HEADER HERE>","Namespace":"<YOUR CUSTOM NAMESPACE HERE>","Algo":"HS256"}
207
+ * ```
208
+ *
209
+ * ### Dgraph.Authorization
210
+ *
211
+ * In order to secure your graphql backend define the `Dgraph.Authorization` object at the
212
+ * bottom of your schema and provide `authHeader` and `jwtSecret` values to the DgraphClient.
213
+ *
214
+ * ```js
215
+ * # Dgraph.Authorization {"VerificationKey":"<YOUR JWT SECRET HERE>","Header":"<YOUR AUTH HEADER HERE>","Namespace":"YOUR CUSTOM NAMESPACE HERE","Algo":"HS256"}
216
+ * ```
217
+ *
218
+ * ### VerificationKey and jwtSecret
219
+ *
220
+ * This is the key used to sign the JWT. Ex. `process.env.SECRET` or `process.env.APP_SECRET`.
221
+ *
222
+ * ### Header and authHeader
223
+ *
224
+ * The `Header` tells Dgraph where to lookup a JWT within the headers of the incoming requests made to the dgraph server.
225
+ * You have to configure it at the bottom of your schema file. This header is the same as the `authHeader` property you
226
+ * provide when you instantiate the `DgraphClient`.
227
+ *
228
+ * ### The nextAuth secret
229
+ *
230
+ * The `$nextAuth` secret is securely generated using the `jwtSecret` and injected by the DgraphAdapter in order to allow interacting with the JWT DgraphClient for anonymous user requests made within the system `ie. login, register`. This allows
231
+ * secure interactions to be made with all the auth types required by next-auth. You have to specify it for each auth rule of
232
+ * each type defined in your secure schema.
233
+ *
234
+ * ```js
235
+ * type VerificationRequest
236
+ * @auth(
237
+ * delete: { rule: "{$nextAuth: { eq: true } }" },
238
+ * add: { rule: "{$nextAuth: { eq: true } }" },
239
+ * query: { rule: "{$nextAuth: { eq: true } }" },
240
+ * update: { rule: "{$nextAuth: { eq: true } }" }
241
+ * ) {
242
+ * ...
243
+ * }
244
+ * ```
245
+ *
246
+ * ### JWT session and `@auth` directive
247
+ *
248
+ * Dgraph only works with HS256 or RS256 algorithms. If you want to use session jwt to securely interact with your dgraph
249
+ * database you must customize next-auth `encode` and `decode` functions, as the default algorithm is HS512. You can
250
+ * further customize the jwt with roles if you want to implement [`RBAC logic`](https://dgraph.io/docs/graphql/authorization/directive/#role-based-access-control).
251
+ *
252
+ * ```js
253
+ * import * as jwt from "jsonwebtoken"
254
+ * export default NextAuth({
255
+ * session: {
256
+ * strategy: "jwt",
257
+ * },
258
+ * jwt: {
259
+ * secret: process.env.SECRET,
260
+ * encode: async ({ secret, token }) => {
261
+ * return jwt.sign({ ...token, userId: token.id }, secret, {
262
+ * algorithm: "HS256",
263
+ * expiresIn: 30 * 24 * 60 * 60, // 30 days
264
+ * })
265
+ * },
266
+ * decode: async ({ secret, token }) => {
267
+ * return jwt.verify(token, secret, { algorithms: ["HS256"] })
268
+ * },
269
+ * },
270
+ * })
271
+ * ```
272
+ *
273
+ * Once your `Dgraph.Authorization` is defined in your schema and the JWT settings are set, this will allow you to define
274
+ * [`@auth rules`](https://dgraph.io/docs/graphql/authorization/authorization-overview/) for every part of your schema.
275
+ **/
276
+ export function DgraphAdapter(
277
+ client: DgraphClientParams,
278
+ options?: DgraphAdapterOptions
279
+ ): Adapter {
280
+ const c = dgraphClient(client)
281
+
282
+ const fragments = { ...defaultFragments, ...options?.fragments }
283
+ return {
284
+ async createUser(input) {
285
+ const result = await c.run<{ user: any[] }>(
286
+ /* GraphQL */ `
287
+ mutation ($input: [AddUserInput!]!) {
288
+ addUser(input: $input) {
289
+ user {
290
+ ...UserFragment
291
+ }
292
+ }
293
+ }
294
+ ${fragments.User}
295
+ `,
296
+ { input }
297
+ )
298
+
299
+ return format.from<any>(result?.user[0])
300
+ },
301
+ async getUser(id) {
302
+ const result = await c.run<any>(
303
+ /* GraphQL */ `
304
+ query ($id: ID!) {
305
+ getUser(id: $id) {
306
+ ...UserFragment
307
+ }
308
+ }
309
+ ${fragments.User}
310
+ `,
311
+ { id }
312
+ )
313
+
314
+ return format.from<any>(result)
315
+ },
316
+ async getUserByEmail(email) {
317
+ const [user] = await c.run<any>(
318
+ /* GraphQL */ `
319
+ query ($email: String = "") {
320
+ queryUser(filter: { email: { eq: $email } }) {
321
+ ...UserFragment
322
+ }
323
+ }
324
+ ${fragments.User}
325
+ `,
326
+ { email }
327
+ )
328
+ return format.from<any>(user)
329
+ },
330
+ async getUserByAccount(provider_providerAccountId) {
331
+ const [account] = await c.run<any>(
332
+ /* GraphQL */ `
333
+ query ($providerAccountId: String = "", $provider: String = "") {
334
+ queryAccount(
335
+ filter: {
336
+ and: {
337
+ providerAccountId: { eq: $providerAccountId }
338
+ provider: { eq: $provider }
339
+ }
340
+ }
341
+ ) {
342
+ user {
343
+ ...UserFragment
344
+ }
345
+ id
346
+ }
347
+ }
348
+ ${fragments.User}
349
+ `,
350
+ provider_providerAccountId
351
+ )
352
+ return format.from<any>(account?.user)
353
+ },
354
+ async updateUser({ id, ...input }) {
355
+ const result = await c.run<any>(
356
+ /* GraphQL */ `
357
+ mutation ($id: [ID!] = "", $input: UserPatch) {
358
+ updateUser(input: { filter: { id: $id }, set: $input }) {
359
+ user {
360
+ ...UserFragment
361
+ }
362
+ }
363
+ }
364
+ ${fragments.User}
365
+ `,
366
+ { id, input }
367
+ )
368
+ return format.from<any>(result.user[0])
369
+ },
370
+ async deleteUser(id) {
371
+ const result = await c.run<any>(
372
+ /* GraphQL */ `
373
+ mutation ($id: [ID!] = "") {
374
+ deleteUser(filter: { id: $id }) {
375
+ numUids
376
+ user {
377
+ accounts {
378
+ id
379
+ }
380
+ sessions {
381
+ id
382
+ }
383
+ }
384
+ }
385
+ }
386
+ `,
387
+ { id }
388
+ )
389
+
390
+ const deletedUser = format.from<any>(result.user[0])
391
+
392
+ await c.run<any>(
393
+ /* GraphQL */ `
394
+ mutation ($accounts: [ID!], $sessions: [ID!]) {
395
+ deleteAccount(filter: { id: $accounts }) {
396
+ numUids
397
+ }
398
+ deleteSession(filter: { id: $sessions }) {
399
+ numUids
400
+ }
401
+ }
402
+ `,
403
+ {
404
+ sessions: deletedUser.sessions.map((x: any) => x.id),
405
+ accounts: deletedUser.accounts.map((x: any) => x.id),
406
+ }
407
+ )
408
+
409
+ return deletedUser
410
+ },
411
+
412
+ async linkAccount(data) {
413
+ const { userId, ...input } = data
414
+ await c.run<any>(
415
+ /* GraphQL */ `
416
+ mutation ($input: [AddAccountInput!]!) {
417
+ addAccount(input: $input) {
418
+ account {
419
+ ...AccountFragment
420
+ }
421
+ }
422
+ }
423
+ ${fragments.Account}
424
+ `,
425
+ { input: { ...input, user: { id: userId } } }
426
+ )
427
+ return data
428
+ },
429
+ async unlinkAccount(provider_providerAccountId) {
430
+ await c.run<any>(
431
+ /* GraphQL */ `
432
+ mutation ($providerAccountId: String = "", $provider: String = "") {
433
+ deleteAccount(
434
+ filter: {
435
+ and: {
436
+ providerAccountId: { eq: $providerAccountId }
437
+ provider: { eq: $provider }
438
+ }
439
+ }
440
+ ) {
441
+ numUids
442
+ }
443
+ }
444
+ `,
445
+ provider_providerAccountId
446
+ )
447
+ },
448
+
449
+ async getSessionAndUser(sessionToken) {
450
+ const [sessionAndUser] = await c.run<any>(
451
+ /* GraphQL */ `
452
+ query ($sessionToken: String = "") {
453
+ querySession(filter: { sessionToken: { eq: $sessionToken } }) {
454
+ ...SessionFragment
455
+ user {
456
+ ...UserFragment
457
+ }
458
+ }
459
+ }
460
+ ${fragments.User}
461
+ ${fragments.Session}
462
+ `,
463
+ { sessionToken }
464
+ )
465
+ if (!sessionAndUser) return null
466
+
467
+ const { user, ...session } = sessionAndUser
468
+
469
+ return {
470
+ user: format.from<any>(user),
471
+ session: { ...format.from<any>(session), userId: user.id },
472
+ }
473
+ },
474
+ async createSession(data) {
475
+ const { userId, ...input } = data
476
+
477
+ await c.run<any>(
478
+ /* GraphQL */ `
479
+ mutation ($input: [AddSessionInput!]!) {
480
+ addSession(input: $input) {
481
+ session {
482
+ ...SessionFragment
483
+ }
484
+ }
485
+ }
486
+ ${fragments.Session}
487
+ `,
488
+ { input: { ...input, user: { id: userId } } }
489
+ )
490
+
491
+ return data as any
492
+ },
493
+ async updateSession({ sessionToken, ...input }) {
494
+ const result = await c.run<any>(
495
+ /* GraphQL */ `
496
+ mutation ($input: SessionPatch = {}, $sessionToken: String) {
497
+ updateSession(
498
+ input: {
499
+ filter: { sessionToken: { eq: $sessionToken } }
500
+ set: $input
501
+ }
502
+ ) {
503
+ session {
504
+ ...SessionFragment
505
+ user {
506
+ id
507
+ }
508
+ }
509
+ }
510
+ }
511
+ ${fragments.Session}
512
+ `,
513
+ { sessionToken, input }
514
+ )
515
+ const session = format.from<any>(result.session[0])
516
+
517
+ if (!session?.user?.id) return null
518
+
519
+ return { ...session, userId: session.user.id }
520
+ },
521
+ async deleteSession(sessionToken) {
522
+ await c.run<any>(
523
+ /* GraphQL */ `
524
+ mutation ($sessionToken: String = "") {
525
+ deleteSession(filter: { sessionToken: { eq: $sessionToken } }) {
526
+ numUids
527
+ }
528
+ }
529
+ `,
530
+ { sessionToken }
531
+ )
532
+ },
533
+
534
+ async createVerificationToken(input) {
535
+ const result = await c.run<any>(
536
+ /* GraphQL */ `
537
+ mutation ($input: [AddVerificationTokenInput!]!) {
538
+ addVerificationToken(input: $input) {
539
+ numUids
540
+ }
541
+ }
542
+ `,
543
+ { input }
544
+ )
545
+ return format.from<any>(result)
546
+ },
547
+
548
+ async useVerificationToken(params) {
549
+ const result = await c.run<any>(
550
+ /* GraphQL */ `
551
+ mutation ($token: String = "", $identifier: String = "") {
552
+ deleteVerificationToken(
553
+ filter: {
554
+ and: { token: { eq: $token }, identifier: { eq: $identifier } }
555
+ }
556
+ ) {
557
+ verificationToken {
558
+ ...VerificationTokenFragment
559
+ }
560
+ }
561
+ }
562
+ ${fragments.VerificationToken}
563
+ `,
564
+ params
565
+ )
566
+
567
+ return format.from<any>(result.verificationToken[0])
568
+ },
569
+ }
570
+ }
@@ -0,0 +1,79 @@
1
+ import * as jwt from "jsonwebtoken"
2
+
3
+ export interface DgraphClientParams {
4
+ endpoint: string
5
+ /**
6
+ * `X-Auth-Token` header value
7
+ *
8
+ * [Dgraph Cloud Authentication](https://dgraph.io/docs/cloud/cloud-api/overview/#dgraph-cloud-authentication)
9
+ */
10
+ authToken: string
11
+ /** [Using JWT and authorization claims](https://dgraph.io/docs/graphql/authorization/authorization-overview#using-jwts-and-authorization-claims) */
12
+ jwtSecret?: string
13
+ /**
14
+ * @default "RS256"
15
+ *
16
+ * [Using JWT and authorization claims](https://dgraph.io/docs/graphql/authorization/authorization-overview#using-jwts-and-authorization-claims)
17
+ */
18
+ jwtAlgorithm?: "HS256" | "RS256"
19
+ /**
20
+ * @default "Authorization"
21
+ *
22
+ * [Using JWT and authorization claims](https://dgraph.io/docs/graphql/authorization/authorization-overview#using-jwts-and-authorization-claims)
23
+ */
24
+ authHeader?: string
25
+ }
26
+
27
+ export class DgraphClientError extends Error {
28
+ name = "DgraphClientError"
29
+ constructor(errors: any[], query: string, variables: any) {
30
+ super(errors.map((error) => error.message).join("\n"))
31
+ console.error({ query, variables })
32
+ }
33
+ }
34
+
35
+ export function client(params: DgraphClientParams) {
36
+ if (!params.authToken) {
37
+ throw new Error("Dgraph client error: Please provide an api key")
38
+ }
39
+ if (!params.endpoint) {
40
+ throw new Error("Dgraph client error: Please provide a graphql endpoint")
41
+ }
42
+
43
+ const {
44
+ endpoint,
45
+ authToken,
46
+ jwtSecret,
47
+ jwtAlgorithm = "HS256",
48
+ authHeader = "Authorization",
49
+ } = params
50
+ const headers: HeadersInit = {
51
+ "Content-Type": "application/json",
52
+ "X-Auth-Token": authToken,
53
+ }
54
+
55
+ if (authHeader && jwtSecret) {
56
+ headers[authHeader] = jwt.sign({ nextAuth: true }, jwtSecret, {
57
+ algorithm: jwtAlgorithm,
58
+ })
59
+ }
60
+
61
+ return {
62
+ async run<T>(
63
+ query: string,
64
+ variables?: Record<string, any>
65
+ ): Promise<T | null> {
66
+ const response = await fetch(endpoint, {
67
+ method: "POST",
68
+ headers,
69
+ body: JSON.stringify({ query, variables }),
70
+ })
71
+
72
+ const { data = {}, errors } = await response.json()
73
+ if (errors?.length) {
74
+ throw new DgraphClientError(errors, query, variables)
75
+ }
76
+ return Object.values(data)[0] as any
77
+ },
78
+ }
79
+ }