mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-06 04:19:20 +00:00
refactor: move db schema to core (#4918)
This commit is contained in:
@@ -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 = ({
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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"
|
||||
>;
|
||||
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "@better-auth/core/db";
|
||||
export * from "./internal-adapter";
|
||||
export * from "./field";
|
||||
export * from "./get-tables";
|
||||
|
||||
@@ -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";
|
||||
},
|
||||
) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
? {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -6,5 +6,6 @@
|
||||
"lib": ["esnext", "dom", "dom.iterable"],
|
||||
"types": ["node", "bun"]
|
||||
},
|
||||
"include": ["src"]
|
||||
"include": ["src"],
|
||||
"references": [{ "path": "../core" }]
|
||||
}
|
||||
|
||||
@@ -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[] = [];
|
||||
|
||||
@@ -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"],
|
||||
});
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
36
packages/core/src/db/index.ts
Normal file
36
packages/core/src/db/index.ts
Normal 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;
|
||||
133
packages/core/src/db/type.ts
Normal file
133
packages/core/src/db/type.ts
Normal 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;
|
||||
}
|
||||
>;
|
||||
1
packages/core/src/types/helper.ts
Normal file
1
packages/core/src/types/helper.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type LiteralString = "" | (string & Record<never, never>);
|
||||
1
packages/core/src/types/index.ts
Normal file
1
packages/core/src/types/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./helper";
|
||||
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
@@ -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': {}
|
||||
|
||||
Reference in New Issue
Block a user