Skip to content

Commit

Permalink
feat(adapters): update typeorm support (nextauthjs#4844)
Browse files Browse the repository at this point in the history
We haven't kept up with the recent TypeORM changes, and since they are still <1, it's likely that users kept upgrading, even if there were breaking changes.

BREAKING CHANGE:

[`typeorm`](https://github.com/typeorm/typeorm) is still in active development and has not yet published a stable release. Because of this, you can expect breaking changes in minor versions. This release of the adapter expects `[email protected]` and is not validated against previous or future releases.

Run `npm i typeorm@latest` and make sure to read the [release notes](https://github.com/typeorm/typeorm/releases) for breaking changes in TypeORM
  • Loading branch information
balazsorban44 authored Jul 8, 2022
1 parent c1f7ce3 commit 88ad25a
Show file tree
Hide file tree
Showing 12 changed files with 242 additions and 140 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ packages/next-auth/middleware.js
# Development app
apps/dev/src/css
apps/dev/prisma/migrations
apps/dev/typeorm

# VS
/.vs/slnx.sqlite-journal
Expand Down
10 changes: 7 additions & 3 deletions apps/dev/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@
},
"license": "ISC",
"dependencies": {
"@next-auth/fauna-adapter": "^1",
"@next-auth/prisma-adapter": "^1",
"@next-auth/fauna-adapter": "workspace:*",
"@next-auth/prisma-adapter": "workspace:*",
"@next-auth/typeorm-legacy-adapter": "workspace:*",
"@prisma/client": "^3",
"faunadb": "^4",
"next": "12.2.0",
Expand All @@ -31,6 +32,9 @@
"concurrently": "^7",
"cpx": "^1.5.0",
"fake-smtp-server": "^0.8.0",
"prisma": "^3"
"pg": "^8.7.3",
"prisma": "^3",
"sqlite3": "^5.0.8",
"typeorm": "0.3.7"
}
}
24 changes: 19 additions & 5 deletions apps/dev/pages/api/auth/[...nextauth].ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,24 +33,38 @@ import TraktProvider from "next-auth/providers/trakt"
import WorkOSProvider from "next-auth/providers/workos"
import BoxyHQSAMLProvider from "next-auth/providers/boxyhq-saml"

// TypeORM
import { TypeORMLegacyAdapter } from "@next-auth/typeorm-legacy-adapter"
const adapter = TypeORMLegacyAdapter({
type: "sqlite",
name: "next-auth-test-memory",
database: "./typeorm/dev.db",
synchronize: true,
})

// // Prisma
// import { PrismaAdapter } from "@next-auth/prisma-adapter"
// import { PrismaClient } from "@prisma/client"
// const prisma = new PrismaClient()
// const adapter = PrismaAdapter(prisma)

// // Fauna
// import { Client as FaunaClient } from "faunadb"
// import { FaunaAdapter } from "@next-auth/fauna-adapter"

// const client = new FaunaClient({
// secret: process.env.FAUNA_SECRET,
// domain: process.env.FAUNA_DOMAIN,
// })
// const adapter = FaunaAdapter(client)

// // Dummy
// const adapter: any = {
// getUserByEmail: (email) => ({ id: "1", email, emailVerified: null }),
// createVerificationToken: (token) => token,
// }

export const authOptions: NextAuthOptions = {
// adapter: {
// getUserByEmail: (email) => ({ id: "1", email, emailVerified: null }),
// createVerificationToken: (token) => token,
// } as any,
adapter,
providers: [
// E-mail
// Start fake e-mail server with `npm run start:email`
Expand Down
19 changes: 10 additions & 9 deletions docs/docs/adapters/typeorm.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,22 @@ title: TypeORM

# TypeORM

This Adapter is used to support SQL-flavored databases (like SQLite, MySQL, MSSQL, MariaDB, CockroachDB, etc.) through [TypeORM](https://typeorm.io), and mostly kept around for legacy reasons. (See the warning below.)
This Adapter is used to support SQL-flavored databases (like SQLite, MySQL, MSSQL, MariaDB, CockroachDB, etc.) through [TypeORM](https://typeorm.io).

:::note
If you previously used this Adapter with MongoDB, check out the [MongoDB Adapter](/adapters/mongodb) instead.
:::

:::warning
:::note
In the future, we might split up this adapter to support single flavors of SQL for easier maintenance and reduced bundle size.
:::

## Usage

:::warning
[`typeorm`](https://github.com/typeorm/typeorm) is still in active development and has not yet published a stable release. Because of this, you can expect breaking changes in minor versions. This adapter expects `[email protected]` and is not validated against previous or future releases.
:::

To use this Adapter, you need to install the following packages:

```bash npm2yarn2pnpm
Expand All @@ -36,7 +40,7 @@ export default NextAuth({
})
```

`TypeORMLegacyAdapter` takes either a connection string, or a [`ConnectionOptions`](https://github.com/typeorm/typeorm/blob/master/docs/connection-options.md) object as its first parameter.
`TypeORMLegacyAdapter` takes either a connection string, or a [`DataSourceOptions`](https://github.com/typeorm/typeorm/blob/master/docs/data-source-options.md) object as its first parameter.

## Custom models

Expand Down Expand Up @@ -217,20 +221,17 @@ For example, you can add the naming convention option to the connection object i
import NextAuth from "next-auth"
import { TypeORMLegacyAdapter } from "@next-auth/typeorm-legacy-adapter"
import { SnakeNamingStrategy } from 'typeorm-naming-strategies'
import { ConnectionOptions } from "typeorm"

const connection: ConnectionOptions = {
export default NextAuth({
adapter: TypeORMLegacyAdapter({
type: "mysql",
host: "localhost",
port: 3306,
username: "test",
password: "test",
database: "test",
namingStrategy: new SnakeNamingStrategy()
}

export default NextAuth({
adapter: TypeORMLegacyAdapter(connection),
}),
...
})
```
10 changes: 8 additions & 2 deletions packages/adapter-test/jest/jest-preset.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
const swcConfig = {
jsc: {
parser: { syntax: "typescript", decorators: true },
transform: { legacyDecorator: true, decoratorMetadata: true },
},
}
module.exports = {
transform: {
".(ts|tsx)$": "@swc/jest",
".(js|jsx)$": "@swc/jest", // jest's default
".(ts|tsx)$": ["@swc/jest", swcConfig],
".(js|jsx)$": ["@swc/jest", swcConfig],
},
transformIgnorePatterns: ["[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$"],
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
Expand Down
17 changes: 9 additions & 8 deletions packages/adapter-typeorm-legacy/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,24 +37,25 @@
"sqlite": "tests/sqlite/test.sh"
},
"devDependencies": {
"@next-auth/adapter-test": "workspace:^0.0.0",
"@next-auth/tsconfig": "workspace:^0.0.0",
"@next-auth/adapter-test": "workspace:*",
"@next-auth/tsconfig": "workspace:*",
"jest": "^27.4.3",
"mssql": "^7.2.1",
"mysql": "^2.18.1",
"next-auth": "workspace:*",
"pg": "^8.7.1",
"sqlite3": "^5.0.2",
"typeorm": "^0.2.37",
"typeorm-naming-strategies": "^2.0.0"
"pg": "^8.7.3",
"sqlite3": "^5.0.8",
"typeorm": "0.3.7",
"typeorm-naming-strategies": "^4.1.0",
"typescript": "^4.7.4"
},
"peerDependencies": {
"mssql": "^6.2.1 || 7",
"mysql": "^2.18.1",
"next-auth": "workspace:*",
"next-auth": "^4",
"pg": "^8.2.1",
"sqlite3": "^5.0.2",
"typeorm": "^0.2.31"
"typeorm": "0.3.7"
},
"peerDependenciesMeta": {
"mysql": {
Expand Down
60 changes: 25 additions & 35 deletions packages/adapter-typeorm-legacy/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import { Adapter, AdapterSession, AdapterUser } from "next-auth/adapters"
import {
Connection,
ConnectionOptions,
EntityManager,
getConnectionManager,
} from "typeorm"
import { Account } from "next-auth"
import type { Adapter, AdapterSession, AdapterUser } from "next-auth/adapters"
import { DataSourceOptions, DataSource, EntityManager } from "typeorm"
import type { Account } from "next-auth"
import * as defaultEntities from "./entities"
import { parseConnectionConfig, updateConnectionEntities } from "./utils"
import { parseDataSourceConfig, updateConnectionEntities } from "./utils"

export const entities = defaultEntities

Expand All @@ -17,43 +12,40 @@ export interface TypeORMLegacyAdapterOptions {
entities?: Entities
}

let _connection: Connection
let _dataSource: DataSource | undefined

export async function getManager(options: {
connection: string | ConnectionOptions
dataSource: string | DataSourceOptions
entities: Entities
}): Promise<EntityManager> {
const { connection, entities } = options
const { dataSource, entities } = options

const config = {
...parseConnectionConfig(connection),
...parseDataSourceConfig(dataSource),
entities: Object.values(entities),
}

const connectionManager = getConnectionManager()
if (!_dataSource) _dataSource = new DataSource(config)

if (connectionManager.has(config.name ?? "default")) {
_connection = connectionManager.get(config.name ?? "default")
const manager = _dataSource?.manager

if (_connection.isConnected) return _connection.manager

if (process.env.NODE_ENV !== "production") {
await updateConnectionEntities(_connection, config.entities)
}
} else {
_connection = await connectionManager.create(config).connect()
if (!manager.connection.isInitialized) {
await manager.connection.initialize()
}

return _connection.manager
if (process.env.NODE_ENV !== "production") {
await updateConnectionEntities(_dataSource, config.entities)
}
return manager
}

export function TypeORMLegacyAdapter(
connection: string | ConnectionOptions,
dataSource: string | DataSourceOptions,
options?: TypeORMLegacyAdapterOptions
): Adapter {
const entities = options?.entities
const c = {
connection,
dataSource,
entities: {
UserEntity: entities?.UserEntity ?? defaultEntities.UserEntity,
SessionEntity: entities?.SessionEntity ?? defaultEntities.SessionEntity,
Expand Down Expand Up @@ -82,23 +74,22 @@ export function TypeORMLegacyAdapter(
// @ts-expect-error
async getUser(id) {
const m = await getManager(c)
const user = await m.findOne("UserEntity", { id })
const user = await m.findOne("UserEntity", { where: { id } })
if (!user) return null
return { ...user }
},
// @ts-expect-error
async getUserByEmail(email) {
const m = await getManager(c)
const user = await m.findOne("UserEntity", { email })
const user = await m.findOne("UserEntity", { where: { email } })
if (!user) return null
return { ...user }
},
async getUserByAccount(provider_providerAccountId) {
const m = await getManager(c)
const account = await m.findOne<Account & { user: AdapterUser }>(
"AccountEntity",
provider_providerAccountId,
{ relations: ["user"] }
{ where: provider_providerAccountId, relations: ["user"] }
)
if (!account) return null
return account.user ?? null
Expand Down Expand Up @@ -136,7 +127,7 @@ export function TypeORMLegacyAdapter(
const m = await getManager(c)
const sessionAndUser = await m.findOne<
AdapterSession & { user: AdapterUser }
>("SessionEntity", { sessionToken }, { relations: ["user"] })
>("SessionEntity", { where: { sessionToken }, relations: ["user"] })

if (!sessionAndUser) return null
const { user, ...session } = sessionAndUser
Expand All @@ -162,10 +153,9 @@ export function TypeORMLegacyAdapter(
// @ts-expect-error
async useVerificationToken(identifier_token) {
const m = await getManager(c)
const verificationToken = await m.findOne(
"VerificationTokenEntity",
identifier_token
)
const verificationToken = await m.findOne("VerificationTokenEntity", {
where: identifier_token,
})
if (!verificationToken) {
return null
}
Expand Down
20 changes: 10 additions & 10 deletions packages/adapter-typeorm-legacy/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Connection, ConnectionOptions } from "typeorm"
import type { DataSource, DataSourceOptions } from "typeorm"
import * as defaultEntities from "./entities"

/** Ensure configOrString is normalized to an object. */
export function parseConnectionConfig(
configOrString: string | ConnectionOptions
): ConnectionOptions {
export function parseDataSourceConfig(
configOrString: string | DataSourceOptions
): DataSourceOptions {
if (typeof configOrString !== "string") {
return {
...configOrString,
Expand Down Expand Up @@ -89,22 +89,22 @@ function entitiesChanged(
}

export async function updateConnectionEntities(
connection: Connection,
dataSource: DataSource,
entities: any[]
) {
if (!entitiesChanged(connection.options.entities, entities)) return
if (!entitiesChanged(dataSource.entityMetadatas, entities)) return

// @ts-expect-error
connection.options.entities = entities
dataSource.entityMetadatas = entities

// @ts-expect-error
connection.buildMetadatas()
dataSource.buildMetadatas()

if (connection.options.synchronize !== false) {
if (dataSource.options.synchronize !== false) {
console.warn(
"[next-auth][warn][adapter_typeorm_updating_entities]",
"\nhttps://next-auth.js.org/warnings#adapter_typeorm_updating_entities"
)
await connection.synchronize()
await dataSource.synchronize()
}
}
Loading

0 comments on commit 88ad25a

Please sign in to comment.