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

View File

@@ -1,5 +1,5 @@
import type { FieldAttribute } from "../../db";
import type { BetterAuthDbSchema } from "../../db/get-tables";
import type { DBFieldAttribute } from "@better-auth/core/db";
import type { BetterAuthDBSchema } from "@better-auth/core/db";
import type {
AdapterSchemaCreation,
BetterAuthOptions,
@@ -164,7 +164,7 @@ export interface AdapterFactoryConfig {
/**
* The fields of the model.
*/
fieldAttributes: FieldAttribute;
fieldAttributes: DBFieldAttribute;
/**
* The field to transform.
*/
@@ -180,7 +180,7 @@ export interface AdapterFactoryConfig {
/**
* The schema of the user's Better-Auth instance.
*/
schema: BetterAuthDbSchema;
schema: BetterAuthDBSchema;
/**
* The options of the user's Better-Auth instance.
*/
@@ -196,7 +196,7 @@ export interface AdapterFactoryConfig {
/**
* The fields of the model.
*/
fieldAttributes: FieldAttribute;
fieldAttributes: DBFieldAttribute;
/**
* The field to transform.
*/
@@ -212,7 +212,7 @@ export interface AdapterFactoryConfig {
/**
* The schema of the user's Better-Auth instance.
*/
schema: BetterAuthDbSchema;
schema: BetterAuthDBSchema;
/**
* 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.
*/
schema: BetterAuthDbSchema;
schema: BetterAuthDBSchema;
/**
* The debug log function.
*
@@ -303,7 +303,7 @@ export type AdapterFactoryCustomizeAdapterCreator = (config: {
}: {
model: string;
field: string;
}) => FieldAttribute;
}) => DBFieldAttribute;
}) => CustomAdapter;
export interface CustomAdapter {
@@ -377,7 +377,7 @@ export interface CustomAdapter {
/**
* The tables from the user's Better-Auth instance schema.
*/
tables: BetterAuthDbSchema;
tables: BetterAuthDBSchema;
}) => Promise<AdapterSchemaCreation>;
/**
* 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(
value: any,
field: FieldAttribute,
field: DBFieldAttribute,
action: "create" | "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 { 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 = <
T extends FieldType,
C extends Omit<FieldAttributeConfig<T>, "type">,
T extends DBFieldType,
C extends DBFieldAttributeConfig,
>(
type: T,
config?: C,
@@ -120,10 +15,10 @@ export const createFieldAttribute = <
return {
type,
...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
: T extends "number"
? number
@@ -141,7 +36,7 @@ export type InferValueType<T extends FieldType> = T extends "string"
export type InferFieldsOutput<Field> = Field extends Record<
infer Key,
FieldAttribute
DBFieldAttribute
>
? {
[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<
infer Key,
FieldAttribute
DBFieldAttribute
>
? {
[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<
infer Key,
FieldAttribute
DBFieldAttribute
>
? {
[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
: T["required"] extends false
? InferValueType<T["type"]> | undefined | null
: InferValueType<T["type"]>;
/**
* Converts a Record<string, FieldAttribute> to an object type
* with keys and value types inferred from FieldAttribute["type"].
* Converts a Record<string, DBFieldAttribute> to an object type
* with keys and value types inferred from DBFieldAttribute["type"].
*/
export type FieldAttributeToObject<
Fields extends Record<string, FieldAttribute>,
Fields extends Record<string, DBFieldAttribute>,
> = AddOptionalFields<
{
[K in keyof Fields]: InferValueType<Fields[K]["type"]>;
@@ -223,7 +118,7 @@ export type FieldAttributeToObject<
type AddOptionalFields<
T extends Record<string, any>,
Fields extends Record<keyof T, FieldAttribute>,
Fields extends Record<keyof T, DBFieldAttribute>,
> = {
// Required fields: required === true
[K in keyof T as Fields[K] extends { required: true } ? K : never]: T[K];
@@ -244,14 +139,14 @@ export type InferAdditionalFieldsFromPluginOptions<
Options extends {
schema?: {
[key in SchemaName]?: {
additionalFields?: Record<string, FieldAttribute>;
additionalFields?: Record<string, DBFieldAttribute>;
};
};
},
isClientSide extends boolean = true,
> = Options["schema"] extends {
[key in SchemaName]?: {
additionalFields: infer Field extends Record<string, FieldAttribute>;
additionalFields: infer Field extends Record<string, DBFieldAttribute>;
};
}
? isClientSide extends true
@@ -259,14 +154,14 @@ export type InferAdditionalFieldsFromPluginOptions<
: 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];
};
type InferFieldInput<T extends FieldAttribute> = InferValueType<T["type"]>;
type InferFieldInput<T extends DBFieldAttribute> = InferValueType<T["type"]>;
export type PluginFieldAttribute = Omit<
FieldAttribute,
DBFieldAttribute,
"transform" | "defaultValue" | "hashValue"
>;

View File

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

View File

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

View File

@@ -1,32 +1,12 @@
import type { FieldAttribute } from ".";
import type { BetterAuthOptions } from "../types";
export type BetterAuthDbSchema = Record<
string,
{
/**
* 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;
}
>;
import type {
BetterAuthDBSchema,
DBFieldAttribute,
} from "@better-auth/core/db";
export const getAuthTables = (
options: BetterAuthOptions,
): BetterAuthDbSchema => {
): BetterAuthDBSchema => {
const pluginSchema = (options.plugins ?? []).reduce(
(acc, plugin) => {
const schema = plugin.schema;
@@ -44,7 +24,7 @@ export const getAuthTables = (
},
{} as Record<
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;
@@ -124,7 +104,7 @@ export const getAuthTables = (
},
order: 2,
},
} satisfies BetterAuthDbSchema;
} satisfies BetterAuthDBSchema;
return {
user: {
@@ -290,5 +270,5 @@ export const getAuthTables = (
},
...pluginTables,
...(shouldAddRateLimitTable ? rateLimitTable : {}),
} satisfies BetterAuthDbSchema;
} satisfies BetterAuthDBSchema;
};

View File

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

View File

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

View File

@@ -1,9 +1,9 @@
import * as z from "zod";
import type { ZodType } from "zod";
import type { FieldAttribute } from ".";
import type { DBFieldAttribute } from "@better-auth/core/db";
export function toZodSchema<
Fields extends Record<string, FieldAttribute | never>,
Fields extends Record<string, DBFieldAttribute | never>,
IsClientSide extends boolean,
>({
fields,
@@ -56,14 +56,14 @@ export function toZodSchema<
}
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
isClientSide extends boolean = false,
> = Field extends { type: any }
? GetInput<isClientSide, Field, GetRequired<Field, GetType<Field>>>
: Record<string, never>;
type GetType<F extends FieldAttribute> = F extends {
type GetType<F extends DBFieldAttribute> = F extends {
type: "string";
}
? z.ZodString
@@ -76,7 +76,7 @@ type GetType<F extends FieldAttribute> = F extends {
: z.ZodAny;
type GetRequired<
F extends FieldAttribute,
F extends DBFieldAttribute,
Schema extends z.core.SomeType,
> = F extends {
required: true;
@@ -86,7 +86,7 @@ type GetRequired<
type GetInput<
isClientSide extends boolean,
Field extends FieldAttribute,
Field extends DBFieldAttribute,
Schema extends z.core.SomeType,
> = Field extends {
input: false;

View File

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

View File

@@ -1,11 +1,6 @@
import { defu } from "defu";
import { hashPassword, verifyPassword } from "./crypto/password";
import {
type BetterAuthDbSchema,
createInternalAdapter,
getAuthTables,
getMigrations,
} from "./db";
import { createInternalAdapter, getAuthTables, getMigrations } from "./db";
import type { Entries } from "type-fest";
import { getAdapter } from "./db/utils";
import type {
@@ -37,6 +32,7 @@ import type { TelemetryEvent } from "./telemetry/types";
import { getKyselyDatabaseType } from "./adapters/kysely-adapter";
import { checkEndpointConflicts } from "./api";
import { isPromise } from "./utils/is-promise";
import type { BetterAuthDBSchema } from "@better-auth/core/db";
export const init = async (options: BetterAuthOptions) => {
const adapter = await getAdapter(options);
@@ -245,7 +241,7 @@ export type AuthContext = {
};
checkPassword: typeof checkPassword;
};
tables: BetterAuthDbSchema;
tables: BetterAuthDBSchema;
runMigrations: () => 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 { BetterAuthPlugin } from "../../types";
@@ -6,10 +6,10 @@ export const inferAdditionalFields = <
T,
S extends {
user?: {
[key: string]: FieldAttribute;
[key: string]: DBFieldAttribute;
};
session?: {
[key: string]: FieldAttribute;
[key: string]: DBFieldAttribute;
};
} = {},
>(
@@ -26,10 +26,10 @@ export const inferAdditionalFields = <
type Plugin = Opts extends never
? S extends {
user?: {
[key: string]: FieldAttribute;
[key: string]: DBFieldAttribute;
};
session?: {
[key: string]: FieldAttribute;
[key: string]: DBFieldAttribute;
};
}
? {

View File

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

View File

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

View File

@@ -6,7 +6,7 @@ import { inferOrgAdditionalFields, organizationClient } from "../client";
import { createAccessControl } from "../../access";
import { adminAc, defaultStatements, memberAc, ownerAc } from "../access";
import { parseSetCookieHeader } from "../../../cookies";
import type { FieldAttribute } from "../../../db";
import type { DBFieldAttribute } from "@better-auth/core/db";
import { ORGANIZATION_ERROR_CODES } from "../error-codes";
describe("dynamic access control", async (it) => {
@@ -43,7 +43,7 @@ describe("dynamic access control", async (it) => {
input: false,
required: true,
},
} satisfies Record<string, FieldAttribute>;
} satisfies Record<string, DBFieldAttribute>;
const { auth, customFetchImpl, sessionSetter, signInWithTestUser } =
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 { AccessControl, Role } from "../access";
import type {
@@ -257,7 +257,7 @@ export interface OrganizationOptions {
[key in keyof Omit<Organization, "id">]?: string;
};
additionalFields?: {
[key in string]: FieldAttribute;
[key in string]: DBFieldAttribute;
};
};
member?: {
@@ -266,7 +266,7 @@ export interface OrganizationOptions {
[key in keyof Omit<Member, "id">]?: string;
};
additionalFields?: {
[key in string]: FieldAttribute;
[key in string]: DBFieldAttribute;
};
};
invitation?: {
@@ -275,7 +275,7 @@ export interface OrganizationOptions {
[key in keyof Omit<Invitation, "id">]?: string;
};
additionalFields?: {
[key in string]: FieldAttribute;
[key in string]: DBFieldAttribute;
};
};
team?: {
@@ -284,7 +284,7 @@ export interface OrganizationOptions {
[key in keyof Omit<Team, "id">]?: string;
};
additionalFields?: {
[key in string]: FieldAttribute;
[key in string]: DBFieldAttribute;
};
};
teamMember?: {
@@ -299,7 +299,7 @@ export interface OrganizationOptions {
[key in keyof Omit<OrganizationRole, "id">]?: string;
};
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 { AdapterInstance, SecondaryStorage } from "./adapter";
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 { AuthContext } from ".";
import type { CookieOptions } from "better-call";
@@ -457,7 +457,7 @@ export type BetterAuthOptions = {
* Additional fields for the session
*/
additionalFields?: {
[key: string]: FieldAttribute;
[key: string]: DBFieldAttribute;
};
/**
* Changing email configuration
@@ -567,7 +567,7 @@ export type BetterAuthOptions = {
* Additional fields for the session
*/
additionalFields?: {
[key: string]: FieldAttribute;
[key: string]: DBFieldAttribute;
};
/**
* By default if secondary storage is provided

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
import {
getAuthTables,
type BetterAuthDbSchema,
type FieldAttribute,
type BetterAuthDBSchema,
type DBFieldAttribute,
} from "better-auth/db";
import type { BetterAuthOptions } from "better-auth/types";
import { existsSync } from "fs";
@@ -43,7 +43,7 @@ export const generateDrizzleSchema: SchemaGenerator = async ({
const modelName = getModelName(table.modelName, adapter.options);
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
if (!databaseType) {
throw new Error(
@@ -224,7 +224,7 @@ function generateImport({
options,
}: {
databaseType: "sqlite" | "mysql" | "pg";
tables: BetterAuthDbSchema;
tables: BetterAuthDBSchema;
options: BetterAuthOptions;
}) {
const rootImports: string[] = [];

View File

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

View File

@@ -12,15 +12,28 @@
"default": "./dist/index.mjs"
},
"require": {
"types": "./dist/index.d.cjs",
"types": "./dist/index.d.cts",
"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": {
"*": {
"index": [
"dist/index.d.ts"
],
"db": [
"dist/db.d.ts"
]
}
},
@@ -31,5 +44,9 @@
},
"devDependencies": {
"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))
packages/core:
dependencies:
better-call:
specifier: 'catalog:'
version: 1.0.19
zod:
specifier: ^4.1.5
version: 4.1.5
devDependencies:
unbuild:
specifier: 'catalog:'
@@ -16013,7 +16020,9 @@ snapshots:
metro-runtime: 0.83.1
transitivePeerDependencies:
- '@babel/core'
- bufferutil
- supports-color
- utf-8-validate
optional: true
'@react-native/normalize-colors@0.79.6': {}