Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(mongodb-adapter): prevent MongoDB client promise being cached #11393

Merged
merged 2 commits into from
Jul 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 15 additions & 18 deletions docs/pages/getting-started/adapters/mongodb.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ MONGODB_URI=
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import { MongoDBAdapter } from "@auth/mongodb-adapter"
import clientPromise from "./lib/db"
import client from "./lib/db"

export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: MongoDBAdapter(clientPromise),
adapter: MongoDBAdapter(client),
providers: [],
})
```
Expand All @@ -44,12 +44,12 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import { MongoDBAdapter } from "@auth/mongodb-adapter"
import clientPromise from "./lib/db"
import client from "./lib/db"

export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [],
adapter: MongoDBAdapter(clientPromise),
adapter: MongoDBAdapter(client),
})
)
```
Expand All @@ -60,10 +60,10 @@ export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
```ts filename="./src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import { MongoDBAdapter } from "@auth/mongodb-adapter"
import clientPromise from "./lib/db"
import client from "./lib/db"

export const { handle, signIn, signOut } = SvelteKitAuth({
adapter: MongoDBAdapter(clientPromise),
adapter: MongoDBAdapter(client),
})
```

Expand All @@ -73,7 +73,7 @@ export const { handle, signIn, signOut } = SvelteKitAuth({
```ts filename="./src/routes/auth.route.ts"
import { ExpressAuth } from "@auth/express"
import { MongoDBAdapter } from "@auth/mongodb-adapter"
import clientPromise from "./lib/db"
import client from "./lib/db"

const app = express()

Expand All @@ -82,7 +82,7 @@ app.use(
"/auth/*",
ExpressAuth({
providers: [],
adapter: MongoDBAdapter(clientPromise),
adapter: MongoDBAdapter(client),
})
)
```
Expand Down Expand Up @@ -111,28 +111,25 @@ const options = {
},
}

let client
let clientPromise: Promise<MongoClient>
let client: MongoClient

if (process.env.NODE_ENV === "development") {
// In development mode, use a global variable so that the value
// is preserved across module reloads caused by HMR (Hot Module Replacement).
let globalWithMongo = global as typeof globalThis & {
_mongoClientPromise?: Promise<MongoClient>
_mongoClient?: MongoClient
}

if (!globalWithMongo._mongoClientPromise) {
client = new MongoClient(uri, options)
globalWithMongo._mongoClientPromise = client.connect()
if (!globalWithMongo._mongoClient) {
globalWithMongo._mongoClient = new MongoClient(uri, options)
}
clientPromise = globalWithMongo._mongoClientPromise
client = globalWithMongo._mongoClient
} else {
// In production mode, it's best to not use a global variable.
client = new MongoClient(uri, options)
clientPromise = client.connect()
}

// Export a module-scoped MongoClient promise. By doing this in a
// Export a module-scoped MongoClient. By doing this in a
// separate module, the client can be shared across functions.
export default clientPromise
export default client
```
26 changes: 18 additions & 8 deletions packages/adapter-mongodb/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,8 @@ export interface MongoDBAdapterOptions {
databaseName?: string
/**
* Callback function for managing the closing of the MongoDB client.
* This could be useful in serverless environments, especially when `client`
* is provided as a function returning Promise<MongoClient>, not just a simple promise.
* It allows for more sophisticated management of database connections,
* This could be useful when `client` is provided as a function returning MongoClient | Promise<MongoClient>.
* It allows for more customized management of database connections,
* addressing persistence, container reuse, and connection closure issues.
*/
onClose?: (client: MongoClient) => Promise<void>
Expand Down Expand Up @@ -108,18 +107,29 @@ export function _id(hex?: string) {

export function MongoDBAdapter(
/**
* The MongoDB client. You can either pass a promise that resolves to a `MongoClient` or a function that returns a promise that resolves to a `MongoClient`.
* Using a function that returns a `Promise<MongoClient>` could be useful in serverless environments, particularly when combined with `options.onClose`, to efficiently handle database connections and address challenges with persistence, container reuse, and connection closure.
* These functions enable either straightforward open-close database connections or more complex caching and connection reuse strategies.
* The MongoDB client.
*
* The MongoDB team recommends providing a non-connected `MongoClient` instance to avoid unhandled promise rejections if the client fails to connect.
*
* Alternatively, you can also pass:
* - A promise that resolves to a connected `MongoClient` (not recommended).
* - A function, to handle more complex and custom connection strategies.
*
* Using a function that returns `MongoClient | Promise<MongoClient>`, combined with `options.onClose`, can be useful when you want a more advanced and customized connection strategy to address challenges related to persistence, container reuse, and connection closure.
*/
client: Promise<MongoClient> | (() => Promise<MongoClient>),
client:
| MongoClient
| Promise<MongoClient>
| (() => MongoClient | Promise<MongoClient>),
options: MongoDBAdapterOptions = {}
): Adapter {
const { collections } = options
const { from, to } = format

const getDb = async () => {
const _client = await (typeof client === "function" ? client() : client)
const _client: MongoClient = await (typeof client === "function"
ndom91 marked this conversation as resolved.
Show resolved Hide resolved
? client()
: client)
const _db = _client.db(options.databaseName)
const c = { ...defaultCollections, ...collections }
return {
Expand Down
3 changes: 1 addition & 2 deletions packages/adapter-mongodb/test/custom.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ import { defaultCollections, format, MongoDBAdapter, _id } from "../src"
import { MongoClient } from "mongodb"
const name = "custom-test"
const client = new MongoClient(`mongodb://localhost:27017/${name}`)
const clientPromise = client.connect()

const collections = { ...defaultCollections, Users: "some_userz" }

runBasicTests({
adapter: MongoDBAdapter(clientPromise, {
adapter: MongoDBAdapter(client, {
collections,
}),
db: {
Expand Down
3 changes: 1 addition & 2 deletions packages/adapter-mongodb/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ import { MongoClient } from "mongodb"

const name = "test"
const client = new MongoClient(`mongodb://localhost:27017/${name}`)
const clientPromise = client.connect()

runBasicTests({
adapter: MongoDBAdapter(clientPromise),
adapter: MongoDBAdapter(client),
db: {
async disconnect() {
await client.db().dropDatabase()
Expand Down
9 changes: 1 addition & 8 deletions packages/adapter-mongodb/test/serverless.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ import { MongoClient } from "mongodb"
import { expect, test, vi } from "vitest"

const name = "serverless-test"
const clientPromise = new MongoClient(
`mongodb://localhost:27017/${name}`
).connect()
const client = new MongoClient(`mongodb://localhost:27017/${name}`)

const onClose = vi.fn(async (client: MongoClient) => {
await client.close()
Expand All @@ -29,12 +27,10 @@ runBasicTests({
),
db: {
async disconnect() {
const client = await clientPromise
await client.db().dropDatabase()
await client.close()
},
async user(id) {
const client = await clientPromise
const user = await client
.db()
.collection(defaultCollections.Users)
Expand All @@ -44,7 +40,6 @@ runBasicTests({
return format.from(user)
},
async account(provider_providerAccountId) {
const client = await clientPromise
const account = await client
.db()
.collection(defaultCollections.Accounts)
Expand All @@ -53,7 +48,6 @@ runBasicTests({
return format.from(account)
},
async session(sessionToken) {
const client = await clientPromise
const session = await client
.db()
.collection(defaultCollections.Sessions)
Expand All @@ -62,7 +56,6 @@ runBasicTests({
return format.from(session)
},
async verificationToken(identifier_token) {
const client = await clientPromise
const token = await client
.db()
.collection(defaultCollections.VerificationTokens)
Expand Down
Loading