Compare commits

..

10 Commits

Author SHA1 Message Date
GitHub Actions
4730429a9f chore(release): bump package version(s) [skip ci] 2023-08-09 09:05:20 +00:00
Adam James
a49236ef62 fix(ts): corrected sqlite condition (#8269) 2023-08-09 10:59:08 +02:00
Balázs Orbán
96ade948ef chore(docs): fix redirect 2023-08-09 01:08:58 +02:00
GitHub Actions
550507b2d1 chore(release): bump package version(s) [skip ci] 2023-08-08 23:07:59 +00:00
Mark
1eddcf643c feat(adapters): add Kysely adapter (#5464)
* feat: kysely-adapter with PostgreSQL and MySQL support

* feat: kysely-adapter with SQLite support

* docs: add docs for kysely-adapter

* chore: cleanup

* chore: update adapter lists

* chore: update column types

* chore: remove pgcrypto install

* chore: add indexes

* chore: Object.assign and cleanup

* feat: add AuthedKysely wrapper

* docs: add Naming Conventions section

* chore: add coerceReturnData to reduce repitition

* chore: add coerceInputData to reduce repitition

* chore: move AuthedKysely export to end

* chore: cleanup

* docs: remove unused import

* feat: add support for using AuthedKysely with generated types from kysely-codegen

* docs: formatting

* chore: CodeGen --> Codegen

* docs: wording update, ts

Co-authored-by: Julius Marminge <julius0216@outlook.com>

* chore: use latest kysely version, update model

* docs: move content to source code

* chore: update deps

* chore: update logo location, add link in overview

* chore: bump kysely version

Co-authored-by: Igal Klebanov <igalklebanov@gmail.com>

* chore: update docs

Co-authored-by: Igal Klebanov <igalklebanov@gmail.com>

* chore: update docs with links to new Kysely docs

Co-authored-by: Jie Peng <dean.leehom@gmail.com>

* feat: emailVerified shouldn't have a default

Co-authored-by: Lars Graubner <lgraubner@users.noreply.github.com>

* simplify, update code

* add README.md

* clean up docs

* fix adapter name

* add to turbo

* fix test

* revert some changes

* test fixes

---------

Co-authored-by: Julius Marminge <julius0216@outlook.com>
Co-authored-by: Igal Klebanov <igalklebanov@gmail.com>
Co-authored-by: Jie Peng <dean.leehom@gmail.com>
Co-authored-by: Lars Graubner <lgraubner@users.noreply.github.com>
Co-authored-by: Balázs Orbán <info@balazsorban.com>
2023-08-09 00:01:59 +01:00
Julius Marminge
17d71a04d6 feat(adapters): support multi-project schema (#8266)
* feat: multi-project schema support

Ref: https://orm.drizzle.team/docs/goodies#multi-project-schema

* Update index.ts

* Update index.ts

* doc

* tests

---------

Co-authored-by: Balázs Orbán <info@balazsorban.com>
2023-08-08 23:59:50 +01:00
Balázs Orbán
3c65e264af chore: add drizzle to issue labeler 2023-08-09 00:44:54 +02:00
Balázs Orbán
28d8d4894d chore: add drizzle to turbo 2023-08-09 00:43:49 +02:00
Balázs Orbán
c6b98a8f08 chore: gitignore generated .npmrc files 2023-08-09 00:03:10 +02:00
Balázs Orbán
d042f933c6 fix(docs): update logo URL 2023-08-09 00:01:44 +02:00
59 changed files with 1599 additions and 213 deletions

View File

@@ -22,9 +22,12 @@ body:
options:
- "Custom adapter"
- "@auth/dgraph-adapter"
- "@auth/drizzle-adapter"
- "@auth/dynamodb-adapter"
- "@auth/drizzle-adapter"
- "@auth/fauna-adapter"
- "@auth/firebase-adapter"
- "@auth/kysely-adapter"
- "@auth/mikro-orm-adapter"
- "@auth/mongodb-adapter"
- "@auth/neo4j-adapter"

View File

@@ -3,6 +3,9 @@
dgraph:
- "@auth/dgraph-adapter"
drizzle:
- "@auth/drizzle-adapter"
dynamodb:
- "@auth/dynamodb-adapter"
@@ -12,6 +15,9 @@ fauna:
firebase:
- "@auth/firebase-adapter"
kysely:
- "@auth/kysely-adapter"
mikro-orm:
- "@auth/mikro-orm-adapter"

View File

@@ -15,6 +15,7 @@ neo4j: ["packages/adapter-neo4j/**/*"]
playgrounds: ["apps/playgrounds/**/*"]
pouchdb: ["packages/adapter-pouchdb/**/*"]
prisma: ["packages/adapter-prisma/**/*"]
kysely: ["packages/adapter-kysely/**/*"]
providers: ["packages/core/src/providers/**/*"]
sequelize: ["packages/adapter-sequelize/**/*"]
solidjs: ["packages/frameworks-solid-start/**/*"]

2
.gitignore vendored
View File

@@ -6,6 +6,8 @@
.env.development.local
.env.test.local
.env.production.local
packages/*/.npmrc
npm-debug.log*
yarn-debug.log*

View File

@@ -25,6 +25,10 @@ Using an Auth.js / NextAuth.js adapter you can connect to any database service o
<img src="/img/adapters/firebase.svg" width="40" />
<h4 class="adapter-card__title">Firebase Adapter</h4>
</a>
<a href="/reference/adapter/kysely" class="adapter-card">
<img src="/img/adapters/kysely.svg" width="40" />
<h4 class="adapter-card__title">Kysely Adapter</h4>
</a>
<a href="/reference/adapter/mikro-orm" class="adapter-card">
<img src="/img/adapters/mikro-orm.png" width="30" />
<h4 class="adapter-card__title">Mikro ORM Adapter</h4>

View File

@@ -269,6 +269,7 @@ const docusaurusConfig = {
typedocAdapter("DynamoDB"),
typedocAdapter("Fauna"),
typedocAdapter("Firebase"),
typedocAdapter("Kysely"),
typedocAdapter("Mikro ORM"),
typedocAdapter("MongoDB"),
typedocAdapter("Neo4j"),

View File

@@ -59,6 +59,7 @@ module.exports = {
{ type: "doc", id: "reference/adapter/dynamodb/index" },
{ type: "doc", id: "reference/adapter/fauna/index" },
{ type: "doc", id: "reference/adapter/firebase/index" },
{ type: "doc", id: "reference/adapter/kysely/index" },
{ type: "doc", id: "reference/adapter/mikro-orm/index" },
{ type: "doc", id: "reference/adapter/mongodb/index" },
{ type: "doc", id: "reference/adapter/neo4j/index" },

14
docs/static/img/adapters/kysely.svg vendored Normal file
View File

@@ -0,0 +1,14 @@
<svg width="132" height="132" viewBox="0 0 132 132" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_8_3)">
<rect x="2" y="2" width="128" height="128" rx="16" fill="white" />
<path
d="M41.2983 109V23.9091H46.4918V73.31H47.0735L91.9457 23.9091H98.8427L61.9062 64.1694L98.5103 109H92.0288L58.5824 67.9087L46.4918 81.2873V109H41.2983Z"
fill="black" />
</g>
<rect x="2" y="2" width="128" height="128" rx="16" stroke="#121212" stroke-width="4" />
<defs>
<clipPath id="clip0_8_3">
<rect x="2" y="2" width="128" height="128" rx="16" fill="white" />
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 637 B

View File

@@ -79,7 +79,7 @@
{
"source": "/:path(.*)",
"has": [{ "type": "host", "value": "adapters.authjs.dev" }],
"destination": "https://authjs.dev/reference/adapters/:path*"
"destination": "https://authjs.dev/reference/adapter/:path*"
},
{
"source": "/:path",

View File

@@ -1 +0,0 @@
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

View File

@@ -1 +0,0 @@
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

View File

@@ -4,7 +4,7 @@
<img height="64px" src="https://authjs.dev/img/logo/logo-sm.png" />
</a>
<a href="https://github.com/drizzle-team/drizzle-orm" target="_blank">
<img height="64px" src="https://pbs.twimg.com/profile_images/1598308842391179266/CtXrfLnk_400x400.jpg"/>
<img height="64px" src="https://authjs.dev/img/adapters/drizzle-orm.png"/>
</a>
<h3 align="center"><b>Drizzle ORM Adapter</b> - NextAuth.js / Auth.js</a></h3>
<p align="center" style="align: center;">

View File

@@ -1,6 +1,6 @@
{
"name": "@auth/drizzle-adapter",
"version": "0.1.0",
"version": "0.2.1",
"description": "Drizzle adapter for Auth.js.",
"homepage": "https://authjs.dev",
"repository": "https://github.com/nextauthjs/next-auth",

View File

@@ -16,6 +16,9 @@
* @module @auth/drizzle-adapter
*/
import { MySqlTableFn } from "drizzle-orm/mysql-core/index.js"
import { PgTableFn } from "drizzle-orm/pg-core/index.js"
import { SQLiteTableFn } from "drizzle-orm/sqlite-core/index.js"
import { mySqlDrizzleAdapter } from "./lib/mysql.js"
import { pgDrizzleAdapter } from "./lib/pg.js"
import { SQLiteDrizzleAdapter } from "./lib/sqlite.js"
@@ -24,12 +27,13 @@ import {
isPgDatabase,
isSQLiteDatabase,
SqlFlavorOptions,
TableFn,
} from "./lib/utils.js"
import type { Adapter } from "@auth/core/adapters"
/**
* Add the adapter to your `app/api/[...nextauth]/route.js` next-auth configuration object.
* Add the adapter to your `pages/api/[...nextauth].ts` next-auth configuration object.
*
* ```ts title="pages/api/auth/[...nextauth].ts"
* import NextAuth from "next-auth"
@@ -47,6 +51,10 @@ import type { Adapter } from "@auth/core/adapters"
* ],
* })
* ```
*
* :::info
* If you're using multi-project schemas, you can pass your table function as a second argument
* :::
*
* ## Setup
*
@@ -249,19 +257,20 @@ import type { Adapter } from "@auth/core/adapters"
*
**/
export function DrizzleAdapter<SqlFlavor extends SqlFlavorOptions>(
db: SqlFlavor
db: SqlFlavor,
table?: TableFn<SqlFlavor>
): Adapter {
if (isMySqlDatabase(db)) {
// We need to cast to unknown since the type overlaps (PScale is MySQL based)
return mySqlDrizzleAdapter(db)
return mySqlDrizzleAdapter(db, table as MySqlTableFn)
}
if (isPgDatabase(db)) {
return pgDrizzleAdapter(db)
return pgDrizzleAdapter(db, table as PgTableFn)
}
if (isSQLiteDatabase(db)) {
return SQLiteDrizzleAdapter(db)
return SQLiteDrizzleAdapter(db, table as SQLiteTableFn)
}
throw new Error("Unsupported database type in Auth.js Drizzle adapter.")

View File

@@ -2,75 +2,87 @@ import { and, eq } from "drizzle-orm"
import {
int,
timestamp,
mysqlTable,
mysqlTable as defaultMySqlTableFn,
primaryKey,
varchar,
MySqlTableFn,
} from "drizzle-orm/mysql-core"
import type { Adapter, AdapterAccount } from "@auth/core/adapters"
import type { MySql2Database } from "drizzle-orm/mysql2"
export const users = mysqlTable("users", {
id: varchar("id", { length: 255 }).notNull().primaryKey(),
name: varchar("name", { length: 255 }),
email: varchar("email", { length: 255 }).notNull(),
emailVerified: timestamp("emailVerified", {
mode: "date",
fsp: 3,
}).defaultNow(),
image: varchar("image", { length: 255 }),
})
export function createTables(mySqlTable: MySqlTableFn) {
const users = mySqlTable("users", {
id: varchar("id", { length: 255 }).notNull().primaryKey(),
name: varchar("name", { length: 255 }),
email: varchar("email", { length: 255 }).notNull(),
emailVerified: timestamp("emailVerified", {
mode: "date",
fsp: 3,
}).defaultNow(),
image: varchar("image", { length: 255 }),
})
export const accounts = mysqlTable(
"accounts",
{
const accounts = mySqlTable(
"accounts",
{
userId: varchar("userId", { length: 255 })
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
type: varchar("type", { length: 255 })
.$type<AdapterAccount["type"]>()
.notNull(),
provider: varchar("provider", { length: 255 }).notNull(),
providerAccountId: varchar("providerAccountId", {
length: 255,
}).notNull(),
refresh_token: varchar("refresh_token", { length: 255 }),
access_token: varchar("access_token", { length: 255 }),
expires_at: int("expires_at"),
token_type: varchar("token_type", { length: 255 }),
scope: varchar("scope", { length: 255 }),
id_token: varchar("id_token", { length: 255 }),
session_state: varchar("session_state", { length: 255 }),
},
(account) => ({
compoundKey: primaryKey(account.provider, account.providerAccountId),
})
)
const sessions = mySqlTable("sessions", {
sessionToken: varchar("sessionToken", { length: 255 })
.notNull()
.primaryKey(),
userId: varchar("userId", { length: 255 })
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
type: varchar("type", { length: 255 })
.$type<AdapterAccount["type"]>()
.notNull(),
provider: varchar("provider", { length: 255 }).notNull(),
providerAccountId: varchar("providerAccountId", { length: 255 }).notNull(),
refresh_token: varchar("refresh_token", { length: 255 }),
access_token: varchar("access_token", { length: 255 }),
expires_at: int("expires_at"),
token_type: varchar("token_type", { length: 255 }),
scope: varchar("scope", { length: 255 }),
id_token: varchar("id_token", { length: 255 }),
session_state: varchar("session_state", { length: 255 }),
},
(account) => ({
compoundKey: primaryKey(account.provider, account.providerAccountId),
})
)
export const sessions = mysqlTable("sessions", {
sessionToken: varchar("sessionToken", { length: 255 }).notNull().primaryKey(),
userId: varchar("userId", { length: 255 })
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
expires: timestamp("expires", { mode: "date" }).notNull(),
})
export const verificationTokens = mysqlTable(
"verificationToken",
{
identifier: varchar("identifier", { length: 255 }).notNull(),
token: varchar("token", { length: 255 }).notNull(),
expires: timestamp("expires", { mode: "date" }).notNull(),
},
(vt) => ({
compoundKey: primaryKey(vt.identifier, vt.token),
})
)
export const schema = { users, accounts, sessions, verificationTokens }
export type DefaultSchema = typeof schema
const verificationTokens = mySqlTable(
"verificationToken",
{
identifier: varchar("identifier", { length: 255 }).notNull(),
token: varchar("token", { length: 255 }).notNull(),
expires: timestamp("expires", { mode: "date" }).notNull(),
},
(vt) => ({
compoundKey: primaryKey(vt.identifier, vt.token),
})
)
return { users, accounts, sessions, verificationTokens }
}
export type DefaultSchema = ReturnType<typeof createTables>
export function mySqlDrizzleAdapter(
client: MySql2Database<Record<string, never>>
client: MySql2Database<Record<string, never>>,
tableFn = defaultMySqlTableFn
): Adapter {
const { users, accounts, sessions, verificationTokens } =
createTables(tableFn)
return {
async createUser(data) {
const id = crypto.randomUUID()

View File

@@ -1,71 +1,79 @@
import { and, eq } from "drizzle-orm"
import {
timestamp,
pgTable,
pgTable as defaultPgTableFn,
text,
primaryKey,
integer,
PgTableFn,
} from "drizzle-orm/pg-core"
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js"
import type { Adapter, AdapterAccount } from "@auth/core/adapters"
export const users = pgTable("users", {
id: text("id").notNull().primaryKey(),
name: text("name"),
email: text("email").notNull(),
emailVerified: timestamp("emailVerified", { mode: "date" }),
image: text("image"),
})
export function createTables(pgTable: PgTableFn) {
const users = pgTable("users", {
id: text("id").notNull().primaryKey(),
name: text("name"),
email: text("email").notNull(),
emailVerified: timestamp("emailVerified", { mode: "date" }),
image: text("image"),
})
export const accounts = pgTable(
"accounts",
{
const accounts = pgTable(
"accounts",
{
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
type: text("type").$type<AdapterAccount["type"]>().notNull(),
provider: text("provider").notNull(),
providerAccountId: text("providerAccountId").notNull(),
refresh_token: text("refresh_token"),
access_token: text("access_token"),
expires_at: integer("expires_at"),
token_type: text("token_type"),
scope: text("scope"),
id_token: text("id_token"),
session_state: text("session_state"),
},
(account) => ({
compoundKey: primaryKey(account.provider, account.providerAccountId),
})
)
const sessions = pgTable("sessions", {
sessionToken: text("sessionToken").notNull().primaryKey(),
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
type: text("type").$type<AdapterAccount["type"]>().notNull(),
provider: text("provider").notNull(),
providerAccountId: text("providerAccountId").notNull(),
refresh_token: text("refresh_token"),
access_token: text("access_token"),
expires_at: integer("expires_at"),
token_type: text("token_type"),
scope: text("scope"),
id_token: text("id_token"),
session_state: text("session_state"),
},
(account) => ({
compoundKey: primaryKey(account.provider, account.providerAccountId),
})
)
export const sessions = pgTable("sessions", {
sessionToken: text("sessionToken").notNull().primaryKey(),
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
expires: timestamp("expires", { mode: "date" }).notNull(),
})
export const verificationTokens = pgTable(
"verificationToken",
{
identifier: text("identifier").notNull(),
token: text("token").notNull(),
expires: timestamp("expires", { mode: "date" }).notNull(),
},
(vt) => ({
compoundKey: primaryKey(vt.identifier, vt.token),
})
)
export const schema = { users, accounts, sessions, verificationTokens }
export type DefaultSchema = typeof schema
const verificationTokens = pgTable(
"verificationToken",
{
identifier: text("identifier").notNull(),
token: text("token").notNull(),
expires: timestamp("expires", { mode: "date" }).notNull(),
},
(vt) => ({
compoundKey: primaryKey(vt.identifier, vt.token),
})
)
return { users, accounts, sessions, verificationTokens }
}
export type DefaultSchema = ReturnType<typeof createTables>
export function pgDrizzleAdapter(
client: PostgresJsDatabase<Record<string, never>>
client: PostgresJsDatabase<Record<string, never>>,
tableFn = defaultPgTableFn
): Adapter {
const { users, accounts, sessions, verificationTokens } =
createTables(tableFn)
return {
async createUser(data) {
return await client

View File

@@ -1,70 +1,78 @@
import { eq, and } from "drizzle-orm"
import {
integer,
sqliteTable,
sqliteTable as defaultSqliteTableFn,
text,
primaryKey,
BaseSQLiteDatabase,
SQLiteTableFn,
} from "drizzle-orm/sqlite-core"
import type { Adapter, AdapterAccount } from "@auth/core/adapters"
export const users = sqliteTable("users", {
id: text("id").notNull().primaryKey(),
name: text("name"),
email: text("email").notNull(),
emailVerified: integer("emailVerified", { mode: "timestamp_ms" }),
image: text("image"),
})
export function createTables(sqliteTable: SQLiteTableFn) {
const users = sqliteTable("users", {
id: text("id").notNull().primaryKey(),
name: text("name"),
email: text("email").notNull(),
emailVerified: integer("emailVerified", { mode: "timestamp_ms" }),
image: text("image"),
})
export const accounts = sqliteTable(
"accounts",
{
const accounts = sqliteTable(
"accounts",
{
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
type: text("type").$type<AdapterAccount["type"]>().notNull(),
provider: text("provider").notNull(),
providerAccountId: text("providerAccountId").notNull(),
refresh_token: text("refresh_token"),
access_token: text("access_token"),
expires_at: integer("expires_at"),
token_type: text("token_type"),
scope: text("scope"),
id_token: text("id_token"),
session_state: text("session_state"),
},
(account) => ({
compoundKey: primaryKey(account.provider, account.providerAccountId),
})
)
const sessions = sqliteTable("sessions", {
sessionToken: text("sessionToken").notNull().primaryKey(),
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
type: text("type").$type<AdapterAccount["type"]>().notNull(),
provider: text("provider").notNull(),
providerAccountId: text("providerAccountId").notNull(),
refresh_token: text("refresh_token"),
access_token: text("access_token"),
expires_at: integer("expires_at"),
token_type: text("token_type"),
scope: text("scope"),
id_token: text("id_token"),
session_state: text("session_state"),
},
(account) => ({
compoundKey: primaryKey(account.provider, account.providerAccountId),
})
)
export const sessions = sqliteTable("sessions", {
sessionToken: text("sessionToken").notNull().primaryKey(),
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
})
export const verificationTokens = sqliteTable(
"verificationToken",
{
identifier: text("identifier").notNull(),
token: text("token").notNull(),
expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
},
(vt) => ({
compoundKey: primaryKey(vt.identifier, vt.token),
})
)
export const schema = { users, accounts, sessions, verificationTokens }
export type DefaultSchema = typeof schema
const verificationTokens = sqliteTable(
"verificationToken",
{
identifier: text("identifier").notNull(),
token: text("token").notNull(),
expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
},
(vt) => ({
compoundKey: primaryKey(vt.identifier, vt.token),
})
)
return { users, accounts, sessions, verificationTokens }
}
export type DefaultSchema = ReturnType<typeof createTables>
export function SQLiteDrizzleAdapter(
client: BaseSQLiteDatabase<any, any>
client: BaseSQLiteDatabase<any, any>,
tableFn = defaultSqliteTableFn
): Adapter {
const { users, accounts, sessions, verificationTokens } =
createTables(tableFn)
return {
createUser(data) {
return client

View File

@@ -2,9 +2,9 @@ import { MySqlDatabase } from "drizzle-orm/mysql-core"
import { PgDatabase } from "drizzle-orm/pg-core"
import { BaseSQLiteDatabase } from "drizzle-orm/sqlite-core"
import type { AnyMySqlTable } from "drizzle-orm/mysql-core"
import type { AnyPgTable } from "drizzle-orm/pg-core"
import type { AnySQLiteTable } from "drizzle-orm/sqlite-core"
import type { AnyMySqlTable, MySqlTableFn } from "drizzle-orm/mysql-core"
import type { AnyPgTable, PgTableFn } from "drizzle-orm/pg-core"
import type { AnySQLiteTable, SQLiteTableFn } from "drizzle-orm/sqlite-core"
import type { DefaultSchema as PgSchema } from "./pg.js"
import type { DefaultSchema as MySqlSchema } from "./mysql.js"
import type { DefaultSchema as SQLiteSchema } from "./sqlite.js"
@@ -32,6 +32,14 @@ export type ClientFlavors<Flavor> = Flavor extends AnyMySqlDatabase
? MinimumSchema["sqlite"]
: never
export type TableFn<Flavor> = Flavor extends AnyMySqlDatabase
? MySqlTableFn
: Flavor extends AnyPgDatabase
? PgTableFn
: Flavor extends AnySQLiteDatabase
? SQLiteTableFn
: AnySQLiteTable
export function isMySqlDatabase(
db: any
): db is MySqlDatabase<any, any, any, any> {

View File

@@ -0,0 +1,13 @@
import type { Config } from "drizzle-kit"
export default {
schema: "./tests/mysql/schema.ts",
out: "./tests/mysql/.drizzle",
driver: "mysql2",
dbCredentials: {
host: "localhost",
user: "root",
password: "password",
database: "next-auth",
},
} satisfies Config

View File

@@ -0,0 +1,71 @@
import { runBasicTests } from "../../../adapter-test"
import { DrizzleAdapter } from "../../src"
import { db, sessions, verificationTokens, accounts, users } from "./schema"
import { eq, and } from "drizzle-orm"
import { fixtures } from "../fixtures"
globalThis.crypto ??= require("node:crypto").webcrypto
runBasicTests({
adapter: DrizzleAdapter(db),
fixtures,
db: {
connect: async () => {
await Promise.all([
db.delete(sessions),
db.delete(accounts),
db.delete(verificationTokens),
db.delete(users),
])
},
disconnect: async () => {
await Promise.all([
db.delete(sessions),
db.delete(accounts),
db.delete(verificationTokens),
db.delete(users),
])
},
user: async (id) => {
const user = await db
.select()
.from(users)
.where(eq(users.id, id))
.then((res) => res[0] ?? null)
return user
},
session: async (sessionToken) => {
const session = await db
.select()
.from(sessions)
.where(eq(sessions.sessionToken, sessionToken))
.then((res) => res[0] ?? null)
return session
},
account: (provider_providerAccountId) => {
const account = db
.select()
.from(accounts)
.where(
eq(
accounts.providerAccountId,
provider_providerAccountId.providerAccountId
)
)
.then((res) => res[0] ?? null)
return account
},
verificationToken: (identifier_token) =>
db
.select()
.from(verificationTokens)
.where(
and(
eq(verificationTokens.token, identifier_token.token),
eq(verificationTokens.identifier, identifier_token.identifier)
)
)
.then((res) => res[0]) ?? null,
},
})

View File

@@ -0,0 +1,19 @@
import { mysqlTableCreator } from "drizzle-orm/mysql-core"
import { drizzle } from "drizzle-orm/mysql2"
import { createPool } from "mysql2"
import { createTables } from "../../src/lib/mysql"
const poolConnection = createPool({
host: "localhost",
user: "root",
password: "password",
database: "next-auth",
})
const mysqlTable = mysqlTableCreator((name) => `foobar_${name}`)
export const { users, accounts, sessions, verificationTokens } =
createTables(mysqlTable)
export const schema = { users, accounts, sessions, verificationTokens }
export const db = drizzle(poolConnection, { schema })

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env bash
echo "Initializing container for MySQL tests."
MYSQL_DATABASE=next-auth
MYSQL_ROOT_PASSWORD=password
MYSQL_CONTAINER_NAME=next-auth-mysql-test
docker run -d --rm \
-e MYSQL_DATABASE=${MYSQL_DATABASE} \
-e MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} \
--name "${MYSQL_CONTAINER_NAME}" \
-p 3306:3306 \
mysql:8 \
--default-authentication-plugin=mysql_native_password
echo "Waiting 15 sec for db to start..." && sleep 15
drizzle-kit generate:mysql --config=./tests/mysql/drizzle.config.ts
drizzle-kit push:mysql --config=./tests/mysql/drizzle.config.ts
jest ./tests/mysql/index.test.ts --forceExit
docker stop ${MYSQL_CONTAINER_NAME}

View File

@@ -1,20 +1,7 @@
import type { AdapterAccount } from "@auth/core/adapters"
import {
mysqlTable,
varchar,
timestamp,
int,
primaryKey,
} from "drizzle-orm/mysql-core"
import { mysqlTable } from "drizzle-orm/mysql-core"
import { drizzle } from "drizzle-orm/mysql2"
import { createPool } from "mysql2"
import {
users,
accounts,
sessions,
verificationTokens,
schema,
} from "../../src/lib/mysql"
import { createTables } from "../../src/lib/mysql"
const poolConnection = createPool({
host: "localhost",
@@ -23,7 +10,8 @@ const poolConnection = createPool({
database: "next-auth",
})
export { users, accounts, sessions, verificationTokens }
export const db = drizzle(poolConnection, {
schema: schema,
})
export const { users, accounts, sessions, verificationTokens } =
createTables(mysqlTable)
export const schema = { users, accounts, sessions, verificationTokens }
export const db = drizzle(poolConnection, { schema })

View File

@@ -0,0 +1,13 @@
import type { Config } from "drizzle-kit"
export default {
schema: "./tests/pg/schema.ts",
out: "./tests/pg/.drizzle",
dbCredentials: {
database: "nextauth",
host: "nextauth",
user: "nextauth",
password: "nextauth",
port: 5432,
},
} satisfies Config

View File

@@ -0,0 +1,65 @@
import { runBasicTests } from "../../../adapter-test"
import { DrizzleAdapter } from "../../src"
import { db, accounts, sessions, users, verificationTokens } from "./schema"
import { eq, and } from "drizzle-orm"
import { fixtures } from "../fixtures"
globalThis.crypto ??= require("node:crypto").webcrypto
runBasicTests({
adapter: DrizzleAdapter(db),
fixtures,
db: {
connect: async () => {
await Promise.all([
db.delete(sessions),
db.delete(accounts),
db.delete(verificationTokens),
db.delete(users),
])
},
disconnect: async () => {
await Promise.all([
db.delete(sessions),
db.delete(accounts),
db.delete(verificationTokens),
db.delete(users),
])
},
user: async (id) =>
db
.select()
.from(users)
.where(eq(users.id, id))
.then((res) => res[0] ?? null),
session: (sessionToken) =>
db
.select()
.from(sessions)
.where(eq(sessions.sessionToken, sessionToken))
.then((res) => res[0] ?? null),
account: (provider_providerAccountId) => {
return db
.select()
.from(accounts)
.where(
eq(
accounts.providerAccountId,
provider_providerAccountId.providerAccountId
)
)
.then((res) => res[0] ?? null)
},
verificationToken: (identifier_token) =>
db
.select()
.from(verificationTokens)
.where(
and(
eq(verificationTokens.token, identifier_token.token),
eq(verificationTokens.identifier, identifier_token.identifier)
)
)
.then((res) => res[0] ?? null),
},
})

View File

@@ -0,0 +1,10 @@
import { migrate } from "drizzle-orm/postgres-js/migrator"
import { db } from "./schema"
const migrator = async () => {
await migrate(db, { migrationsFolder: "./tests/pg/.drizzle" })
}
migrator()
.then(() => process.exit(0))
.catch(() => process.exit(1))

View File

@@ -0,0 +1,17 @@
import { drizzle } from "drizzle-orm/postgres-js"
import postgres from "postgres"
import { createTables } from "../../src/lib/pg"
import { pgTableCreator } from "drizzle-orm/pg-core"
const connectionString = "postgres://nextauth:nextauth@localhost:5432/nextauth"
const sql = postgres(connectionString, { max: 1 })
const pgTable = pgTableCreator((name) => `foobar_${name}`)
export const { users, accounts, sessions, verificationTokens } =
createTables(pgTable)
export const schema = { users, accounts, sessions, verificationTokens }
export const db = drizzle(sql, {
schema,
})

View File

@@ -0,0 +1,25 @@
#!/usr/bin/env bash
echo "Initializing container for PostgreSQL tests."
PGUSER=nextauth
PGPASSWORD=nextauth
PGDATABASE=nextauth
PGPORT=5432
PG_CONTAINER_NAME=next-auth-postgres-test
docker run -d --rm \
-e POSTGRES_USER=${PGUSER} \
-e POSTGRES_PASSWORD=${PGUSER} \
-e POSTGRES_DB=${PGDATABASE} \
-e POSTGRES_HOST_AUTH_METHOD=trust \
--name "${PG_CONTAINER_NAME}" \
-p ${PGPORT}:5432 \
postgres:15.3
echo "Waiting 15 sec for db to start..." && sleep 15
drizzle-kit generate:pg --config=./tests/pg/drizzle.config.ts
npx tsx ./tests/pg/migrator.ts
jest ./tests/pg/index.test.ts --forceExit
docker stop ${PG_CONTAINER_NAME}

View File

@@ -1,11 +1,15 @@
import { drizzle } from "drizzle-orm/postgres-js"
import postgres from "postgres"
import { users, accounts, sessions, verificationTokens } from "../../src/lib/pg"
import { createTables } from "../../src/lib/pg"
import { pgTable } from "drizzle-orm/pg-core"
const connectionString = "postgres://nextauth:nextauth@localhost:5432/nextauth"
const sql = postgres(connectionString, { max: 1 })
export const { users, accounts, sessions, verificationTokens } =
createTables(pgTable)
export const schema = { users, accounts, sessions, verificationTokens }
export const db = drizzle(sql, {
schema: { users, accounts, sessions, verificationTokens },
schema,
})
export { users, accounts, sessions, verificationTokens }

View File

@@ -0,0 +1,10 @@
import type { Config } from "drizzle-kit"
export default {
schema: "./tests/sqlite/schema.ts",
out: "./tests/sqlite/.drizzle",
driver: "better-sqlite",
dbCredentials: {
url: "./db.sqlite",
},
} satisfies Config

View File

@@ -0,0 +1,60 @@
import { runBasicTests } from "../../../adapter-test"
import { DrizzleAdapter } from "../../src"
import { db, accounts, sessions, users, verificationTokens } from "./schema"
import { eq, and } from "drizzle-orm"
globalThis.crypto ??= require("node:crypto").webcrypto
runBasicTests({
adapter: DrizzleAdapter(db),
db: {
connect: async () => {
await Promise.all([
db.delete(sessions),
db.delete(accounts),
db.delete(verificationTokens),
db.delete(users),
])
},
disconnect: async () => {
await Promise.all([
db.delete(sessions),
db.delete(accounts),
db.delete(verificationTokens),
db.delete(users),
])
},
user: (id) => db.select().from(users).where(eq(users.id, id)).get() ?? null,
session: (sessionToken) =>
db
.select()
.from(sessions)
.where(eq(sessions.sessionToken, sessionToken))
.get() ?? null,
account: (provider_providerAccountId) => {
return (
db
.select()
.from(accounts)
.where(
eq(
accounts.providerAccountId,
provider_providerAccountId.providerAccountId
)
)
.get() ?? null
)
},
verificationToken: (identifier_token) =>
db
.select()
.from(verificationTokens)
.where(
and(
eq(verificationTokens.token, identifier_token.token),
eq(verificationTokens.identifier, identifier_token.identifier)
)
)
.get() ?? null,
},
})

View File

@@ -0,0 +1,14 @@
import { drizzle } from "drizzle-orm/better-sqlite3"
import Database from "better-sqlite3"
import { createTables } from "../../src/lib/sqlite"
import { sqliteTableCreator } from "drizzle-orm/sqlite-core"
const sqlite = new Database("db.sqlite")
const sqliteTable = sqliteTableCreator((name) => `foobar_${name}`)
export const { users, accounts, sessions, verificationTokens } =
createTables(sqliteTable)
export const schema = { users, accounts, sessions, verificationTokens }
export const db = drizzle(sqlite, { schema })

View File

@@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -eu
echo "Running SQLite tests."
rm -f db.sqlite
drizzle-kit generate:sqlite --config=./tests/sqlite/drizzle.config.ts
drizzle-kit push:sqlite --config=./tests/sqlite/drizzle.config.ts
jest ./tests/sqlite/index.test.ts --forceExit

View File

@@ -1,20 +1,12 @@
import { drizzle } from "drizzle-orm/better-sqlite3"
import Database from "better-sqlite3"
import {
users,
accounts,
sessions,
verificationTokens,
} from "../../src/lib/sqlite"
import { createTables } from "../../src/lib/sqlite"
import { sqliteTable } from "drizzle-orm/sqlite-core"
const sqlite = new Database("db.sqlite")
export { users, accounts, sessions, verificationTokens }
export const db = drizzle(sqlite, {
schema: {
users,
accounts,
sessions,
verificationTokens,
},
})
export const { users, accounts, sessions, verificationTokens } =
createTables(sqliteTable)
export const schema = { users, accounts, sessions, verificationTokens }
export const db = drizzle(sqlite, { schema })

View File

@@ -1 +0,0 @@
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

View File

@@ -1 +0,0 @@
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

View File

@@ -1 +0,0 @@
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

View File

@@ -0,0 +1,28 @@
<p align="center">
<br/>
<a href="https://authjs.dev" target="_blank">
<img height="64px" src="https://authjs.dev/img/logo/logo-sm.png" />
</a>
<a href="https://kysely.io" target="_blank">
<img height="64px" src="https://authjs.dev/img/adapters/kysely.svg"/>
</a>
<h3 align="center"><b>Kysely Adapter</b> - NextAuth.js / Auth.js</a></h3>
<p align="center" style="align: center;">
<a href="https://npm.im/@auth/kysely-adapter">
<img src="https://img.shields.io/badge/TypeScript-blue?style=flat-square" alt="TypeScript" />
</a>
<a href="https://npm.im/@auth/kysely-adapter">
<img alt="npm" src="https://img.shields.io/npm/v/@auth/kysely-adapter?color=green&label=@auth/kysely-adapter&style=flat-square">
</a>
<a href="https://www.npmtrends.com/@auth/kysely-adapter">
<img src="https://img.shields.io/npm/dm/@auth/kysely-adapter?label=%20downloads&style=flat-square" alt="Downloads" />
</a>
<a href="https://github.com/nextauthjs/next-auth/stargazers">
<img src="https://img.shields.io/github/stars/nextauthjs/next-auth?style=flat-square" alt="Github Stars" />
</a>
</p>
</p>
---
Check out the documentation at [authjs.dev](https://authjs.dev/reference/adapter/kysely).

View File

@@ -0,0 +1,56 @@
{
"name": "@auth/kysely-adapter",
"version": "0.1.0",
"description": "Kysely adapter for Auth.js",
"homepage": "https://authjs.dev/reference/adapter/kysely",
"repository": "https://github.com/nextauthjs/next-auth",
"bugs": {
"url": "https://github.com/nextauthjs/next-auth/issues"
},
"author": "mwojtul <mark.wojtul@gmail.com> (https://github.com/mwojtul)",
"license": "ISC",
"keywords": [
"authjs",
"next-auth",
"next.js",
"oauth",
"kysely"
],
"type": "module",
"types": "./index.d.ts",
"files": [
"*.js",
"*.d.ts*",
"src"
],
"exports": {
".": {
"types": "./index.d.ts",
"import": "./index.js"
}
},
"scripts": {
"build": "tsc",
"test": "./tests/test.sh"
},
"dependencies": {
"@auth/core": "workspace:*"
},
"peerDependencies": {
"kysely": "^0.26.1"
},
"devDependencies": {
"@next-auth/adapter-test": "workspace:*",
"@next-auth/tsconfig": "workspace:*",
"@types/better-sqlite3": "^7.6.3",
"@types/pg": "^8.6.5",
"better-sqlite3": "^8.2.0",
"jest": "^27.4.3",
"kysely": "^0.24.2",
"mysql2": "^3.2.0",
"pg": "^8.10.0"
},
"jest": {
"preset": "@next-auth/adapter-test/jest"
}
}

View File

@@ -0,0 +1,472 @@
/**
* <div style={{display: "flex", justifyContent: "space-between", alignItems: "center", padding: 16}}>
* <p style={{fontWeight: "normal"}}>Official <a href="https://kysely.dev/">Kysely</a> adapter for Auth.js / NextAuth.js.</p>
* <a href="https://kysely.dev/">
* <img style={{display: "block"}} src="/img/adapters/kysely.svg" width="38" />
* </a>
* </div>
*
* ## Installation
*
* ```bash npm2yarn2pnpm
* npm install kysely @auth/kysely-adapter
* ```
*
* @module @auth/kysely-adapter
*/
import { Kysely, SqliteAdapter } from "kysely"
import type { Adapter } from "@auth/core/adapters"
import type { GeneratedAlways } from "kysely"
export interface Database {
User: {
id: GeneratedAlways<string>
name: string | null
email: string
emailVerified: Date | string | null
image: string | null
}
Account: {
id: GeneratedAlways<string>
userId: string
type: string
provider: string
providerAccountId: string
refresh_token: string | null
access_token: string | null
expires_at: number | null
token_type: string | null
scope: string | null
id_token: string | null
session_state: string | null
}
Session: {
id: GeneratedAlways<string>
userId: string
sessionToken: string
expires: Date | string
}
VerificationToken: {
identifier: string
token: string
expires: Date | string
}
}
export const format = {
/**
* Helper function to return the passed in object and its specified prop
* as an ISO string if SQLite is being used.
*/
from<T extends Partial<Record<K, Date | null>>, K extends keyof T>(
data: T,
key: K,
isSqlite: boolean
) {
const value = data[key]
return {
...data,
[key]: value && isSqlite ? value.toISOString() : value,
}
},
to,
}
type ReturnData<T = never> = Record<string, Date | string | T>
/**
* Helper function to return the passed in object and its specified prop as a date.
* Necessary because SQLite has no date type so we store dates as ISO strings.
*/
function to<T extends Partial<ReturnData>, K extends keyof T>(
data: T,
key: K
): Omit<T, K> & Record<K, Date>
function to<T extends Partial<ReturnData<null>>, K extends keyof T>(
data: T,
key: K
): Omit<T, K> & Record<K, Date | null>
function to<T extends Partial<ReturnData<null>>, K extends keyof T>(
data: T,
key: K
) {
const value = data[key]
return Object.assign(data, {
[key]: value && typeof value === "string" ? new Date(value) : value,
})
}
/**
*
* ## Setup
*
* This adapter supports the same first party dialects that Kysely (as of v0.24.2) supports: PostgreSQL, MySQL, and SQLite. The examples below use PostgreSQL with the [pg](https://www.npmjs.com/package/pg) client.
*
* ```bash npm2yarn2pnpm
* npm install pg
* npm install --save-dev @types/pg
* ```
*
* ```typescript title="pages/api/auth/[...nextauth].ts"
* import NextAuth from "next-auth"
* import GoogleProvider from "next-auth/providers/google"
* import { KyselyAdapter } from "@auth/kysely-adapter"
* import { db } from "../../../db"
*
* export default NextAuth({
* adapter: KyselyAdapter(db),
* providers: [
* GoogleProvider({
* clientId: process.env.GOOGLE_CLIENT_ID,
* clientSecret: process.env.GOOGLE_CLIENT_SECRET,
* }),
* ],
* })
* ```
*
* Kysely's constructor requires a database interface that contains an entry with an interface for each of your tables. You can define these types manually, or use `kysely-codegen` / `prisma-kysely` to automatically generate them. Check out the default [models](/reference/adapters#models) required by Auth.js.
*
* ```ts title="db.ts"
* import { PostgresDialect } from "kysely"
* import { Pool } from "pg"
*
* // This adapter exports a wrapper of the original `Kysely` class called `KyselyAuth`,
* // that can be used to provide additional type-safety.
* // While using it isn't required, it is recommended as it will verify
* // that the database interface has all the fields that Auth.js expects.
* import { KyselyAuth } from "@auth/kysely-adapter"
*
* import type { GeneratedAlways } from "kysely"
*
* interface Database {
* User: {
* id: GeneratedAlways<string>
* name: string | null
* email: string
* emailVerified: Date | null
* image: string | null
* }
* Account: {
* id: GeneratedAlways<string>
* userId: string
* type: string
* provider: string
* providerAccountId: string
* refresh_token: string | null
* access_token: string | null
* expires_at: number | null
* token_type: string | null
* scope: string | null
* id_token: string | null
* session_state: string | null
* }
* Session: {
* id: GeneratedAlways<string>
* userId: string
* sessionToken: string
* expires: Date
* }
* VerificationToken: {
* identifier: string
* token: string
* expires: Date
* }
* }
*
* export const db = new KyselyAuth<Database>({
* dialect: new PostgresDialect({
* pool: new Pool({
* host: process.env.DATABASE_HOST,
* database: process.env.DATABASE_NAME,
* user: process.env.DATABASE_USER,
* password: process.env.DATABASE_PASSWORD,
* }),
* }),
* })
```
*
*
* :::note
* An alternative to manually defining types is generating them from the database schema using [kysely-codegen](https://github.com/RobinBlomberg/kysely-codegen), or from Prisma schemas using [prisma-kysely](https://github.com/valtyr/prisma-kysely). When using generated types with `KyselyAuth`, import `Codegen` and pass it as the second generic arg:
* ```ts
* import type { Codegen } from "@auth/kysely-adapter"
* new KyselyAuth<Database, Codegen>(...)
* ```
* :::
* ### Schema
* ```ts title="db/migrations/001_create_db.ts"
* import { Kysely, sql } from "kysely"
*
* export async function up(db: Kysely<any>): Promise<void> {
* await db.schema
* .createTable("User")
* .addColumn("id", "uuid", (col) =>
* col.primaryKey().defaultTo(sql`gen_random_uuid()`)
* )
* .addColumn("name", "text")
* .addColumn("email", "text", (col) => col.unique().notNull())
* .addColumn("emailVerified", "timestamptz")
* .addColumn("image", "text")
* .execute()
*
* await db.schema
* .createTable("Account")
* .addColumn("id", "uuid", (col) =>
* col.primaryKey().defaultTo(sql`gen_random_uuid()`)
* )
* .addColumn("userId", "uuid", (col) =>
* col.references("User.id").onDelete("cascade").notNull()
* )
* .addColumn("type", "text", (col) => col.notNull())
* .addColumn("provider", "text", (col) => col.notNull())
* .addColumn("providerAccountId", "text", (col) => col.notNull())
* .addColumn("refresh_token", "text")
* .addColumn("access_token", "text")
* .addColumn("expires_at", "bigint")
* .addColumn("token_type", "text")
* .addColumn("scope", "text")
* .addColumn("id_token", "text")
* .addColumn("session_state", "text")
* .execute()
*
* await db.schema
* .createTable("Session")
* .addColumn("id", "uuid", (col) =>
* col.primaryKey().defaultTo(sql`gen_random_uuid()`)
* )
* .addColumn("userId", "uuid", (col) =>
* col.references("User.id").onDelete("cascade").notNull()
* )
* .addColumn("sessionToken", "text", (col) => col.notNull().unique())
* .addColumn("expires", "timestamptz", (col) => col.notNull())
* .execute()
*
* await db.schema
* .createTable("VerificationToken")
* .addColumn("identifier", "text", (col) => col.notNull())
* .addColumn("token", "text", (col) => col.notNull().unique())
* .addColumn("expires", "timestamptz", (col) => col.notNull())
* .execute()
*
* await db.schema
* .createIndex("Account_userId_index")
* .on("Account")
* .column("userId")
* .execute()
*
* await db.schema
* .createIndex("Session_userId_index")
* .on("Session")
* .column("userId")
* .execute()
* }
*
* export async function down(db: Kysely<any>): Promise<void> {
* await db.schema.dropTable("Account").ifExists().execute()
* await db.schema.dropTable("Session").ifExists().execute()
* await db.schema.dropTable("User").ifExists().execute()
* await db.schema.dropTable("VerificationToken").ifExists().execute()
* }
* ```
* > This schema is adapted for use in Kysely and is based upon our main [schema](/reference/adapters/models).
*
* For more information about creating and running migrations with Kysely, refer to the [Kysely migrations documentation](https://kysely.dev/docs/migrations).
*
* ### Naming conventions
* If mixed snake_case and camelCase column names is an issue for you and/or your underlying database system, we recommend using Kysely's `CamelCasePlugin` ([see the documentation here](https://kysely-org.github.io/kysely/classes/CamelCasePlugin.html)) feature to change the field names. This won't affect NextAuth.js, but will allow you to have consistent casing when using Kysely.
*/
export function KyselyAdapter(db: Kysely<Database>): Adapter {
const { adapter } = db.getExecutor()
const supportsReturning = adapter.supportsReturning
const isSqlite = adapter instanceof SqliteAdapter
return {
async createUser(data) {
const userData = format.from(data, "emailVerified", isSqlite)
const query = db.insertInto("User").values(userData)
const result = supportsReturning
? await query.returningAll().executeTakeFirstOrThrow()
: await query.executeTakeFirstOrThrow().then(async () => {
return await db
.selectFrom("User")
.selectAll()
.where("email", "=", `${userData.email}`)
.executeTakeFirstOrThrow()
})
return to(result, "emailVerified")
},
async getUser(id) {
const result =
(await db
.selectFrom("User")
.selectAll()
.where("id", "=", id)
.executeTakeFirst()) ?? null
if (!result) return null
return to(result, "emailVerified")
},
async getUserByEmail(email) {
const result =
(await db
.selectFrom("User")
.selectAll()
.where("email", "=", email)
.executeTakeFirst()) ?? null
if (!result) return null
return to(result, "emailVerified")
},
async getUserByAccount({ providerAccountId, provider }) {
const result =
(await db
.selectFrom("User")
.innerJoin("Account", "User.id", "Account.userId")
.selectAll("User")
.where("Account.providerAccountId", "=", providerAccountId)
.where("Account.provider", "=", provider)
.executeTakeFirst()) ?? null
if (!result) return null
return to(result, "emailVerified")
},
async updateUser({ id, ...user }) {
if (!id) throw new Error("User not found")
const userData = format.from(user, "emailVerified", isSqlite)
const query = db.updateTable("User").set(userData).where("id", "=", id)
const result = supportsReturning
? await query.returningAll().executeTakeFirstOrThrow()
: await query.executeTakeFirstOrThrow().then(async () => {
return await db
.selectFrom("User")
.selectAll()
.where("id", "=", id)
.executeTakeFirstOrThrow()
})
return to(result, "emailVerified")
},
async deleteUser(userId) {
await db.deleteFrom("User").where("User.id", "=", userId).execute()
},
async linkAccount(account) {
await db.insertInto("Account").values(account).executeTakeFirstOrThrow()
},
async unlinkAccount({ providerAccountId, provider }) {
await db
.deleteFrom("Account")
.where("Account.providerAccountId", "=", providerAccountId)
.where("Account.provider", "=", provider)
.executeTakeFirstOrThrow()
},
async createSession(data) {
const sessionData = format.from(data, "expires", isSqlite)
const query = db.insertInto("Session").values(sessionData)
const result = supportsReturning
? await query.returningAll().executeTakeFirstOrThrow()
: await (async () => {
await query.executeTakeFirstOrThrow()
return await db
.selectFrom("Session")
.selectAll()
.where("sessionToken", "=", sessionData.sessionToken)
.executeTakeFirstOrThrow()
})()
return to(result, "expires")
},
async getSessionAndUser(sessionTokenArg) {
const result = await db
.selectFrom("Session")
.innerJoin("User", "User.id", "Session.userId")
.selectAll("User")
.select([
"Session.id as sessionId",
"Session.userId",
"Session.sessionToken",
"Session.expires",
])
.where("Session.sessionToken", "=", sessionTokenArg)
.executeTakeFirst()
if (!result) return null
const { sessionId: id, userId, sessionToken, expires, ...user } = result
return {
user: to({ ...user }, "emailVerified"),
session: to({ id, userId, sessionToken, expires }, "expires"),
}
},
async updateSession(session) {
const sessionData = format.from(session, "expires", isSqlite)
const query = db
.updateTable("Session")
.set(sessionData)
.where("Session.sessionToken", "=", session.sessionToken)
const result = supportsReturning
? await query.returningAll().executeTakeFirstOrThrow()
: await query.executeTakeFirstOrThrow().then(async () => {
return await db
.selectFrom("Session")
.selectAll()
.where("Session.sessionToken", "=", sessionData.sessionToken)
.executeTakeFirstOrThrow()
})
return to(result, "expires")
},
async deleteSession(sessionToken) {
await db
.deleteFrom("Session")
.where("Session.sessionToken", "=", sessionToken)
.executeTakeFirstOrThrow()
},
async createVerificationToken(verificationToken) {
const verificationTokenData = format.from(
verificationToken,
"expires",
isSqlite
)
const query = db
.insertInto("VerificationToken")
.values(verificationTokenData)
const result = supportsReturning
? await query.returningAll().executeTakeFirstOrThrow()
: await query.executeTakeFirstOrThrow().then(async () => {
return await db
.selectFrom("VerificationToken")
.selectAll()
.where("token", "=", verificationTokenData.token)
.executeTakeFirstOrThrow()
})
return to(result, "expires")
},
async useVerificationToken({ identifier, token }) {
const query = db
.deleteFrom("VerificationToken")
.where("VerificationToken.token", "=", token)
.where("VerificationToken.identifier", "=", identifier)
const result = supportsReturning
? (await query.returningAll().executeTakeFirst()) ?? null
: await db
.selectFrom("VerificationToken")
.selectAll()
.where("token", "=", token)
.executeTakeFirst()
.then(async (res) => {
await query.executeTakeFirst()
return res
})
if (!result) return null
return to(result, "expires")
},
}
}
/**
* Wrapper over the original `Kysely` class in order to validate the passed in
* database interface. A regular Kysely instance may also be used, but wrapping
* it ensures the database interface implements the fields that Auth.js
* requires. When used with `kysely-codegen`, the `Codegen` type can be passed as
* the second generic argument. The generated types will be used, and
* `KyselyAuth` will only verify that the correct fields exist.
**/
export class KyselyAuth<DB extends T, T = Database> extends Kysely<DB> {}
export type Codegen = {
[K in keyof Database]: { [J in keyof Database[K]]: unknown }
}

View File

@@ -0,0 +1,250 @@
import { runBasicTests } from "@next-auth/adapter-test"
import { Pool } from "pg"
import {
Kysely,
MysqlDialect,
PostgresDialect,
SchemaModule,
sql,
SqliteAdapter,
SqliteDialect,
} from "kysely"
import { KyselyAdapter, KyselyAuth } from "../src"
import { createPool } from "mysql2"
import SqliteDatabase from "better-sqlite3"
import type { Database } from "../src"
import { DataTypeExpression } from "kysely/dist/cjs/parser/data-type-parser"
type BuiltInDialect = "postgres" | "mysql" | "sqlite"
const POOL_SIZE = 20
const DIALECT_CONFIGS = {
postgres: {
host: "localhost",
database: "kysely_test",
user: "kysely",
port: 5434,
max: POOL_SIZE,
},
mysql: {
database: "kysely_test",
host: "localhost",
user: "kysely",
password: "kysely",
port: 3308,
supportBigNumbers: true,
bigNumberStrings: true,
connectionLimit: POOL_SIZE,
},
sqlite: {
databasePath: ":memory:",
},
} as const
async function dropDatabase(db: Kysely<Database>): Promise<void> {
await Promise.all([
db.schema.dropTable("Account").ifExists().execute(),
db.schema.dropTable("Session").ifExists().execute(),
db.schema.dropTable("User").ifExists().execute(),
db.schema.dropTable("VerificationToken").ifExists().execute(),
])
}
export function createTableWithId(
schema: SchemaModule,
dialect: BuiltInDialect,
tableName: string
) {
const builder = schema.createTable(tableName)
if (dialect === "postgres") {
return builder.addColumn("id", "uuid", (col) =>
col.primaryKey().defaultTo(sql`gen_random_uuid()`)
)
} else if (dialect === "mysql") {
return builder.addColumn("id", "varchar(36)", (col) =>
col.primaryKey().defaultTo(sql`(UUID())`)
)
} else {
return builder.addColumn("id", "integer", (col) =>
col.autoIncrement().primaryKey()
)
}
}
async function createDatabase(
db: Kysely<Database>,
dialect: BuiltInDialect
): Promise<void> {
const defaultTimestamp = {
postgres: sql`NOW()`,
mysql: sql`NOW(3)`,
sqlite: sql`CURRENT_TIMESTAMP`,
}[dialect]
const uuidColumnType: DataTypeExpression =
dialect === "mysql" ? "varchar(36)" : "uuid"
const dateColumnType: DataTypeExpression =
dialect === "mysql" ? sql`DATETIME(3)` : "timestamptz"
const textColumnType: DataTypeExpression =
dialect === "mysql" ? "varchar(255)" : "text"
await dropDatabase(db)
await createTableWithId(db.schema, dialect, "User")
.addColumn("name", textColumnType)
.addColumn("email", textColumnType, (col) => col.unique().notNull())
.addColumn("emailVerified", dateColumnType, (col) =>
col.defaultTo(defaultTimestamp)
)
.addColumn("image", textColumnType)
.execute()
let createAccountTable = createTableWithId(db.schema, dialect, "Account")
.addColumn("userId", uuidColumnType, (col) =>
col.references("User.id").onDelete("cascade").notNull()
)
.addColumn("type", textColumnType, (col) => col.notNull())
.addColumn("provider", textColumnType, (col) => col.notNull())
.addColumn("providerAccountId", textColumnType, (col) => col.notNull())
.addColumn("refresh_token", textColumnType)
.addColumn("access_token", textColumnType)
.addColumn("expires_at", "bigint")
.addColumn("token_type", textColumnType)
.addColumn("scope", textColumnType)
.addColumn("id_token", textColumnType)
.addColumn("session_state", textColumnType)
if (dialect === "mysql")
createAccountTable = createAccountTable.addForeignKeyConstraint(
"Account_userId_fk",
["userId"],
"User",
["id"],
(cb) => cb.onDelete("cascade")
)
await createAccountTable.execute()
let createSessionTable = createTableWithId(db.schema, dialect, "Session")
.addColumn("userId", uuidColumnType, (col) =>
col.references("User.id").onDelete("cascade").notNull()
)
.addColumn("sessionToken", textColumnType, (col) => col.notNull().unique())
.addColumn("expires", dateColumnType, (col) => col.notNull())
if (dialect === "mysql")
createSessionTable = createSessionTable.addForeignKeyConstraint(
"Session_userId_fk",
["userId"],
"User",
["id"],
(cb) => cb.onDelete("cascade")
)
await createSessionTable.execute()
await db.schema
.createTable("VerificationToken")
.addColumn("identifier", textColumnType, (col) => col.notNull())
.addColumn("token", textColumnType, (col) => col.notNull().unique())
.addColumn("expires", dateColumnType, (col) => col.notNull())
.execute()
await db.schema
.createIndex("Account_userId_index")
.on("Account")
.column("userId")
.execute()
}
const runDialectBasicTests = (
db: Kysely<Database>,
dialect: BuiltInDialect
) => {
const datesStoredAsISOStrings =
db.getExecutor().adapter instanceof SqliteAdapter
runBasicTests({
adapter: KyselyAdapter(db),
db: {
async connect() {
await dropDatabase(db)
await createDatabase(db, dialect)
},
async disconnect() {
await db.destroy()
},
async user(userId) {
const user =
(await db
.selectFrom("User")
.selectAll()
.where("id", "=", userId)
.executeTakeFirst()) ?? null
if (datesStoredAsISOStrings && user?.emailVerified)
user.emailVerified = new Date(user.emailVerified)
return user
},
async account({ provider, providerAccountId }) {
const result = await db
.selectFrom("Account")
.selectAll()
.where("provider", "=", provider)
.where("providerAccountId", "=", providerAccountId)
.executeTakeFirst()
if (!result) return null
const { ...account } = result
if (typeof account.expires_at === "string")
account.expires_at = Number(account.expires_at)
return account
},
async session(sessionToken) {
const session =
(await db
.selectFrom("Session")
.selectAll()
.where("sessionToken", "=", sessionToken)
.executeTakeFirst()) ?? null
if (datesStoredAsISOStrings && session?.expires)
session.expires = new Date(session.expires)
return session
},
async verificationToken({ identifier, token }) {
const verificationToken = await db
.selectFrom("VerificationToken")
.selectAll()
.where("identifier", "=", identifier)
.where("token", "=", token)
.executeTakeFirstOrThrow()
if (datesStoredAsISOStrings)
verificationToken.expires = new Date(verificationToken.expires)
return verificationToken
},
},
})
}
describe("Testing PostgresDialect", () => {
const db = new KyselyAuth<Database>({
dialect: new PostgresDialect({
pool: new Pool(DIALECT_CONFIGS.postgres),
}),
})
runDialectBasicTests(db, "postgres")
})
describe("Testing MysqlDialect", () => {
const db = new KyselyAuth<Database>({
dialect: new MysqlDialect({
pool: createPool(DIALECT_CONFIGS.mysql),
}),
})
runDialectBasicTests(db, "mysql")
})
describe("Testing SqliteDialect", () => {
const db = new KyselyAuth<Database>({
dialect: new SqliteDialect({
database: async () =>
new SqliteDatabase(DIALECT_CONFIGS.sqlite.databasePath),
}),
})
runDialectBasicTests(db, "sqlite")
})

View File

@@ -0,0 +1,3 @@
CREATE USER 'kysely'@'%' IDENTIFIED WITH mysql_native_password BY 'kysely';
GRANT ALL ON *.* TO 'kysely'@'%';
CREATE DATABASE kysely_test;

View File

@@ -0,0 +1,30 @@
#!/usr/bin/env bash
docker run -d \
--name mysql \
--rm \
-e MYSQL_ROOT_PASSWORD=root \
-e MYSQL_DATABASE=kysely_test \
-p 3308:3306 \
-v "$(pwd)"/tests/scripts/mysql-init.sql:/data/application/init.sql \
mysql/mysql-server \
--init-file /data/application/init.sql
docker run -d \
--name postgres \
--rm \
-e POSTGRES_DB=kysely_test \
-e POSTGRES_USER=kysely \
-e POSTGRES_HOST_AUTH_METHOD=trust \
-p 5434:5432 \
postgres
echo "waiting 15 seconds for databases to start..."
sleep 15
# Always stop container, but exit with 1 when tests are failing
if npx jest tests; then
docker stop mysql && docker stop postgres
else
docker stop mysql && docker stop postgres && exit 1
fi

View File

@@ -0,0 +1,25 @@
{
"extends": "@next-auth/tsconfig/tsconfig.base.json",
"compilerOptions": {
"allowJs": true,
"baseUrl": ".",
"isolatedModules": true,
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node",
"outDir": ".",
"rootDir": "src",
"skipDefaultLibCheck": true,
"strictNullChecks": true,
"stripInternal": true,
"declarationMap": true,
"declaration": true
},
"include": [
"src/**/*"
],
"exclude": [
"*.js",
"*.d.ts",
]
}

View File

@@ -1 +0,0 @@
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

View File

@@ -1 +0,0 @@
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

View File

@@ -1 +0,0 @@
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

View File

@@ -1 +0,0 @@
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

View File

@@ -1 +0,0 @@
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

View File

@@ -1 +0,0 @@
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

View File

@@ -1 +0,0 @@
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

View File

@@ -1 +0,0 @@
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

View File

@@ -1 +0,0 @@
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

View File

@@ -1 +0,0 @@
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

View File

@@ -1 +0,0 @@
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

View File

@@ -1 +0,0 @@
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

View File

@@ -1 +0,0 @@
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

124
pnpm-lock.yaml generated
View File

@@ -318,6 +318,31 @@ importers:
firebase-tools: 11.16.1
jest: 29.3.1
packages/adapter-kysely:
specifiers:
'@auth/core': workspace:*
'@next-auth/adapter-test': workspace:*
'@next-auth/tsconfig': workspace:*
'@types/better-sqlite3': ^7.6.3
'@types/pg': ^8.6.5
better-sqlite3: ^8.2.0
jest: ^27.4.3
kysely: ^0.24.2
mysql2: ^3.2.0
pg: ^8.10.0
dependencies:
'@auth/core': link:../core
devDependencies:
'@next-auth/adapter-test': link:../adapter-test
'@next-auth/tsconfig': link:../tsconfig
'@types/better-sqlite3': 7.6.4
'@types/pg': 8.10.2
better-sqlite3: 8.5.0
jest: 27.5.1
kysely: 0.24.2
mysql2: 3.6.0
pg: 8.11.2
packages/adapter-mikro-orm:
specifiers:
'@auth/core': workspace:*
@@ -10139,6 +10164,14 @@ packages:
resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==}
dev: true
/@types/pg/8.10.2:
resolution: {integrity: sha512-MKFs9P6nJ+LAeHLU3V0cODEOgyThJ3OAnmOlsZsxux6sfQs3HRXR5bBn7xG5DjckEFhTAxsXi7k7cd0pCMxpJw==}
dependencies:
'@types/node': 20.4.8
pg-protocol: 1.5.0
pg-types: 4.0.1
dev: true
/@types/phoenix/1.5.4:
resolution: {integrity: sha512-L5eZmzw89eXBKkiqVBcJfU1QGx9y+wurRIEgt0cuLH0hwNtVUxtx+6cu0R2STwWj468sjXyBYPYDtGclUd1kjQ==}
@@ -21590,6 +21623,11 @@ packages:
resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==}
dev: true
/kysely/0.24.2:
resolution: {integrity: sha512-+7eaTJNUYm2yRq1x+lEOZc+78TO35dTZ9b0dh49+Z9CTt2byMSbMiOKpwPlOyCAaHD4kILkAYWYZNywFlmBwRA==}
engines: {node: '>=14.0.0'}
dev: true
/latest-version/5.1.0:
resolution: {integrity: sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==}
engines: {node: '>=8'}
@@ -23801,15 +23839,30 @@ packages:
resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==}
dev: true
/pg-cloudflare/1.1.1:
resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==}
requiresBuild: true
dev: true
optional: true
/pg-connection-string/2.5.0:
resolution: {integrity: sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==}
dev: true
/pg-connection-string/2.6.2:
resolution: {integrity: sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==}
dev: true
/pg-int8/1.0.1:
resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==}
engines: {node: '>=4.0.0'}
dev: true
/pg-numeric/1.0.2:
resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==}
engines: {node: '>=4'}
dev: true
/pg-pool/3.5.1_pg@8.7.3:
resolution: {integrity: sha512-6iCR0wVrro6OOHFsyavV+i6KYL4lVNyYAB9RD18w66xSzN+d8b66HiwuP30Gp1SH5O9T82fckkzsRjlrhD0ioQ==}
peerDependencies:
@@ -23818,10 +23871,22 @@ packages:
pg: 8.7.3
dev: true
/pg-pool/3.6.1_pg@8.11.2:
resolution: {integrity: sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==}
peerDependencies:
pg: '>=8.0'
dependencies:
pg: 8.11.2
dev: true
/pg-protocol/1.5.0:
resolution: {integrity: sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==}
dev: true
/pg-protocol/1.6.0:
resolution: {integrity: sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==}
dev: true
/pg-types/2.2.0:
resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==}
engines: {node: '>=4'}
@@ -23833,6 +23898,39 @@ packages:
postgres-interval: 1.2.0
dev: true
/pg-types/4.0.1:
resolution: {integrity: sha512-hRCSDuLII9/LE3smys1hRHcu5QGcLs9ggT7I/TCs0IE+2Eesxi9+9RWAAwZ0yaGjxoWICF/YHLOEjydGujoJ+g==}
engines: {node: '>=10'}
dependencies:
pg-int8: 1.0.1
pg-numeric: 1.0.2
postgres-array: 3.0.2
postgres-bytea: 3.0.0
postgres-date: 2.0.1
postgres-interval: 3.0.0
postgres-range: 1.1.3
dev: true
/pg/8.11.2:
resolution: {integrity: sha512-l4rmVeV8qTIrrPrIR3kZQqBgSN93331s9i6wiUiLOSk0Q7PmUxZD/m1rQI622l3NfqBby9Ar5PABfS/SulfieQ==}
engines: {node: '>= 8.0.0'}
peerDependencies:
pg-native: '>=3.0.1'
peerDependenciesMeta:
pg-native:
optional: true
dependencies:
buffer-writer: 2.0.0
packet-reader: 1.0.0
pg-connection-string: 2.6.2
pg-pool: 3.6.1_pg@8.11.2
pg-protocol: 1.6.0
pg-types: 2.2.0
pgpass: 1.0.5
optionalDependencies:
pg-cloudflare: 1.1.1
dev: true
/pg/8.7.3:
resolution: {integrity: sha512-HPmH4GH4H3AOprDJOazoIcpI49XFsHCe8xlrjHkWiapdbHK+HLtbm/GQzXYAZwmPju/kzKhjaSfMACG+8cgJcw==}
engines: {node: '>= 8.0.0'}
@@ -25032,16 +25130,33 @@ packages:
engines: {node: '>=4'}
dev: true
/postgres-array/3.0.2:
resolution: {integrity: sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==}
engines: {node: '>=12'}
dev: true
/postgres-bytea/1.0.0:
resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==}
engines: {node: '>=0.10.0'}
dev: true
/postgres-bytea/3.0.0:
resolution: {integrity: sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==}
engines: {node: '>= 6'}
dependencies:
obuf: 1.1.2
dev: true
/postgres-date/1.0.7:
resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==}
engines: {node: '>=0.10.0'}
dev: true
/postgres-date/2.0.1:
resolution: {integrity: sha512-YtMKdsDt5Ojv1wQRvUhnyDJNSr2dGIC96mQVKz7xufp07nfuFONzdaowrMHjlAzY6GDLd4f+LUHHAAM1h4MdUw==}
engines: {node: '>=12'}
dev: true
/postgres-interval/1.2.0:
resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==}
engines: {node: '>=0.10.0'}
@@ -25049,6 +25164,15 @@ packages:
xtend: 4.0.2
dev: true
/postgres-interval/3.0.0:
resolution: {integrity: sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==}
engines: {node: '>=12'}
dev: true
/postgres-range/1.1.3:
resolution: {integrity: sha512-VdlZoocy5lCP0c/t66xAfclglEapXPCIVhqqJRncYpvbCgImF0w67aPKfbqUMr72tO2k5q0TdTZwCLjPTI6C9g==}
dev: true
/postgres/3.3.5:
resolution: {integrity: sha512-+JD93VELV9gHkqpV5gdL5/70HdGtEw4/XE1S4BC8f1mcPmdib3K5XsKVbnR1XcAyC41zOnifJ+9YRKxdIsXiUw==}
dev: true

View File

@@ -60,9 +60,11 @@
"@auth/prisma-adapter#build",
"@auth/sveltekit#build",
"@auth/dgraph-adapter#build",
"@auth/drizzle-adapter#build",
"@auth/dynamodb-adapter#build",
"@auth/fauna-adapter#build",
"@auth/firebase-adapter#build",
"@auth/kysely-adapter#build",
"@auth/mikro-orm-adapter#build",
"@auth/mongodb-adapter#build",
"@auth/neo4j-adapter#build",
@@ -83,9 +85,11 @@
"@auth/prisma-adapter#build",
"@auth/sveltekit#build",
"@auth/dgraph-adapter#build",
"@auth/drizzle-adapter#build",
"@auth/dynamodb-adapter#build",
"@auth/fauna-adapter#build",
"@auth/firebase-adapter#build",
"@auth/kysely-adapter#build",
"@auth/mikro-orm-adapter#build",
"@auth/mongodb-adapter#build",
"@auth/neo4j-adapter#build",
@@ -107,4 +111,4 @@
]
}
}
}
}