From 98e41692bde640eb37b0726e1d060cbba2ab506e Mon Sep 17 00:00:00 2001 From: Zach Grimaldi Date: Sun, 20 Jul 2025 10:31:48 -0400 Subject: [PATCH 1/8] feat: hyperdrive postgres/mysql support --- README.md | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++- src/index.ts | 37 +++++++++++++++++++++++++++------ src/types.ts | 41 ++++++++++++++++++++++++++----------- 3 files changed, 117 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 98b58de..2551005 100644 --- a/README.md +++ b/README.md @@ -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 @@ -205,6 +207,60 @@ 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. +**Using Hyperdrive (MySQL):** + +```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 with D1 Better Auth uses Drizzle ORM for database interactions, allowing for automatic schema management for your D1 database. diff --git a/src/index.ts b/src/index.ts index 6af99ac..087a0a2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -186,14 +186,39 @@ export const withCloudflare = ( // If user explicitly set it to true/false, that will be respected. } + // Assert that only one database configuration is provided + let definedDatabases = 0; + if (cloudFlareOptions.postgres) definedDatabases++; + if (cloudFlareOptions.mysql) definedDatabases++; + if (cloudFlareOptions.d1) definedDatabases++; + if (definedDatabases > 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..ba13c0c 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 any> = { + /** + * 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. From d9877a550b6f9d563efaff4a8b86504cfa10ab1b Mon Sep 17 00:00:00 2001 From: Zach Grimaldi Date: Sun, 20 Jul 2025 10:51:43 -0400 Subject: [PATCH 2/8] docs: better clarify D1 + Hyperdrive --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 2551005..aa2704c 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, 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) @@ -16,7 +16,7 @@ Demo implementations are available in the [`examples/`](./examples/) directory f ## Features - 🗄️ **Database Integration**: Support for D1 (SQLite), Postgres, and MySQL databases via Drizzle ORM. -- ⚡ **Hyperdrive Support**: Connect to Postgres and MySQL databases through Cloudflare Hyperdrive. +- 🚀 **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. @@ -49,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) @@ -106,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"; @@ -261,9 +261,9 @@ const auth = betterAuth({ }); ``` -### 4. Generate and Manage Auth Schema with D1 +### 4. Generate and Manage Auth Schema -Better Auth uses Drizzle ORM for database interactions, allowing for automatic schema management for your D1 database. +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: @@ -287,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. From 4eee9fb75a7db3845aa3e0aedcc97a54ac30f983 Mon Sep 17 00:00:00 2001 From: Zach Grimaldi Date: Sun, 20 Jul 2025 10:54:22 -0400 Subject: [PATCH 3/8] docs: plan for SvelteKit w/ Hyperdrive --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aa2704c..966d548 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Demo implementations are available in the [`examples/`](./examples/) directory f - [x] Hono - [x] OpenNextJS -- [ ] SvelteKit +- [ ] SvelteKit (Hyperdrive) - [ ] TanStack Start ## Table of Contents From b8cb2968dc24de4b4cffe30f87f2f9f77060fdc7 Mon Sep 17 00:00:00 2001 From: Zach Grimaldi Date: Sun, 20 Jul 2025 11:11:01 -0400 Subject: [PATCH 4/8] fix: simplify assertion --- src/index.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/index.ts b/src/index.ts index 087a0a2..cf2cd5f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -187,11 +187,8 @@ export const withCloudflare = ( } // Assert that only one database configuration is provided - let definedDatabases = 0; - if (cloudFlareOptions.postgres) definedDatabases++; - if (cloudFlareOptions.mysql) definedDatabases++; - if (cloudFlareOptions.d1) definedDatabases++; - if (definedDatabases > 1) { + 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." ); From aae557ddbfcfd7eb7c0b565966015ab0a189fce9 Mon Sep 17 00:00:00 2001 From: Zach Grimaldi Date: Sun, 20 Jul 2025 11:13:41 -0400 Subject: [PATCH 5/8] fix: update DrizzleConfig to use never[] for args --- src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index ba13c0c..491a9ac 100644 --- a/src/types.ts +++ b/src/types.ts @@ -34,7 +34,7 @@ export interface CloudflarePluginOptions { /** * Generic drizzle database configuration */ -export type DrizzleConfig any> = { +export type DrizzleConfig any> = { /** * The drizzle database instance */ From b0739e1bb215e86073c337e4f26988264c6405d2 Mon Sep 17 00:00:00 2001 From: Zach Grimaldi Date: Sun, 20 Jul 2025 11:20:05 -0400 Subject: [PATCH 6/8] docs: missing R2 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 966d548..dd14ef6 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, Hyperdrive, 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) From 8cc9c47d6d3f89872350692a49a9b5a881e40e61 Mon Sep 17 00:00:00 2001 From: Zach Grimaldi Date: Sun, 20 Jul 2025 11:22:19 -0400 Subject: [PATCH 7/8] docs: planning for durable objects --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dd14ef6..4bee024 100644 --- a/README.md +++ b/README.md @@ -38,8 +38,8 @@ Demo implementations are available in the [`examples/`](./examples/) directory f - [x] Hono - [x] OpenNextJS -- [ ] SvelteKit (Hyperdrive) -- [ ] TanStack Start +- [ ] SvelteKit (+ Hyperdrive) +- [ ] TanStack Start (+ Durable Objects) ## Table of Contents From e70863bb0550d0f04433edc92059f50993200f0e Mon Sep 17 00:00:00 2001 From: Zach Grimaldi Date: Sun, 20 Jul 2025 11:34:50 -0400 Subject: [PATCH 8/8] fix: use union of the three options instead --- src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index 491a9ac..e08e0c4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -34,7 +34,7 @@ export interface CloudflarePluginOptions { /** * Generic drizzle database configuration */ -export type DrizzleConfig any> = { +export type DrizzleConfig = { /** * The drizzle database instance */