Skip to content

fix(adapter): transistion to surrealdb@^1.0.0 #11911

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

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
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
244 changes: 177 additions & 67 deletions docs/pages/getting-started/adapters/surrealdb.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,25 @@ import { Code } from "@/components/Code"
### Installation

```bash npm2yarn
npm install @auth/surrealdb-adapter surrealdb.js
npm install @auth/surrealdb-adapter surrealdb
```

### Environment Variables

A valid authentication combination must be provided. The following authentication combinations are supported:

- RootAuth
- NamespaceAuth
- DatabaseAuth
- ScopeAuth

```sh
AUTH_SURREALDB_CONNECTION
AUTH_SURREALDB_USERNAME
AUTH_SURREALDB_PASSWORD
AUTH_SURREALDB_NS
AUTH_SURREALDB_DB
AUTH_SURREAL_URL (required)
AUTH_SURREAL_NS
AUTH_SURREAL_DB
AUTH_SURREAL_USER
AUTH_SURREAL_PW
AUTH_SURREAL_SCOPE
```

### Configuration
Expand Down Expand Up @@ -97,84 +105,186 @@ app.use(

The SurrealDB adapter does not handle connections automatically, so you will have to make sure that you pass the Adapter a `SurrealDBClient` that is connected already. Below you can see an example how to do this.

### Utils File

```ts filename="./lib/surrealdb_utils.ts"
import { Surreal, ConnectionStatus, type AnyAuth } from "surrealdb"

/**
* Maintains a single instance of surrealdb
*/
export class MySurreal {
// A single instantiation of a surreal connection
private _surrealdb: Surreal | undefined

async surrealdb(url: string | URL, auth: AnyAuth | string): Promise<Surreal> {
// Init Surreal
if (!this._surrealdb) {
this._surrealdb = new Surreal()
}

if (this._surrealdb.status == ConnectionStatus.Connected) {
return this._surrealdb
} else if (this._surrealdb.status == ConnectionStatus.Disconnected) {
try {
// Connect as a database user
await this._surrealdb.connect(url, {
auth,
})
if (process.env.NODE_ENV === "development") {
let str = `${this._surrealdb.status}`
if (typeof auth !== "string") {
if ("username" in auth) str += ` as ${auth.username}`
if ("database" in auth && "namespace" in auth)
str += ` for ${auth.namespace} ${auth.database}`
else if ("namespace" in auth) str += ` for ${auth.namespace}`
else if ("database" in auth) str += ` for ${auth.database}`
}
console.info(str)
}
} catch (error) {
if (error instanceof Error) throw error
throw new Error(error as unknown as string)
}
}
return this._surrealdb
}
}

export const surrealdb = new MySurreal()

/**
* Converts environment variables to an AnyAuth type
* to connect with the database
* @param param0 - environment variables
* @returns {RootAuth | NamespaceAuth | DatabaseAuth | ScopeAuth}
*/
export function toAnyAuth({
username,
password,
namespace,
database,
scope,
}: Record<string, string | undefined>) {
let auth: AnyAuth
if (username && password && namespace && database) {
auth = {
database,
namespace,
username,
password,
}
} else if (username && password && namespace) {
auth = {
namespace,
username,
password,
}
} else if (username && password) {
auth = {
username,
password,
}
} else if (scope) {
auth = {
namespace,
database,
username,
password,
scope,
}
} else {
throw new Error("unsupported any auth configuration")
}
return auth
}

/**
* Handles a disconnection with the database with exponential backoff
* @param surreal - the single instance of surreal
* @param url - the database url
* @param auth - auth credentials
* @param {number} [reconnectDelay = 1000] - initial delay amount
* @param {number} [max_retries = 5] - maximum number of retries
* @param {number} [retry = 0] - current retry
*/
export async function handleDisconnect(
surreal: MySurreal,
url: string | URL,
auth: AnyAuth,
reconnectDelay: number = 1000,
max_retries: number = 5,
retry: number = 0
) {
if (retry >= max_retries) {
console.error("Shutting down.")
process.exit(1) // Graceful exit or handle gracefully in a production environment.
}

retry++
console.log(`Reconnect in ${reconnectDelay}ms...`)
setTimeout(async () => {
try {
await surreal.surrealdb(url, auth)
console.log("reconnected")
} catch (err) {
console.error("Reconnection attempt failed")
handleDisconnect(
surreal,
url,
auth,
retry,
max_retries,
reconnectDelay * 2
) // Recursively try to reconnect.
}
}, reconnectDelay)
}
```

### Authorization

#### Option 1 – Using RPC:
The clientPromise provides a connection to the database.

```ts filename="./lib/surrealdb.ts"
import { Surreal } from "surrealdb.js"

const connectionString = process.env.AUTH_SURREALDB_CONNECTION
const username = process.env.AUTH_SURREALDB_USERNAME
const password = process.env.AUTH_SURREALDB_PASSWORD
const namespace = process.env.AUTH_SURREALDB_NAMESPACE
const database = process.env.AUTH_SURREALDB_DATABASE
if (!connectionString || !username || !password || !namespace || !database) {
throw new Error(
"SurrealDB connection string, username, password, namespace, and database are required"
)
}
import { type Surreal } from "surrealdb"
import { surrealdb, toAnyAuth } from "../lib/surrealdb"
import { handleDisconnect, MySurreal, toAnyAuth } from "./lib/surrealdb_utils"

const clientPromise = new Promise<Surreal>(async (resolve, reject) => {
const db = new Surreal()
try {
await db.connect(`${connectionString}/rpc`, {
const {
AUTH_SURREAL_URL: url,
AUTH_SURREAL_NS: namespace,
AUTH_SURREAL_DB: database,
AUTH_SURREAL_USER: username,
AUTH_SURREAL_PW: password,
AUTH_SURREAL_SCOPE: scope,
} = process.env
if (!url) throw new Error('required auth url')
const auth = toAnyAuth({
namespace,
database,
auth: {
username,
password,
},
username,
password,
scope,
})
const surreal = new MySurreal()
const db = await surreal.surrealdb(url, auth)
db.emitter.subscribe("disconnected", async () => {
handleDisconnect(surreal, url, auth)
})
resolve(db)
} catch (e) {
reject(e)
}
})

// Export a module-scoped Promise<Surreal>. By doing this in a
// separate module, the client can be shared across functions.
export default clientPromise
```

#### Option 2 – Using HTTP:

Useful in serverless environments like Vercel.
#### HTTP ENGINE

```ts filename="./lib/surrealdb.ts"
import { ExperimentalSurrealHTTP } from "surrealdb.js"

const connectionString = process.env.AUTH_SURREALDB_CONNECTION
const username = process.env.AUTH_SURREALDB_USERNAME
const password = process.env.AUTH_SURREALDB_PASSWORD
const namespace = process.env.AUTH_SURREALDB_NAMESPACE
const database = process.env.AUTH_SURREALDB_DATABASE
if (!connectionString || !username || !password || !namespace || !database) {
throw new Error(
"SurrealDB connection string, username, password, namespace, and database are required"
)
}
With this configuration, we can use the database's http endpoint. Thus, the `AUTH_SURREAL_URL` should begin with `http` or `https`.

const clientPromise = new Promise<ExperimentalSurrealHTTP<typeof fetch>>(
async (resolve, reject) => {
try {
const db = new ExperimentalSurrealHTTP(connectionString, {
fetch,
namespace,
database,
auth: {
username,
password,
},
})
resolve(db)
} catch (e) {
reject(e)
}
}
)
#### Websocket ENGINE

// Export a module-scoped Promise<Surreal>. By doing this in a
// separate module, the client can be shared across functions.
export default clientPromise
```
With this configuration, we can use the database's websocket endpoint. Thus, the `AUTH_SURREAL_URL` should begin with `ws` or `wss`.
9 changes: 5 additions & 4 deletions packages/adapter-surrealdb/package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
{
"name": "@auth/surrealdb-adapter",
"version": "1.8.0",
"version": "2.0.0",
"description": "SurrealDB adapter for next-auth.",
"homepage": "https://authjs.dev",
"repository": "https://github.com/nextauthjs/next-auth",
"bugs": {
"url": "https://github.com/nextauthjs/next-auth/issues"
},
"author": "Martin Schaer <martin@schaerweb.com>",
"author": "Dylan Vanmali <https://github.com/dvanmali>",
"contributors": [
"Martin Schaer <[email protected]>",
"Thang Huu Vu <[email protected]>"
],
"type": "module",
Expand All @@ -29,7 +30,7 @@
"next-auth",
"next.js",
"oauth",
"mongodb",
"surrealdb",
"adapter"
],
"private": false,
Expand All @@ -45,6 +46,6 @@
"@auth/core": "workspace:*"
},
"peerDependencies": {
"surrealdb.js": "^0.11.0"
"surrealdb": "^1.0.0"
}
}
Loading