From 6fafffb91cfcc6e7f5ffe9c7d69ce75bd0ce642b Mon Sep 17 00:00:00 2001 From: Bereket Engida Date: Mon, 30 Sep 2024 09:50:44 +0300 Subject: [PATCH] docs: cli --- dev/schema-gen/auth.ts | 2 +- docs/components/sidebar-content.tsx | 17 ++++ docs/content/docs/concepts/cli.mdx | 32 ++++++ docs/content/docs/concepts/database.mdx | 47 ++++++++- docs/content/docs/installation.mdx | 62 +++++++----- .../src/adapters/drizzle-adapter/index.ts | 1 + packages/better-auth/src/adapters/index.ts | 1 + .../src/adapters/kysely-adapter/dialect.ts | 89 +++++++++++++++++ .../kysely-adapter/index.ts} | 97 +------------------ .../src/adapters/mongodb-adapter/index.ts | 1 + .../src/adapters/prisma-adapter/index.ts | 1 + .../better-auth/src/cli/commands/migrate.ts | 2 +- .../src/cli/utils/get-migration.ts | 5 +- packages/better-auth/src/db/utils.ts | 6 +- packages/better-auth/src/init.ts | 2 +- packages/better-auth/src/types/adapter.ts | 1 + 16 files changed, 244 insertions(+), 122 deletions(-) create mode 100644 docs/content/docs/concepts/cli.mdx create mode 100644 packages/better-auth/src/adapters/kysely-adapter/dialect.ts rename packages/better-auth/src/{db/kysely.ts => adapters/kysely-adapter/index.ts} (68%) diff --git a/dev/schema-gen/auth.ts b/dev/schema-gen/auth.ts index 5b45df6e4e..1cbcfc92f8 100644 --- a/dev/schema-gen/auth.ts +++ b/dev/schema-gen/auth.ts @@ -1,5 +1,5 @@ import { betterAuth } from "better-auth"; -import { prismaAdapter } from "better-auth/adapters"; +import { prismaAdapter, } from "better-auth/adapters"; import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient(); diff --git a/docs/components/sidebar-content.tsx b/docs/components/sidebar-content.tsx index b7ac7f21b6..92ce918537 100644 --- a/docs/components/sidebar-content.tsx +++ b/docs/components/sidebar-content.tsx @@ -133,6 +133,23 @@ export const contents: Content[] = [ ), }, + { + title: "CLI", + icon: () => ( + + + + ), + href: "/docs/concepts/cli", + }, { title: "Cookies", href: "/docs/concepts/cookies", diff --git a/docs/content/docs/concepts/cli.mdx b/docs/content/docs/concepts/cli.mdx new file mode 100644 index 0000000000..c4dbb6138d --- /dev/null +++ b/docs/content/docs/concepts/cli.mdx @@ -0,0 +1,32 @@ +--- +title: CLI +description: built in CLI for managing your project +--- + +Better Auth comes with a built-in CLI to help you manage the database schema needed for both core functionality and plugins. + +## Generate + +The `generate` command creates the schema required by Better Auth. If you're using a database adapter like Prisma or Drizzle, this command will generate the right schema for your ORM. If you're using the built-in Kysely adapter, it will generate an SQL file you can run directly on your database. + +```bash title="Terminal" +pnpm better-auth generate +``` + +### Options + +- `--output` - Where to save the generated schema. For Prisma, it will be saved in prisma/schema.prisma. For Drizzle, it goes to schema.ts in your project root. For Kysely, it’s an SQL file saved as schema.sql in your project root. +- `--config` - The path to your Better Auth config file. By default, the CLI will search for a better-auth.ts file in **./**, **./utils**, **./lib**, or any of these directories under `src` directory. + + +## Migrate + +The migrate command applies the Better Auth schema directly to your database. This is available if you’re using the built-in Kysely adapter. + +```bash title="Terminal" +pnpm better-auth migrate +``` + +### Options + +- `--config` - The path to your Better Auth config file. By default, the CLI will search for a better-auth.ts file in **./**, **./utils**, **./lib**, or any of these directories under `src` directory. \ No newline at end of file diff --git a/docs/content/docs/concepts/database.mdx b/docs/content/docs/concepts/database.mdx index f0705a7e10..253e6ef4e3 100644 --- a/docs/content/docs/concepts/database.mdx +++ b/docs/content/docs/concepts/database.mdx @@ -60,9 +60,42 @@ See Kysley Dialec +## Using Adapters + +If your database is managed by an ORM like Prisma or Drizzle, you can use the corresponding adapter to connect to the database. Better auth comes with built-in adapters for Prisma and Drizzle. You can pass the adapter to the `database` object in the auth options. + +**Example: Prisma** +```ts title="auth.ts" +import { betterAuth } from "better-auth" +import { PrismaAdapter } from "better-auth/adapters/prisma" + +const prisma = new PrismaAdapter({ + prisma: prismaClient +}) + +export const auth = await betterAuth({ + database: prismaAdapter(prisma, { + provider: "sqlite" // or "postgres" or "mysql" or any other supported by prisma + }) +}) +``` + +**Example: Drizzle** +```ts title="auth.ts" +import { betterAuth } from "better-auth" +import { db } from "./drizzle" +import { drizzleAdapter } from "better-auth/adapters" + +export const auth = await betterAuth({ + database: drizzleAdapter(db, { + provider: "sqlite" // or "pg" or "mysql" + }) +}) +``` + ## Running Migrations -Better auth comes with a CLI tool to manage database migrations. Use the `migrate` command to create or update tables as needed. +Better auth comes with a CLI tool to manage database migrations. Use the `migrate` command to create or update tables as needed. The cli checks your database and prompts you to add missing tables or update existing ones with new columns. @@ -70,7 +103,19 @@ The cli checks your database and prompts you to add missing tables or update exi npx better-auth migrate ``` +## Running Migrations Manually and Generting Schema + +Better auth also provides a `generate` command to generate the schema required by better auth. The `generate` command creates the schema required by Better Auth. If you're using a database adapter like Prisma or Drizzle, this command will generate the right schema for your ORM. If you're using the built-in Kysely adapter, it will generate an SQL file you can run directly on your database. + +```bash +npx better-auth generate +``` + +See the [CLI](/docs/concepts/cli) documentation for more information on the CLI. + + If you prefer adding tables manually, you can do that as well. The core schema required by better auth is described below and you can find additional schema required by plugins in the plugin documentation. + ## Core Schema diff --git a/docs/content/docs/installation.mdx b/docs/content/docs/installation.mdx index 433b124a85..83e4679ce0 100644 --- a/docs/content/docs/installation.mdx +++ b/docs/content/docs/installation.mdx @@ -66,40 +66,39 @@ export const auth = betterAuth({ ### Configure Database - -Better Auth requires a database to store user data. By default, it uses Kysely under the hood to connect and query your database. `postgresql`, `mysql`, and `sqlite` are supported out of the box. - -```ts title="auth.ts" + + Better Auth requires a database to store user data. By default, it uses [Kysely](https://kysely.dev/) for database connections and queries, with support for `postgresql`, `mysql`, and `sqlite` out of the box. + +```ts title="auth.ts" import { betterAuth } from "better-auth" export const auth = betterAuth({ - database: { // [!code highlight] - provider: "sqlite", // or "mysql", "postgresql" // [!code highlight] - url: "./db.sqlite", // path to your database or connection string // [!code highlight] - } // [!code highlight] + database: { + provider: "sqlite", // or "mysql", "postgresql" + url: "./db.sqlite", // path to your database or connection string + } }) ``` -You can also pass any dialect that is supported by Kysely to the database configration. + +You can also use any dialect supported by Kysely in the database configuration. **Example with LibsqlDialect:** -```ts title="auth.ts" +```ts title="auth.ts" import { betterAuth } from "better-auth"; import { LibsqlDialect } from "@libsql/kysely-libsql"; export const auth = betterAuth({ - database: new LibsqlDialect({ - url: process.env.TURSO_DATABASE_URL || "", - authToken: process.env.TURSO_AUTH_TOKEN || "", - }), + database: new LibsqlDialect({ + url: process.env.TURSO_DATABASE_URL || "", + authToken: process.env.TURSO_AUTH_TOKEN || "", + }), }); ``` **Adapters** -If your database is not supported by Kysely, you can use an adapter to connect to your database. - -To use an adapter, import the adapter and pass it to the database option. +If your database isn’t supported by Kysely, you can use an adapter to connect. Simply import the adapter and pass it into the `database` option. @@ -111,7 +110,9 @@ To use an adapter, import the adapter and pass it to the database option. const prisma = new PrismaClient(); export const auth = betterAuth({ database: { - provider: prismaAdapter(prisma), + provider: prismaAdapter(prisma, { + provider: "sqlite", // or "mysql", "postgresql", ...etc + }), }, }); ``` @@ -121,10 +122,13 @@ To use an adapter, import the adapter and pass it to the database option. ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters"; + import { db } from "@/db"; // your drizzle instance export const auth = betterAuth({ database: { - provider: drizzleAdapter(db), + provider: drizzleAdapter(db, { + provider: "pg", // or "mysql", "sqlite" + }), }, }); ``` @@ -148,17 +152,27 @@ To use an adapter, import the adapter and pass it to the database option. - ### Migrate Schema + ### Create Database Tables + + Better Auth includes a CLI tool to help manage your database tables. You can either migrate tables directly or generate an ORM schema or SQL migration file. - Better Auth includes a CLI tool to migrate the required schema to your database. It introspects the database and creates the required tables. Run the following command to perform the migration: + **Migrate**: Creates the necessary tables in your database. ```bash title="Terminal" npx better-auth migrate ``` - - If you're using an adapter and your database is not supported by Kysely, you need to create required tables manually. You can find the core schema required in the [database section](/docs/concepts/database#core-schema). - + **Generate**: Generates the required ORM schema (e.g., Prisma, Drizzle) or an SQL migration file. + + ```bash title="Terminal" + npx better-auth generate + ``` + + see the [CLI documentation](/docs/concepts/cli) for more information. + + + If you instead want to create the schema manually, you can find the core schema required in the [database section](/docs/concepts/database#core-schema). + diff --git a/packages/better-auth/src/adapters/drizzle-adapter/index.ts b/packages/better-auth/src/adapters/drizzle-adapter/index.ts index 80add51b49..8e77603160 100644 --- a/packages/better-auth/src/adapters/drizzle-adapter/index.ts +++ b/packages/better-auth/src/adapters/drizzle-adapter/index.ts @@ -68,6 +68,7 @@ export const drizzleAdapter = ( } const databaseType = options?.provider; return { + id: "drizzle", async create(data) { const { model, data: val } = data; const schemaModel = getSchema(model, schema); diff --git a/packages/better-auth/src/adapters/index.ts b/packages/better-auth/src/adapters/index.ts index a5a81e6047..39c468613a 100644 --- a/packages/better-auth/src/adapters/index.ts +++ b/packages/better-auth/src/adapters/index.ts @@ -1,3 +1,4 @@ export * from "./prisma-adapter"; export * from "./drizzle-adapter"; export * from "./mongodb-adapter"; +export * from "./kysely-adapter"; diff --git a/packages/better-auth/src/adapters/kysely-adapter/dialect.ts b/packages/better-auth/src/adapters/kysely-adapter/dialect.ts new file mode 100644 index 0000000000..2593ae4ef1 --- /dev/null +++ b/packages/better-auth/src/adapters/kysely-adapter/dialect.ts @@ -0,0 +1,89 @@ +import Database from "better-sqlite3"; +import { Kysely } from "kysely"; +import { + type Dialect, + MysqlDialect, + PostgresDialect, + SqliteDialect, +} from "kysely"; +import { createPool } from "mysql2"; +import type { BetterAuthOptions } from "../../types"; +import pg from "pg"; +import { BetterAuthError } from "../../error/better-auth-error"; + +const { Pool } = pg; + +export const getDialect = (config: BetterAuthOptions) => { + if (!config.database) { + return undefined; + } + if ("createDriver" in config.database) { + return config.database; + } + let dialect: Dialect | undefined = undefined; + if ("provider" in config.database) { + const provider = config.database.provider; + const connectionString = config.database?.url?.trim(); + if (provider === "postgres") { + dialect = new PostgresDialect({ + pool: new Pool({ + connectionString, + }), + }); + } + if (provider === "mysql") { + try { + const params = new URL(connectionString); + const pool = createPool({ + host: params.hostname, + user: params.username, + password: params.password, + database: params.pathname.split("/")[1], + port: Number(params.port), + }); + dialect = new MysqlDialect({ pool }); + } catch (e) { + if (e instanceof TypeError) { + throw new BetterAuthError("Invalid database URL"); + } + } + } + + if (provider === "sqlite") { + const db = new Database(connectionString); + dialect = new SqliteDialect({ + database: db, + }); + } + } + return dialect; +}; + +export const createKyselyAdapter = (config: BetterAuthOptions) => { + const dialect = getDialect(config); + if (!dialect) { + return dialect; + } + const db = new Kysely({ + dialect, + }); + return db; +}; + +export const getDatabaseType = (config: BetterAuthOptions) => { + if ("provider" in config.database) { + return config.database.provider; + } + if ("dialect" in config.database) { + if (config.database.dialect instanceof PostgresDialect) { + return "postgres"; + } + if (config.database.dialect instanceof MysqlDialect) { + return "mysql"; + } + if (config.database.dialect instanceof SqliteDialect) { + return "sqlite"; + } + } + return "sqlite"; +}; diff --git a/packages/better-auth/src/db/kysely.ts b/packages/better-auth/src/adapters/kysely-adapter/index.ts similarity index 68% rename from packages/better-auth/src/db/kysely.ts rename to packages/better-auth/src/adapters/kysely-adapter/index.ts index 9c1ca7f634..fe13826b97 100644 --- a/packages/better-auth/src/db/kysely.ts +++ b/packages/better-auth/src/adapters/kysely-adapter/index.ts @@ -1,20 +1,7 @@ -import Database from "better-sqlite3"; -import { Kysely } from "kysely"; -import { - type Dialect, - MysqlDialect, - PostgresDialect, - SqliteDialect, -} from "kysely"; -import { createPool } from "mysql2"; -import type { FieldAttribute } from "."; -import type { BetterAuthOptions } from "../types"; -import type { Adapter, Where } from "../types/adapter"; -import pg from "pg"; -import { BetterAuthError } from "../error/better-auth-error"; -import { getMigrations } from "../cli/utils/get-migration"; - -const { Pool } = pg; +import type { Kysely } from "kysely"; +import type { FieldAttribute } from "../../db"; +import type { Adapter, Where } from "../../types"; +import { getMigrations } from "../../cli/utils/get-migration"; function convertWhere(w?: Where[]) { if (!w) @@ -107,6 +94,7 @@ export const kyselyAdapter = ( config?: KyselyAdapterConfig, ): Adapter => { return { + id: "kysely", async create(data) { let { model, data: val, select } = data; if (config?.transform) { @@ -240,78 +228,3 @@ export const kyselyAdapter = ( }, }; }; - -export const getDialect = (config: BetterAuthOptions) => { - if (!config.database) { - return undefined; - } - if ("createDriver" in config.database) { - return config.database; - } - let dialect: Dialect | undefined = undefined; - if ("provider" in config.database) { - const provider = config.database.provider; - const connectionString = config.database?.url?.trim(); - if (provider === "postgres") { - dialect = new PostgresDialect({ - pool: new Pool({ - connectionString, - }), - }); - } - if (provider === "mysql") { - try { - const params = new URL(connectionString); - const pool = createPool({ - host: params.hostname, - user: params.username, - password: params.password, - database: params.pathname.split("/")[1], - port: Number(params.port), - }); - dialect = new MysqlDialect({ pool }); - } catch (e) { - if (e instanceof TypeError) { - throw new BetterAuthError("Invalid database URL"); - } - } - } - - if (provider === "sqlite") { - const db = new Database(connectionString); - dialect = new SqliteDialect({ - database: db, - }); - } - } - return dialect; -}; - -export const createKyselyAdapter = (config: BetterAuthOptions) => { - const dialect = getDialect(config); - if (!dialect) { - return dialect; - } - const db = new Kysely({ - dialect, - }); - return db; -}; - -export const getDatabaseType = (config: BetterAuthOptions) => { - if ("provider" in config.database) { - return config.database.provider; - } - if ("dialect" in config.database) { - if (config.database.dialect instanceof PostgresDialect) { - return "postgres"; - } - if (config.database.dialect instanceof MysqlDialect) { - return "mysql"; - } - if (config.database.dialect instanceof SqliteDialect) { - return "sqlite"; - } - } - return "sqlite"; -}; diff --git a/packages/better-auth/src/adapters/mongodb-adapter/index.ts b/packages/better-auth/src/adapters/mongodb-adapter/index.ts index de9f2b415b..03f26193fc 100644 --- a/packages/better-auth/src/adapters/mongodb-adapter/index.ts +++ b/packages/better-auth/src/adapters/mongodb-adapter/index.ts @@ -73,6 +73,7 @@ interface MongoClient { export const mongodbAdapter = (mongo: any) => { const db: MongoClient = mongo; return { + id: "mongodb", async create(data) { const { model, data: val } = data; const res = await db.collection(model).insertOne({ diff --git a/packages/better-auth/src/adapters/prisma-adapter/index.ts b/packages/better-auth/src/adapters/prisma-adapter/index.ts index 9174f7a0ba..ba12036d71 100644 --- a/packages/better-auth/src/adapters/prisma-adapter/index.ts +++ b/packages/better-auth/src/adapters/prisma-adapter/index.ts @@ -64,6 +64,7 @@ export const prismaAdapter = ({ }): Adapter => { const db: PrismaClient = prisma; return { + id: "prisma", async create(data) { const { model, data: val, select } = data; diff --git a/packages/better-auth/src/cli/commands/migrate.ts b/packages/better-auth/src/cli/commands/migrate.ts index 0e77225aef..122240d537 100644 --- a/packages/better-auth/src/cli/commands/migrate.ts +++ b/packages/better-auth/src/cli/commands/migrate.ts @@ -4,7 +4,7 @@ import { z } from "zod"; import { existsSync } from "fs"; import path from "path"; import { logger } from "../../utils/logger"; -import { createKyselyAdapter } from "../../db/kysely"; +import { createKyselyAdapter } from "../../adapters/kysely-adapter/dialect"; import ora from "ora"; import chalk from "chalk"; import prompts from "prompts"; diff --git a/packages/better-auth/src/cli/utils/get-migration.ts b/packages/better-auth/src/cli/utils/get-migration.ts index 2804b7460c..dae3fec281 100644 --- a/packages/better-auth/src/cli/utils/get-migration.ts +++ b/packages/better-auth/src/cli/utils/get-migration.ts @@ -7,7 +7,10 @@ import type { FieldAttribute, FieldType } from "../../db"; import { logger } from "../../utils/logger"; import type { BetterAuthOptions } from "../../types"; import { getSchema } from "./get-schema"; -import { createKyselyAdapter, getDatabaseType } from "../../db/kysely"; +import { + createKyselyAdapter, + getDatabaseType, +} from "../../adapters/kysely-adapter/dialect"; const postgresMap = { string: ["character varying", "text"], diff --git a/packages/better-auth/src/db/utils.ts b/packages/better-auth/src/db/utils.ts index de00fe07f6..fd7dd17bef 100644 --- a/packages/better-auth/src/db/utils.ts +++ b/packages/better-auth/src/db/utils.ts @@ -3,7 +3,11 @@ import { BetterAuthError } from "../error/better-auth-error"; import type { BetterAuthOptions } from "../types"; import type { Adapter } from "../types/adapter"; import { getAuthTables } from "./get-tables"; -import { createKyselyAdapter, getDatabaseType, kyselyAdapter } from "./kysely"; +import { + createKyselyAdapter, + getDatabaseType, +} from "../adapters/kysely-adapter/dialect"; +import { kyselyAdapter } from "../adapters/kysely-adapter"; export function getAdapter(options: BetterAuthOptions): Adapter { if (!options.database) { diff --git a/packages/better-auth/src/init.ts b/packages/better-auth/src/init.ts index 225c69e9c8..3b8273f9c2 100644 --- a/packages/better-auth/src/init.ts +++ b/packages/better-auth/src/init.ts @@ -1,6 +1,6 @@ import type { Kysely } from "kysely"; import { getAuthTables } from "./db/get-tables"; -import { createKyselyAdapter } from "./db/kysely"; +import { createKyselyAdapter } from "./adapters/kysely-adapter/dialect"; import { getAdapter } from "./db/utils"; import { hashPassword, verifyPassword } from "./crypto/password"; import { createInternalAdapter } from "./db"; diff --git a/packages/better-auth/src/types/adapter.ts b/packages/better-auth/src/types/adapter.ts index 9a02ffd9c2..ff31960a02 100644 --- a/packages/better-auth/src/types/adapter.ts +++ b/packages/better-auth/src/types/adapter.ts @@ -16,6 +16,7 @@ export type Where = { * Adapter Interface */ export interface Adapter { + id: string; create: (data: { model: string; data: T;