diff --git a/README.md b/README.md index 98b58de..4bee024 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # better-auth-cloudflare -Seamlessly integrate [Better Auth](https://github.com/better-auth/better-auth) with Cloudflare Workers, D1, KV, and geolocation services. +Seamlessly integrate [Better Auth](https://github.com/better-auth/better-auth) with Cloudflare Workers, D1, Hyperdrive, KV, R2, and geolocation services. [![NPM Version](https://img.shields.io/npm/v/better-auth-cloudflare)](https://www.npmjs.com/package/better-auth-cloudflare) [![NPM Downloads](https://img.shields.io/npm/dt/better-auth-cloudflare)](https://www.npmjs.com/package/better-auth-cloudflare) @@ -15,7 +15,8 @@ Demo implementations are available in the [`examples/`](./examples/) directory f ## Features -- 🗄️ **D1 Database Integration**: Leverage Cloudflare D1 as your primary database via Drizzle ORM. +- 🗄️ **Database Integration**: Support for D1 (SQLite), Postgres, and MySQL databases via Drizzle ORM. +- 🚀 **Hyperdrive Support**: Connect to Postgres and MySQL databases through Cloudflare Hyperdrive. - 🔌 **KV Storage Integration**: Optionally use Cloudflare KV for secondary storage (e.g., session caching). - 📁 **R2 File Storage**: Upload, download, and manage user files with Cloudflare R2 object storage and database tracking. - 📍 **Automatic Geolocation Tracking**: Enrich user sessions with location data derived from Cloudflare. @@ -27,6 +28,7 @@ Demo implementations are available in the [`examples/`](./examples/) directory f - [x] IP Detection - [x] Geolocation - [x] D1 +- [x] Hyperdrive (Postgres/MySQL) - [x] KV - [x] R2 - [ ] Cloudflare Images @@ -36,8 +38,8 @@ Demo implementations are available in the [`examples/`](./examples/) directory f - [x] Hono - [x] OpenNextJS -- [ ] SvelteKit -- [ ] TanStack Start +- [ ] SvelteKit (+ Hyperdrive) +- [ ] TanStack Start (+ Durable Objects) ## Table of Contents @@ -47,7 +49,7 @@ Demo implementations are available in the [`examples/`](./examples/) directory f - [1. Define Your Database Schema (`src/db/schema.ts`)](#1-define-your-database-schema-srcdbschemats) - [2. Initialize Drizzle ORM (`src/db/index.ts`)](#2-initialize-drizzle-orm-srcdbindexts) - [3. Configure Better Auth (`src/auth/index.ts`)](#3-configure-better-auth-srcauthindexts) - - [4. Generate and Manage Auth Schema with D1](#4-generate-and-manage-auth-schema-with-d1) + - [4. Generate and Manage Auth Schema](#4-generate-and-manage-auth-schema) - [5. Configure KV as Secondary Storage (Optional)](#5-configure-kv-as-secondary-storage-optional) - [6. Set Up API Routes](#6-set-up-api-routes) - [7. Initialize the Client](#7-initialize-the-client) @@ -104,7 +106,7 @@ _Note: The `auth.schema.ts` file will be generated by the Better Auth CLI in a s ### 2. Initialize Drizzle ORM (`src/db/index.ts`) -Properly initialize Drizzle with your Cloudflare D1 binding. This function will provide a database client instance to your application, configured to use your D1 database. +Properly initialize Drizzle with your database. This function will provide a database client instance to your application. For D1, you'll use Cloudflare D1 bindings, while Postgres/MySQL will use Hyperdrive connection strings. ```typescript import { getCloudflareContext } from "@opennextjs/cloudflare"; @@ -205,9 +207,63 @@ export { createAuth }; **For OpenNext.js with complex async requirements:** See the [OpenNext.js example](./examples/opennextjs/README.md) for a more complex configuration that handles async database initialization and singleton patterns. -### 4. Generate and Manage Auth Schema with D1 +**Using Hyperdrive (MySQL):** -Better Auth uses Drizzle ORM for database interactions, allowing for automatic schema management for your D1 database. +```typescript +import { drizzle } from "drizzle-orm/mysql2"; +import mysql from "mysql2/promise"; + +async function getDb() { + const { env } = await getCloudflareContext({ async: true }); + const connection = mysql.createPool(env.HYPERDRIVE_URL); + return drizzle(connection, { schema }); +} + +const auth = betterAuth({ + ...withCloudflare( + { + mysql: { + db: await getDb(), + }, + // other cloudflare options... + }, + { + // your auth options... + } + ), +}); +``` + +**Using Hyperdrive (Postgres):** + +```typescript +import { drizzle } from "drizzle-orm/postgres-js"; +import postgres from "postgres"; + +async function getDb() { + const { env } = await getCloudflareContext({ async: true }); + const sql = postgres(env.HYPERDRIVE_URL); + return drizzle(sql, { schema }); +} + +const auth = betterAuth({ + ...withCloudflare( + { + postgres: { + db: await getDb(), + }, + // other cloudflare options... + }, + { + // your auth options... + } + ), +}); +``` + +### 4. Generate and Manage Auth Schema + +Better Auth uses Drizzle ORM for database interactions, allowing for automatic schema management for your database (D1/SQLite, Postgres, or MySQL). To generate or update your authentication-related database schema, run the Better Auth CLI: @@ -231,13 +287,13 @@ This command will: - Output the generated Drizzle schema to `src/db/auth.schema.ts`. - Automatically confirm prompts (`-y`). -After generation, you can use Drizzle Kit to create and apply migrations to your D1 database. Refer to the [Drizzle ORM documentation](https://orm.drizzle.team/kit/overview) for managing migrations. +After generation, you can use Drizzle Kit to create and apply migrations to your database. Refer to the [Drizzle ORM documentation](https://orm.drizzle.team/kit/overview) for managing migrations. For integrating the generated `auth.schema.ts` with your existing Drizzle schema, see [managing schema across multiple files](https://orm.drizzle.team/docs/sql-schema-declaration#schema-in-multiple-files). More details on schema generation are available in the [Better Auth docs](https://www.better-auth.com/docs/adapters/drizzle#schema-generation--migration). ### 5. Configure KV as Secondary Storage (Optional) -If you provide a KV namespace in the `withCloudflare` configuration (as shown in `src/auth/index.ts`), it will be used as [Secondary Storage](https://www.better-auth.com/docs/concepts/database#secondary-storage) by Better Auth. This is typically used for caching or storing session data that doesn't need to reside in your primary D1 database. +If you provide a KV namespace in the `withCloudflare` configuration (as shown in `src/auth/index.ts`), it will be used as [Secondary Storage](https://www.better-auth.com/docs/concepts/database#secondary-storage) by Better Auth. This is typically used for caching or storing session data that doesn't need to reside in your primary database. Ensure your KV namespace (e.g., `USER_SESSIONS`) is correctly bound in your `wrangler.toml` file. diff --git a/src/index.ts b/src/index.ts index 6af99ac..cf2cd5f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -186,14 +186,36 @@ export const withCloudflare = ( // If user explicitly set it to true/false, that will be respected. } + // Assert that only one database configuration is provided + const dbConfigs = [cloudFlareOptions.postgres, cloudFlareOptions.mysql, cloudFlareOptions.d1].filter(Boolean); + if (dbConfigs.length > 1) { + throw new Error( + "Only one database configuration can be provided. Please provide only one of postgres, mysql, or d1." + ); + } + + // Determine which database configuration to use + let database; + if (cloudFlareOptions.postgres) { + database = drizzleAdapter(cloudFlareOptions.postgres.db, { + provider: "pg", + ...cloudFlareOptions.postgres.options, + }); + } else if (cloudFlareOptions.mysql) { + database = drizzleAdapter(cloudFlareOptions.mysql.db, { + provider: "mysql", + ...cloudFlareOptions.mysql.options, + }); + } else if (cloudFlareOptions.d1) { + database = drizzleAdapter(cloudFlareOptions.d1.db, { + provider: "sqlite", + ...cloudFlareOptions.d1.options, + }); + } + return { ...options, - database: cloudFlareOptions.d1 - ? drizzleAdapter(cloudFlareOptions.d1.db, { - provider: "sqlite", - ...cloudFlareOptions.d1.options, - }) - : undefined, + database, secondaryStorage: cloudFlareOptions.kv ? createKVStorage(cloudFlareOptions.kv) : undefined, plugins: [cloudflare(cloudFlareOptions), ...(options.plugins ?? [])], advanced: updatedAdvanced, diff --git a/src/types.ts b/src/types.ts index 85ad3c8..e08e0c4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -2,7 +2,9 @@ import type { KVNamespace } from "@cloudflare/workers-types"; import type { AuthContext } from "better-auth"; import type { DrizzleAdapterConfig } from "better-auth/adapters/drizzle"; import type { FieldAttribute } from "better-auth/db"; -import type { drizzle } from "drizzle-orm/d1"; +import type { drizzle as d1Drizzle } from "drizzle-orm/d1"; +import type { drizzle as postgresDrizzle } from "drizzle-orm/postgres-js"; +import type { drizzle as mysqlDrizzle } from "drizzle-orm/mysql2"; export interface CloudflarePluginOptions { /** @@ -29,20 +31,35 @@ export interface CloudflarePluginOptions { r2?: R2Config; } +/** + * Generic drizzle database configuration + */ +export type DrizzleConfig = { + /** + * The drizzle database instance + */ + db: ReturnType; + /** + * Drizzle adapter options + */ + options?: Omit; +}; + export interface WithCloudflareOptions extends CloudflarePluginOptions { /** - * D1 database for primary storage, if that's what you're using. + * D1 database configuration for SQLite */ - d1?: { - /** - * D1 database for primary storage, if that's what you're using. - */ - db: ReturnType; - /** - * Drizzle adapter options for primary storage, if you're using D1. - */ - options?: Omit; - }; + d1?: DrizzleConfig; + + /** + * Postgres database configuration for Hyperdrive + */ + postgres?: DrizzleConfig; + + /** + * MySQL database configuration for Hyperdrive + */ + mysql?: DrizzleConfig; /** * KV namespace for secondary storage, if you want to use that.