docs: cli

This commit is contained in:
Bereket Engida
2024-09-30 09:50:44 +03:00
parent 2c7bc23e7f
commit 6fafffb91c
16 changed files with 244 additions and 122 deletions

View File

@@ -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();

View File

@@ -133,6 +133,23 @@ export const contents: Content[] = [
</svg>
),
},
{
title: "CLI",
icon: () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1.2em"
height="1.2em"
viewBox="0 0 256 256"
>
<path
fill="currentColor"
d="M216 40H40a16 16 0 0 0-16 16v144a16 16 0 0 0 16 16h176a16 16 0 0 0 16-16V56a16 16 0 0 0-16-16m-91 94.25l-40 32a8 8 0 1 1-10-12.5L107.19 128L75 102.25a8 8 0 1 1 10-12.5l40 32a8 8 0 0 1 0 12.5M176 168h-40a8 8 0 0 1 0-16h40a8 8 0 0 1 0 16"
></path>
</svg>
),
href: "/docs/concepts/cli",
},
{
title: "Cookies",
href: "/docs/concepts/cookies",

View File

@@ -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, its 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 youre 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.

View File

@@ -60,9 +60,42 @@ See <Link href="https://kysely.dev/docs/dialects" target="_blank"> Kysley Dialec
</Callout>
## 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.
<Callout>
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.
</Callout>
## Core Schema

View File

@@ -66,40 +66,39 @@ export const auth = betterAuth({
<Step>
### Configure Database
Better Auth requires a database to store user data. By default, it uses <Link href="https://kysely.dev/">Kysely </Link> 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 isnt supported by Kysely, you can use an adapter to connect. Simply import the adapter and pass it into the `database` option.
<Tabs items={["prisma", "drizzle", "mongodb"]}>
<Tab value="prisma">
@@ -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.
</Step>
<Step>
### 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
```
<Callout type="warn">
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).
</Callout>
**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.
<Callout>
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).
</Callout>
</Step>
<Step>

View File

@@ -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);

View File

@@ -1,3 +1,4 @@
export * from "./prisma-adapter";
export * from "./drizzle-adapter";
export * from "./mongodb-adapter";
export * from "./kysely-adapter";

View File

@@ -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<any>({
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";
};

View File

@@ -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<any>({
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";
};

View File

@@ -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({

View File

@@ -64,6 +64,7 @@ export const prismaAdapter = ({
}): Adapter => {
const db: PrismaClient = prisma;
return {
id: "prisma",
async create(data) {
const { model, data: val, select } = data;

View File

@@ -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";

View File

@@ -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"],

View File

@@ -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) {

View File

@@ -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";

View File

@@ -16,6 +16,7 @@ export type Where = {
* Adapter Interface
*/
export interface Adapter {
id: string;
create: <T, R = T>(data: {
model: string;
data: T;