@auth/d1-adapter 0.2.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/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2022-2023, Balázs Orbán
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,28 @@
1
+ <p align="center">
2
+ <br/>
3
+ <a href="https://authjs.dev" target="_blank">
4
+ <img height="64px" src="https://authjs.dev/img/logo/logo-sm.png" />
5
+ </a>
6
+ <a href="https://developers.cloudflare.com/d1/" target="_blank">
7
+ <img height="64px" src="https://authjs.dev/img/adapters/d1.svg"/>
8
+ </a>
9
+ <h3 align="center"><b>Cloudflare D1 Adapter</b> - NextAuth.js / Auth.js</a></h3>
10
+ <p align="center" style="align: center;">
11
+ <a href="https://npm.im/@auth/drizzle-adapter">
12
+ <img src="https://img.shields.io/badge/TypeScript-blue?style=flat-square" alt="TypeScript" />
13
+ </a>
14
+ <a href="https://npm.im/@auth/d1-adapter">
15
+ <img alt="npm" src="https://img.shields.io/npm/v/@auth/d1-adapter?color=green&label=@auth/d1-adapter&style=flat-square">
16
+ </a>
17
+ <a href="https://www.npmtrends.com/@auth/d1-adapter">
18
+ <img src="https://img.shields.io/npm/dm/@auth/d1-adapter?label=%20downloads&style=flat-square" alt="Downloads" />
19
+ </a>
20
+ <a href="https://github.com/nextauthjs/next-auth/stargazers">
21
+ <img src="https://img.shields.io/github/stars/nextauthjs/next-auth?style=flat-square" alt="Github Stars" />
22
+ </a>
23
+ </p>
24
+ </p>
25
+
26
+ ---
27
+
28
+ Check out the documentation at [authjs.dev](https://authjs.dev/reference/adapter/d1).
package/index.d.ts ADDED
@@ -0,0 +1,107 @@
1
+ /**
2
+ * <div style={{display: "flex", justifyContent: "space-between", alignItems: "center", padding: 16}}>
3
+ * <p style={{fontWeight: "normal"}}>An unofficial <a href="https://developers.cloudflare.com/d1/">Cloudflare D1</a> adapter for Auth.js / NextAuth.js.</p>
4
+ * <a href="https://developers.cloudflare.com/d1/">
5
+ * <img style={{display: "block"}} src="/img/adapters/d1.svg" width="48" />
6
+ * </a>
7
+ * </div>
8
+ *
9
+ * ## Warning
10
+ * This adapter is not developed or maintained by Clouflare and they haven't declared the D1 api stable. The author will make an effort to keep this adapter up to date.
11
+ * The adapter is compatible with the D1 api as of March 22, 2023.
12
+ *
13
+ * ## Installation
14
+ *
15
+ * ```bash npm2yarn2pnpm
16
+ * npm install next-auth @auth/d1-adapter
17
+ * ```
18
+ *
19
+ * @module @auth/d1-adapter
20
+ */
21
+ import type { D1Database as WorkerDatabase } from "@cloudflare/workers-types";
22
+ import type { D1Database as MiniflareD1Database } from "@miniflare/d1";
23
+ import type { Adapter } from "@auth/core/adapters";
24
+ export { up } from "./migrations";
25
+ /**
26
+ * @type @cloudflare/workers-types.D1Database | @miniflare/d1.D1Database
27
+ */
28
+ export type D1Database = WorkerDatabase | MiniflareD1Database;
29
+ export declare const CREATE_USER_SQL = "INSERT INTO users (id, name, email, emailVerified, image) VALUES (?, ?, ?, ?, ?)";
30
+ export declare const GET_USER_BY_ID_SQL = "SELECT * FROM users WHERE id = ?";
31
+ export declare const GET_USER_BY_EMAIL_SQL = "SELECT * FROM users WHERE email = ?";
32
+ export declare const GET_USER_BY_ACCOUNTL_SQL = "\n SELECT u.*\n FROM users u JOIN accounts a ON a.userId = u.id\n WHERE a.providerAccountId = ? AND a.provider = ?";
33
+ export declare const UPDATE_USER_BY_ID_SQL = "\n UPDATE users \n SET name = ?, email = ?, emailVerified = ?, image = ?\n WHERE id = ? ";
34
+ export declare const DELETE_USER_SQL = "DELETE FROM users WHERE id = ?";
35
+ export declare const CREATE_SESSION_SQL = "INSERT INTO sessions (id, sessionToken, userId, expires) VALUES (?,?,?,?)";
36
+ export declare const GET_SESSION_BY_TOKEN_SQL = "\n SELECT id, sessionToken, userId, expires\n FROM sessions\n WHERE sessionToken = ?";
37
+ export declare const UPDATE_SESSION_BY_SESSION_TOKEN_SQL = "UPDATE sessions SET expires = ? WHERE sessionToken = ?";
38
+ export declare const DELETE_SESSION_SQL = "DELETE FROM sessions WHERE sessionToken = ?";
39
+ export declare const DELETE_SESSION_BY_USER_ID_SQL = "DELETE FROM sessions WHERE userId = ?";
40
+ export declare const CREATE_ACCOUNT_SQL = "\n INSERT INTO accounts (\n id, userId, type, provider, \n providerAccountId, refresh_token, access_token, \n expires_at, token_type, scope, id_token, session_state,\n oauth_token, oauth_token_secret\n ) \n VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
41
+ export declare const GET_ACCOUNT_BY_ID_SQL = "SELECT * FROM accounts WHERE id = ? ";
42
+ export declare const GET_ACCOUNT_BY_PROVIDER_AND_PROVIDER_ACCOUNT_ID_SQL = "SELECT * FROM accounts WHERE provider = ? AND providerAccountId = ?";
43
+ export declare const DELETE_ACCOUNT_BY_PROVIDER_AND_PROVIDER_ACCOUNT_ID_SQL = "DELETE FROM accounts WHERE provider = ? AND providerAccountId = ?";
44
+ export declare const DELETE_ACCOUNT_BY_USER_ID_SQL = "DELETE FROM accounts WHERE userId = ?";
45
+ export declare const GET_VERIFICATION_TOKEN_BY_IDENTIFIER_AND_TOKEN_SQL = "SELECT * FROM verification_tokens WHERE identifier = ? AND token = ?";
46
+ export declare const CREATE_VERIFICATION_TOKEN_SQL = "INSERT INTO verification_tokens (identifier, expires, token) VALUES (?,?,?)";
47
+ export declare const DELETE_VERIFICATION_TOKEN_SQL = "DELETE FROM verification_tokens WHERE identifier = ? and token = ?";
48
+ export declare function createRecord<RecordType>(db: D1Database, CREATE_SQL: string, bindings: any[], GET_SQL: string, getBindings: any[]): Promise<RecordType | null>;
49
+ export declare function getRecord<RecordType>(db: D1Database, SQL: string, bindings: any[]): Promise<RecordType | null>;
50
+ export declare function updateRecord(db: D1Database, SQL: string, bindings: any[]): Promise<import("@miniflare/d1").D1Result<unknown> | import("@cloudflare/workers-types").D1Result<unknown>>;
51
+ export declare function deleteRecord(db: D1Database, SQL: string, bindings: any[]): Promise<void>;
52
+ /**
53
+ *
54
+ * ## Setup
55
+ *
56
+ * This is the D1 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.
57
+ *
58
+ * ### Configure Auth.js
59
+ *
60
+ * ```javascript title="pages/api/auth/[...nextauth].js"
61
+ * import NextAuth from "next-auth"
62
+ * import { D1Adapter, up } from "@auth/d1-adapter"
63
+ *
64
+ *
65
+ * // For more information on each option (and a full list of options) go to
66
+ * // https://authjs.dev/reference/configuration/auth-options
67
+ * export default NextAuth({
68
+ * // https://authjs.dev/reference/providers/
69
+ * providers: [],
70
+ * adapter: D1Adapter(env.db)
71
+ * ...
72
+ * })
73
+ * ```
74
+ *
75
+ * ### Migrations
76
+ *
77
+ * Somewhere in the initialization of your application you need to run the `up(env.db)` function to create the tables in D1.
78
+ * It will create 4 tables if they don't already exist:
79
+ * `accounts`, `sessions`, `users`, `verification_tokens`.
80
+ *
81
+ * The table prefix "" is not configurable at this time.
82
+ *
83
+ * You can use something like the following to attempt the migration once each time your worker starts up. Running migrations more than once will not erase your existing tables.
84
+ * ```javascript
85
+ * import { up } from "@auth/d1-adapter"
86
+ *
87
+ * let migrated = false;
88
+ * async function migrationHandle({event, resolve}) {
89
+ * if(!migrated) {
90
+ * try {
91
+ * await up(event.platform.env.db)
92
+ * migrated = true
93
+ * } catch(e) {
94
+ * console.log(e.cause.message, e.message)
95
+ * }
96
+ * }
97
+ * return resolve(event)
98
+ * }
99
+ * ```
100
+ *
101
+ *
102
+ * You can also initialize your tables manually. Look in [init.ts](https://github.com/nextauthjs/next-auth/packages/adapter-d1/src/migrations/init.ts) for the relevant sql.
103
+ * Paste and run the SQL into your D1 dashboard query tool.
104
+ *
105
+ **/
106
+ export declare function D1Adapter(db: D1Database): Adapter;
107
+ //# sourceMappingURL=index.d.ts.map
package/index.d.ts.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,UAAU,IAAI,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAC7E,OAAO,KAAK,EAAE,UAAU,IAAI,mBAAmB,EAAE,MAAM,eAAe,CAAA;AACtE,OAAO,KAAK,EACV,OAAO,EAKR,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAA;AAEjC;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,cAAc,GAAG,mBAAmB,CAAA;AAI7D,eAAO,MAAM,eAAe,qFAAqF,CAAA;AACjH,eAAO,MAAM,kBAAkB,qCAAqC,CAAA;AACpE,eAAO,MAAM,qBAAqB,wCAAwC,CAAA;AAC1E,eAAO,MAAM,wBAAwB,0HAGc,CAAA;AACnD,eAAO,MAAM,qBAAqB,gGAGlB,CAAA;AAChB,eAAO,MAAM,eAAe,mCAAmC,CAAA;AAG/D,eAAO,MAAM,kBAAkB,8EAC8C,CAAA;AAC7E,eAAO,MAAM,wBAAwB,4FAGZ,CAAA;AACzB,eAAO,MAAM,mCAAmC,2DAA2D,CAAA;AAC3G,eAAO,MAAM,kBAAkB,gDAAgD,CAAA;AAC/E,eAAO,MAAM,6BAA6B,0CAA0C,CAAA;AAGpF,eAAO,MAAM,kBAAkB,uQAOQ,CAAA;AACvC,eAAO,MAAM,qBAAqB,yCAAyC,CAAA;AAC3E,eAAO,MAAM,mDAAmD,wEAAwE,CAAA;AACxI,eAAO,MAAM,sDAAsD,sEAAsE,CAAA;AACzI,eAAO,MAAM,6BAA6B,0CAA0C,CAAA;AAGpF,eAAO,MAAM,kDAAkD,yEAAyE,CAAA;AACxI,eAAO,MAAM,6BAA6B,gFAAgF,CAAA;AAC1H,eAAO,MAAM,6BAA6B,uEAAuE,CAAA;AAiCjH,wBAAsB,YAAY,CAAC,UAAU,EAC3C,EAAE,EAAE,UAAU,EACd,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,GAAG,EAAE,EACf,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,GAAG,EAAE,8BAanB;AAED,wBAAsB,SAAS,CAAC,UAAU,EACxC,EAAE,EAAE,UAAU,EACd,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,GAAG,EAAE,GACd,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAgB5B;AAED,wBAAsB,YAAY,CAChC,EAAE,EAAE,UAAU,EACd,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,GAAG,EAAE,8GAYhB;AAED,wBAAsB,YAAY,CAChC,EAAE,EAAE,UAAU,EACd,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,GAAG,EAAE,iBAahB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAqDI;AACJ,wBAAgB,SAAS,CAAC,EAAE,EAAE,UAAU,GAAG,OAAO,CA0LjD"}
package/index.js ADDED
@@ -0,0 +1,346 @@
1
+ /**
2
+ * <div style={{display: "flex", justifyContent: "space-between", alignItems: "center", padding: 16}}>
3
+ * <p style={{fontWeight: "normal"}}>An unofficial <a href="https://developers.cloudflare.com/d1/">Cloudflare D1</a> adapter for Auth.js / NextAuth.js.</p>
4
+ * <a href="https://developers.cloudflare.com/d1/">
5
+ * <img style={{display: "block"}} src="/img/adapters/d1.svg" width="48" />
6
+ * </a>
7
+ * </div>
8
+ *
9
+ * ## Warning
10
+ * This adapter is not developed or maintained by Clouflare and they haven't declared the D1 api stable. The author will make an effort to keep this adapter up to date.
11
+ * The adapter is compatible with the D1 api as of March 22, 2023.
12
+ *
13
+ * ## Installation
14
+ *
15
+ * ```bash npm2yarn2pnpm
16
+ * npm install next-auth @auth/d1-adapter
17
+ * ```
18
+ *
19
+ * @module @auth/d1-adapter
20
+ */
21
+ export { up } from "./migrations";
22
+ // all the sqls
23
+ // USER
24
+ export const CREATE_USER_SQL = `INSERT INTO users (id, name, email, emailVerified, image) VALUES (?, ?, ?, ?, ?)`;
25
+ export const GET_USER_BY_ID_SQL = `SELECT * FROM users WHERE id = ?`;
26
+ export const GET_USER_BY_EMAIL_SQL = `SELECT * FROM users WHERE email = ?`;
27
+ export const GET_USER_BY_ACCOUNTL_SQL = `
28
+ SELECT u.*
29
+ FROM users u JOIN accounts a ON a.userId = u.id
30
+ WHERE a.providerAccountId = ? AND a.provider = ?`;
31
+ export const UPDATE_USER_BY_ID_SQL = `
32
+ UPDATE users
33
+ SET name = ?, email = ?, emailVerified = ?, image = ?
34
+ WHERE id = ? `;
35
+ export const DELETE_USER_SQL = `DELETE FROM users WHERE id = ?`;
36
+ // SESSION
37
+ export const CREATE_SESSION_SQL = "INSERT INTO sessions (id, sessionToken, userId, expires) VALUES (?,?,?,?)";
38
+ export const GET_SESSION_BY_TOKEN_SQL = `
39
+ SELECT id, sessionToken, userId, expires
40
+ FROM sessions
41
+ WHERE sessionToken = ?`;
42
+ export const UPDATE_SESSION_BY_SESSION_TOKEN_SQL = `UPDATE sessions SET expires = ? WHERE sessionToken = ?`;
43
+ export const DELETE_SESSION_SQL = `DELETE FROM sessions WHERE sessionToken = ?`;
44
+ export const DELETE_SESSION_BY_USER_ID_SQL = `DELETE FROM sessions WHERE userId = ?`;
45
+ // ACCOUNT
46
+ export const CREATE_ACCOUNT_SQL = `
47
+ INSERT INTO accounts (
48
+ id, userId, type, provider,
49
+ providerAccountId, refresh_token, access_token,
50
+ expires_at, token_type, scope, id_token, session_state,
51
+ oauth_token, oauth_token_secret
52
+ )
53
+ VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)`;
54
+ export const GET_ACCOUNT_BY_ID_SQL = `SELECT * FROM accounts WHERE id = ? `;
55
+ export const GET_ACCOUNT_BY_PROVIDER_AND_PROVIDER_ACCOUNT_ID_SQL = `SELECT * FROM accounts WHERE provider = ? AND providerAccountId = ?`;
56
+ export const DELETE_ACCOUNT_BY_PROVIDER_AND_PROVIDER_ACCOUNT_ID_SQL = `DELETE FROM accounts WHERE provider = ? AND providerAccountId = ?`;
57
+ export const DELETE_ACCOUNT_BY_USER_ID_SQL = `DELETE FROM accounts WHERE userId = ?`;
58
+ // VERIFICATION_TOKEN
59
+ export const GET_VERIFICATION_TOKEN_BY_IDENTIFIER_AND_TOKEN_SQL = `SELECT * FROM verification_tokens WHERE identifier = ? AND token = ?`;
60
+ export const CREATE_VERIFICATION_TOKEN_SQL = `INSERT INTO verification_tokens (identifier, expires, token) VALUES (?,?,?)`;
61
+ export const DELETE_VERIFICATION_TOKEN_SQL = `DELETE FROM verification_tokens WHERE identifier = ? and token = ?`;
62
+ // helper functions
63
+ // isDate is borrowed from the supabase adapter, graciously
64
+ // depending on error messages ("Invalid Date") is always precarious, but probably fine for a built in native like Date
65
+ function isDate(date) {
66
+ return (new Date(date).toString() !== "Invalid Date" && !isNaN(Date.parse(date)));
67
+ }
68
+ // format is borrowed from the supabase adapter, graciously
69
+ function format(obj) {
70
+ for (const [key, value] of Object.entries(obj)) {
71
+ if (value === null) {
72
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
73
+ delete obj[key];
74
+ }
75
+ if (isDate(value)) {
76
+ obj[key] = new Date(value);
77
+ }
78
+ }
79
+ return obj;
80
+ }
81
+ // D1 doesnt like undefined, it wants null when calling bind
82
+ function cleanBindings(bindings) {
83
+ return bindings.map((e) => (e === undefined ? null : e));
84
+ }
85
+ export async function createRecord(db, CREATE_SQL, bindings, GET_SQL, getBindings) {
86
+ try {
87
+ bindings = cleanBindings(bindings);
88
+ await db
89
+ .prepare(CREATE_SQL)
90
+ .bind(...bindings)
91
+ .run();
92
+ return await getRecord(db, GET_SQL, getBindings);
93
+ }
94
+ catch (e) {
95
+ console.error(e.message, e.cause?.message);
96
+ throw e;
97
+ }
98
+ }
99
+ export async function getRecord(db, SQL, bindings) {
100
+ try {
101
+ bindings = cleanBindings(bindings);
102
+ const res = await db
103
+ .prepare(SQL)
104
+ .bind(...bindings)
105
+ .first();
106
+ if (res) {
107
+ return format(res);
108
+ }
109
+ else {
110
+ return null;
111
+ }
112
+ }
113
+ catch (e) {
114
+ console.error(e.message, e.cause?.message);
115
+ throw e;
116
+ }
117
+ }
118
+ export async function updateRecord(db, SQL, bindings) {
119
+ try {
120
+ bindings = cleanBindings(bindings);
121
+ return await db
122
+ .prepare(SQL)
123
+ .bind(...bindings)
124
+ .run();
125
+ }
126
+ catch (e) {
127
+ console.error(e.message, e.cause?.message);
128
+ throw e;
129
+ }
130
+ }
131
+ export async function deleteRecord(db, SQL, bindings) {
132
+ // eslint-disable-next-line no-useless-catch
133
+ try {
134
+ bindings = cleanBindings(bindings);
135
+ await db
136
+ .prepare(SQL)
137
+ .bind(...bindings)
138
+ .run();
139
+ }
140
+ catch (e) {
141
+ console.error(e.message, e.cause?.message);
142
+ throw e;
143
+ }
144
+ }
145
+ /**
146
+ *
147
+ * ## Setup
148
+ *
149
+ * This is the D1 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.
150
+ *
151
+ * ### Configure Auth.js
152
+ *
153
+ * ```javascript title="pages/api/auth/[...nextauth].js"
154
+ * import NextAuth from "next-auth"
155
+ * import { D1Adapter, up } from "@auth/d1-adapter"
156
+ *
157
+ *
158
+ * // For more information on each option (and a full list of options) go to
159
+ * // https://authjs.dev/reference/configuration/auth-options
160
+ * export default NextAuth({
161
+ * // https://authjs.dev/reference/providers/
162
+ * providers: [],
163
+ * adapter: D1Adapter(env.db)
164
+ * ...
165
+ * })
166
+ * ```
167
+ *
168
+ * ### Migrations
169
+ *
170
+ * Somewhere in the initialization of your application you need to run the `up(env.db)` function to create the tables in D1.
171
+ * It will create 4 tables if they don't already exist:
172
+ * `accounts`, `sessions`, `users`, `verification_tokens`.
173
+ *
174
+ * The table prefix "" is not configurable at this time.
175
+ *
176
+ * You can use something like the following to attempt the migration once each time your worker starts up. Running migrations more than once will not erase your existing tables.
177
+ * ```javascript
178
+ * import { up } from "@auth/d1-adapter"
179
+ *
180
+ * let migrated = false;
181
+ * async function migrationHandle({event, resolve}) {
182
+ * if(!migrated) {
183
+ * try {
184
+ * await up(event.platform.env.db)
185
+ * migrated = true
186
+ * } catch(e) {
187
+ * console.log(e.cause.message, e.message)
188
+ * }
189
+ * }
190
+ * return resolve(event)
191
+ * }
192
+ * ```
193
+ *
194
+ *
195
+ * You can also initialize your tables manually. Look in [init.ts](https://github.com/nextauthjs/next-auth/packages/adapter-d1/src/migrations/init.ts) for the relevant sql.
196
+ * Paste and run the SQL into your D1 dashboard query tool.
197
+ *
198
+ **/
199
+ export function D1Adapter(db) {
200
+ // we need to run migrations if we dont have the right tables
201
+ return {
202
+ async createUser(user) {
203
+ const id = crypto.randomUUID();
204
+ const createBindings = [
205
+ id,
206
+ user.name,
207
+ user.email,
208
+ user.emailVerified?.toISOString(),
209
+ user.image,
210
+ ];
211
+ const getBindings = [id];
212
+ const newUser = await createRecord(db, CREATE_USER_SQL, createBindings, GET_USER_BY_ID_SQL, getBindings);
213
+ if (newUser)
214
+ return newUser;
215
+ throw new Error("Error creating user: Cannot get user after creation.");
216
+ },
217
+ async getUser(id) {
218
+ return await getRecord(db, GET_USER_BY_ID_SQL, [id]);
219
+ },
220
+ async getUserByEmail(email) {
221
+ return await getRecord(db, GET_USER_BY_EMAIL_SQL, [email]);
222
+ },
223
+ async getUserByAccount({ providerAccountId, provider }) {
224
+ return await getRecord(db, GET_USER_BY_ACCOUNTL_SQL, [
225
+ providerAccountId,
226
+ provider,
227
+ ]);
228
+ },
229
+ async updateUser(user) {
230
+ const params = await getRecord(db, GET_USER_BY_ID_SQL, [
231
+ user.id,
232
+ ]);
233
+ if (params) {
234
+ // copy any properties not in the update into the existing one and use that for bind params
235
+ // covers the scenario where the user arg doesnt have all of the current users properties
236
+ Object.assign(params, user);
237
+ const res = await updateRecord(db, UPDATE_USER_BY_ID_SQL, [
238
+ params.name,
239
+ params.email,
240
+ params.emailVerified?.toISOString(),
241
+ params.image,
242
+ params.id,
243
+ ]);
244
+ if (res.success) {
245
+ // we could probably just return
246
+ const user = await getRecord(db, GET_USER_BY_ID_SQL, [
247
+ params.id,
248
+ ]);
249
+ if (user)
250
+ return user;
251
+ throw new Error("Error updating user: Cannot get user after updating.");
252
+ }
253
+ }
254
+ throw new Error("Error updating user: Failed to run the update SQL.");
255
+ },
256
+ async deleteUser(userId) {
257
+ // this should probably be in a db.batch but batch has problems right now in miniflare
258
+ // no multi line sql statements
259
+ await deleteRecord(db, DELETE_ACCOUNT_BY_USER_ID_SQL, [userId]);
260
+ await deleteRecord(db, DELETE_SESSION_BY_USER_ID_SQL, [userId]);
261
+ await deleteRecord(db, DELETE_USER_SQL, [userId]);
262
+ return null;
263
+ },
264
+ async linkAccount(a) {
265
+ // convert user_id to userId and provider_account_id to providerAccountId
266
+ const id = crypto.randomUUID();
267
+ const createBindings = [
268
+ id,
269
+ a.userId,
270
+ a.type,
271
+ a.provider,
272
+ a.providerAccountId,
273
+ a.refresh_token,
274
+ a.access_token,
275
+ a.expires_at,
276
+ a.token_type,
277
+ a.scope,
278
+ a.id_token,
279
+ a.session_state,
280
+ a.oauth_token ?? null,
281
+ a.oauth_token_secret ?? null,
282
+ ];
283
+ const getBindings = [id];
284
+ return await createRecord(db, CREATE_ACCOUNT_SQL, createBindings, GET_ACCOUNT_BY_ID_SQL, getBindings);
285
+ },
286
+ async unlinkAccount({ providerAccountId, provider }) {
287
+ await deleteRecord(db, DELETE_ACCOUNT_BY_PROVIDER_AND_PROVIDER_ACCOUNT_ID_SQL, [provider, providerAccountId]);
288
+ },
289
+ async createSession({ sessionToken, userId, expires }) {
290
+ const id = crypto.randomUUID();
291
+ const createBindings = [id, sessionToken, userId, expires.toISOString()];
292
+ const getBindings = [sessionToken];
293
+ const session = await createRecord(db, CREATE_SESSION_SQL, createBindings, GET_SESSION_BY_TOKEN_SQL, getBindings);
294
+ if (session)
295
+ return session;
296
+ throw new Error(`Couldn't create session`);
297
+ },
298
+ async getSessionAndUser(sessionToken) {
299
+ const session = await getRecord(db, GET_SESSION_BY_TOKEN_SQL, [sessionToken]);
300
+ // no session? no user!
301
+ if (session === null)
302
+ return null;
303
+ // this shouldnt happen, but just in case
304
+ const user = await getRecord(db, GET_USER_BY_ID_SQL, [
305
+ session.userId,
306
+ ]);
307
+ if (user === null)
308
+ return null;
309
+ return { session, user };
310
+ },
311
+ async updateSession({ sessionToken, expires }) {
312
+ // kinda strange that we have to deal with an undefined expires,
313
+ // we dont have any policy to enforce, lets just expire it now.
314
+ if (expires === undefined) {
315
+ await deleteRecord(db, DELETE_SESSION_SQL, [sessionToken]);
316
+ return null;
317
+ }
318
+ const session = await getRecord(db, GET_SESSION_BY_TOKEN_SQL, [sessionToken]);
319
+ if (!session)
320
+ return null;
321
+ session.expires = expires;
322
+ await updateRecord(db, UPDATE_SESSION_BY_SESSION_TOKEN_SQL, [
323
+ expires?.toISOString(),
324
+ sessionToken,
325
+ ]);
326
+ return await db
327
+ .prepare(UPDATE_SESSION_BY_SESSION_TOKEN_SQL)
328
+ .bind(expires?.toISOString(), sessionToken)
329
+ .first();
330
+ },
331
+ async deleteSession(sessionToken) {
332
+ await deleteRecord(db, DELETE_SESSION_SQL, [sessionToken]);
333
+ return null;
334
+ },
335
+ async createVerificationToken({ identifier, expires, token }) {
336
+ return await createRecord(db, CREATE_VERIFICATION_TOKEN_SQL, [identifier, expires.toISOString(), token], GET_VERIFICATION_TOKEN_BY_IDENTIFIER_AND_TOKEN_SQL, [identifier, token]);
337
+ },
338
+ async useVerificationToken({ identifier, token }) {
339
+ const verificationToken = await getRecord(db, GET_VERIFICATION_TOKEN_BY_IDENTIFIER_AND_TOKEN_SQL, [identifier, token]);
340
+ if (!verificationToken)
341
+ return null;
342
+ await deleteRecord(db, DELETE_VERIFICATION_TOKEN_SQL, [identifier, token]);
343
+ return verificationToken;
344
+ },
345
+ };
346
+ }
@@ -0,0 +1,10 @@
1
+ import type { D1Database } from ".";
2
+ export declare const upSQLStatements: string[];
3
+ export declare const down: string[];
4
+ /**
5
+ *
6
+ * @param db
7
+ */
8
+ declare function up(db: D1Database): Promise<void>;
9
+ export { up };
10
+ //# sourceMappingURL=migrations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["src/migrations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,GAAG,CAAA;AAEnC,eAAO,MAAM,eAAe,UAuC3B,CAAA;AAED,eAAO,MAAM,IAAI,UAKhB,CAAA;AAED;;;GAGG;AACH,iBAAe,EAAE,CAAC,EAAE,EAAE,UAAU,iBAS/B;AAED,OAAO,EAAE,EAAE,EAAE,CAAA"}
package/migrations.js ADDED
@@ -0,0 +1,62 @@
1
+ export const upSQLStatements = [
2
+ `CREATE TABLE IF NOT EXISTS "accounts" (
3
+ "id" text NOT NULL,
4
+ "userId" text NOT NULL DEFAULT NULL,
5
+ "type" text NOT NULL DEFAULT NULL,
6
+ "provider" text NOT NULL DEFAULT NULL,
7
+ "providerAccountId" text NOT NULL DEFAULT NULL,
8
+ "refresh_token" text DEFAULT NULL,
9
+ "access_token" text DEFAULT NULL,
10
+ "expires_at" number DEFAULT NULL,
11
+ "token_type" text DEFAULT NULL,
12
+ "scope" text DEFAULT NULL,
13
+ "id_token" text DEFAULT NULL,
14
+ "session_state" text DEFAULT NULL,
15
+ "oauth_token_secret" text DEFAULT NULL,
16
+ "oauth_token" text DEFAULT NULL,
17
+ PRIMARY KEY (id)
18
+ );`,
19
+ `CREATE TABLE IF NOT EXISTS "sessions" (
20
+ "id" text NOT NULL,
21
+ "sessionToken" text NOT NULL,
22
+ "userId" text NOT NULL DEFAULT NULL,
23
+ "expires" datetime NOT NULL DEFAULT NULL,
24
+ PRIMARY KEY (sessionToken)
25
+ );`,
26
+ `CREATE TABLE IF NOT EXISTS "users" (
27
+ "id" text NOT NULL DEFAULT '',
28
+ "name" text DEFAULT NULL,
29
+ "email" text DEFAULT NULL,
30
+ "emailVerified" datetime DEFAULT NULL,
31
+ "image" text DEFAULT NULL,
32
+ PRIMARY KEY (id)
33
+ );`,
34
+ `CREATE TABLE IF NOT EXISTS "verification_tokens" (
35
+ "identifier" text NOT NULL,
36
+ "token" text NOT NULL DEFAULT NULL,
37
+ "expires" datetime NOT NULL DEFAULT NULL,
38
+ PRIMARY KEY (token)
39
+ );`,
40
+ ];
41
+ export const down = [
42
+ `DROP TABLE IF EXISTS "accounts";`,
43
+ `DROP TABLE IF EXISTS "sessions";`,
44
+ `DROP TABLE IF EXISTS "users";`,
45
+ `DROP TABLE IF EXISTS "verification_token";`,
46
+ ];
47
+ /**
48
+ *
49
+ * @param db
50
+ */
51
+ async function up(db) {
52
+ // run the migration
53
+ upSQLStatements.forEach(async (sql) => {
54
+ try {
55
+ const res = await db.prepare(sql).run();
56
+ }
57
+ catch (e) {
58
+ console.error(e.cause?.message, e.message);
59
+ }
60
+ });
61
+ }
62
+ export { up };
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@auth/d1-adapter",
3
+ "version": "0.2.0",
4
+ "description": "A Cloudflare D1 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": "Josh Schlesser <josh@schlesser.dev>",
11
+ "contributors": [
12
+ "Thang Huu Vu <hi@thvu.dev>"
13
+ ],
14
+ "license": "ISC",
15
+ "keywords": [
16
+ "next-auth",
17
+ "@auth",
18
+ "Auth.js",
19
+ "next.js",
20
+ "oauth",
21
+ "d1"
22
+ ],
23
+ "type": "module",
24
+ "types": "dist/index.d.ts",
25
+ "exports": {
26
+ ".": {
27
+ "types": "./index.d.ts",
28
+ "import": "./index.js"
29
+ }
30
+ },
31
+ "files": [
32
+ "*.d.ts*",
33
+ "*.js",
34
+ "src"
35
+ ],
36
+ "private": false,
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "dependencies": {
41
+ "@auth/core": "0.15.0"
42
+ },
43
+ "devDependencies": {
44
+ "@cloudflare/workers-types": "^4.20230321.0",
45
+ "@miniflare/d1": "^2.12.2",
46
+ "better-sqlite3": "^7.0.0",
47
+ "jest": "^29.3.0",
48
+ "@auth/adapter-test": "0.0.0",
49
+ "@auth/tsconfig": "0.0.0"
50
+ },
51
+ "jest": {
52
+ "preset": "@auth/adapter-test/jest"
53
+ },
54
+ "scripts": {
55
+ "build": "tsc",
56
+ "clean": "rm -rf dist",
57
+ "test": "jest"
58
+ }
59
+ }
package/src/index.ts ADDED
@@ -0,0 +1,432 @@
1
+ /**
2
+ * <div style={{display: "flex", justifyContent: "space-between", alignItems: "center", padding: 16}}>
3
+ * <p style={{fontWeight: "normal"}}>An unofficial <a href="https://developers.cloudflare.com/d1/">Cloudflare D1</a> adapter for Auth.js / NextAuth.js.</p>
4
+ * <a href="https://developers.cloudflare.com/d1/">
5
+ * <img style={{display: "block"}} src="/img/adapters/d1.svg" width="48" />
6
+ * </a>
7
+ * </div>
8
+ *
9
+ * ## Warning
10
+ * This adapter is not developed or maintained by Clouflare and they haven't declared the D1 api stable. The author will make an effort to keep this adapter up to date.
11
+ * The adapter is compatible with the D1 api as of March 22, 2023.
12
+ *
13
+ * ## Installation
14
+ *
15
+ * ```bash npm2yarn2pnpm
16
+ * npm install next-auth @auth/d1-adapter
17
+ * ```
18
+ *
19
+ * @module @auth/d1-adapter
20
+ */
21
+
22
+ import type { D1Database as WorkerDatabase } from "@cloudflare/workers-types"
23
+ import type { D1Database as MiniflareD1Database } from "@miniflare/d1"
24
+ import type {
25
+ Adapter,
26
+ AdapterSession,
27
+ AdapterUser,
28
+ AdapterAccount,
29
+ VerificationToken as AdapterVerificationToken,
30
+ } from "@auth/core/adapters"
31
+
32
+ export { up } from "./migrations"
33
+
34
+ /**
35
+ * @type @cloudflare/workers-types.D1Database | @miniflare/d1.D1Database
36
+ */
37
+ export type D1Database = WorkerDatabase | MiniflareD1Database
38
+
39
+ // all the sqls
40
+ // USER
41
+ export const CREATE_USER_SQL = `INSERT INTO users (id, name, email, emailVerified, image) VALUES (?, ?, ?, ?, ?)`
42
+ export const GET_USER_BY_ID_SQL = `SELECT * FROM users WHERE id = ?`
43
+ export const GET_USER_BY_EMAIL_SQL = `SELECT * FROM users WHERE email = ?`
44
+ export const GET_USER_BY_ACCOUNTL_SQL = `
45
+ SELECT u.*
46
+ FROM users u JOIN accounts a ON a.userId = u.id
47
+ WHERE a.providerAccountId = ? AND a.provider = ?`
48
+ export const UPDATE_USER_BY_ID_SQL = `
49
+ UPDATE users
50
+ SET name = ?, email = ?, emailVerified = ?, image = ?
51
+ WHERE id = ? `
52
+ export const DELETE_USER_SQL = `DELETE FROM users WHERE id = ?`
53
+
54
+ // SESSION
55
+ export const CREATE_SESSION_SQL =
56
+ "INSERT INTO sessions (id, sessionToken, userId, expires) VALUES (?,?,?,?)"
57
+ export const GET_SESSION_BY_TOKEN_SQL = `
58
+ SELECT id, sessionToken, userId, expires
59
+ FROM sessions
60
+ WHERE sessionToken = ?`
61
+ export const UPDATE_SESSION_BY_SESSION_TOKEN_SQL = `UPDATE sessions SET expires = ? WHERE sessionToken = ?`
62
+ export const DELETE_SESSION_SQL = `DELETE FROM sessions WHERE sessionToken = ?`
63
+ export const DELETE_SESSION_BY_USER_ID_SQL = `DELETE FROM sessions WHERE userId = ?`
64
+
65
+ // ACCOUNT
66
+ export const CREATE_ACCOUNT_SQL = `
67
+ INSERT INTO accounts (
68
+ id, userId, type, provider,
69
+ providerAccountId, refresh_token, access_token,
70
+ expires_at, token_type, scope, id_token, session_state,
71
+ oauth_token, oauth_token_secret
72
+ )
73
+ VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)`
74
+ export const GET_ACCOUNT_BY_ID_SQL = `SELECT * FROM accounts WHERE id = ? `
75
+ export const GET_ACCOUNT_BY_PROVIDER_AND_PROVIDER_ACCOUNT_ID_SQL = `SELECT * FROM accounts WHERE provider = ? AND providerAccountId = ?`
76
+ export const DELETE_ACCOUNT_BY_PROVIDER_AND_PROVIDER_ACCOUNT_ID_SQL = `DELETE FROM accounts WHERE provider = ? AND providerAccountId = ?`
77
+ export const DELETE_ACCOUNT_BY_USER_ID_SQL = `DELETE FROM accounts WHERE userId = ?`
78
+
79
+ // VERIFICATION_TOKEN
80
+ export const GET_VERIFICATION_TOKEN_BY_IDENTIFIER_AND_TOKEN_SQL = `SELECT * FROM verification_tokens WHERE identifier = ? AND token = ?`
81
+ export const CREATE_VERIFICATION_TOKEN_SQL = `INSERT INTO verification_tokens (identifier, expires, token) VALUES (?,?,?)`
82
+ export const DELETE_VERIFICATION_TOKEN_SQL = `DELETE FROM verification_tokens WHERE identifier = ? and token = ?`
83
+
84
+ // helper functions
85
+
86
+ // isDate is borrowed from the supabase adapter, graciously
87
+ // depending on error messages ("Invalid Date") is always precarious, but probably fine for a built in native like Date
88
+ function isDate(date: any) {
89
+ return (
90
+ new Date(date).toString() !== "Invalid Date" && !isNaN(Date.parse(date))
91
+ )
92
+ }
93
+
94
+ // format is borrowed from the supabase adapter, graciously
95
+ function format<T>(obj: Record<string, any>): T {
96
+ for (const [key, value] of Object.entries(obj)) {
97
+ if (value === null) {
98
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
99
+ delete obj[key]
100
+ }
101
+
102
+ if (isDate(value)) {
103
+ obj[key] = new Date(value)
104
+ }
105
+ }
106
+
107
+ return obj as T
108
+ }
109
+
110
+ // D1 doesnt like undefined, it wants null when calling bind
111
+ function cleanBindings(bindings: any[]) {
112
+ return bindings.map((e) => (e === undefined ? null : e))
113
+ }
114
+
115
+ export async function createRecord<RecordType>(
116
+ db: D1Database,
117
+ CREATE_SQL: string,
118
+ bindings: any[],
119
+ GET_SQL: string,
120
+ getBindings: any[]
121
+ ) {
122
+ try {
123
+ bindings = cleanBindings(bindings)
124
+ await db
125
+ .prepare(CREATE_SQL)
126
+ .bind(...bindings)
127
+ .run()
128
+ return await getRecord<RecordType>(db, GET_SQL, getBindings)
129
+ } catch (e: any) {
130
+ console.error(e.message, e.cause?.message)
131
+ throw e
132
+ }
133
+ }
134
+
135
+ export async function getRecord<RecordType>(
136
+ db: D1Database,
137
+ SQL: string,
138
+ bindings: any[]
139
+ ): Promise<RecordType | null> {
140
+ try {
141
+ bindings = cleanBindings(bindings)
142
+ const res: any = await db
143
+ .prepare(SQL)
144
+ .bind(...bindings)
145
+ .first()
146
+ if (res) {
147
+ return format<RecordType>(res)
148
+ } else {
149
+ return null
150
+ }
151
+ } catch (e: any) {
152
+ console.error(e.message, e.cause?.message)
153
+ throw e
154
+ }
155
+ }
156
+
157
+ export async function updateRecord(
158
+ db: D1Database,
159
+ SQL: string,
160
+ bindings: any[]
161
+ ) {
162
+ try {
163
+ bindings = cleanBindings(bindings)
164
+ return await db
165
+ .prepare(SQL)
166
+ .bind(...bindings)
167
+ .run()
168
+ } catch (e: any) {
169
+ console.error(e.message, e.cause?.message)
170
+ throw e
171
+ }
172
+ }
173
+
174
+ export async function deleteRecord(
175
+ db: D1Database,
176
+ SQL: string,
177
+ bindings: any[]
178
+ ) {
179
+ // eslint-disable-next-line no-useless-catch
180
+ try {
181
+ bindings = cleanBindings(bindings)
182
+ await db
183
+ .prepare(SQL)
184
+ .bind(...bindings)
185
+ .run()
186
+ } catch (e: any) {
187
+ console.error(e.message, e.cause?.message)
188
+ throw e
189
+ }
190
+ }
191
+
192
+ /**
193
+ *
194
+ * ## Setup
195
+ *
196
+ * This is the D1 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.
197
+ *
198
+ * ### Configure Auth.js
199
+ *
200
+ * ```javascript title="pages/api/auth/[...nextauth].js"
201
+ * import NextAuth from "next-auth"
202
+ * import { D1Adapter, up } from "@auth/d1-adapter"
203
+ *
204
+ *
205
+ * // For more information on each option (and a full list of options) go to
206
+ * // https://authjs.dev/reference/configuration/auth-options
207
+ * export default NextAuth({
208
+ * // https://authjs.dev/reference/providers/
209
+ * providers: [],
210
+ * adapter: D1Adapter(env.db)
211
+ * ...
212
+ * })
213
+ * ```
214
+ *
215
+ * ### Migrations
216
+ *
217
+ * Somewhere in the initialization of your application you need to run the `up(env.db)` function to create the tables in D1.
218
+ * It will create 4 tables if they don't already exist:
219
+ * `accounts`, `sessions`, `users`, `verification_tokens`.
220
+ *
221
+ * The table prefix "" is not configurable at this time.
222
+ *
223
+ * You can use something like the following to attempt the migration once each time your worker starts up. Running migrations more than once will not erase your existing tables.
224
+ * ```javascript
225
+ * import { up } from "@auth/d1-adapter"
226
+ *
227
+ * let migrated = false;
228
+ * async function migrationHandle({event, resolve}) {
229
+ * if(!migrated) {
230
+ * try {
231
+ * await up(event.platform.env.db)
232
+ * migrated = true
233
+ * } catch(e) {
234
+ * console.log(e.cause.message, e.message)
235
+ * }
236
+ * }
237
+ * return resolve(event)
238
+ * }
239
+ * ```
240
+ *
241
+ *
242
+ * You can also initialize your tables manually. Look in [init.ts](https://github.com/nextauthjs/next-auth/packages/adapter-d1/src/migrations/init.ts) for the relevant sql.
243
+ * Paste and run the SQL into your D1 dashboard query tool.
244
+ *
245
+ **/
246
+ export function D1Adapter(db: D1Database): Adapter {
247
+ // we need to run migrations if we dont have the right tables
248
+
249
+ return {
250
+ async createUser(user) {
251
+ const id: string = crypto.randomUUID()
252
+ const createBindings = [
253
+ id,
254
+ user.name,
255
+ user.email,
256
+ user.emailVerified?.toISOString(),
257
+ user.image,
258
+ ]
259
+ const getBindings = [id]
260
+
261
+ const newUser = await createRecord<AdapterUser>(
262
+ db,
263
+ CREATE_USER_SQL,
264
+ createBindings,
265
+ GET_USER_BY_ID_SQL,
266
+ getBindings
267
+ )
268
+ if (newUser) return newUser
269
+ throw new Error("Error creating user: Cannot get user after creation.")
270
+ },
271
+ async getUser(id) {
272
+ return await getRecord<AdapterUser>(db, GET_USER_BY_ID_SQL, [id])
273
+ },
274
+ async getUserByEmail(email) {
275
+ return await getRecord<AdapterUser>(db, GET_USER_BY_EMAIL_SQL, [email])
276
+ },
277
+ async getUserByAccount({ providerAccountId, provider }) {
278
+ return await getRecord<AdapterUser>(db, GET_USER_BY_ACCOUNTL_SQL, [
279
+ providerAccountId,
280
+ provider,
281
+ ])
282
+ },
283
+ async updateUser(user) {
284
+ const params = await getRecord<AdapterUser>(db, GET_USER_BY_ID_SQL, [
285
+ user.id,
286
+ ])
287
+ if (params) {
288
+ // copy any properties not in the update into the existing one and use that for bind params
289
+ // covers the scenario where the user arg doesnt have all of the current users properties
290
+ Object.assign(params, user)
291
+ const res = await updateRecord(db, UPDATE_USER_BY_ID_SQL, [
292
+ params.name,
293
+ params.email,
294
+ params.emailVerified?.toISOString(),
295
+ params.image,
296
+ params.id,
297
+ ])
298
+ if (res.success) {
299
+ // we could probably just return
300
+ const user = await getRecord<AdapterUser>(db, GET_USER_BY_ID_SQL, [
301
+ params.id,
302
+ ])
303
+ if (user) return user
304
+ throw new Error(
305
+ "Error updating user: Cannot get user after updating."
306
+ )
307
+ }
308
+ }
309
+ throw new Error("Error updating user: Failed to run the update SQL.")
310
+ },
311
+ async deleteUser(userId) {
312
+ // this should probably be in a db.batch but batch has problems right now in miniflare
313
+ // no multi line sql statements
314
+ await deleteRecord(db, DELETE_ACCOUNT_BY_USER_ID_SQL, [userId])
315
+ await deleteRecord(db, DELETE_SESSION_BY_USER_ID_SQL, [userId])
316
+ await deleteRecord(db, DELETE_USER_SQL, [userId])
317
+ return null
318
+ },
319
+ async linkAccount(a) {
320
+ // convert user_id to userId and provider_account_id to providerAccountId
321
+ const id = crypto.randomUUID()
322
+ const createBindings = [
323
+ id,
324
+ a.userId,
325
+ a.type,
326
+ a.provider,
327
+ a.providerAccountId,
328
+ a.refresh_token,
329
+ a.access_token,
330
+ a.expires_at,
331
+ a.token_type,
332
+ a.scope,
333
+ a.id_token,
334
+ a.session_state,
335
+ a.oauth_token ?? null,
336
+ a.oauth_token_secret ?? null,
337
+ ]
338
+ const getBindings = [id]
339
+ return await createRecord<AdapterAccount>(
340
+ db,
341
+ CREATE_ACCOUNT_SQL,
342
+ createBindings,
343
+ GET_ACCOUNT_BY_ID_SQL,
344
+ getBindings
345
+ )
346
+ },
347
+ async unlinkAccount({ providerAccountId, provider }) {
348
+ await deleteRecord(
349
+ db,
350
+ DELETE_ACCOUNT_BY_PROVIDER_AND_PROVIDER_ACCOUNT_ID_SQL,
351
+ [provider, providerAccountId]
352
+ )
353
+ },
354
+ async createSession({ sessionToken, userId, expires }) {
355
+ const id = crypto.randomUUID()
356
+ const createBindings = [id, sessionToken, userId, expires.toISOString()]
357
+ const getBindings = [sessionToken]
358
+ const session = await createRecord<AdapterSession>(
359
+ db,
360
+ CREATE_SESSION_SQL,
361
+ createBindings,
362
+ GET_SESSION_BY_TOKEN_SQL,
363
+ getBindings
364
+ )
365
+ if (session) return session
366
+ throw new Error(`Couldn't create session`)
367
+ },
368
+ async getSessionAndUser(sessionToken) {
369
+ const session: any = await getRecord<AdapterSession>(
370
+ db,
371
+ GET_SESSION_BY_TOKEN_SQL,
372
+ [sessionToken]
373
+ )
374
+ // no session? no user!
375
+ if (session === null) return null
376
+
377
+ // this shouldnt happen, but just in case
378
+ const user = await getRecord<AdapterUser>(db, GET_USER_BY_ID_SQL, [
379
+ session.userId,
380
+ ])
381
+ if (user === null) return null
382
+
383
+ return { session, user }
384
+ },
385
+ async updateSession({ sessionToken, expires }) {
386
+ // kinda strange that we have to deal with an undefined expires,
387
+ // we dont have any policy to enforce, lets just expire it now.
388
+ if (expires === undefined) {
389
+ await deleteRecord(db, DELETE_SESSION_SQL, [sessionToken])
390
+ return null
391
+ }
392
+ const session = await getRecord<AdapterSession>(
393
+ db,
394
+ GET_SESSION_BY_TOKEN_SQL,
395
+ [sessionToken]
396
+ )
397
+ if (!session) return null
398
+ session.expires = expires
399
+ await updateRecord(db, UPDATE_SESSION_BY_SESSION_TOKEN_SQL, [
400
+ expires?.toISOString(),
401
+ sessionToken,
402
+ ])
403
+ return await db
404
+ .prepare(UPDATE_SESSION_BY_SESSION_TOKEN_SQL)
405
+ .bind(expires?.toISOString(), sessionToken)
406
+ .first()
407
+ },
408
+ async deleteSession(sessionToken) {
409
+ await deleteRecord(db, DELETE_SESSION_SQL, [sessionToken])
410
+ return null
411
+ },
412
+ async createVerificationToken({ identifier, expires, token }) {
413
+ return await createRecord(
414
+ db,
415
+ CREATE_VERIFICATION_TOKEN_SQL,
416
+ [identifier, expires.toISOString(), token],
417
+ GET_VERIFICATION_TOKEN_BY_IDENTIFIER_AND_TOKEN_SQL,
418
+ [identifier, token]
419
+ )
420
+ },
421
+ async useVerificationToken({ identifier, token }) {
422
+ const verificationToken = await getRecord<AdapterVerificationToken>(
423
+ db,
424
+ GET_VERIFICATION_TOKEN_BY_IDENTIFIER_AND_TOKEN_SQL,
425
+ [identifier, token]
426
+ )
427
+ if (!verificationToken) return null
428
+ await deleteRecord(db, DELETE_VERIFICATION_TOKEN_SQL, [identifier, token])
429
+ return verificationToken
430
+ },
431
+ }
432
+ }
@@ -0,0 +1,66 @@
1
+ import type { D1Database } from "."
2
+
3
+ export const upSQLStatements = [
4
+ `CREATE TABLE IF NOT EXISTS "accounts" (
5
+ "id" text NOT NULL,
6
+ "userId" text NOT NULL DEFAULT NULL,
7
+ "type" text NOT NULL DEFAULT NULL,
8
+ "provider" text NOT NULL DEFAULT NULL,
9
+ "providerAccountId" text NOT NULL DEFAULT NULL,
10
+ "refresh_token" text DEFAULT NULL,
11
+ "access_token" text DEFAULT NULL,
12
+ "expires_at" number DEFAULT NULL,
13
+ "token_type" text DEFAULT NULL,
14
+ "scope" text DEFAULT NULL,
15
+ "id_token" text DEFAULT NULL,
16
+ "session_state" text DEFAULT NULL,
17
+ "oauth_token_secret" text DEFAULT NULL,
18
+ "oauth_token" text DEFAULT NULL,
19
+ PRIMARY KEY (id)
20
+ );`,
21
+ `CREATE TABLE IF NOT EXISTS "sessions" (
22
+ "id" text NOT NULL,
23
+ "sessionToken" text NOT NULL,
24
+ "userId" text NOT NULL DEFAULT NULL,
25
+ "expires" datetime NOT NULL DEFAULT NULL,
26
+ PRIMARY KEY (sessionToken)
27
+ );`,
28
+ `CREATE TABLE IF NOT EXISTS "users" (
29
+ "id" text NOT NULL DEFAULT '',
30
+ "name" text DEFAULT NULL,
31
+ "email" text DEFAULT NULL,
32
+ "emailVerified" datetime DEFAULT NULL,
33
+ "image" text DEFAULT NULL,
34
+ PRIMARY KEY (id)
35
+ );`,
36
+ `CREATE TABLE IF NOT EXISTS "verification_tokens" (
37
+ "identifier" text NOT NULL,
38
+ "token" text NOT NULL DEFAULT NULL,
39
+ "expires" datetime NOT NULL DEFAULT NULL,
40
+ PRIMARY KEY (token)
41
+ );`,
42
+ ]
43
+
44
+ export const down = [
45
+ `DROP TABLE IF EXISTS "accounts";`,
46
+ `DROP TABLE IF EXISTS "sessions";`,
47
+ `DROP TABLE IF EXISTS "users";`,
48
+ `DROP TABLE IF EXISTS "verification_token";`,
49
+ ]
50
+
51
+ /**
52
+ *
53
+ * @param db
54
+ */
55
+ async function up(db: D1Database) {
56
+ // run the migration
57
+ upSQLStatements.forEach(async (sql) => {
58
+ try {
59
+ const res = await db.prepare(sql).run()
60
+ } catch (e: any) {
61
+ console.error(e.cause?.message, e.message)
62
+ }
63
+ })
64
+ }
65
+
66
+ export { up }