refactor: move db schema to core (#4918)

This commit is contained in:
Alex Yang
2025-09-26 01:11:10 -07:00
committed by GitHub
parent dad381b5ce
commit 30fa5fc0d2
28 changed files with 310 additions and 238 deletions

View File

@@ -14,7 +14,7 @@ import type {
AdapterTestDebugLogs, AdapterTestDebugLogs,
CleanedWhere, CleanedWhere,
} from "./types"; } from "./types";
import type { FieldAttribute } from "../../db"; import type { DBFieldAttribute } from "@better-auth/core/db";
export * from "./types"; export * from "./types";
let debugLogs: any[] = []; let debugLogs: any[] = [];
@@ -303,7 +303,7 @@ export const createAdapterFactory =
}, },
} }
: {}), : {}),
} satisfies FieldAttribute; } satisfies DBFieldAttribute;
}; };
const getFieldAttributes = ({ const getFieldAttributes = ({

View File

@@ -1,5 +1,5 @@
import type { FieldAttribute } from "../../db"; import type { DBFieldAttribute } from "@better-auth/core/db";
import type { BetterAuthDbSchema } from "../../db/get-tables"; import type { BetterAuthDBSchema } from "@better-auth/core/db";
import type { import type {
AdapterSchemaCreation, AdapterSchemaCreation,
BetterAuthOptions, BetterAuthOptions,
@@ -164,7 +164,7 @@ export interface AdapterFactoryConfig {
/** /**
* The fields of the model. * The fields of the model.
*/ */
fieldAttributes: FieldAttribute; fieldAttributes: DBFieldAttribute;
/** /**
* The field to transform. * The field to transform.
*/ */
@@ -180,7 +180,7 @@ export interface AdapterFactoryConfig {
/** /**
* The schema of the user's Better-Auth instance. * The schema of the user's Better-Auth instance.
*/ */
schema: BetterAuthDbSchema; schema: BetterAuthDBSchema;
/** /**
* The options of the user's Better-Auth instance. * The options of the user's Better-Auth instance.
*/ */
@@ -196,7 +196,7 @@ export interface AdapterFactoryConfig {
/** /**
* The fields of the model. * The fields of the model.
*/ */
fieldAttributes: FieldAttribute; fieldAttributes: DBFieldAttribute;
/** /**
* The field to transform. * The field to transform.
*/ */
@@ -212,7 +212,7 @@ export interface AdapterFactoryConfig {
/** /**
* The schema of the user's Better-Auth instance. * The schema of the user's Better-Auth instance.
*/ */
schema: BetterAuthDbSchema; schema: BetterAuthDBSchema;
/** /**
* The options of the user's Better-Auth instance. * The options of the user's Better-Auth instance.
*/ */
@@ -246,7 +246,7 @@ export type AdapterFactoryCustomizeAdapterCreator = (config: {
/** /**
* The schema of the user's Better-Auth instance. * The schema of the user's Better-Auth instance.
*/ */
schema: BetterAuthDbSchema; schema: BetterAuthDBSchema;
/** /**
* The debug log function. * The debug log function.
* *
@@ -303,7 +303,7 @@ export type AdapterFactoryCustomizeAdapterCreator = (config: {
}: { }: {
model: string; model: string;
field: string; field: string;
}) => FieldAttribute; }) => DBFieldAttribute;
}) => CustomAdapter; }) => CustomAdapter;
export interface CustomAdapter { export interface CustomAdapter {
@@ -377,7 +377,7 @@ export interface CustomAdapter {
/** /**
* The tables from the user's Better-Auth instance schema. * The tables from the user's Better-Auth instance schema.
*/ */
tables: BetterAuthDbSchema; tables: BetterAuthDBSchema;
}) => Promise<AdapterSchemaCreation>; }) => Promise<AdapterSchemaCreation>;
/** /**
* Your adapter's options. * Your adapter's options.

View File

@@ -1,8 +1,8 @@
import type { FieldAttribute } from "../db"; import type { DBFieldAttribute } from "@better-auth/core/db";
export function withApplyDefault( export function withApplyDefault(
value: any, value: any,
field: FieldAttribute, field: DBFieldAttribute,
action: "create" | "update", action: "create" | "update",
) { ) {
if (action === "update") { if (action === "update") {

View File

@@ -1,118 +1,13 @@
import type { ZodType } from "zod"; import type {
DBFieldAttribute,
DBFieldAttributeConfig,
DBFieldType,
} from "@better-auth/core/db";
import type { BetterAuthOptions } from "../types"; import type { BetterAuthOptions } from "../types";
import type { LiteralString } from "../types/helper";
export type FieldType =
| "string"
| "number"
| "boolean"
| "date"
| "json"
| `${"string" | "number"}[]`
| Array<LiteralString>;
type Primitive =
| string
| number
| boolean
| Date
| null
| undefined
| string[]
| number[];
export type FieldAttributeConfig<T extends FieldType = FieldType> = {
/**
* If the field should be required on a new record.
* @default true
*/
required?: boolean;
/**
* If the value should be returned on a response body.
* @default true
*/
returned?: boolean;
/**
* If a value should be provided when creating a new record.
* @default true
*/
input?: boolean;
/**
* Default value for the field
*
* Note: This will not create a default value on the database level. It will only
* be used when creating a new record.
*/
defaultValue?: Primitive | (() => Primitive);
/**
* Update value for the field
*
* Note: This will create an onUpdate trigger on the database level for supported adapters.
* It will be called when updating a record.
*/
onUpdate?: () => Primitive;
/**
* transform the value before storing it.
*/
transform?: {
input?: (value: Primitive) => Primitive | Promise<Primitive>;
output?: (value: Primitive) => Primitive | Promise<Primitive>;
};
/**
* Reference to another model.
*/
references?: {
/**
* The model to reference.
*/
model: string;
/**
* The field on the referenced model.
*/
field: string;
/**
* The action to perform when the reference is deleted.
* @default "cascade"
*/
onDelete?:
| "no action"
| "restrict"
| "cascade"
| "set null"
| "set default";
};
unique?: boolean;
/**
* If the field should be a bigint on the database instead of integer.
*/
bigint?: boolean;
/**
* A zod schema to validate the value.
*/
validator?: {
input?: ZodType;
output?: ZodType;
};
/**
* The name of the field on the database.
*/
fieldName?: string;
/**
* If the field should be sortable.
*
* applicable only for `text` type.
* It's useful to mark fields varchar instead of text.
*/
sortable?: boolean;
};
export type FieldAttribute<T extends FieldType = FieldType> = {
type: T;
} & FieldAttributeConfig<T>;
export const createFieldAttribute = < export const createFieldAttribute = <
T extends FieldType, T extends DBFieldType,
C extends Omit<FieldAttributeConfig<T>, "type">, C extends DBFieldAttributeConfig,
>( >(
type: T, type: T,
config?: C, config?: C,
@@ -120,10 +15,10 @@ export const createFieldAttribute = <
return { return {
type, type,
...config, ...config,
} satisfies FieldAttribute<T>; } satisfies DBFieldAttribute<T>;
}; };
export type InferValueType<T extends FieldType> = T extends "string" export type InferValueType<T extends DBFieldType> = T extends "string"
? string ? string
: T extends "number" : T extends "number"
? number ? number
@@ -141,7 +36,7 @@ export type InferValueType<T extends FieldType> = T extends "string"
export type InferFieldsOutput<Field> = Field extends Record< export type InferFieldsOutput<Field> = Field extends Record<
infer Key, infer Key,
FieldAttribute DBFieldAttribute
> >
? { ? {
[key in Key as Field[key]["required"] extends false [key in Key as Field[key]["required"] extends false
@@ -158,7 +53,7 @@ export type InferFieldsOutput<Field> = Field extends Record<
export type InferFieldsInput<Field> = Field extends Record< export type InferFieldsInput<Field> = Field extends Record<
infer Key, infer Key,
FieldAttribute DBFieldAttribute
> >
? { ? {
[key in Key as Field[key]["required"] extends false [key in Key as Field[key]["required"] extends false
@@ -181,7 +76,7 @@ export type InferFieldsInput<Field> = Field extends Record<
*/ */
export type InferFieldsInputClient<Field> = Field extends Record< export type InferFieldsInputClient<Field> = Field extends Record<
infer Key, infer Key,
FieldAttribute DBFieldAttribute
> >
? { ? {
[key in Key as Field[key]["required"] extends false [key in Key as Field[key]["required"] extends false
@@ -202,18 +97,18 @@ export type InferFieldsInputClient<Field> = Field extends Record<
} }
: {}; : {};
type InferFieldOutput<T extends FieldAttribute> = T["returned"] extends false type InferFieldOutput<T extends DBFieldAttribute> = T["returned"] extends false
? never ? never
: T["required"] extends false : T["required"] extends false
? InferValueType<T["type"]> | undefined | null ? InferValueType<T["type"]> | undefined | null
: InferValueType<T["type"]>; : InferValueType<T["type"]>;
/** /**
* Converts a Record<string, FieldAttribute> to an object type * Converts a Record<string, DBFieldAttribute> to an object type
* with keys and value types inferred from FieldAttribute["type"]. * with keys and value types inferred from DBFieldAttribute["type"].
*/ */
export type FieldAttributeToObject< export type FieldAttributeToObject<
Fields extends Record<string, FieldAttribute>, Fields extends Record<string, DBFieldAttribute>,
> = AddOptionalFields< > = AddOptionalFields<
{ {
[K in keyof Fields]: InferValueType<Fields[K]["type"]>; [K in keyof Fields]: InferValueType<Fields[K]["type"]>;
@@ -223,7 +118,7 @@ export type FieldAttributeToObject<
type AddOptionalFields< type AddOptionalFields<
T extends Record<string, any>, T extends Record<string, any>,
Fields extends Record<keyof T, FieldAttribute>, Fields extends Record<keyof T, DBFieldAttribute>,
> = { > = {
// Required fields: required === true // Required fields: required === true
[K in keyof T as Fields[K] extends { required: true } ? K : never]: T[K]; [K in keyof T as Fields[K] extends { required: true } ? K : never]: T[K];
@@ -244,14 +139,14 @@ export type InferAdditionalFieldsFromPluginOptions<
Options extends { Options extends {
schema?: { schema?: {
[key in SchemaName]?: { [key in SchemaName]?: {
additionalFields?: Record<string, FieldAttribute>; additionalFields?: Record<string, DBFieldAttribute>;
}; };
}; };
}, },
isClientSide extends boolean = true, isClientSide extends boolean = true,
> = Options["schema"] extends { > = Options["schema"] extends {
[key in SchemaName]?: { [key in SchemaName]?: {
additionalFields: infer Field extends Record<string, FieldAttribute>; additionalFields: infer Field extends Record<string, DBFieldAttribute>;
}; };
} }
? isClientSide extends true ? isClientSide extends true
@@ -259,14 +154,14 @@ export type InferAdditionalFieldsFromPluginOptions<
: FieldAttributeToObject<Field> : FieldAttributeToObject<Field>
: {}; : {};
type RemoveFieldsWithInputFalse<T extends Record<string, FieldAttribute>> = { type RemoveFieldsWithInputFalse<T extends Record<string, DBFieldAttribute>> = {
[K in keyof T as T[K]["input"] extends false ? never : K]: T[K]; [K in keyof T as T[K]["input"] extends false ? never : K]: T[K];
}; };
type InferFieldInput<T extends FieldAttribute> = InferValueType<T["type"]>; type InferFieldInput<T extends DBFieldAttribute> = InferValueType<T["type"]>;
export type PluginFieldAttribute = Omit< export type PluginFieldAttribute = Omit<
FieldAttribute, DBFieldAttribute,
"transform" | "defaultValue" | "hashValue" "transform" | "defaultValue" | "hashValue"
>; >;

View File

@@ -2,7 +2,7 @@ import type {
AlterTableColumnAlteringBuilder, AlterTableColumnAlteringBuilder,
CreateTableBuilder, CreateTableBuilder,
} from "kysely"; } from "kysely";
import type { FieldAttribute, FieldType } from "."; import type { DBFieldAttribute, DBFieldType } from "@better-auth/core/db";
import { sql } from "kysely"; import { sql } from "kysely";
import { createLogger } from "../utils/logger"; import { createLogger } from "../utils/logger";
import type { BetterAuthOptions } from "../types"; import type { BetterAuthOptions } from "../types";
@@ -66,7 +66,7 @@ const map = {
export function matchType( export function matchType(
columnDataType: string, columnDataType: string,
fieldType: FieldType, fieldType: DBFieldType,
dbType: KyselyDatabaseType, dbType: KyselyDatabaseType,
) { ) {
function normalize(type: string) { function normalize(type: string) {
@@ -104,12 +104,12 @@ export async function getMigrations(config: BetterAuthOptions) {
const tableMetadata = await db.introspection.getTables(); const tableMetadata = await db.introspection.getTables();
const toBeCreated: { const toBeCreated: {
table: string; table: string;
fields: Record<string, FieldAttribute>; fields: Record<string, DBFieldAttribute>;
order: number; order: number;
}[] = []; }[] = [];
const toBeAdded: { const toBeAdded: {
table: string; table: string;
fields: Record<string, FieldAttribute>; fields: Record<string, DBFieldAttribute>;
order: number; order: number;
}[] = []; }[] = [];
@@ -141,7 +141,7 @@ export async function getMigrations(config: BetterAuthOptions) {
} }
continue; continue;
} }
let toBeAddedFields: Record<string, FieldAttribute> = {}; let toBeAddedFields: Record<string, DBFieldAttribute> = {};
for (const [fieldName, field] of Object.entries(value.fields)) { for (const [fieldName, field] of Object.entries(value.fields)) {
const column = table.columns.find((c) => c.name === fieldName); const column = table.columns.find((c) => c.name === fieldName);
if (!column) { if (!column) {
@@ -171,7 +171,7 @@ export async function getMigrations(config: BetterAuthOptions) {
| CreateTableBuilder<string, string> | CreateTableBuilder<string, string>
)[] = []; )[] = [];
function getType(field: FieldAttribute, fieldName: string) { function getType(field: DBFieldAttribute, fieldName: string) {
const type = field.type; const type = field.type;
const typeMap = { const typeMap = {
string: { string: {

View File

@@ -1,19 +1,20 @@
import { getAuthTables, type FieldAttribute } from "."; import { getAuthTables } from ".";
import type { BetterAuthOptions } from "../types"; import type { BetterAuthOptions } from "../types";
import type { DBFieldAttribute } from "@better-auth/core/db";
export function getSchema(config: BetterAuthOptions) { export function getSchema(config: BetterAuthOptions) {
const tables = getAuthTables(config); const tables = getAuthTables(config);
let schema: Record< let schema: Record<
string, string,
{ {
fields: Record<string, FieldAttribute>; fields: Record<string, DBFieldAttribute>;
order: number; order: number;
} }
> = {}; > = {};
for (const key in tables) { for (const key in tables) {
const table = tables[key]!; const table = tables[key]!;
const fields = table.fields; const fields = table.fields;
let actualFields: Record<string, FieldAttribute> = {}; let actualFields: Record<string, DBFieldAttribute> = {};
Object.entries(fields).forEach(([key, field]) => { Object.entries(fields).forEach(([key, field]) => {
actualFields[field.fieldName || key] = field; actualFields[field.fieldName || key] = field;
if (field.references) { if (field.references) {

View File

@@ -1,32 +1,12 @@
import type { FieldAttribute } from ".";
import type { BetterAuthOptions } from "../types"; import type { BetterAuthOptions } from "../types";
import type {
export type BetterAuthDbSchema = Record< BetterAuthDBSchema,
string, DBFieldAttribute,
{ } from "@better-auth/core/db";
/**
* The name of the table in the database
*/
modelName: string;
/**
* The fields of the table
*/
fields: Record<string, FieldAttribute>;
/**
* Whether to disable migrations for this table
* @default false
*/
disableMigrations?: boolean;
/**
* The order of the table
*/
order?: number;
}
>;
export const getAuthTables = ( export const getAuthTables = (
options: BetterAuthOptions, options: BetterAuthOptions,
): BetterAuthDbSchema => { ): BetterAuthDBSchema => {
const pluginSchema = (options.plugins ?? []).reduce( const pluginSchema = (options.plugins ?? []).reduce(
(acc, plugin) => { (acc, plugin) => {
const schema = plugin.schema; const schema = plugin.schema;
@@ -44,7 +24,7 @@ export const getAuthTables = (
}, },
{} as Record< {} as Record<
string, string,
{ fields: Record<string, FieldAttribute>; modelName: string } { fields: Record<string, DBFieldAttribute>; modelName: string }
>, >,
); );
@@ -68,7 +48,7 @@ export const getAuthTables = (
}, },
}, },
}, },
} satisfies BetterAuthDbSchema; } satisfies BetterAuthDBSchema;
const { user, session, account, ...pluginTables } = pluginSchema; const { user, session, account, ...pluginTables } = pluginSchema;
@@ -124,7 +104,7 @@ export const getAuthTables = (
}, },
order: 2, order: 2,
}, },
} satisfies BetterAuthDbSchema; } satisfies BetterAuthDBSchema;
return { return {
user: { user: {
@@ -290,5 +270,5 @@ export const getAuthTables = (
}, },
...pluginTables, ...pluginTables,
...(shouldAddRateLimitTable ? rateLimitTable : {}), ...(shouldAddRateLimitTable ? rateLimitTable : {}),
} satisfies BetterAuthDbSchema; } satisfies BetterAuthDBSchema;
}; };

View File

@@ -1,3 +1,4 @@
export * from "@better-auth/core/db";
export * from "./internal-adapter"; export * from "./internal-adapter";
export * from "./field"; export * from "./field";
export * from "./get-tables"; export * from "./get-tables";

View File

@@ -1,9 +1,9 @@
import * as z from "zod"; import * as z from "zod";
import type { FieldAttribute } from ".";
import type { AuthPluginSchema } from "../types/plugins"; import type { AuthPluginSchema } from "../types/plugins";
import type { BetterAuthOptions } from "../types/options"; import type { BetterAuthOptions } from "../types/options";
import { APIError } from "better-call"; import { APIError } from "better-call";
import type { Account, Session, User } from "../types"; import type { Account, Session, User } from "../types";
import type { DBFieldAttribute } from "@better-auth/core/db";
export const coreSchema = z.object({ export const coreSchema = z.object({
id: z.string(), id: z.string(),
@@ -60,7 +60,7 @@ export const verificationSchema = coreSchema.extend({
export function parseOutputData<T extends Record<string, any>>( export function parseOutputData<T extends Record<string, any>>(
data: T, data: T,
schema: { schema: {
fields: Record<string, FieldAttribute>; fields: Record<string, DBFieldAttribute>;
}, },
) { ) {
const fields = schema.fields; const fields = schema.fields;
@@ -80,7 +80,7 @@ export function parseOutputData<T extends Record<string, any>>(
} }
export function getAllFields(options: BetterAuthOptions, table: string) { export function getAllFields(options: BetterAuthOptions, table: string) {
let schema: Record<string, FieldAttribute> = { let schema: Record<string, DBFieldAttribute> = {
...(table === "user" ? options.user?.additionalFields : {}), ...(table === "user" ? options.user?.additionalFields : {}),
...(table === "session" ? options.session?.additionalFields : {}), ...(table === "session" ? options.session?.additionalFields : {}),
}; };
@@ -119,7 +119,7 @@ export function parseSessionOutput(
export function parseInputData<T extends Record<string, any>>( export function parseInputData<T extends Record<string, any>>(
data: T, data: T,
schema: { schema: {
fields: Record<string, FieldAttribute>; fields: Record<string, DBFieldAttribute>;
action?: "create" | "update"; action?: "create" | "update";
}, },
) { ) {

View File

@@ -1,9 +1,9 @@
import * as z from "zod"; import * as z from "zod";
import type { ZodType } from "zod"; import type { ZodType } from "zod";
import type { FieldAttribute } from "."; import type { DBFieldAttribute } from "@better-auth/core/db";
export function toZodSchema< export function toZodSchema<
Fields extends Record<string, FieldAttribute | never>, Fields extends Record<string, DBFieldAttribute | never>,
IsClientSide extends boolean, IsClientSide extends boolean,
>({ >({
fields, fields,
@@ -56,14 +56,14 @@ export function toZodSchema<
} }
export type FieldAttributeToSchema< export type FieldAttributeToSchema<
Field extends FieldAttribute | Record<string, never>, Field extends DBFieldAttribute | Record<string, never>,
// if it's client side, then field attributes of `input` that are false should be removed // if it's client side, then field attributes of `input` that are false should be removed
isClientSide extends boolean = false, isClientSide extends boolean = false,
> = Field extends { type: any } > = Field extends { type: any }
? GetInput<isClientSide, Field, GetRequired<Field, GetType<Field>>> ? GetInput<isClientSide, Field, GetRequired<Field, GetType<Field>>>
: Record<string, never>; : Record<string, never>;
type GetType<F extends FieldAttribute> = F extends { type GetType<F extends DBFieldAttribute> = F extends {
type: "string"; type: "string";
} }
? z.ZodString ? z.ZodString
@@ -76,7 +76,7 @@ type GetType<F extends FieldAttribute> = F extends {
: z.ZodAny; : z.ZodAny;
type GetRequired< type GetRequired<
F extends FieldAttribute, F extends DBFieldAttribute,
Schema extends z.core.SomeType, Schema extends z.core.SomeType,
> = F extends { > = F extends {
required: true; required: true;
@@ -86,7 +86,7 @@ type GetRequired<
type GetInput< type GetInput<
isClientSide extends boolean, isClientSide extends boolean,
Field extends FieldAttribute, Field extends DBFieldAttribute,
Schema extends z.core.SomeType, Schema extends z.core.SomeType,
> = Field extends { > = Field extends {
input: false; input: false;

View File

@@ -1,10 +1,11 @@
import { getAuthTables, type FieldAttribute } from "."; import { getAuthTables } from ".";
import { BetterAuthError } from "../error"; import { BetterAuthError } from "../error";
import type { Adapter, BetterAuthOptions } from "../types"; import type { Adapter, BetterAuthOptions } from "../types";
import { createKyselyAdapter } from "../adapters/kysely-adapter/dialect"; import { createKyselyAdapter } from "../adapters/kysely-adapter/dialect";
import { kyselyAdapter } from "../adapters/kysely-adapter"; import { kyselyAdapter } from "../adapters/kysely-adapter";
import { memoryAdapter, type MemoryDB } from "../adapters/memory-adapter"; import { memoryAdapter, type MemoryDB } from "../adapters/memory-adapter";
import { logger } from "../utils"; import { logger } from "../utils";
import type { DBFieldAttribute } from "@better-auth/core/db";
export async function getAdapter(options: BetterAuthOptions): Promise<Adapter> { export async function getAdapter(options: BetterAuthOptions): Promise<Adapter> {
if (!options.database) { if (!options.database) {
@@ -37,7 +38,7 @@ export async function getAdapter(options: BetterAuthOptions): Promise<Adapter> {
} }
export function convertToDB<T extends Record<string, any>>( export function convertToDB<T extends Record<string, any>>(
fields: Record<string, FieldAttribute>, fields: Record<string, DBFieldAttribute>,
values: T, values: T,
) { ) {
let result: Record<string, any> = values.id let result: Record<string, any> = values.id
@@ -57,7 +58,7 @@ export function convertToDB<T extends Record<string, any>>(
} }
export function convertFromDB<T extends Record<string, any>>( export function convertFromDB<T extends Record<string, any>>(
fields: Record<string, FieldAttribute>, fields: Record<string, DBFieldAttribute>,
values: T | null, values: T | null,
) { ) {
if (!values) { if (!values) {

View File

@@ -1,11 +1,6 @@
import { defu } from "defu"; import { defu } from "defu";
import { hashPassword, verifyPassword } from "./crypto/password"; import { hashPassword, verifyPassword } from "./crypto/password";
import { import { createInternalAdapter, getAuthTables, getMigrations } from "./db";
type BetterAuthDbSchema,
createInternalAdapter,
getAuthTables,
getMigrations,
} from "./db";
import type { Entries } from "type-fest"; import type { Entries } from "type-fest";
import { getAdapter } from "./db/utils"; import { getAdapter } from "./db/utils";
import type { import type {
@@ -37,6 +32,7 @@ import type { TelemetryEvent } from "./telemetry/types";
import { getKyselyDatabaseType } from "./adapters/kysely-adapter"; import { getKyselyDatabaseType } from "./adapters/kysely-adapter";
import { checkEndpointConflicts } from "./api"; import { checkEndpointConflicts } from "./api";
import { isPromise } from "./utils/is-promise"; import { isPromise } from "./utils/is-promise";
import type { BetterAuthDBSchema } from "@better-auth/core/db";
export const init = async (options: BetterAuthOptions) => { export const init = async (options: BetterAuthOptions) => {
const adapter = await getAdapter(options); const adapter = await getAdapter(options);
@@ -245,7 +241,7 @@ export type AuthContext = {
}; };
checkPassword: typeof checkPassword; checkPassword: typeof checkPassword;
}; };
tables: BetterAuthDbSchema; tables: BetterAuthDBSchema;
runMigrations: () => Promise<void>; runMigrations: () => Promise<void>;
publishTelemetry: (event: TelemetryEvent) => Promise<void>; publishTelemetry: (event: TelemetryEvent) => Promise<void>;
}; };

View File

@@ -1,4 +1,4 @@
import type { FieldAttribute } from "../../db"; import type { DBFieldAttribute } from "@better-auth/core/db";
import type { BetterAuthClientPlugin, BetterAuthOptions } from "../../types"; import type { BetterAuthClientPlugin, BetterAuthOptions } from "../../types";
import type { BetterAuthPlugin } from "../../types"; import type { BetterAuthPlugin } from "../../types";
@@ -6,10 +6,10 @@ export const inferAdditionalFields = <
T, T,
S extends { S extends {
user?: { user?: {
[key: string]: FieldAttribute; [key: string]: DBFieldAttribute;
}; };
session?: { session?: {
[key: string]: FieldAttribute; [key: string]: DBFieldAttribute;
}; };
} = {}, } = {},
>( >(
@@ -26,10 +26,10 @@ export const inferAdditionalFields = <
type Plugin = Opts extends never type Plugin = Opts extends never
? S extends { ? S extends {
user?: { user?: {
[key: string]: FieldAttribute; [key: string]: DBFieldAttribute;
}; };
session?: { session?: {
[key: string]: FieldAttribute; [key: string]: DBFieldAttribute;
}; };
} }
? { ? {

View File

@@ -6,13 +6,13 @@ import type {
} from "better-call"; } from "better-call";
import * as z from "zod"; import * as z from "zod";
import { getEndpoints } from "../../api"; import { getEndpoints } from "../../api";
import { import { getAuthTables } from "../../db";
type FieldAttributeConfig,
type FieldType,
getAuthTables,
} from "../../db";
import type { AuthContext, BetterAuthOptions } from "../../types"; import type { AuthContext, BetterAuthOptions } from "../../types";
import type { FieldAttribute } from "../../db"; import type {
DBFieldAttribute,
DBFieldAttributeConfig,
DBFieldType,
} from "@better-auth/core/db";
export interface Path { export interface Path {
get?: { get?: {
@@ -81,8 +81,8 @@ function getTypeFromZodType(zodType: z.ZodType<any>) {
} }
type FieldSchema = { type FieldSchema = {
type: FieldType; type: DBFieldType;
default?: FieldAttributeConfig["defaultValue"] | "Generated at runtime"; default?: DBFieldAttributeConfig["defaultValue"] | "Generated at runtime";
readOnly?: boolean; readOnly?: boolean;
}; };
@@ -92,7 +92,7 @@ type OpenAPIModelSchema = {
required?: string[]; required?: string[];
}; };
function getFieldSchema(field: FieldAttribute) { function getFieldSchema(field: DBFieldAttribute) {
const schema: FieldSchema = { const schema: FieldSchema = {
type: field.type === "date" ? "string" : field.type, type: field.type === "date" ? "string" : field.type,
}; };

View File

@@ -19,7 +19,7 @@ import {
ownerAc, ownerAc,
defaultRoles, defaultRoles,
} from "./access"; } from "./access";
import type { FieldAttribute } from "../../db"; import type { DBFieldAttribute } from "@better-auth/core/db";
import type { BetterAuthOptions, BetterAuthPlugin } from "../../types"; import type { BetterAuthOptions, BetterAuthPlugin } from "../../types";
import type { OrganizationOptions } from "./types"; import type { OrganizationOptions } from "./types";
import type { HasPermissionBaseInput } from "./permission"; import type { HasPermissionBaseInput } from "./permission";
@@ -47,27 +47,27 @@ interface OrganizationClientOptions {
schema?: { schema?: {
organization?: { organization?: {
additionalFields?: { additionalFields?: {
[key: string]: FieldAttribute; [key: string]: DBFieldAttribute;
}; };
}; };
member?: { member?: {
additionalFields?: { additionalFields?: {
[key: string]: FieldAttribute; [key: string]: DBFieldAttribute;
}; };
}; };
invitation?: { invitation?: {
additionalFields?: { additionalFields?: {
[key: string]: FieldAttribute; [key: string]: DBFieldAttribute;
}; };
}; };
team?: { team?: {
additionalFields?: { additionalFields?: {
[key: string]: FieldAttribute; [key: string]: DBFieldAttribute;
}; };
}; };
organizationRole?: { organizationRole?: {
additionalFields?: { additionalFields?: {
[key: string]: FieldAttribute; [key: string]: DBFieldAttribute;
}; };
}; };
}; };

View File

@@ -6,7 +6,7 @@ import { inferOrgAdditionalFields, organizationClient } from "../client";
import { createAccessControl } from "../../access"; import { createAccessControl } from "../../access";
import { adminAc, defaultStatements, memberAc, ownerAc } from "../access"; import { adminAc, defaultStatements, memberAc, ownerAc } from "../access";
import { parseSetCookieHeader } from "../../../cookies"; import { parseSetCookieHeader } from "../../../cookies";
import type { FieldAttribute } from "../../../db"; import type { DBFieldAttribute } from "@better-auth/core/db";
import { ORGANIZATION_ERROR_CODES } from "../error-codes"; import { ORGANIZATION_ERROR_CODES } from "../error-codes";
describe("dynamic access control", async (it) => { describe("dynamic access control", async (it) => {
@@ -43,7 +43,7 @@ describe("dynamic access control", async (it) => {
input: false, input: false,
required: true, required: true,
}, },
} satisfies Record<string, FieldAttribute>; } satisfies Record<string, DBFieldAttribute>;
const { auth, customFetchImpl, sessionSetter, signInWithTestUser } = const { auth, customFetchImpl, sessionSetter, signInWithTestUser } =
await getTestInstance({ await getTestInstance({

View File

@@ -1,4 +1,4 @@
import type { FieldAttribute } from "../../db"; import type { DBFieldAttribute } from "@better-auth/core/db";
import type { User, Session, AuthContext } from "../../types"; import type { User, Session, AuthContext } from "../../types";
import type { AccessControl, Role } from "../access"; import type { AccessControl, Role } from "../access";
import type { import type {
@@ -257,7 +257,7 @@ export interface OrganizationOptions {
[key in keyof Omit<Organization, "id">]?: string; [key in keyof Omit<Organization, "id">]?: string;
}; };
additionalFields?: { additionalFields?: {
[key in string]: FieldAttribute; [key in string]: DBFieldAttribute;
}; };
}; };
member?: { member?: {
@@ -266,7 +266,7 @@ export interface OrganizationOptions {
[key in keyof Omit<Member, "id">]?: string; [key in keyof Omit<Member, "id">]?: string;
}; };
additionalFields?: { additionalFields?: {
[key in string]: FieldAttribute; [key in string]: DBFieldAttribute;
}; };
}; };
invitation?: { invitation?: {
@@ -275,7 +275,7 @@ export interface OrganizationOptions {
[key in keyof Omit<Invitation, "id">]?: string; [key in keyof Omit<Invitation, "id">]?: string;
}; };
additionalFields?: { additionalFields?: {
[key in string]: FieldAttribute; [key in string]: DBFieldAttribute;
}; };
}; };
team?: { team?: {
@@ -284,7 +284,7 @@ export interface OrganizationOptions {
[key in keyof Omit<Team, "id">]?: string; [key in keyof Omit<Team, "id">]?: string;
}; };
additionalFields?: { additionalFields?: {
[key in string]: FieldAttribute; [key in string]: DBFieldAttribute;
}; };
}; };
teamMember?: { teamMember?: {
@@ -299,7 +299,7 @@ export interface OrganizationOptions {
[key in keyof Omit<OrganizationRole, "id">]?: string; [key in keyof Omit<OrganizationRole, "id">]?: string;
}; };
additionalFields?: { additionalFields?: {
[key in string]: FieldAttribute; [key in string]: DBFieldAttribute;
}; };
}; };
}; };

View File

@@ -10,7 +10,7 @@ import type { BetterAuthPlugin } from "./plugins";
import type { SocialProviderList, SocialProviders } from "../social-providers"; import type { SocialProviderList, SocialProviders } from "../social-providers";
import type { AdapterInstance, SecondaryStorage } from "./adapter"; import type { AdapterInstance, SecondaryStorage } from "./adapter";
import type { KyselyDatabaseType } from "../adapters/kysely-adapter/types"; import type { KyselyDatabaseType } from "../adapters/kysely-adapter/types";
import type { FieldAttribute } from "../db"; import type { DBFieldAttribute } from "@better-auth/core/db";
import type { Models, RateLimit } from "./models"; import type { Models, RateLimit } from "./models";
import type { AuthContext } from "."; import type { AuthContext } from ".";
import type { CookieOptions } from "better-call"; import type { CookieOptions } from "better-call";
@@ -457,7 +457,7 @@ export type BetterAuthOptions = {
* Additional fields for the session * Additional fields for the session
*/ */
additionalFields?: { additionalFields?: {
[key: string]: FieldAttribute; [key: string]: DBFieldAttribute;
}; };
/** /**
* Changing email configuration * Changing email configuration
@@ -567,7 +567,7 @@ export type BetterAuthOptions = {
* Additional fields for the session * Additional fields for the session
*/ */
additionalFields?: { additionalFields?: {
[key: string]: FieldAttribute; [key: string]: DBFieldAttribute;
}; };
/** /**
* By default if secondary storage is provided * By default if secondary storage is provided

View File

@@ -1,6 +1,5 @@
import type { Migration } from "kysely"; import type { Migration } from "kysely";
import { type AuthMiddleware } from "../api/call"; import { type AuthMiddleware } from "../api/call";
import type { FieldAttribute } from "../db/field";
import type { HookEndpointContext } from "."; import type { HookEndpointContext } from ".";
import type { import type {
Awaitable, Awaitable,
@@ -11,11 +10,12 @@ import type {
import type { AuthContext, BetterAuthOptions } from "."; import type { AuthContext, BetterAuthOptions } from ".";
import type { Endpoint, Middleware } from "better-call"; import type { Endpoint, Middleware } from "better-call";
import type { DBFieldAttribute } from "@better-auth/core/db";
export type AuthPluginSchema = { export type AuthPluginSchema = {
[table in string]: { [table in string]: {
fields: { fields: {
[field in string]: FieldAttribute; [field in string]: DBFieldAttribute;
}; };
disableMigration?: boolean; disableMigration?: boolean;
modelName?: string; modelName?: string;

View File

@@ -6,5 +6,6 @@
"lib": ["esnext", "dom", "dom.iterable"], "lib": ["esnext", "dom", "dom.iterable"],
"types": ["node", "bun"] "types": ["node", "bun"]
}, },
"include": ["src"] "include": ["src"],
"references": [{ "path": "../core" }]
} }

View File

@@ -1,7 +1,7 @@
import { import {
getAuthTables, getAuthTables,
type BetterAuthDbSchema, type BetterAuthDBSchema,
type FieldAttribute, type DBFieldAttribute,
} from "better-auth/db"; } from "better-auth/db";
import type { BetterAuthOptions } from "better-auth/types"; import type { BetterAuthOptions } from "better-auth/types";
import { existsSync } from "fs"; import { existsSync } from "fs";
@@ -43,7 +43,7 @@ export const generateDrizzleSchema: SchemaGenerator = async ({
const modelName = getModelName(table.modelName, adapter.options); const modelName = getModelName(table.modelName, adapter.options);
const fields = table.fields; const fields = table.fields;
function getType(name: string, field: FieldAttribute) { function getType(name: string, field: DBFieldAttribute) {
// Not possible to reach, it's here to make typescript happy // Not possible to reach, it's here to make typescript happy
if (!databaseType) { if (!databaseType) {
throw new Error( throw new Error(
@@ -224,7 +224,7 @@ function generateImport({
options, options,
}: { }: {
databaseType: "sqlite" | "mysql" | "pg"; databaseType: "sqlite" | "mysql" | "pg";
tables: BetterAuthDbSchema; tables: BetterAuthDBSchema;
options: BetterAuthOptions; options: BetterAuthOptions;
}) { }) {
const rootImports: string[] = []; const rootImports: string[] = [];

View File

@@ -8,5 +8,5 @@ export default defineBuildConfig({
outDir: "dist", outDir: "dist",
clean: true, clean: true,
failOnWarn: false, failOnWarn: false,
entries: ["./src/index.ts"], entries: ["./src/index.ts", "./src/db/index.ts"],
}); });

View File

@@ -12,15 +12,28 @@
"default": "./dist/index.mjs" "default": "./dist/index.mjs"
}, },
"require": { "require": {
"types": "./dist/index.d.cjs", "types": "./dist/index.d.cts",
"default": "./dist/index.cjs" "default": "./dist/index.cjs"
} }
},
"./db": {
"import": {
"types": "./dist/db/index.d.ts",
"default": "./dist/db/index.mjs"
},
"require": {
"types": "./dist/db/index.d.cts",
"default": "./dist/db/index.cjs"
}
} }
}, },
"typesVersions": { "typesVersions": {
"*": { "*": {
"index": [ "index": [
"dist/index.d.ts" "dist/index.d.ts"
],
"db": [
"dist/db.d.ts"
] ]
} }
}, },
@@ -31,5 +44,9 @@
}, },
"devDependencies": { "devDependencies": {
"unbuild": "catalog:" "unbuild": "catalog:"
},
"dependencies": {
"better-call": "catalog:",
"zod": "^4.1.5"
} }
} }

View File

@@ -0,0 +1,36 @@
import type {
DBFieldAttribute,
DBFieldAttributeConfig,
DBFieldType,
DBPrimitive,
BetterAuthDBSchema,
} from "./type";
export type {
DBFieldAttribute,
DBFieldAttributeConfig,
DBFieldType,
DBPrimitive,
BetterAuthDBSchema,
};
/**
* @deprecated Backport for 1.3.x, we will remove this in 1.4.x
*/
export type FieldAttribute = DBFieldAttribute;
/**
* @deprecated Backport for 1.3.x, we will remove this in 1.4.x
*/
export type FieldAttributeConfig = DBFieldAttributeConfig;
/**
* @deprecated Backport for 1.3.x, we will remove this in 1.4.x
*/
export type FieldType = DBFieldType;
/**
* @deprecated Backport for 1.3.x, we will remove this in 1.4.x
*/
export type Primitive = DBPrimitive;
/**
* @deprecated Backport for 1.3.x, we will remove this in 1.4.x
*/
export type BetterAuthDbSchema = BetterAuthDBSchema;

View File

@@ -0,0 +1,133 @@
import type { ZodType } from "zod";
import type { LiteralString } from "../types";
export type DBFieldType =
| "string"
| "number"
| "boolean"
| "date"
| "json"
| `${"string" | "number"}[]`
| Array<LiteralString>;
export type DBPrimitive =
| string
| number
| boolean
| Date
| null
| undefined
| string[]
| number[];
export type DBFieldAttributeConfig = {
/**
* If the field should be required on a new record.
* @default true
*/
required?: boolean;
/**
* If the value should be returned on a response body.
* @default true
*/
returned?: boolean;
/**
* If a value should be provided when creating a new record.
* @default true
*/
input?: boolean;
/**
* Default value for the field
*
* Note: This will not create a default value on the database level. It will only
* be used when creating a new record.
*/
defaultValue?: DBPrimitive | (() => DBPrimitive);
/**
* Update value for the field
*
* Note: This will create an onUpdate trigger on the database level for supported adapters.
* It will be called when updating a record.
*/
onUpdate?: () => DBPrimitive;
/**
* transform the value before storing it.
*/
transform?: {
input?: (value: DBPrimitive) => DBPrimitive | Promise<DBPrimitive>;
output?: (value: DBPrimitive) => DBPrimitive | Promise<DBPrimitive>;
};
/**
* Reference to another model.
*/
references?: {
/**
* The model to reference.
*/
model: string;
/**
* The field on the referenced model.
*/
field: string;
/**
* The action to perform when the reference is deleted.
* @default "cascade"
*/
onDelete?:
| "no action"
| "restrict"
| "cascade"
| "set null"
| "set default";
};
unique?: boolean;
/**
* If the field should be a bigint on the database instead of integer.
*/
bigint?: boolean;
/**
* A zod schema to validate the value.
*/
validator?: {
input?: ZodType;
output?: ZodType;
};
/**
* The name of the field on the database.
*/
fieldName?: string;
/**
* If the field should be sortable.
*
* applicable only for `text` type.
* It's useful to mark fields varchar instead of text.
*/
sortable?: boolean;
};
export type DBFieldAttribute<T extends DBFieldType = DBFieldType> = {
type: T;
} & DBFieldAttributeConfig;
export type BetterAuthDBSchema = Record<
string,
{
/**
* The name of the table in the database
*/
modelName: string;
/**
* The fields of the table
*/
fields: Record<string, DBFieldAttribute>;
/**
* Whether to disable migrations for this table
* @default false
*/
disableMigrations?: boolean;
/**
* The order of the table
*/
order?: number;
}
>;

View File

@@ -0,0 +1 @@
export type LiteralString = "" | (string & Record<never, never>);

View File

@@ -0,0 +1 @@
export * from "./helper";

9
pnpm-lock.yaml generated
View File

@@ -906,6 +906,13 @@ importers:
version: 3.6.1(sass@1.90.0)(typescript@5.9.2)(vue@3.5.19(typescript@5.9.2)) version: 3.6.1(sass@1.90.0)(typescript@5.9.2)(vue@3.5.19(typescript@5.9.2))
packages/core: packages/core:
dependencies:
better-call:
specifier: 'catalog:'
version: 1.0.19
zod:
specifier: ^4.1.5
version: 4.1.5
devDependencies: devDependencies:
unbuild: unbuild:
specifier: 'catalog:' specifier: 'catalog:'
@@ -16013,7 +16020,9 @@ snapshots:
metro-runtime: 0.83.1 metro-runtime: 0.83.1
transitivePeerDependencies: transitivePeerDependencies:
- '@babel/core' - '@babel/core'
- bufferutil
- supports-color - supports-color
- utf-8-validate
optional: true optional: true
'@react-native/normalize-colors@0.79.6': {} '@react-native/normalize-colors@0.79.6': {}