@auth/fauna-adapter 1.4.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  ISC License
2
2
 
3
- Copyright (c) 2022-2023, Balázs Orbán
3
+ Copyright (c) 2022-2024, Balázs Orbán
4
4
 
5
5
  Permission to use, copy, modify, and/or distribute this software for any
6
6
  purpose with or without fee is hereby granted, provided that the above
package/index.d.ts CHANGED
@@ -9,58 +9,40 @@
9
9
  * ## Installation
10
10
  *
11
11
  * ```bash npm2yarn
12
- * npm install @auth/fauna-adapter faunadb
12
+ * npm install @auth/fauna-adapter fauna
13
13
  * ```
14
14
  *
15
15
  * @module @auth/fauna-adapter
16
16
  */
17
- import { Client as FaunaClient, ExprArg } from "faunadb";
18
- import { Adapter } from "@auth/core/adapters";
19
- export declare const collections: {
20
- readonly Users: import("faunadb").Expr;
21
- readonly Accounts: import("faunadb").Expr;
22
- readonly Sessions: import("faunadb").Expr;
23
- readonly VerificationTokens: import("faunadb").Expr;
17
+ import { Client, TimeStub, QueryValue, QueryValueObject } from "fauna";
18
+ import type { Adapter, AdapterUser, AdapterSession, VerificationToken, AdapterAccount } from "@auth/core/adapters";
19
+ type ToFauna<T> = {
20
+ [P in keyof T]: T[P] extends Date | null ? TimeStub | null : T[P] extends undefined ? null : T[P] extends QueryValue ? T[P] : QueryValueObject;
24
21
  };
25
- export declare const indexes: {
26
- readonly AccountByProviderAndProviderAccountId: import("faunadb").Expr;
27
- readonly UserByEmail: import("faunadb").Expr;
28
- readonly SessionByToken: import("faunadb").Expr;
29
- readonly VerificationTokenByIdentifierAndToken: import("faunadb").Expr;
30
- readonly SessionsByUser: import("faunadb").Expr;
31
- readonly AccountsByUser: import("faunadb").Expr;
32
- };
33
- export declare const format: {
34
- /** Takes a plain old JavaScript object and turns it into a Fauna object */
35
- to(object: Record<string, any>): Record<string, unknown>;
36
- /** Takes a Fauna object and returns a plain old JavaScript object */
37
- from<T = Record<string, unknown>>(object: Record<string, any>): T;
22
+ export type FaunaUser = ToFauna<AdapterUser>;
23
+ export type FaunaSession = ToFauna<AdapterSession>;
24
+ export type FaunaVerificationToken = ToFauna<VerificationToken> & {
25
+ id: string;
38
26
  };
39
- /**
40
- * Fauna throws an error when something is not found in the db,
41
- * `next-auth` expects `null` to be returned
42
- */
43
- export declare function query(f: FaunaClient, format: (...args: any) => any): <T>(expr: ExprArg) => Promise<T | null>;
27
+ export type FaunaAccount = ToFauna<AdapterAccount>;
44
28
  /**
45
29
  *
46
30
  * ## Setup
47
31
  *
48
- * This is the Fauna Adapter for [`next-auth`](https://authjs.dev). This package can only be used in conjunction with the primary `next-auth` package. It is not a standalone package.
32
+ * This is the Fauna Adapter for [Auth.js](https://authjs.dev). This package can only be used in conjunction with the primary `next-auth` and other framework packages. It is not a standalone package.
49
33
  *
50
- * You can find the Fauna schema and seed information in the docs at [authjs.dev/reference/core/adapters/fauna](https://authjs.dev/reference/core/adapters/fauna).
34
+ * You can find the Fauna schema and seed information in the docs at [authjs.dev/reference/adapter/fauna](https://authjs.dev/reference/adapter/fauna).
51
35
  *
52
36
  * ### Configure Auth.js
53
37
  *
54
38
  * ```javascript title="pages/api/auth/[...nextauth].js"
55
39
  * import NextAuth from "next-auth"
56
- * import { Client as FaunaClient } from "faunadb"
40
+ * import { Client } from "fauna"
57
41
  * import { FaunaAdapter } from "@auth/fauna-adapter"
58
42
  *
59
- * const client = new FaunaClient({
43
+ * const client = new Client({
60
44
  * secret: "secret",
61
- * scheme: "http",
62
- * domain: "localhost",
63
- * port: 8443,
45
+ * endpoint: new URL('http://localhost:8443')
64
46
  * })
65
47
  *
66
48
  * // For more information on each option (and a full list of options) go to
@@ -75,46 +57,138 @@ export declare function query(f: FaunaClient, format: (...args: any) => any): <T
75
57
  *
76
58
  * ### Schema
77
59
  *
78
- * Run the following commands inside of the `Shell` tab in the Fauna dashboard to setup the appropriate collections and indexes.
60
+ * Run the following FQL code inside the `Shell` tab in the Fauna dashboard to set up the appropriate collections and indexes.
79
61
  *
80
62
  * ```javascript
81
- * CreateCollection({ name: "accounts" })
82
- * CreateCollection({ name: "sessions" })
83
- * CreateCollection({ name: "users" })
84
- * CreateCollection({ name: "verification_tokens" })
63
+ * Collection.create({
64
+ * name: "Account",
65
+ * indexes: {
66
+ * byUserId: {
67
+ * terms: [
68
+ * { field: "userId" }
69
+ * ]
70
+ * },
71
+ * byProviderAndProviderAccountId: {
72
+ * terms [
73
+ * { field: "provider" },
74
+ * { field: "providerAccountId" }
75
+ * ]
76
+ * },
77
+ * }
78
+ * })
79
+ * Collection.create({
80
+ * name: "Session",
81
+ * constraints: [
82
+ * {
83
+ * unique: ["sessionToken"],
84
+ * status: "active",
85
+ * }
86
+ * ],
87
+ * indexes: {
88
+ * bySessionToken: {
89
+ * terms: [
90
+ * { field: "sessionToken" }
91
+ * ]
92
+ * },
93
+ * byUserId: {
94
+ * terms [
95
+ * { field: "userId" }
96
+ * ]
97
+ * },
98
+ * }
99
+ * })
100
+ * Collection.create({
101
+ * name: "User",
102
+ * constraints: [
103
+ * {
104
+ * unique: ["email"],
105
+ * status: "active",
106
+ * }
107
+ * ],
108
+ * indexes: {
109
+ * byEmail: {
110
+ * terms [
111
+ * { field: "email" }
112
+ * ]
113
+ * },
114
+ * }
115
+ * })
116
+ * Collection.create({
117
+ * name: "VerificationToken",
118
+ * indexes: {
119
+ * byIdentifierAndToken: {
120
+ * terms [
121
+ * { field: "identifier" },
122
+ * { field: "token" }
123
+ * ]
124
+ * },
125
+ * }
126
+ * })
85
127
  * ```
86
128
  *
129
+ * > This schema is adapted for use in Fauna and based upon our main [schema](https://authjs.dev/reference/core/adapters#models)
130
+ *
131
+ * ### Migrating from v1
132
+ * In v2, we've renamed the collections to use uppercase naming, in accordance with Fauna best practices. If you're migrating from v1, you'll need to rename your collections to match the new naming scheme.
133
+ * Additionally, we've renamed the indexes to match the new method-like index names.
134
+ *
135
+ * #### Migration script
136
+ * Run this FQL script inside a Fauna shell for the database you're migrating from v1 to v2 (it will rename your collections and indexes to match):
137
+ *
87
138
  * ```javascript
88
- * CreateIndex({
89
- * name: "account_by_provider_and_provider_account_id",
90
- * source: Collection("accounts"),
91
- * unique: true,
92
- * terms: [
93
- * { field: ["data", "provider"] },
94
- * { field: ["data", "providerAccountId"] },
95
- * ],
139
+ * Collection.byName("accounts")!.update({
140
+ * name: "Account"
141
+ * indexes: {
142
+ * byUserId: {
143
+ * terms: [{ field: "userId" }]
144
+ * },
145
+ * byProviderAndProviderAccountId: {
146
+ * terms: [{ field: "provider" }, { field: "providerAccountId" }]
147
+ * },
148
+ * account_by_provider_and_provider_account_id: null,
149
+ * accounts_by_user_id: null
150
+ * }
96
151
  * })
97
- * CreateIndex({
98
- * name: "session_by_session_token",
99
- * source: Collection("sessions"),
100
- * unique: true,
101
- * terms: [{ field: ["data", "sessionToken"] }],
152
+ * Collection.byName("sessions")!.update({
153
+ * name: "Session",
154
+ * indexes: {
155
+ * bySessionToken: {
156
+ * terms: [{ field: "sessionToken" }]
157
+ * },
158
+ * byUserId: {
159
+ * terms: [{ field: "userId" }]
160
+ * },
161
+ * session_by_session_token: null,
162
+ * sessions_by_user_id: null
163
+ * }
102
164
  * })
103
- * CreateIndex({
104
- * name: "user_by_email",
105
- * source: Collection("users"),
106
- * unique: true,
107
- * terms: [{ field: ["data", "email"] }],
165
+ * Collection.byName("users")!.update({
166
+ * name: "User",
167
+ * indexes: {
168
+ * byEmail: {
169
+ * terms: [{ field: "email" }]
170
+ * },
171
+ * user_by_email: null
172
+ * }
108
173
  * })
109
- * CreateIndex({
110
- * name: "verification_token_by_identifier_and_token",
111
- * source: Collection("verification_tokens"),
112
- * unique: true,
113
- * terms: [{ field: ["data", "identifier"] }, { field: ["data", "token"] }],
174
+ * Collection.byName("verification_tokens")!.update({
175
+ * name: "VerificationToken",
176
+ * indexes: {
177
+ * byIdentifierAndToken: {
178
+ * terms: [{ field: "identifier" }, { field: "token" }]
179
+ * },
180
+ * verification_token_by_identifier_and_token: null
181
+ * }
114
182
  * })
115
183
  * ```
116
184
  *
117
- * > This schema is adapted for use in Fauna and based upon our main [schema](https://authjs.dev/reference/core/adapters#models)
118
185
  **/
119
- export declare function FaunaAdapter(f: FaunaClient): Adapter;
186
+ export declare function FaunaAdapter(client: Client): Adapter;
187
+ export declare const format: {
188
+ /** Takes an object that's coming from the database and converts it to plain JavaScript. */
189
+ from<T>(object?: Record<string, any>): T;
190
+ /** Takes an object that's coming from Auth.js and prepares it to be written to the database. */
191
+ to<T_1>(object: Record<string, any>): T_1;
192
+ };
193
+ export {};
120
194
  //# sourceMappingURL=index.d.ts.map
package/index.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["src/index.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EACL,MAAM,IAAI,WAAW,EACrB,OAAO,EAoBR,MAAM,SAAS,CAAA;AAEhB,OAAO,EACL,OAAO,EAIR,MAAM,qBAAqB,CAAA;AAE5B,eAAO,MAAM,WAAW;;;;;CAKd,CAAA;AAEV,eAAO,MAAM,OAAO;;;;;;;CAWV,CAAA;AAEV,eAAO,MAAM,MAAM;IACjB,2EAA2E;eAChE,OAAO,MAAM,EAAE,GAAG,CAAC;IAY9B,qEAAqE;8CAC3B,OAAO,MAAM,EAAE,GAAG,CAAC;CAY9D,CAAA;AAED;;;GAGG;AACH,wBAAgB,KAAK,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,KAAK,GAAG,aACjC,OAAO,uBAsBxC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA0EI;AACJ,wBAAgB,YAAY,CAAC,CAAC,EAAE,WAAW,GAAG,OAAO,CAmGpD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAqB,UAAU,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAA;AAEzF,OAAO,KAAK,EACV,OAAO,EACP,WAAW,EACX,cAAc,EACd,iBAAiB,EACjB,cAAc,EACf,MAAM,qBAAqB,CAAA;AAE5B,KAAK,OAAO,CAAC,CAAC,IAAI;KACf,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,IAAI,GAAG,IAAI,GAAG,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,SAAS,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,gBAAgB;CAC/I,CAAA;AAED,MAAM,MAAM,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;AAC5C,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,cAAc,CAAC,CAAA;AAClD,MAAM,MAAM,sBAAsB,GAAG,OAAO,CAAC,iBAAiB,CAAC,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAA;AAChF,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,cAAc,CAAC,CAAA;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA6JI;AACJ,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CA4HpD;AAED,eAAO,MAAM,MAAM;IACjB,2FAA2F;qBAC3E,OAAO,MAAM,EAAE,GAAG,CAAC;IASnC,gGAAgG;oBAClF,OAAO,MAAM,EAAE,GAAG,CAAC;CAUlC,CAAA"}
package/index.js CHANGED
@@ -1,4 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-non-null-assertion */
2
1
  /**
3
2
  * <div style={{display: "flex", justifyContent: "space-between", alignItems: "center", padding: 16}}>
4
3
  * <p style={{fontWeight: "normal"}}>Official <a href="https://docs.fauna.com/fauna/current/">Fauna</a> adapter for Auth.js / NextAuth.js.</p>
@@ -10,99 +9,30 @@
10
9
  * ## Installation
11
10
  *
12
11
  * ```bash npm2yarn
13
- * npm install @auth/fauna-adapter faunadb
12
+ * npm install @auth/fauna-adapter fauna
14
13
  * ```
15
14
  *
16
15
  * @module @auth/fauna-adapter
17
16
  */
18
- import { Collection, Create, Delete, Exists, Get, If, Index, Let, Match, Ref, Select, Time, Update, Var, Paginate, Lambda, Do, Foreach, } from "faunadb";
19
- export const collections = {
20
- Users: Collection("users"),
21
- Accounts: Collection("accounts"),
22
- Sessions: Collection("sessions"),
23
- VerificationTokens: Collection("verification_tokens"),
24
- };
25
- export const indexes = {
26
- AccountByProviderAndProviderAccountId: Index("account_by_provider_and_provider_account_id"),
27
- UserByEmail: Index("user_by_email"),
28
- SessionByToken: Index("session_by_session_token"),
29
- VerificationTokenByIdentifierAndToken: Index("verification_token_by_identifier_and_token"),
30
- SessionsByUser: Index("sessions_by_user_id"),
31
- AccountsByUser: Index("accounts_by_user_id"),
32
- };
33
- export const format = {
34
- /** Takes a plain old JavaScript object and turns it into a Fauna object */
35
- to(object) {
36
- const newObject = {};
37
- for (const key in object) {
38
- const value = object[key];
39
- if (value instanceof Date) {
40
- newObject[key] = Time(value.toISOString());
41
- }
42
- else {
43
- newObject[key] = value;
44
- }
45
- }
46
- return newObject;
47
- },
48
- /** Takes a Fauna object and returns a plain old JavaScript object */
49
- from(object) {
50
- const newObject = {};
51
- for (const key in object) {
52
- const value = object[key];
53
- if (value?.value && typeof value.value === "string") {
54
- newObject[key] = new Date(value.value);
55
- }
56
- else {
57
- newObject[key] = value;
58
- }
59
- }
60
- return newObject;
61
- },
62
- };
63
- /**
64
- * Fauna throws an error when something is not found in the db,
65
- * `next-auth` expects `null` to be returned
66
- */
67
- export function query(f, format) {
68
- return async function (expr) {
69
- try {
70
- const result = await f.query(expr);
71
- if (!result)
72
- return null;
73
- return format({ ...result.data, id: result.ref.id });
74
- }
75
- catch (error) {
76
- if (error.name === "NotFound")
77
- return null;
78
- if (error.description?.includes("Number or numeric String expected"))
79
- return null;
80
- if (process.env.NODE_ENV === "test")
81
- console.error(error);
82
- throw error;
83
- }
84
- };
85
- }
17
+ import { TimeStub, fql, NullDocument } from "fauna";
86
18
  /**
87
19
  *
88
20
  * ## Setup
89
21
  *
90
- * This is the Fauna Adapter for [`next-auth`](https://authjs.dev). This package can only be used in conjunction with the primary `next-auth` package. It is not a standalone package.
22
+ * This is the Fauna Adapter for [Auth.js](https://authjs.dev). This package can only be used in conjunction with the primary `next-auth` and other framework packages. It is not a standalone package.
91
23
  *
92
- * You can find the Fauna schema and seed information in the docs at [authjs.dev/reference/core/adapters/fauna](https://authjs.dev/reference/core/adapters/fauna).
24
+ * You can find the Fauna schema and seed information in the docs at [authjs.dev/reference/adapter/fauna](https://authjs.dev/reference/adapter/fauna).
93
25
  *
94
26
  * ### Configure Auth.js
95
27
  *
96
28
  * ```javascript title="pages/api/auth/[...nextauth].js"
97
29
  * import NextAuth from "next-auth"
98
- * import { Client as FaunaClient } from "faunadb"
30
+ * import { Client } from "fauna"
99
31
  * import { FaunaAdapter } from "@auth/fauna-adapter"
100
32
  *
101
- * const client = new FaunaClient({
33
+ * const client = new Client({
102
34
  * secret: "secret",
103
- * scheme: "http",
104
- * domain: "localhost",
105
- * port: 8443,
35
+ * endpoint: new URL('http://localhost:8443')
106
36
  * })
107
37
  *
108
38
  * // For more information on each option (and a full list of options) go to
@@ -117,102 +47,258 @@ export function query(f, format) {
117
47
  *
118
48
  * ### Schema
119
49
  *
120
- * Run the following commands inside of the `Shell` tab in the Fauna dashboard to setup the appropriate collections and indexes.
50
+ * Run the following FQL code inside the `Shell` tab in the Fauna dashboard to set up the appropriate collections and indexes.
121
51
  *
122
52
  * ```javascript
123
- * CreateCollection({ name: "accounts" })
124
- * CreateCollection({ name: "sessions" })
125
- * CreateCollection({ name: "users" })
126
- * CreateCollection({ name: "verification_tokens" })
53
+ * Collection.create({
54
+ * name: "Account",
55
+ * indexes: {
56
+ * byUserId: {
57
+ * terms: [
58
+ * { field: "userId" }
59
+ * ]
60
+ * },
61
+ * byProviderAndProviderAccountId: {
62
+ * terms [
63
+ * { field: "provider" },
64
+ * { field: "providerAccountId" }
65
+ * ]
66
+ * },
67
+ * }
68
+ * })
69
+ * Collection.create({
70
+ * name: "Session",
71
+ * constraints: [
72
+ * {
73
+ * unique: ["sessionToken"],
74
+ * status: "active",
75
+ * }
76
+ * ],
77
+ * indexes: {
78
+ * bySessionToken: {
79
+ * terms: [
80
+ * { field: "sessionToken" }
81
+ * ]
82
+ * },
83
+ * byUserId: {
84
+ * terms [
85
+ * { field: "userId" }
86
+ * ]
87
+ * },
88
+ * }
89
+ * })
90
+ * Collection.create({
91
+ * name: "User",
92
+ * constraints: [
93
+ * {
94
+ * unique: ["email"],
95
+ * status: "active",
96
+ * }
97
+ * ],
98
+ * indexes: {
99
+ * byEmail: {
100
+ * terms [
101
+ * { field: "email" }
102
+ * ]
103
+ * },
104
+ * }
105
+ * })
106
+ * Collection.create({
107
+ * name: "VerificationToken",
108
+ * indexes: {
109
+ * byIdentifierAndToken: {
110
+ * terms [
111
+ * { field: "identifier" },
112
+ * { field: "token" }
113
+ * ]
114
+ * },
115
+ * }
116
+ * })
127
117
  * ```
128
118
  *
119
+ * > This schema is adapted for use in Fauna and based upon our main [schema](https://authjs.dev/reference/core/adapters#models)
120
+ *
121
+ * ### Migrating from v1
122
+ * In v2, we've renamed the collections to use uppercase naming, in accordance with Fauna best practices. If you're migrating from v1, you'll need to rename your collections to match the new naming scheme.
123
+ * Additionally, we've renamed the indexes to match the new method-like index names.
124
+ *
125
+ * #### Migration script
126
+ * Run this FQL script inside a Fauna shell for the database you're migrating from v1 to v2 (it will rename your collections and indexes to match):
127
+ *
129
128
  * ```javascript
130
- * CreateIndex({
131
- * name: "account_by_provider_and_provider_account_id",
132
- * source: Collection("accounts"),
133
- * unique: true,
134
- * terms: [
135
- * { field: ["data", "provider"] },
136
- * { field: ["data", "providerAccountId"] },
137
- * ],
129
+ * Collection.byName("accounts")!.update({
130
+ * name: "Account"
131
+ * indexes: {
132
+ * byUserId: {
133
+ * terms: [{ field: "userId" }]
134
+ * },
135
+ * byProviderAndProviderAccountId: {
136
+ * terms: [{ field: "provider" }, { field: "providerAccountId" }]
137
+ * },
138
+ * account_by_provider_and_provider_account_id: null,
139
+ * accounts_by_user_id: null
140
+ * }
138
141
  * })
139
- * CreateIndex({
140
- * name: "session_by_session_token",
141
- * source: Collection("sessions"),
142
- * unique: true,
143
- * terms: [{ field: ["data", "sessionToken"] }],
142
+ * Collection.byName("sessions")!.update({
143
+ * name: "Session",
144
+ * indexes: {
145
+ * bySessionToken: {
146
+ * terms: [{ field: "sessionToken" }]
147
+ * },
148
+ * byUserId: {
149
+ * terms: [{ field: "userId" }]
150
+ * },
151
+ * session_by_session_token: null,
152
+ * sessions_by_user_id: null
153
+ * }
144
154
  * })
145
- * CreateIndex({
146
- * name: "user_by_email",
147
- * source: Collection("users"),
148
- * unique: true,
149
- * terms: [{ field: ["data", "email"] }],
155
+ * Collection.byName("users")!.update({
156
+ * name: "User",
157
+ * indexes: {
158
+ * byEmail: {
159
+ * terms: [{ field: "email" }]
160
+ * },
161
+ * user_by_email: null
162
+ * }
150
163
  * })
151
- * CreateIndex({
152
- * name: "verification_token_by_identifier_and_token",
153
- * source: Collection("verification_tokens"),
154
- * unique: true,
155
- * terms: [{ field: ["data", "identifier"] }, { field: ["data", "token"] }],
164
+ * Collection.byName("verification_tokens")!.update({
165
+ * name: "VerificationToken",
166
+ * indexes: {
167
+ * byIdentifierAndToken: {
168
+ * terms: [{ field: "identifier" }, { field: "token" }]
169
+ * },
170
+ * verification_token_by_identifier_and_token: null
171
+ * }
156
172
  * })
157
173
  * ```
158
174
  *
159
- * > This schema is adapted for use in Fauna and based upon our main [schema](https://authjs.dev/reference/core/adapters#models)
160
175
  **/
161
- export function FaunaAdapter(f) {
162
- const { Users, Accounts, Sessions, VerificationTokens } = collections;
163
- const { AccountByProviderAndProviderAccountId, AccountsByUser, SessionByToken, SessionsByUser, UserByEmail, VerificationTokenByIdentifierAndToken, } = indexes;
164
- const { to, from } = format;
165
- const q = query(f, from);
176
+ export function FaunaAdapter(client) {
166
177
  return {
167
- createUser: async (data) => (await q(Create(Users, { data: to(data) }))),
168
- getUser: async (id) => await q(Get(Ref(Users, id))),
169
- getUserByEmail: async (email) => await q(Get(Match(UserByEmail, email))),
178
+ async createUser(user) {
179
+ const response = await client.query(fql `User.create(${format.to(user)})`);
180
+ return format.from(response.data);
181
+ },
182
+ async getUser(id) {
183
+ const response = await client.query(fql `User.byId(${id})`);
184
+ if (response.data instanceof NullDocument)
185
+ return null;
186
+ return format.from(response.data);
187
+ },
188
+ async getUserByEmail(email) {
189
+ const response = await client.query(fql `User.byEmail(${email}).first()`);
190
+ if (response.data === null)
191
+ return null;
192
+ return format.from(response.data);
193
+ },
170
194
  async getUserByAccount({ provider, providerAccountId }) {
171
- const key = [provider, providerAccountId];
172
- const ref = Match(AccountByProviderAndProviderAccountId, key);
173
- const user = await q(Let({ ref }, If(Exists(Var("ref")), Get(Ref(Users, Select(["data", "userId"], Get(Var("ref"))))), null)));
174
- return user;
195
+ const response = await client.query(fql `
196
+ let account = Account.byProviderAndProviderAccountId(${provider}, ${providerAccountId}).first()
197
+ if (account != null) {
198
+ User.byId(account.userId)
199
+ } else {
200
+ null
201
+ }
202
+ `);
203
+ return format.from(response.data);
204
+ },
205
+ async updateUser(user) {
206
+ const _user = { ...user };
207
+ delete _user.id;
208
+ const response = await client.query(fql `User.byId(${user.id}).update(${format.to(_user)})`);
209
+ return format.from(response.data);
175
210
  },
176
- updateUser: async (data) => (await q(Update(Ref(Users, data.id), { data: to(data) }))),
177
211
  async deleteUser(userId) {
178
- await f.query(Do(Foreach(Paginate(Match(SessionsByUser, userId)), Lambda("ref", Delete(Var("ref")))), Foreach(Paginate(Match(AccountsByUser, userId)), Lambda("ref", Delete(Var("ref")))), Delete(Ref(Users, userId))));
212
+ await client.query(fql `
213
+ // Delete the user's sessions
214
+ Session.byUserId(${userId}).forEach(session => session.delete())
215
+
216
+ // Delete the user's accounts
217
+ Account.byUserId(${userId}).forEach(account => account.delete())
218
+
219
+ // Delete the user
220
+ User.byId(${userId}).delete()
221
+ `);
222
+ },
223
+ async linkAccount(account) {
224
+ await client.query(fql `Account.create(${format.to(account)})`);
225
+ return account;
179
226
  },
180
- linkAccount: async (data) => (await q(Create(Accounts, { data: to(data) }))),
181
227
  async unlinkAccount({ provider, providerAccountId }) {
182
- const id = [provider, providerAccountId];
183
- await q(Delete(Select("ref", Get(Match(AccountByProviderAndProviderAccountId, id)))));
228
+ const response = await client.query(fql `Account.byProviderAndProviderAccountId(${provider}, ${providerAccountId}).first().delete()`);
229
+ return format.from(response.data);
184
230
  },
185
- createSession: async (data) => (await q(Create(Sessions, { data: to(data) }))),
186
231
  async getSessionAndUser(sessionToken) {
187
- const session = await q(Get(Match(SessionByToken, sessionToken)));
188
- if (!session)
232
+ const response = await client.query(fql `
233
+ let session = Session.bySessionToken(${sessionToken}).first()
234
+ if (session != null) {
235
+ let user = User.byId(session.userId)
236
+ if (user != null) {
237
+ [user, session]
238
+ } else {
239
+ null
240
+ }
241
+ } else {
242
+ null
243
+ }
244
+ `);
245
+ if (response.data === null)
189
246
  return null;
190
- const user = await q(Get(Ref(Users, session.userId)));
191
- return { session, user: user };
247
+ const [user, session] = response.data ?? [];
248
+ return { session: format.from(session), user: format.from(user) };
192
249
  },
193
- async updateSession(data) {
194
- const ref = Select("ref", Get(Match(SessionByToken, data.sessionToken)));
195
- return await q(Update(ref, { data: to(data) }));
250
+ async createSession(session) {
251
+ await client.query(fql `Session.create(${format.to(session)})`);
252
+ return session;
253
+ },
254
+ async updateSession(session) {
255
+ const response = await client.query(fql `Session.bySessionToken(${session.sessionToken}).first().update(${format.to(session)})`);
256
+ return format.from(response.data);
196
257
  },
197
258
  async deleteSession(sessionToken) {
198
- await q(Delete(Select("ref", Get(Match(SessionByToken, sessionToken)))));
259
+ await client.query(fql `Session.bySessionToken(${sessionToken}).first().delete()`);
199
260
  },
200
- async createVerificationToken(data) {
201
- // @ts-expect-error
202
- const { id: _id, ...verificationToken } = await q(Create(VerificationTokens, { data: to(data) }));
261
+ async createVerificationToken(verificationToken) {
262
+ await client.query(fql `VerificationToken.create(${format.to(verificationToken)})`);
203
263
  return verificationToken;
204
264
  },
205
265
  async useVerificationToken({ identifier, token }) {
206
- const key = [identifier, token];
207
- const object = Get(Match(VerificationTokenByIdentifierAndToken, key));
208
- const verificationToken = await q(object);
209
- if (!verificationToken)
266
+ const response = await client.query(fql `VerificationToken.byIdentifierAndToken(${identifier}, ${token}).first()`);
267
+ if (response.data === null)
210
268
  return null;
211
- // Verification tokens can be used only once
212
- await q(Delete(Select("ref", object)));
213
- // @ts-expect-error
214
- delete verificationToken.id;
215
- return verificationToken;
269
+ // Delete the verification token so it can only be used once
270
+ await client.query(fql `VerificationToken.byId(${response.data.id}).delete()`);
271
+ const _verificationToken = { ...response.data };
272
+ delete _verificationToken.id;
273
+ return format.from(_verificationToken);
216
274
  },
217
275
  };
218
276
  }
277
+ export const format = {
278
+ /** Takes an object that's coming from the database and converts it to plain JavaScript. */
279
+ from(object = {}) {
280
+ if (!object)
281
+ return null;
282
+ const newObject = {};
283
+ for (const [key, value] of Object.entries(object))
284
+ if (key === "coll" || key === "ts")
285
+ continue;
286
+ else if (value instanceof TimeStub)
287
+ newObject[key] = value.toDate();
288
+ else
289
+ newObject[key] = value;
290
+ return newObject;
291
+ },
292
+ /** Takes an object that's coming from Auth.js and prepares it to be written to the database. */
293
+ to(object) {
294
+ const newObject = {};
295
+ for (const [key, value] of Object.entries(object))
296
+ if (value instanceof Date)
297
+ newObject[key] = TimeStub.fromDate(value);
298
+ else if (typeof value === "string" && !isNaN(Date.parse(value)))
299
+ newObject[key] = TimeStub.from(value);
300
+ else
301
+ newObject[key] = value ?? null;
302
+ return newObject;
303
+ },
304
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@auth/fauna-adapter",
3
- "version": "1.4.0",
3
+ "version": "2.1.1",
4
4
  "description": "Fauna Adapter for Auth.js",
5
5
  "homepage": "https://authjs.dev",
6
6
  "repository": "https://github.com/nextauthjs/next-auth",
@@ -10,7 +10,8 @@
10
10
  "author": "Bhanu Teja P",
11
11
  "contributors": [
12
12
  "Nico Domino <yo@ndo.dev>",
13
- "Balázs Orbán <info@balazsorban.com>"
13
+ "Balázs Orbán <info@balazsorban.com>",
14
+ "Aske Hippe Brun <mail@askehippebrun.com>"
14
15
  ],
15
16
  "type": "module",
16
17
  "types": "./index.d.ts",
@@ -37,20 +38,19 @@
37
38
  "access": "public"
38
39
  },
39
40
  "dependencies": {
40
- "@auth/core": "0.27.0"
41
+ "@auth/core": "0.28.1"
41
42
  },
42
43
  "peerDependencies": {
43
- "faunadb": "^4.3.0"
44
+ "fauna": "^1.3.1"
44
45
  },
45
46
  "devDependencies": {
46
- "@fauna-labs/fauna-schema-migrate": "^2.1.3",
47
- "faunadb": "^4.3.0"
47
+ "fauna": "^1.3.1",
48
+ "fauna-shell": "1.2.1"
48
49
  },
49
50
  "scripts": {
51
+ "fauna": "fauna",
50
52
  "build": "tsc",
51
53
  "dev": "tsc -w",
52
- "clean": "rm -rf dist",
53
- "migrate": "fauna-schema-migrate generate",
54
54
  "test": "./test/test.sh"
55
55
  }
56
56
  }
package/src/index.ts CHANGED
@@ -1,4 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-non-null-assertion */
2
1
  /**
3
2
  * <div style={{display: "flex", justifyContent: "space-between", alignItems: "center", padding: 16}}>
4
3
  * <p style={{fontWeight: "normal"}}>Official <a href="https://docs.fauna.com/fauna/current/">Fauna</a> adapter for Auth.js / NextAuth.js.</p>
@@ -10,140 +9,48 @@
10
9
  * ## Installation
11
10
  *
12
11
  * ```bash npm2yarn
13
- * npm install @auth/fauna-adapter faunadb
12
+ * npm install @auth/fauna-adapter fauna
14
13
  * ```
15
14
  *
16
15
  * @module @auth/fauna-adapter
17
16
  */
18
- import {
19
- Client as FaunaClient,
20
- ExprArg,
21
- Collection,
22
- Create,
23
- Delete,
24
- Exists,
25
- Get,
26
- If,
27
- Index,
28
- Let,
29
- Match,
30
- Ref,
31
- Select,
32
- Time,
33
- Update,
34
- Var,
35
- Paginate,
36
- Lambda,
37
- Do,
38
- Foreach,
39
- errors,
40
- } from "faunadb"
17
+ import { Client, TimeStub, fql, NullDocument, QueryValue, QueryValueObject } from "fauna"
41
18
 
42
- import {
19
+ import type {
43
20
  Adapter,
44
- AdapterSession,
45
21
  AdapterUser,
22
+ AdapterSession,
46
23
  VerificationToken,
24
+ AdapterAccount,
47
25
  } from "@auth/core/adapters"
48
26
 
49
- export const collections = {
50
- Users: Collection("users"),
51
- Accounts: Collection("accounts"),
52
- Sessions: Collection("sessions"),
53
- VerificationTokens: Collection("verification_tokens"),
54
- } as const
55
-
56
- export const indexes = {
57
- AccountByProviderAndProviderAccountId: Index(
58
- "account_by_provider_and_provider_account_id"
59
- ),
60
- UserByEmail: Index("user_by_email"),
61
- SessionByToken: Index("session_by_session_token"),
62
- VerificationTokenByIdentifierAndToken: Index(
63
- "verification_token_by_identifier_and_token"
64
- ),
65
- SessionsByUser: Index("sessions_by_user_id"),
66
- AccountsByUser: Index("accounts_by_user_id"),
67
- } as const
68
-
69
- export const format = {
70
- /** Takes a plain old JavaScript object and turns it into a Fauna object */
71
- to(object: Record<string, any>) {
72
- const newObject: Record<string, unknown> = {}
73
- for (const key in object) {
74
- const value = object[key]
75
- if (value instanceof Date) {
76
- newObject[key] = Time(value.toISOString())
77
- } else {
78
- newObject[key] = value
79
- }
80
- }
81
- return newObject
82
- },
83
- /** Takes a Fauna object and returns a plain old JavaScript object */
84
- from<T = Record<string, unknown>>(object: Record<string, any>): T {
85
- const newObject: Record<string, unknown> = {}
86
- for (const key in object) {
87
- const value = object[key]
88
- if (value?.value && typeof value.value === "string") {
89
- newObject[key] = new Date(value.value)
90
- } else {
91
- newObject[key] = value
92
- }
93
- }
94
- return newObject as T
95
- },
27
+ type ToFauna<T> = {
28
+ [P in keyof T]: T[P] extends Date | null ? TimeStub | null : T[P] extends undefined ? null : T[P] extends QueryValue ? T[P] : QueryValueObject
96
29
  }
97
30
 
98
- /**
99
- * Fauna throws an error when something is not found in the db,
100
- * `next-auth` expects `null` to be returned
101
- */
102
- export function query(f: FaunaClient, format: (...args: any) => any) {
103
- return async function <T>(expr: ExprArg): Promise<T | null> {
104
- try {
105
- const result = await f.query<{
106
- data: T
107
- ref: { id: string }
108
- } | null>(expr)
109
- if (!result) return null
110
- return format({ ...result.data, id: result.ref.id })
111
- } catch (error) {
112
- if ((error as errors.FaunaError).name === "NotFound") return null
113
- if (
114
- (error as errors.FaunaError).description?.includes(
115
- "Number or numeric String expected"
116
- )
117
- )
118
- return null
119
-
120
- if (process.env.NODE_ENV === "test") console.error(error)
121
-
122
- throw error
123
- }
124
- }
125
- }
31
+ export type FaunaUser = ToFauna<AdapterUser>
32
+ export type FaunaSession = ToFauna<AdapterSession>
33
+ export type FaunaVerificationToken = ToFauna<VerificationToken> & { id: string }
34
+ export type FaunaAccount = ToFauna<AdapterAccount>
126
35
 
127
36
  /**
128
37
  *
129
38
  * ## Setup
130
39
  *
131
- * This is the Fauna Adapter for [`next-auth`](https://authjs.dev). This package can only be used in conjunction with the primary `next-auth` package. It is not a standalone package.
40
+ * This is the Fauna Adapter for [Auth.js](https://authjs.dev). This package can only be used in conjunction with the primary `next-auth` and other framework packages. It is not a standalone package.
132
41
  *
133
- * You can find the Fauna schema and seed information in the docs at [authjs.dev/reference/core/adapters/fauna](https://authjs.dev/reference/core/adapters/fauna).
42
+ * You can find the Fauna schema and seed information in the docs at [authjs.dev/reference/adapter/fauna](https://authjs.dev/reference/adapter/fauna).
134
43
  *
135
44
  * ### Configure Auth.js
136
45
  *
137
46
  * ```javascript title="pages/api/auth/[...nextauth].js"
138
47
  * import NextAuth from "next-auth"
139
- * import { Client as FaunaClient } from "faunadb"
48
+ * import { Client } from "fauna"
140
49
  * import { FaunaAdapter } from "@auth/fauna-adapter"
141
50
  *
142
- * const client = new FaunaClient({
51
+ * const client = new Client({
143
52
  * secret: "secret",
144
- * scheme: "http",
145
- * domain: "localhost",
146
- * port: 8443,
53
+ * endpoint: new URL('http://localhost:8443')
147
54
  * })
148
55
  *
149
56
  * // For more information on each option (and a full list of options) go to
@@ -158,144 +65,278 @@ export function query(f: FaunaClient, format: (...args: any) => any) {
158
65
  *
159
66
  * ### Schema
160
67
  *
161
- * Run the following commands inside of the `Shell` tab in the Fauna dashboard to setup the appropriate collections and indexes.
68
+ * Run the following FQL code inside the `Shell` tab in the Fauna dashboard to set up the appropriate collections and indexes.
162
69
  *
163
70
  * ```javascript
164
- * CreateCollection({ name: "accounts" })
165
- * CreateCollection({ name: "sessions" })
166
- * CreateCollection({ name: "users" })
167
- * CreateCollection({ name: "verification_tokens" })
71
+ * Collection.create({
72
+ * name: "Account",
73
+ * indexes: {
74
+ * byUserId: {
75
+ * terms: [
76
+ * { field: "userId" }
77
+ * ]
78
+ * },
79
+ * byProviderAndProviderAccountId: {
80
+ * terms [
81
+ * { field: "provider" },
82
+ * { field: "providerAccountId" }
83
+ * ]
84
+ * },
85
+ * }
86
+ * })
87
+ * Collection.create({
88
+ * name: "Session",
89
+ * constraints: [
90
+ * {
91
+ * unique: ["sessionToken"],
92
+ * status: "active",
93
+ * }
94
+ * ],
95
+ * indexes: {
96
+ * bySessionToken: {
97
+ * terms: [
98
+ * { field: "sessionToken" }
99
+ * ]
100
+ * },
101
+ * byUserId: {
102
+ * terms [
103
+ * { field: "userId" }
104
+ * ]
105
+ * },
106
+ * }
107
+ * })
108
+ * Collection.create({
109
+ * name: "User",
110
+ * constraints: [
111
+ * {
112
+ * unique: ["email"],
113
+ * status: "active",
114
+ * }
115
+ * ],
116
+ * indexes: {
117
+ * byEmail: {
118
+ * terms [
119
+ * { field: "email" }
120
+ * ]
121
+ * },
122
+ * }
123
+ * })
124
+ * Collection.create({
125
+ * name: "VerificationToken",
126
+ * indexes: {
127
+ * byIdentifierAndToken: {
128
+ * terms [
129
+ * { field: "identifier" },
130
+ * { field: "token" }
131
+ * ]
132
+ * },
133
+ * }
134
+ * })
168
135
  * ```
169
136
  *
137
+ * > This schema is adapted for use in Fauna and based upon our main [schema](https://authjs.dev/reference/core/adapters#models)
138
+ *
139
+ * ### Migrating from v1
140
+ * In v2, we've renamed the collections to use uppercase naming, in accordance with Fauna best practices. If you're migrating from v1, you'll need to rename your collections to match the new naming scheme.
141
+ * Additionally, we've renamed the indexes to match the new method-like index names.
142
+ *
143
+ * #### Migration script
144
+ * Run this FQL script inside a Fauna shell for the database you're migrating from v1 to v2 (it will rename your collections and indexes to match):
145
+ *
170
146
  * ```javascript
171
- * CreateIndex({
172
- * name: "account_by_provider_and_provider_account_id",
173
- * source: Collection("accounts"),
174
- * unique: true,
175
- * terms: [
176
- * { field: ["data", "provider"] },
177
- * { field: ["data", "providerAccountId"] },
178
- * ],
147
+ * Collection.byName("accounts")!.update({
148
+ * name: "Account"
149
+ * indexes: {
150
+ * byUserId: {
151
+ * terms: [{ field: "userId" }]
152
+ * },
153
+ * byProviderAndProviderAccountId: {
154
+ * terms: [{ field: "provider" }, { field: "providerAccountId" }]
155
+ * },
156
+ * account_by_provider_and_provider_account_id: null,
157
+ * accounts_by_user_id: null
158
+ * }
179
159
  * })
180
- * CreateIndex({
181
- * name: "session_by_session_token",
182
- * source: Collection("sessions"),
183
- * unique: true,
184
- * terms: [{ field: ["data", "sessionToken"] }],
160
+ * Collection.byName("sessions")!.update({
161
+ * name: "Session",
162
+ * indexes: {
163
+ * bySessionToken: {
164
+ * terms: [{ field: "sessionToken" }]
165
+ * },
166
+ * byUserId: {
167
+ * terms: [{ field: "userId" }]
168
+ * },
169
+ * session_by_session_token: null,
170
+ * sessions_by_user_id: null
171
+ * }
185
172
  * })
186
- * CreateIndex({
187
- * name: "user_by_email",
188
- * source: Collection("users"),
189
- * unique: true,
190
- * terms: [{ field: ["data", "email"] }],
173
+ * Collection.byName("users")!.update({
174
+ * name: "User",
175
+ * indexes: {
176
+ * byEmail: {
177
+ * terms: [{ field: "email" }]
178
+ * },
179
+ * user_by_email: null
180
+ * }
191
181
  * })
192
- * CreateIndex({
193
- * name: "verification_token_by_identifier_and_token",
194
- * source: Collection("verification_tokens"),
195
- * unique: true,
196
- * terms: [{ field: ["data", "identifier"] }, { field: ["data", "token"] }],
182
+ * Collection.byName("verification_tokens")!.update({
183
+ * name: "VerificationToken",
184
+ * indexes: {
185
+ * byIdentifierAndToken: {
186
+ * terms: [{ field: "identifier" }, { field: "token" }]
187
+ * },
188
+ * verification_token_by_identifier_and_token: null
189
+ * }
197
190
  * })
198
191
  * ```
199
192
  *
200
- * > This schema is adapted for use in Fauna and based upon our main [schema](https://authjs.dev/reference/core/adapters#models)
201
193
  **/
202
- export function FaunaAdapter(f: FaunaClient): Adapter {
203
- const { Users, Accounts, Sessions, VerificationTokens } = collections
204
- const {
205
- AccountByProviderAndProviderAccountId,
206
- AccountsByUser,
207
- SessionByToken,
208
- SessionsByUser,
209
- UserByEmail,
210
- VerificationTokenByIdentifierAndToken,
211
- } = indexes
212
- const { to, from } = format
213
- const q = query(f, from)
194
+ export function FaunaAdapter(client: Client): Adapter {
214
195
  return {
215
- createUser: async (data) => (await q(Create(Users, { data: to(data) })))!,
216
- getUser: async (id) => await q(Get(Ref(Users, id))),
217
- getUserByEmail: async (email) => await q(Get(Match(UserByEmail, email))),
196
+ async createUser(user) {
197
+ const response = await client.query<FaunaUser>(
198
+ fql`User.create(${format.to(user)})`,
199
+ )
200
+ return format.from(response.data)
201
+ },
202
+ async getUser(id) {
203
+ const response = await client.query<FaunaUser | NullDocument>(
204
+ fql`User.byId(${id})`,
205
+ )
206
+ if (response.data instanceof NullDocument) return null
207
+ return format.from(response.data)
208
+ },
209
+ async getUserByEmail(email) {
210
+ const response = await client.query<FaunaUser>(
211
+ fql`User.byEmail(${email}).first()`,
212
+ )
213
+ if (response.data === null) return null
214
+ return format.from(response.data)
215
+ },
218
216
  async getUserByAccount({ provider, providerAccountId }) {
219
- const key = [provider, providerAccountId]
220
- const ref = Match(AccountByProviderAndProviderAccountId, key)
221
- const user = await q<AdapterUser>(
222
- Let(
223
- { ref },
224
- If(
225
- Exists(Var("ref")),
226
- Get(Ref(Users, Select(["data", "userId"], Get(Var("ref"))))),
227
- null
228
- )
229
- )
217
+ const response = await client.query<FaunaUser>(fql`
218
+ let account = Account.byProviderAndProviderAccountId(${provider}, ${providerAccountId}).first()
219
+ if (account != null) {
220
+ User.byId(account.userId)
221
+ } else {
222
+ null
223
+ }
224
+ `)
225
+ return format.from(response.data)
226
+ },
227
+ async updateUser(user) {
228
+ const _user: Partial<AdapterUser> = { ...user }
229
+ delete _user.id
230
+ const response = await client.query<FaunaUser>(
231
+ fql`User.byId(${user.id}).update(${format.to(_user)})`,
230
232
  )
231
- return user
233
+ return format.from(response.data)
232
234
  },
233
- updateUser: async (data) =>
234
- (await q(Update(Ref(Users, data.id), { data: to(data) })))!,
235
235
  async deleteUser(userId) {
236
- await f.query(
237
- Do(
238
- Foreach(
239
- Paginate(Match(SessionsByUser, userId)),
240
- Lambda("ref", Delete(Var("ref")))
241
- ),
242
- Foreach(
243
- Paginate(Match(AccountsByUser, userId)),
244
- Lambda("ref", Delete(Var("ref")))
245
- ),
246
- Delete(Ref(Users, userId))
247
- )
236
+ await client.query(fql`
237
+ // Delete the user's sessions
238
+ Session.byUserId(${userId}).forEach(session => session.delete())
239
+
240
+ // Delete the user's accounts
241
+ Account.byUserId(${userId}).forEach(account => account.delete())
242
+
243
+ // Delete the user
244
+ User.byId(${userId}).delete()
245
+ `)
246
+ },
247
+ async linkAccount(account) {
248
+ await client.query<FaunaAccount>(
249
+ fql`Account.create(${format.to(account)})`,
248
250
  )
251
+ return account
249
252
  },
250
- linkAccount: async (data) =>
251
- (await q(Create(Accounts, { data: to(data) })))!,
252
253
  async unlinkAccount({ provider, providerAccountId }) {
253
- const id = [provider, providerAccountId]
254
- await q(
255
- Delete(
256
- Select("ref", Get(Match(AccountByProviderAndProviderAccountId, id)))
257
- )
254
+ const response = await client.query<FaunaAccount>(
255
+ fql`Account.byProviderAndProviderAccountId(${provider}, ${providerAccountId}).first().delete()`,
258
256
  )
257
+ return format.from<AdapterAccount>(response.data)
259
258
  },
260
- createSession: async (data) =>
261
- (await q<AdapterSession>(Create(Sessions, { data: to(data) })))!,
262
259
  async getSessionAndUser(sessionToken) {
263
- const session = await q<AdapterSession>(
264
- Get(Match(SessionByToken, sessionToken))
260
+ const response = await client.query<[FaunaUser, FaunaSession]>(fql`
261
+ let session = Session.bySessionToken(${sessionToken}).first()
262
+ if (session != null) {
263
+ let user = User.byId(session.userId)
264
+ if (user != null) {
265
+ [user, session]
266
+ } else {
267
+ null
268
+ }
269
+ } else {
270
+ null
271
+ }
272
+ `)
273
+ if (response.data === null) return null
274
+ const [user, session] = response.data ?? []
275
+ return { session: format.from(session), user: format.from(user) }
276
+ },
277
+ async createSession(session) {
278
+ await client.query<FaunaSession>(
279
+ fql`Session.create(${format.to(session)})`,
265
280
  )
266
- if (!session) return null
267
-
268
- const user = await q<AdapterUser>(Get(Ref(Users, session.userId)))
269
-
270
- return { session, user: user! }
281
+ return session
271
282
  },
272
- async updateSession(data) {
273
- const ref = Select("ref", Get(Match(SessionByToken, data.sessionToken)))
274
- return await q(Update(ref, { data: to(data) }))
283
+ async updateSession(session) {
284
+ const response = await client.query<FaunaSession>(
285
+ fql`Session.bySessionToken(${
286
+ session.sessionToken
287
+ }).first().update(${format.to(session)})`,
288
+ )
289
+ return format.from(response.data)
275
290
  },
276
291
  async deleteSession(sessionToken) {
277
- await q(Delete(Select("ref", Get(Match(SessionByToken, sessionToken)))))
292
+ await client.query(
293
+ fql`Session.bySessionToken(${sessionToken}).first().delete()`,
294
+ )
278
295
  },
279
- async createVerificationToken(data) {
280
- // @ts-expect-error
281
- const { id: _id, ...verificationToken } = await q<VerificationToken>(
282
- Create(VerificationTokens, { data: to(data) })
296
+ async createVerificationToken(verificationToken) {
297
+ await client.query<FaunaVerificationToken>(
298
+ fql`VerificationToken.create(${format.to(
299
+ verificationToken,
300
+ )})`,
283
301
  )
284
302
  return verificationToken
285
303
  },
286
304
  async useVerificationToken({ identifier, token }) {
287
- const key = [identifier, token]
288
- const object = Get(Match(VerificationTokenByIdentifierAndToken, key))
289
-
290
- const verificationToken = await q<VerificationToken>(object)
291
- if (!verificationToken) return null
292
-
293
- // Verification tokens can be used only once
294
- await q(Delete(Select("ref", object)))
295
-
296
- // @ts-expect-error
297
- delete verificationToken.id
298
- return verificationToken
305
+ const response = await client.query<FaunaVerificationToken>(
306
+ fql`VerificationToken.byIdentifierAndToken(${identifier}, ${token}).first()`,
307
+ )
308
+ if (response.data === null) return null
309
+ // Delete the verification token so it can only be used once
310
+ await client.query(
311
+ fql`VerificationToken.byId(${response.data.id}).delete()`,
312
+ )
313
+ const _verificationToken: Partial<FaunaVerificationToken> = { ...response.data }
314
+ delete _verificationToken.id
315
+ return format.from(_verificationToken)
299
316
  },
300
317
  }
301
318
  }
319
+
320
+ export const format = {
321
+ /** Takes an object that's coming from the database and converts it to plain JavaScript. */
322
+ from<T>(object: Record<string, any> = {}): T {
323
+ if (!object) return null as unknown as T
324
+ const newObject: Record<string, unknown> = {}
325
+ for (const [key, value] of Object.entries(object))
326
+ if (key === "coll" || key === "ts") continue
327
+ else if (value instanceof TimeStub) newObject[key] = value.toDate()
328
+ else newObject[key] = value
329
+ return newObject as T
330
+ },
331
+ /** Takes an object that's coming from Auth.js and prepares it to be written to the database. */
332
+ to<T>(object: Record<string, any>): T {
333
+ const newObject: Record<string, unknown> = {}
334
+ for (const [key, value] of Object.entries(object))
335
+ if (value instanceof Date) newObject[key] = TimeStub.fromDate(value)
336
+ else if (typeof value === "string" && !isNaN(Date.parse(value)))
337
+ newObject[key] = TimeStub.from(value)
338
+ else newObject[key] = value ?? null
339
+
340
+ return newObject as T
341
+ },
342
+ }